Vue 3.0 上手(一)基础 API 的上手
2020 年年初,Vue 的 3.0 alpha 版本就发布了。五一前后,beta 版页开始了。按照计划,预计十一前就可以发布正式版了。新的 Vue 和 React 一样,加入了 Hooks 方案,叫做 composition-api
(组合式 API
)。我比较感兴趣,也比较关注
composition-api
和 React-hooks
的一些区别,特来上手体验一番。
本篇文章开始写于 2020 年 7 月底,当时为 3.0.0-rc.2
。后来部分内容已经升级到 3.0.0-rc.5
。
建立项目
Vue 3.0 目前还需要通过 2.0 的架子升级而来。
- 全局安装 vue-cli
npm install -g @vue/cli
- 创建一个测试项目,创建项目时候,要把需要的功能都选择上,否则后续自己升级比较麻烦
vue create vue-next-test
- 把测试项目,升级为 vue-next(Vue 3.0)
cd vue-next-test
vue add vue-next
- 所有的文件,都将会被升级。之后就可以愉快玩耍 vue-next(Vue 3.0)了。
文档和新增内容
我只参考了官方的文档,参见这里:组合式 API 征求意见稿 和这里 API 参考。
Vue 3 新增了很多语法、API,以及 Vue 3 对于 Vue 2 的变化等。故需要学习的点主要有:
- 项目中 Vue 的实例启动引导,如改用
createApp
创建 Vue 实例等; - 基础响应式系统API:
reactive
ref
computed
readonly
watchEffect
watch
。其中ref
的理解、以及ref
拆包是重中之重; - 生命周期钩子:
onBeforeMount
onMounted
onBeforeUpdate
onUpdated
onBeforeUnmount
onUnmounted
onErrorCaptured
,以及和原有钩子关系; - 依赖注入:
provide
inject
,提供类似简易 VueX 的能力; - html 模板中使用
ref
获取 DOM 元素; - 响应式工具:
unref
toRef
toRefs
isRef
isProxy
isReactive
isReadonly
; - 高级API:
customRef
markRaw
shallowReactive
shallowReadonly
shallowRef
toRaw
。
我认为,以上内容大部分都需要比较熟悉,除了:
- 生命周期可以只看自己常用的,剩下的大部分估计很少用到;
- 依赖注入可以先不看,等其他都了解后,再学习也不迟;
- 响应式工具中,
is
开头的判断工具可以不着急,目前来看,一般项目开发应该用不到;但ref
系列工具得了解,真正做项目时候要用的; - 高级 API 不用着急,这些都是提供更强大的自定义能力,目前感觉主要作用还是来提升性能的,可以后续了解学习。
基础 API 的上手
过于基础的演示,可以自行查看官方例子以及尝试下。这里仅说明我遇到的一些细节点。
setup 的返回值中 state 处理
export default {
setup() {
const state = reactive({
count: 0,
sum: computed(() => state.count * 9.99)
})
const onAddCountClick = () => {
state.count++
}
const onTimeoutAddCountClick = () => {
setTimeout(() => {
state.count++
}, 1000)
}
return {
// ...state <-- 错误,不能展开,否则会失去响应
// count: state.count <-- 错误,同样不可以返回数值型,也会失去响应
// 必须直接返回 state
state,
onAddCountClick,
onTimeoutAddCountClick,
}
}
}
返回 state
时,不能进行展开返回,否则会失去响应式。
此外,在事件中增加 state
数值,和 React-hooks 截然不同。Vue 写 setTimeout
异步增加数值,最终真的会多次改变数值,相当于每次执行逻辑都会获取最新的 state
,这点非常容易理解。React 则完全不同,React 需要采用
ref
才可以实现多次改变数值。
watchEffect / watch
watchEfflect
相当于 React-hooks useEffect
watch
就是 Vue2.0 中的 $watch
watchEffect
/watch
区别:
watchEffect
会立即执行一次,watch
只有在观察对象改变时执行watchEffect
自动收集那些元素需要观察,watch
需要主动声明watchEffect
只关注改变及改变后的结果,watch
关注改变同时关注改变前后的具体内容watchEffect
默认是在状态改变并 视图更新后触发(可以配置为状态改变后触发以及视图更新同时触发,和 React-hooksuseEffect
useLayoutEffect
差不多的意思),watch
估计是状态改变并视图更新后触发。在默认不改变watchEffect
的flush
情况下,watchEffect
watch
代码先写谁,谁就被先执行。
export default {
setup() {
const state = reactive({
count: 0
})
const onAddCountClick = () => {
state.count++
}
// watchEffect 相当于 React-hooks useEffect
// 只不过不需要手动指定依赖项
watchEffect(() => {
console.log('[watchEffect]', state.count)
}, {flush: 'pre'})
return {
state,
onAddCountClick,
}
// watch 要求指定观察对象的,和Vue2.0的$watch基本相同
// vm.$watch('a.b.c', function (newVal, oldVal) {
// do something..
// })
watch(
() => state.sum,
(newVal, oldVal) => {
console.log('[watch]', newVal, oldVal)
}
)
}
}
如果需要关注数值前后的变化,还是要用 watch
。
如果仅仅关注新值,可以使用更简单的 watchEffect
。比如状态改变时,触发请求服务器。这样写还可以省去单独触发第一次默认请求。
不过要注意的是,要保证 watchEffect
流程稳定。这一点类似于 React-hooks 的 useState
数量顺序必须一致。例如:
watchEffect(() => {
const rnd = Math.random() > 0.5
if (!rnd) {
console.log(state.count)
}
})
一旦 rnd
是 true
,则后续 state.count
无论怎么变化,也不会触发 watchEffect
了。
Ref 是包裹对象,和 React ref类似
注意:
computed
也是ref
的一个特例。
export default {
setup() {
const count = ref(0)
const onAddCountClick = () => {
count.value++
}
return {
count, // 由于 ref 本身就是对象,所以可以直接进行返回
onAddCountClick,
}
}
}
ref
包裹的内容,要使用 .value
进行拆包后取值或者操作,如 count.value++
但,以下情况除外:
- 在模板中使用,会自动解析
.value
,无需人工拆包,例如直接写:<p>{{count}}</p>
; - 在响应式方法中,比如
computed
就是ref
高级用法,以及在reactive
中,无需.value
。例如:
const count = ref(0)
const count1 = reactive({count})
const count2 = reactive([count])
const count3 = computed(() => count1)
console.log('[Ref]0:', count.value)
console.log('[Ref]1:', count1.count) // 对象形式reactive,不用写也不能写 count1.count.value,因为会自动拆包
console.log('[Ref]2:', count2[0].value) // 非对象形式的reactive,必须手动拆包
console.log('[Ref]3:', count3.value.count) // computed 需要手动拆包
ref
应该非常常用,犹如 React-hooks 中一样重要。除了 reactive
保存响应式数据外,引用数据就靠 ref
了。
Readonly
const origin = reactive({count: 0})
const copy = readonly(origin)
origin.count++
console.log('[readonly]1:', origin.count)
console.log('[readonly]2:', copy.count)
copy.count++ // 无法修改,会被warn警告,但不是报错
console.log('[readonly]3:', copy.count)
Vue 的开发者不就是反复修改 state 么,我没想出为啥要出这个。我唯一想到的就是,父组件给子组件传参,参数是对象,此时可以用 readonly
,防止子组件修改参数。能想到这个,是因为早期我就是这么操作来实现父子组件通讯的...