Vue 3.0 上手(一)基础 API 的上手

2020 年年初,Vue 的 3.0 alpha 版本就发布了。五一前后,beta 版页开始了。按照计划,预计十一前就可以发布正式版了。新的 Vue 和 React 一样,加入了 Hooks 方案,叫做 composition-api组合式 API)。我比较感兴趣,也比较关注 composition-apiReact-hooks 的一些区别,特来上手体验一番。

本篇文章开始写于 2020 年 7 月底,当时为 3.0.0-rc.2。后来部分内容已经升级到 3.0.0-rc.5

建立项目

Vue 3.0 目前还需要通过 2.0 的架子升级而来。

  1. 全局安装 vue-cli

    1
    npm install -g @vue/cli
  2. 创建一个测试项目,创建项目时候,要把需要的功能都选择上,否则后续自己升级比较麻烦

    1
    vue create vue-next-test
  3. 把测试项目,升级为 vue-next(Vue 3.0)

    1
    2
    cd vue-next-test
    vue add vue-next
  4. 所有的文件,都将会被升级。之后就可以愉快玩耍 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

我认为,以上内容大部分都需要比较熟悉,除了:

  1. 生命周期可以只看自己常用的,剩下的大部分估计很少用到;
  2. 依赖注入可以先不看,等其他都了解后,再学习也不迟;
  3. 响应式工具中,is 开头的判断工具可以不着急,目前来看,一般项目开发应该用不到;但 ref 系列工具得了解,真正做项目时候要用的;
  4. 高级 API 不用着急,这些都是提供更强大的自定义能力,目前感觉主要作用还是来提升性能的,可以后续了解学习。

基础 API 的上手

过于基础的演示,可以自行查看官方例子以及尝试下。这里仅说明我遇到的一些细节点。

setup 的返回值中 state 处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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-hooks useEffect useLayoutEffect 差不多的意思),watch 估计是状态改变并视图更新后触发。在默认不改变 watchEffectflush 情况下,watchEffect watch 代码先写谁,谁就被先执行。

此外,两者都支持停止侦听清除副作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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 数量顺序必须一致。例如:

1
2
3
4
5
6
watchEffect(() => {
const rnd = Math.random() > 0.5
if (!rnd) {
console.log(state.count)
}
})

一旦 rndtrue,则后续 state.count 无论怎么变化,也不会触发 watchEffect 了。

Ref 是包裹对象,和 React ref类似

注意:computed 也是 ref 的一个特例。

1
2
3
4
5
6
7
8
9
10
11
12
setup () {
const count = ref(0)

const onAddCountClick = () => {
count.value++
}

return {
count, // 由于 ref 本身就是对象,所以可以直接进行返回
onAddCountClick,
}
},

ref 包裹的内容,要使用 .value 进行拆包后取值或者操作,如 count.value++

但,以下情况除外:

  • 在模板中使用,会自动解析 .value,无需人工拆包,例如直接写:<p></p>
  • 在响应式方法中,比如 computed 就是 ref 高级用法,以及在 reactive 中,无需 .value。例如:
1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
7
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,防止子组件修改参数。能想到这个,是因为早期我就是这么操作来实现父子组件通讯的…

–END–