[ Vue ] 基于 Vue 3 的新特性的理解和思考
vue 新特性的出现往往需要关注的就是,他的特性对我之前开发的项目或者即将开发的项目有什么能进行更好的技术方案的优化和调整,也可以说是它解决了什么问题。而 Composition API 是这一次 vue 3 的更新重点。
Composition API
Composition API 的出现就是为了解决交互逻辑繁重的情况下,让函数更加方便的调用和易于让开发者理解而出现的。其核心思想就是将相关的代码收集在一起。
先抛开 Vue2 或者 Vue3 ,在我之前的一个项目开发中,由于处理的逻辑相对较多,我就将一些逻辑做了抽离放在了不同的函数里面, Vue2 代码如下:
methods: {
format() {
this.fun1();
this.fun2();
this.fun3();
this.fun4();
this.fun5();
},
fun1() { },
fun2() { },
fun3() { },
fun4() { },
fun5() { },
},
当然这里是做了逻辑简化,写肯定不是这样写,只是想说明 format 这个方法里面引用和至少 5 个函数,我在抽离逻辑的时候是弄得很爽的,抽了一大摞的代码之后再调用感觉贼溜。
但是在提 codeReview 给大佬的时候,他看了之后马上说出了这个地方的不足,因为自己写起来很爽,但是看的人却不是这样一会滚动到 20 行看 fun1 ,一会要滚动到 130行看 fun2 ,再一会要滚动到 1000 行看 fun3 ,所以都最后不但是别人看累了,自己看懵了。
所以,他提出了尽可能将相关的函数点放在同一个函数之中,代码如下
methods: {
format() {
const fun1 = () => { };
const fun2 = () => { };
const fun3 = () => { };
const fun4 = () => { };
const fun5 = () => { };
},
},
他的实际优化由原来的:
改为了:
这样能更加清晰的了解这个 format 函数模块是做了什么依赖了什么, 不但自己能非常快的理解函数的执行,别人也容易阅读,这是简化代码开发的思想。
回到 vue 这里,在 Vue2 的版本,开发页面逻辑使用 watch ,methods,data ,computed 等组件选项来组织逻辑都是非常的方便,但是面对交互比较繁重的页面,这些逻辑看起来也会比较臃肿,不容易理解。
官网的说明文档也给出了一个偏向于实际的简单例子:
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
data () {
return {
repositories: [],
filters: { ... },
searchQuery: ''
}
},
computed: {
filteredRepositories () { ... },
repositoriesMatchingSearchQuery () { ... },
},
watch: {
user: 'getUserRepositories'
},
methods: {
getUserRepositories () {
},
updateFilters () { ... },
},
mounted () {
this.getUserRepositories()
}
}
这里组件的选项全用上,再后面交互越多,各种逻辑的交互代码肯定是以一个个“块”的单位垒起来。而 Composition API 的使用的目的就是要将逻辑进行聚合。
Composition API 的使用
vue 3 中新增加了一个组件选项 setup,他是在创建之前执行,在 props 在解析的时候,就作为 Composition API 的入口。
export default {
components: { ... },
props: {
user: {
type: String,
required: true
}
},
setup(props) {
console.log(props)
return {}
}
}
他和 props 等其他组件选项一样统一,返回的是一个对象,这个对象可以在其他的地方进行使用,可以说是组件内提供了一个 api 通用函数。
带 ref 的响应式变量
在 vue3 中 , 可以通过一个新的 ref 函数使任何现响应式变量在任何地方起作用。
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
将一个值封装在一个对象内,好处是由于对象取的是一个引用,他在修改新数据的时候不会影响旧的数据, 官网 DEMO 展示。
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
return {
repositories,
getUserRepositories
}
},
data () {
return {
filters: { ... },
searchQuery: ''
}
},
computed: {
filteredRepositories () { ... },
repositoriesMatchingSearchQuery () { ... },
},
watch: {
user: 'getUserRepositories'
},
methods: {
updateFilters () { ... },
},
mounted () {
this.getUserRepositories()
}
}
需要注意的是 ,需要将原来 data 中的 repositories 去掉,因为 repositories 这个变量现在由 setup 这个组件选项返回回来。
setup 中钩子函数的使用
在 setup 中,生命周期的钩子函数与选项式 API 是一样的,只不过他的钩子函数的名字在前面增加了 on 。举例 mount 和 beforeount 。
setup(props) {
const resList = ref([]);
const fetchUserList = () => {
resList.value = props.user;
}
onBeforeMount(getClass);
onMounted(fetchUserList);
return {
resList,
fetchUserList
}
},
那么在这里有一些方法也可以不用挂在外层,只要通过 onMounted 来执行。
setup 中的 watch 监听
监听器的方法和选项式 API 方法上大师是一样的,watch 在这里同样是可以监听 props 的内容,但是还是有不同,监听的对象是经过了 toRefs 方法的一层解析, 就是给 props 中 user 的响应式引用。
import {toRefs } from 'vue';
const { user } = toRefs(props);
这么做的目的是更好的监听 props 中 user 的变化,在使用上,watch 的第二个参数就是当监听对象发生改变的时候执行回调函数。
import { ajax } from 'majax';
import { ref, onMounted, watch, toRefs } from 'vue'
setup (props) {
const { user } = toRefs(props)
const resList = ref([])
const fetchUserList = async () => {
resList.value = await ajax(user.value)
}
onMounted(fetchUserList)
watch(user, fetchUserList)
return {
resList,
fetchUserList
}
}
setup 的中 computed 属性
结合最基本的 demo 和说明:
import { ref, computed } from 'vue'
const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)
counter.value++
console.log(counter.value)
console.log(twiceTheCounter.value)
computed 接受一个参数,这个参数是相当于一个回掉函数,在 setup 中还是需要 对新建的变量进行计算 ,如上例 conter.value。真实的使用情况则是:
import { majax } from 'majax'
import { ref, onMounted, watch, toRefs, computed } from 'vue'
setup (props) {
const { user } = toRefs(props)
const resList = ref([])
const fetchUserList = async () => {
resList.value = await majax(user.value)
}
onMounted(fetchUserList)
watch(user, fetchUserList)
const searchQuery = ref('')
const MatchingQuery = computed(() => {
return resList.value.filter(
item => item.name.includes(searchQuery.value)
)
})
return {
resList,
fetchUserList,
searchQuery,
MatchingQuery
}
}
直到这里整一个 setup 就有个一个完整的功能,他的选项api 包含了 watch,computed,data 还有ref 函数。
在把功能点都挪到了 setup 中之后,虽然逻辑上会更加的清晰明了 ,但是随之而来的也是 setup 的体积越来越大。需要将刚刚的方法再进一步的抽离,举例有查询和列表渲染的两个功能,将他们的功能逻辑和钩子等Composition API 所需属性抽离放在两个不同的 JS 模块导出。我将官网的 DEMO 代码精简了一下,下面的两个代码块,分别是查询和列表获取的功能:
获取列表模块的代码:
// fetchUserList.js
import { ajax } from 'majax';
import { ref, onMounted, watch } from 'vue';
export default function fetchUserList(user) {
const resList = ref([])
const fetchUserList = async () => {
resList.value = await ajax(user.value)
}
onMounted(fetchUserList)
watch(user, fetchUserList)
return {
resList,
fetchUserList
}
}
筛选模块的代码:
// searchUserList.js
import { ref, computed } from 'vue'
export default function searchName(resList) {
const searchQuery = ref('')
const MatchingQuery = computed(() => {
return resList.value.filter(repository => {
return repository.name.includes(searchQuery.value)
})
})
return {
searchQuery,
MatchingQuery
}
}
有了这两个模块的代码之后,再将它引入回 setup 选项中:
import fetchUserList from 'fetchUserList.js'
import searchUserList from 'searchUserList.js'
import { toRefs } from 'vue'
export default {
...
setup (props) {
const { user } = toRefs(props)
const { resList, fetchUserList } = fetchUserList(user)
const { searchQuery, MatchingQuery } = searchUserList(repositories)
return {
resList: MatchingQuery,
fetchUserList,
searchQuery,
}
},
...
}
总结一下,Composition API 做了一件这样事情,他将功能相同的逻辑不在像之前一样,逻辑代码分别散落在 watch 或者是 computed 中,而是将所相关的代码逻辑聚合到一个功能模块上,再来使用这个模块上的功能,用一张图来表示就是:
他们转化为以功能为单位的模块。以上是对 Composition API 的功能理解。
![](https://jkboy.com/wp-content/uploads/2021/10/Pasted-11.png)