这一篇的主题是组合式API
一,什么是组合式API?
平时使用vue开发的小伙伴们不难发现,在我们封装组件的时候大部分使用组件的选项(data、computed、methods、watch)组织逻辑在大部分情况下都是有效。然而当我们组件变得更大时,逻辑关注点列表增加,组件中的代码量越来越大,导致维护起来变得很困难。
Vue 选项式 API: 按选项类型分组的代码
一个大型组件的示例,其中逻辑关注点是按颜色分组。
这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。
如果我们能够将与同一个逻辑关注点相关的代码配置在一起会更好。而这正是组合式 API 使我们能够做到的。
于是就有了组合式API
既然我们知道了为什么,我们就可以知道怎么做。为了开始使用组合式 API,我们首先需要一个可以实际使用它的地方。在 Vue组件中,我们将此位置称为 setup
setup 在组件创建之前执行,一旦props被解析,就被充当成API的入口点
由于执行setup时组件实例未被创建,因此setup选项中没有this。这意味着除了props,我们无法访问组件中生命的任何属性–本地状态、计算属性【computed】、方法【methods】
setup的使用:
// src/components/UserRepositories.vue
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
setup(props) {
console.log(props) // { user: '' }
return {} // 这里返回的任何内容都可以用于组件的其余部分
}
// 组件的“其余部分”
}
代码实战:
1,从假定的外部 API 获取该用户名的仓库,并在用户更改时刷新它
先来看第一段代码⬇️因为我们的 repositories 变量是非响应式的,所以我们要进行修改
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
// 在我们的组件内
setup (props) {
let repositories = []
const getUserRepositories = async () => {
repositories = await fetchUserRepositories(props.user)
}
return {
repositories,
getUserRepositories // 返回的函数与方法的行为相同
}
}
带ref的响应式变量
在 Vue 3.0 中,我们可以通过一个新的 ref 函数使任何响应式变量在任何地方起作用,如下所示:
import { ref } from 'vue'
const counter = ref(0)
console.log(counter) // { value: 0 }
console.log(counter.value) // 0
//ref接受参数并返回它包装在具有value的property对象中,然后可以使用property 修改响应式变量的值
counter.value++
console.log(counter.value) // 1
//在对象中对于包装值就不是必要的,但是在javaScript中基本类型是通过值来传递的
//如 let val1 = 1 const val2 = val1 值的传递
//对象则是通过引用来传递
在任何值周围都有一个包装器对象,这样我们就可以在整个应用程序中安全地传递它,而不必担心在某个地方失去它的响应性。
提示
换句话说,ref 对我们的值创建了一个响应式引用。使用引用的概念将在整个组合式 API 中经常使用。
回到之前的代码,我们来完善它:
// 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
}
}
mounted 钩子调用 getUserRepositores
// 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
}
}
现在我们使用watch来监听user
// 组合式API
import { ref, watch } from 'vue'
// 如同vue2我们可以从vue中导入watch,他们的功能基本相同,现在watch接收三个参数:
//1,一个响应式回应或者我们想要监听的getter函数
//2,一个回调
//3,可选的配置项
const counter = ref(0)
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
我们来看一下和vue2(选项式API)的对比
// 选项式API
export default {
data() {
return {
counter: 0
}
},
watch: {
counter(newValue, oldValue) {
console.log('The new counter value is: ' + this.counter)
}
}
}
现在配合我们上面的例子来继续vue3组合式API开发的模式来完成代码
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs } from 'vue'
// 在我们组件中
setup (props) {
// 使用 `toRefs` 创建对prop的 `user` property 的响应式引用
const { user } = toRefs(props)
const repositories = ref([])
const getUserRepositories = async () => {
// 更新 `prop.user` 到 `user.value` 访问引用值
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
// 在用户 prop 的响应式引用上设置一个侦听器
watch(user, getUserRepositories)
return {
repositories,
getUserRepositories
}
}
你可能已经注意到在我们的 setup 的顶部使用了 toRefs。这是为了确保我们的侦听器能够对 user prop 所做的更改做出反应。
有了这些变化,我们就把第一个逻辑关注点移到了一个地方。我们现在可以对第二个关注点执行相同的操作——基于 searchQuery 进行过滤,这次是使用计算属性。先来看下代码
//与ref 和 watch类似,也可以使用从vue导入的computed 函数在vue组件外部创建计算属性
//下面是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
// vue3简直太顶了,作者忍不住要夸一下,牛*
在这里,computed
函数返回一个作为computed
的第一个参数传递的getter
类回调的输出的一个只读的响应式引用。为了访问新创建的计算变量的value
,我们需要像使用ref一样使用.value
property
接下来就是搜索功能,同样使用vue3的组合式API
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs, computed } from 'vue'
// in our component
setup (props) {
// 使用 `toRefs` 创建对 props 的 `user` property 的响应式引用
const { user } = toRefs(props)
const repositories = ref([])
const getUserRepositories = async () => {
// 更新 `props.user ` 到 `user.value` 访问引用值
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
// 在用户 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
}
}
看完上面的代码,你会发现,组合式API不就是把选项式API组合起来放到setup中使其具有聚合功能的作用吗?那说明你已经好好地看了本篇文章的上半部分。但实战却刚刚开始,接下来就让我们一起看实战中组合式API是如何使用的:
//封装组合式函数--根据响应式的参数动态获取接口数据并返回
// src/composables/useUserRepositories.js
import { retApi } from '@/api/repositories'
import { ref, onMounted, watch} from 'vue'
export default function userRepositories(user){
const repositories = ref([])
const getUserRepositor = async() =>{
repositories.value = await retApi(user.value)
}
onMounted(getUserRepositor)
watch(user,getUserRepositor)
return {
repositories,
getUserRepositor
}
}
将搜索功能也同样封装(聪明的小伙伴已经开始跟着作者敲代码了 )
// src/composables/useRepositoryNameSearch.js
//搜索功能显然要写在computed函数里面
import { ref, computed } from 'vue'
export default function userSearch(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 { roRefs } 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 {
// 因为我们并不关心未经过滤的仓库
// 我们可以在 `repositories` 名称下暴露过滤后的结果
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 {
// 因为我们并不关心未经过滤的仓库
// 我们可以在 `repositories` 名称下暴露过滤后的结果
repositories: filteredRepositories,
getUserRepositories,
searchQuery,
filters,
updateFilters
}
}
}
随着前端生态的不断发展,前端岗位对于开发人员的挑战也越来越大。释放焦虑,才有动力,有压力,才有成长!
本文章参考vue3文档
第一篇 end