看完上一章 初入茅庐
之后,相信大家已经对vue-next(Vue 3.0)有所了解了。本章带你掌握 vue-next
函数式的API,了解这些的话,无论是对于源码的阅读,还是当正式版发布时开始学习,应该都会有起到一定的辅助作用。
基本例子
直接拷贝下面代码,去运行看效果吧。推荐使用高版本的chrome浏览器,记得打开F12调试工具哦!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="https://s1.zhuanstatic.com/common/js/vue-next-3.0.0-alpha.0.js"></script>
<div id="app"></div>
<script>
const { ref, reactive, createApp, watch, effect } = Vue
function useMouse() {
const x = ref(0)
const y = ref(0)
const update = e => {
x.value = e.pageX
y.value = e.pageY
}
Vue.onMounted(() => {
window.addEventListener('mousemove', update)
})
Vue.onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
const App = {
props: {
age: Number
},
// Composition API 使用的入口
setup(props, context){
console.log('props.age', props.age)
// 定义响应数据
const state = reactive({name:'zhuanzhuan'});
// 使用公共逻辑
const {x,y} = useMouse();
Vue.onMounted(()=>{
console.log('当组挂载完成')
});
Vue.onUpdated(()=>{
console.log('数据发生更新')
});
Vue.onUnmounted(()=>{
console.log('组件将要卸载')
})
function changeName(){
state.name = '转转';
}
// 创建监视,并得到 停止函数
const stop = watch(() => console.log(`watch state.name:`, state.name))
// 调用停止函数,清除对应的监视
// stop()
// 观察包装对象
watch(() => state.name, (value, oldValue) => console.log(`watch state.name value:${value} oldValue:${oldValue}`))
effect(() => {
console.log(`effect 触发了! 名字是:${state.name},年龄:${props.age}`)
})
// 返回上下文,可以在模板中使用
return {
// state: Vue.toRefs(state), // 也可以这样写,将 state 上的每个属性,都转化为 ref 形式的响应式数据
state,
x,
y,
changeName,
}
},
template:`<button @click="changeName">名字是:{
{state.name}} 鼠标x: {
{x}} 鼠标: {
{y}}</button>`
}
createApp().mount(App, '#app', {age: 123});
</script>
</body>
</html>
设计动机
逻辑组合与复用
组件 API 设计所面对的核心问题之一就是如何组织逻辑,以及如何在多个组件之间抽取和复用逻辑。基于 Vue 2.x 目前的 API 有一些常见的逻辑复用模式,但都或多或少存在一些问题。这些模式包括:
Mixins
高阶组件 (Higher-order Components, aka HOCs)
Renderless Components (基于 scoped slots / 作用域插槽封装逻辑的组件)
网络上关于这些模式的介绍很多,这里就不再赘述细节。总体来说,以上这些模式存在以下问题:
模版中的数据来源不清晰。举例来说,当一个组件中使用了多个
mixin
的时候,光看模版会很难分清一个属性到底是来自哪一个mixin
。HOC
也有类似的问题。命名空间冲突。由不同开发者开发的
mixin
无法保证不会正好用到一样的属性或是方法名。HOC
在注入的props
中也存在类似问题。性能。
HOC
和RenderlessComponents
都需要额外的组件实例嵌套来封装逻辑,导致无谓的性能开销。
从以上 useMouse
例子中可以看到:
暴露给模版的属性来源清晰(从函数返回);
返回值可以被任意重命名,所以不存在命名空间冲突;
没有创建额外的组件实例所带来的性能损耗。
类型推导
vue-next
的一个主要设计目标是增强对 TypeScript
的支持。原本期望通过 ClassAPI
来达成这个目标,但是经过讨论和原型开发,认为 Class
并不是解决这个问题的正确路线,基于 Class
的 API
依然存在类型问题。
基于函数的 API
天然对类型推导很友好,因为 TS
对函数的参数、返回值和泛型的支持已经非常完备。更值得一提的是基于函数的 API
在使用 TS
或是原生 JS
时写出来的代码几乎是完全一样的。
setup() 函数
我们将会引入一个新的组件选项, setup()
。顾名思义,这个函数将会是我们 setup
我们组件逻辑的地方,它会在一个组件实例被创建时,初始化了 props 之后调用。它为我们使用 vue-next
的 CompositionAPI
新特性提供了统一的入口。
执行时机
setup
函数会在 beforeCreate
之后、 created
之前执行。
state
声明 state
主要有以下几种类型。
基础类型
基础类型可以通过 ref
这个 api
来声明,如下:
const App = {
setup(props, context){
const msg = ref('hello')
function appendName(){
msg.value = `hello ${props.name}`
}
return {appendName, msg}
},
template:`<div @click="appendName">{
{ msg }}</div>`
}
我们知道在 JavaScript
中,原始值类型如 string
和 number
是只有值,没有引用的。如果在一个函数中返回一个字符串变量,接收到这个字符串的代码只会获得一个值,是无法追踪原始变量后续的变化的。
因此,包装对象的意义就在于提供一个让我们能够在函数之间以引用的方式传递任意类型值的容器。这有点像 ReactHooks
中的