Vue 3.0 上手(二)高级 API 及生命周期的上手

上一次学习了基础的 API ,这次学习一些高级一点的 API。

Vue 初始化变化(上一篇章遗漏了)

原来的写法是:

1
2
3
4
5
6
7
8
9
// 创建实例及挂载插件
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')

// 全局挂载
Vue.prototype.$toast = msg => console.log(msg)

新的写法是:

1
2
3
4
5
6
// 创建实例,第二个可以配置全局参数
const app = createApp(App, { user: 'yukaPriL' })
// 挂载插件
app.use(router).use(store).mount('#app')
// 全局挂载
app.config.globalProperties.$toast = msg => console.log(msg)

main.js 一般都是项目生成出来,很少自己改,所以还好。

高级 API 用法

高级用法主要是更好的实现逻辑,是对基础 API 的封装和补充。

toRef / toRefs

之前文章提到过一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
setup () {
const state = reactive({
count: 0,
sum: computed(() => state.count * 9.99)
})

...

return {
// ...state <-- 错误,不能展开,否则会失去响应
// count: state.count <-- 错误,同样不可以返回数值型,也会失去响应
// 必须直接返回 state
state
}
}

直接返回时候,是不能进行对象展开的,展开后会导致失去响应。

但我们可以对返回内容进行包裹,再实现一次响应式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
setup () {
const state = reactive({
v1: 1,
v2: 2
})
const state2 = reactive({
v3: 3
})
setTimeout(() => {
state.v1++
state.v2++
state2.v3++
}, 2000)

return {
...toRefs(state),
v3: toRef(state2, 'v3')
}
}

通过使用 toRef toRefs,使得展开后的对象仍然具有响应性,不管我们后续再模板中使用,还是写 hooks 组件,都可以直接使用其值(而不是需要访问对象的属性):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 现在可以这样写: -->
<template>
<div>
<h1>ToRefs</h1>
<p>v1:{{ v1 }}</p>
<p>v2:{{ v2 }}</p>
<p>v3:{{ v3 }}</p>
</div>
</template>

<!-- 之前只能这样写: -->
<template>
<div>
<h1>ToRefs</h1>
<p>v1:{{ state.v1 }}</p>
<p>v2:{{ state.v2 }}</p>
<p>v3:{{ state2.v3 }}</p>
</div>
</template>
1
2
3
4
5
6
// 封装 hooks 后,别人调用可以这样写:
const { v1, v2, v3 } = useDemo()

// 封装 hooks 后,之前只能这样写:
// state 不可以再进行解构,否则失去响应
const { state, state2 } = useDemo()

总之,toRef toRefs 在项目中应该非常常用!

新的内置组件:Teleport (传送门)

比如官网的例子:

1
2
3
4
5
6
7
8
<body>
<div style="position: relative;">
<h3>Tooltips with Vue 3 Teleport</h3>
<div>
<modal-button></modal-button>
</div>
</div>
</body>

这个 modal-button 最后生成 html 后,就在 body > div > div 下,如果想把它生成在 body 最后一个节点后,基本上比较难实现。

之所以认为比较难实现,是因为我只会 vue 2 的基础用法,复杂的根本不会…

React 一直都有一个 createPortal,可以很方便的解决这个问题。

这次,Vue 3 提供了这样一个组件来实现。

is 系列 isRef、isProxy、isReactive、isReadonly

最早使用 rc2 版本测试过一次,后来发现 rc5 版本发生了一些变化,倒是无法解释了。所以在出正式版之前,不会在测试了。

等正式版出来后再测试看看,如果能解释就放出来,解释不了就等等看其他人有没有高见。

生命周期

估计是为了更容易上手新的 composition-api,Vue 3.0 还提供了一套生命周期概念。这点比 React-hooks 容易多了。不过这些生命周期应该在实际项目中很少被用到,因为没有生命周期的 React-hooks 一样可以做的很好。

1
2
3
4
5
6
7
8
beforeCreate -> 使用 setup()
created -> 使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted

beforeCreate:我从来没有接触过,这个不多说,感觉是模拟不了的。

created:现在直接在 setup() 里写就行了。

beforeMount mounted:对于 Vue 确实需要,否则无法判断元素是否挂载完成。虽然可以进行模拟,但比较场景化,不同场合写法会有出入。React 是通过 useEffect(()=>{}, []) 来模拟。对应的 Vue 可以用 watchEffect 模拟。watch 应该不行,因为默认不会执行第一次监听。具体实现可以参考下文例子。要注意的是,功能虽然实现了,但执行次数超过一次,需要特殊处理。

destroyed:由于 Vue watchEffect 也支持清理副作用 onInvalidate,所以可能模拟出组件销毁,不过估计也要借助 ref 等其他辅助参数来实现,成本一样非常高。

beforeUpdate:我也从来没用过,也不多说了,感觉是模拟不了的。

updated:肯定可以通过 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
29
30
31
32
33
export default {
name: 'Lifecycles',
setup () {
console.log('[Lifecycles] setup')

const state = reactive({ value: 0, list: [] })
const textRef = ref(null)

const onTestClick = () => {
state.value++
}

watchEffect(() => {
console.log('Lifecycles 模拟mounted -- L1') // 这里会执行2次,第一次相当于 beforeMount,第二次是mounted
// 所以这里要判断,如果是 beforeMount,是没有 textRef.value 值的
if (textRef.value) textRef.value.textContent = '12345'
})

watchEffect(() => {
// 比如发送请求
console.log('Lifecycles 模拟mounted -- L2') // 如果 state.list 后续不会有任何修改,这里可以模拟mounted
setTimeout(() => {
state.list = [1, 2, 3, 4, 5]
})
})

return {
state,
textRef,
onTestClick,
}
}
}

–END–