vue3.0最近发布了,可是好像还没看到有中文的文档,于是本人根据英文的文档进行了简单的翻译,仅仅是想提高自己对文档的理解,同时也作个记录,将会缓慢更新,相信官方很快会推出正式的中文文档...... 如果发现文中哪里的翻译、描述不太对的,欢迎留言哈~~
Note:
阅读此部分内容之前,你需要先熟悉vue的基础以及如何创建组件
通过使用vue组件,我们可以将接口中的某些重复的部分,以合适的方式组合成可复用的代码,而使得我们的应用更具可维护性和灵活性。但当我们的应用变得越来越庞大时,例如调用了上百个组件,仅仅靠这是还不够的。在处理这种庞大的应用时,共享、复用代码变得尤为重要。
设想一下,我们要在一个应用中展示某个用户的仓库列表,并且要有搜索和过滤的能力。这个应用可能长这样:
// src/components/UserRepositories.vue
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
data () {
return {
repositories: [], // 1
filters: { ... }, // 3
searchQuery: '' // 2
}
},
computed: {
filteredRepositories () { ... }, // 3
repositoriesMatchingSearchQuery () { ... }, // 2
},
watch: {
user: 'getUserRepositories' // 1
},
methods: {
getUserRepositories () {
// using `this.user` to fetch user repositories
}, // 1
updateFilters () { ... }, // 3
},
mounted () {
this.getUserRepositories() // 1
}
}
这个组件要实现这几个功能:
通过vue组件原有的组件选项(data,computed,methods,watch)来组织逻辑在大部分场合或许已经够用。然而,随着组件变大,当中的逻辑关注点也会增多,这会使得组件难以被理解,尤其是对于组件开发者以外的人。
上图是一个vue组件的示例,里面的逻辑关注点通过颜色进行了分组。如此的碎片化使得组件难以理解,并且复杂,底层的逻辑关注点被分离的选项所遮掩。当我们要处理某个逻辑关注点时,我们就要不断地上下滚动,这就非常不方便了。因此我们希望可以把与某个逻辑关注点相关的代码放到一起,而这正式composition api所能够给我们带来的。
既然已经了解我们为什么要用composition api,那应该怎样用呢?首先,我们需要一个能够使用它的地方。在vue组件中,这个地方叫setup。
setup是在props解析完成后、组件created之前被执行,作为composition api的入口。
Warm:
由于setup被执行的时候,组件示例还没有创建完成,因此在setup里面没有this。这意味着除了props,你无法使用组件内定义的其他任何属性,包括本地状态、计算属性和方法等。
选项setup是一个方法,它接收props和context作为参数,这我们稍后再作讨论。另外,在setup里面return的内容将会暴露给这个组件的除setup以外的剩下的部分以供调用,例如计算属性、方法、生命周期钩子等,以及组件的template模板。
现在我们将setup添加到组件之中:
// src/components/UserRepositories.vue
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
setup(props) {
console.log(props) // { user: '' }
return {} // anything returned here will be available for the rest of the component
}
// the "rest" of the component
}
现在我们进行第一步:提取第一个逻辑关注点(原始片段中标记为“1”的部分)。
- 从一个外部接口中获取某用户的仓库数据,并且当用户改变时刷新这个数据。
我们将从显而易见的部分开始:
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
// inside our component
setup (props) {
let repositories = []
const getUserRepositories = async () => {
repositories = await fetchUserRepositories(props.user)
}
return {
repositories,
getUserRepositories // functions returned behave the same as methods
}
}
这是我们的起点,但它还不会正常运作,因为变量respositories不是响应式的。这意味着对于用户来说,仓库列表还是空的,我们现在来修复它。
通过ref创建的响应式变量
在vue3.0,我们可以在任何地方通过ref方法创建响应式变量,像这样:
import { ref } from 'vue'
const counter = ref(0)
ref接收参数并将这个参数封装成一个对象,这个对象里有一个value属性,该属性可被用来获取或改变这个响应式变量的值:
import { ref } from 'vue'
const counter = ref(0)
console.log(counter) // { value: 0 }
console.log(counter.value) // 0
counter.value++
console.log(counter.value) // 1
将一个值封装在一个对象中,看似是没必要的,但要在拥有众多不同数据的javascript中保持表现一致,这是有需要的。这是因为在javascript中,基本数据类型(如Number,String)是通过值进行传递的,而非引用。
将值封装在对象中,使得我们可以安全地在整个应用中传递,而不必担心在传递的过程中失去响应式的特性。
Note:
换一种说法,ref给我们的值带来响应式的引用,使用引用的概念将在通篇对composition api的描述中经常用到。
回到我们的例子,我们将创建一个响应式变量repositories:
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'
// in our component
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
return {
repositories,
getUserRepositories
}
}
完成了!无论我们什么时候调用getUserRepositories,都会改变repositories,并且视图将会更新来反映这个改变。我们的组件现在看起来是这样的:
// src/components/UserRepositories.vue
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
return {
repositories,
getUserRepositories
}
},
data () {
return {
filters: { ... }, // 3
searchQuery: '' // 2
}
},
computed: {
filteredRepositories () { ... }, // 3
repositoriesMatchingSearchQuery () { ... }, // 2
},
watch: {
user: 'getUserRepositories' // 1
},
methods: {
updateFilters () { ... }, // 3
},
mounted () {
this.getUserRepositories() // 1
}
}
我们已经将第一个逻辑关注点的几个片段放到setup的方法中,很好地将它们放在相靠近的位置了。剩下一个在mounted生命周期钩子中的叫做getUserRepositories的方法,以及要设置一个watcher,在user prop改变时调用这个方法。
我们将利用生命周期钩子来开始。
在setup内注册生命周期钩子
与option api相比,为了使得composition api的特性更加齐全,我们也需要在setup内部注册生命周期钩子。这也许要归功于几个可以从vue中导出的新方法。composition api中的生命周期钩子的名字与option api中的相同,除了多了个前缀on。例如,option api中的mounted,在composition api中叫做onMounted。
这些方法接收一个回调函数,这些回调函数将在组件调用生命周期钩子的时候被执行。
现在我们将这些方法放到setup中:
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'
// in our component
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
onMounted(getUserRepositories) // on `mounted` call `getUserRepositories`
return {
repositories,
getUserRepositories
}
}
现在我们需要在user prop改变时作出响应。为了实现这个,我们将用到一个独立的watch方法。
通过watch来应对改变
Just like how we set up a watcher on the user
property inside our component using the watch
option, we can do the same using the watch
function imported from Vue. It accepts 3 arguments:就像在组件中利用watch选项来建立一个user的侦听器一样,我们通过从vue中导入的watch方法也一样这么做。这个方法接收3个参数:
现在我们看看它是如何运作的:
import { ref, watch } from 'vue'
const counter = ref(0)
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
只要counter发生变动,比方说counter.value = 5,这个侦听器就会被触发,并且调用回调函数(第二个参数)。在上面的例子中,将会在控制台输出“The new counter value is: 5”。
下面的options api的代码与上例是等价的:
export default {
data() {
return {
counter: 0
}
},
watch: {
counter(newValue, oldValue) {
console.log('The new counter value is: ' + this.counter)
}
}
}
想了解更多关于watch的细节,请参考in-depth指南。
现在来看看怎么将它运用到我们的例子中:
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs } from 'vue'
// in our component
setup (props) {
// using `toRefs` to create a Reactive Reference to the `user` property of props
const { user } = toRefs(props)
const repositories = ref([])
const getUserRepositories = async () => {
// update `props.user` to `user.value` to access the Reference value
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
// set a watcher on the Reactive Reference to user prop
watch(user, getUserRepositories)
return {
repositories,
getUserRepositories
}
}
你或许注意到了在setup里面顶部使用了toRefs。这是为了确保侦听器能够对user prop的改变作出响应。
通过以上位置上的调整,我们已经将第一个逻辑关注点的全部内容移动到了同一个位置了。同样地,我们可以对第二个逻辑关注点––根据serchQuery进行过滤,进行同样的改动。这次我们将使用计算属性。
与ref和watch类似,计算属性也能在vue组件外部创建––通过从vue中导入的computed方法。现在我们一起回到我们的counter例子:
import { ref, computed } from 'vue'
const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)
counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2
在这里,computed的方法返回一个只读的响应式引用,这个引用是对一个类似与getter方法的回调函数的返回结果的响应式引用,这个回调函数作为computed方法的第一个参数。为了获取到这个新创建的computed变量,我们需要像ref一样,使用.value的属性。
现在我们将search功能移到setup里面:
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs, computed } from 'vue'
// in our component
setup (props) {
// using `toRefs` to create a Reactive Reference to the `user` property of props
const { user } = toRefs(props)
const repositories = ref([])
const getUserRepositories = async () => {
// update `props.user` to `user.value` to access the Reference value
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
// set a watcher on the Reactive Reference to user prop
watch(user, getUserRepositories)
const searchQuery = ref('')
const repositoriesMatchingSearchQuery = computed(() => {
return repositories.value.filter(
repository => repository.name.includes(searchQuery.value)
)
})
return {
repositories,
getUserRepositories,
searchQuery,
repositoriesMatchingSearchQuery
}
}
同样地,我们也可以对其他逻辑关注点这么做,但你或许会这样问––将所有东西都放到setup里面,不会使它变得非常大吗?这没错,因此我们在继续另外要做的事情前,我们需要将上面的代码提取到一个独立的composition方法中,现在我们先来创建一个useUserRepositories:
// src/composables/useUserRepositories.js
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch } from 'vue'
export default function useUserRepositories(user) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
watch(user, getUserRepositories)
return {
repositories,
getUserRepositories
}
}
然后是搜索功能:
// src/composables/useRepositoryNameSearch.js
import { ref, computed } from 'vue'
export default function useRepositoryNameSearch(repositories) {
const searchQuery = ref('')
const repositoriesMatchingSearchQuery = computed(() => {
return repositories.value.filter(repository => {
return repository.name.includes(searchQuery.value)
})
})
return {
searchQuery,
repositoriesMatchingSearchQuery
}
}
在将这两个功能分别放到两个文件中之后,我们可以在我们的组件中使用它们了。下面是实现方法:
// src/components/UserRepositories.vue
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import { toRefs } from 'vue'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
setup (props) {
const { user } = toRefs(props)
const { repositories, getUserRepositories } = useUserRepositories(user)
const {
searchQuery,
repositoriesMatchingSearchQuery
} = useRepositoryNameSearch(repositories)
return {
// Since we don’t really care about the unfiltered repositories
// we can expose the filtered results under the `repositories` name
repositories: repositoriesMatchingSearchQuery,
getUserRepositories,
searchQuery,
}
},
data () {
return {
filters: { ... }, // 3
}
},
computed: {
filteredRepositories () { ... }, // 3
},
methods: {
updateFilters () { ... }, // 3
}
}
此时你或许已经懂得了这个技巧了,因此我们快速跳到末尾,将剩下的过滤功能也进行迁移。我们无需研究具体的实现细节,因为那不是本篇教程的重点。
// src/components/UserRepositories.vue
import { toRefs } from 'vue'
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import useRepositoryFilters from '@/composables/useRepositoryFilters'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
setup(props) {
const { user } = toRefs(props)
const { repositories, getUserRepositories } = useUserRepositories(user)
const {
searchQuery,
repositoriesMatchingSearchQuery
} = useRepositoryNameSearch(repositories)
const {
filters,
updateFilters,
filteredRepositories
} = useRepositoryFilters(repositoriesMatchingSearchQuery)
return {
// Since we don’t really care about the unfiltered repositories
// we can expose the end results under the `repositories` name
repositories: filteredRepositories,
getUserRepositories,
searchQuery,
filters,
updateFilters
}
}
}
于是所有都完成了!
记住,以上我们只是学习到composition api的表层以及它所能给我们带来的好处。如果想学习更多关于composition api的,请参考in-depth指南。