开始
不知不觉 vue3
已经发布一年多了,大部分 vue 项目开始拥抱 composition-api ,这个新特性可以说是 vue3
的最大的惊喜。但 composition-api
相较于过去的 options-api
写法需要有许多新的思维方式,使用 composition-api
也有一段时间了,总结一下过去我在 composition-api
一些实践。
功能区与逻辑
composition-api 文档中详细介绍了 composition-api
的一大优势: 强大的逻辑组合与功能划分能力。但这很容易陷入一个误区,是否所有的代码都要按照功能划分的方式来写。初使用 vue3
的时候我写过这样的实践。
import { reactive, computed, ref, watchEffect, toRefs } from 'vue'
// 搜索功能
const searchData = reactive({ name: '', age: '', sex: 0 })
const searchOption = reactive({ nameOption: [], ageOption: [] ,sexOption: [] })
const getSearchOption = () => {
fetch('searchOption').then(( { nameOption,ageOption,sexOption }) => {
searchOption.nameOption = nameOption
searchOption.ageOption = ageOption
searchOption.sexOption = sexOption
})
})
}
// table 列表
const tableData = reactive({
list: [],
total: 0,
page: 1,
pageSize: 10,
loading: false,
})
const getList = () => {
tableData.loading = true
fetch('getList', searchData).then(({ list, total }) => {
tableData.list = list
tableData.total = total
tableData.loading = false
})
}
其实这样的代码没什么问题,功能清晰,一目了然,但是写了一段时间我就放弃了这种写法,因为现实中的业务场景更复杂,并没有那么多的心力去拆解和维护功能区。上面的代码其实是一个相对理想的状态,搜索去的代码和表格并没有太多耦合,维护者可以一眼看到当前页面的两个功能,但实际中业务的复杂度更大,耦合更深,功能越多,就会发现搜索和表格之间的功能就会很难界定,需要花费更多的时间思考这段逻辑属于哪个功能区,于是我又回到了类 vue2
的写法。
import { reactive, computed, ref, watchEffect, toRefs } from 'vue'
// data
const searchData = reactive({ name: '', age: '', sex: 0 })
const searchOption = reactive({ nameOption: [], ageOption: [] ,sexOption: [] })
const tableData = reactive({
list: [],
total: 0,
page: 1,
pageSize: 10,
loading: false,
})
// methods
const getSearchOption = () => {
fetch('searchOption').then(( { nameOption,ageOption,sexOption }) => {
searchOption.nameOption = nameOption
searchOption.ageOption = ageOption
searchOption.sexOption = sexOption
})
})
}
const getList = () => {
this.loading = true
fetch('getList', searchData).then(({ list, total }) => {
tableData.list = list
tableData.total = total
tableData.loading = false
})
}
开始的我也会疑惑,这不就是面条代码么?但长久的实践让我意识到 composition-api
并不是全盘否定 vue2
时代 options-api
data 、 methods 分离的理念,而是在此之外提供了逻辑组合能力。如果想要抽象逻辑,可以使用 自定义hook
。
import { reactive, computed, ref, watchEffect, toRef } from 'vue'
// data
const searchData = reactive({ name: '', age: '', sex: 0 })
const searchOption = reactive({ nameOption: [], ageOption: [] ,sexOption: [] })
const tableData = reactive({
list: [],
total: 0,
page: 1,
pageSize: 10,
loading: false,
})
const { pageChange } = usePagination(toRef(tableData, 'page'), toRef(tableData, 'pageSize'))
const { x, y } = useScroll()
// methods
const getSearchOption = () => {
fetch('searchOption').then(( { nameOption,ageOption,sexOption }) => {
searchOption.nameOption = nameOption
searchOption.ageOption = ageOption
searchOption.sexOption = sexOption
})
})
}
const getList = () => {
this.loading = true
fetch('getList', searchData).then(({ list, total }) => {
tableData.list = list
tableData.total = total
tableData.loading = false
})
}
简而言之就是,当代码保持在一个合理的行数
(比如 500 行)和进行合理命名
,业务耦合不深或者一些复杂逻辑可以单独抽取成自定义 hook
,这样我想很难写成面条代码。
ref 和 reactive
vue3
中通过 ref 和 reactive 两个 API 来为数据添加响应式。当然,这并不算是一个很舒服的方式。
import { reactive, ref } from 'vue'
const count = ref(0)
const data = ref([])
count.value++
data.value.push(1)
data.value = [1, 2, 3]
想要为基本数据类型添加响应式就绕不过去 .value
这个操作,但还算一个可接受操作,如果加持了 ts
也不会感到不适,真正麻烦的是 Array
和 Object
。
import { reactive, ref } from 'vue'
const data = ref([])
const data2 = reactive([])
data.value.push(1)
data2.push(1)
data.value = [1, 2, 3]
data2.splice(0, data2.length, ...[1, 2, 3])
上述例子,
ref
和reactive
对数组的操作都不是很舒服,ref
利于重置,reactive
在数组读写操作上更直观,但无法中和,个中取舍只能自己去衡量。
我实验了一些方案,比如只用 ref
或者只用 reactive
,最后还是决定混用。我发现业务代码中对于重置和读写行为并不需要太多的心智负担,大部分的 table
list
tree
数据基本不需要什么写入操作,只需要调取接口的时候重置就可以。而 form
表单一类需要频繁操作的数据,可以用 reactive
,很少会出现覆盖掉整个对象或者数组的情况。
watch 和 watchEffect
在
vue3
中,可以通过watchEffect
和watch
监听数据触发响应,无论哪个相比vue2
中的watch
功能上都有所加强。watchEffect
更类似vue2
中的computed
隐式监听响应,watch
显示监听并且可监听多个。
在业务场景中一些比较复杂的逻辑我更喜欢使用 watch
,因为 watch
API 更加 显式
。
import { watchEffect, watch } from 'vue'
watchEffect(() => {
if (searchData.name) {
getTableData()
} else {
// ...
}
})
watch(
() => searchData.name,
name => {
if (name) {
getTableData()
} else {
// ...
}
}
)
可以看出,虽然 watch
繁琐了一些,但是相对 watchEffect
不需要太多的理解成本,显式的表露当前响应是基于 searchData.name
,在后期维护中更直观 。
另一点,触发的 getTableData()
函数,如果其内部也有响应式数据,使用 watchEffect
会增加不必要的侦听,甚至导致无限递归。复杂逻辑下使用 watch
可以让代码更可控。
结束
上面就是我写 vue3
的一些小总结,如果你也在用,可以一起交流一下。