共计 3555 个字符,预计需要花费 9 分钟才能阅读完成。
写在前面
在分享 vue-next
各个子模块的实现之前,我觉的有必要比较全面的整理下 vue-next
中提出的函数式 api
,了解这些的话,无论是对于源码的阅读,还是当正式版发布时开始学习,应该都会有起到一定的辅助作用。
类似的东西在网上有很多,只是会比较零碎,同时有些也相对过时了,当然当前整理的这些也有可能会过时,毕竟代码还处于 pre-alpha
的阶段,但其中的设计思想应该是不会改变了。
往期文章
- 15 分钟掌握 vue-next 响应式原理
- vue-next/runtime-core 源码阅读指南
setup
setup
会作为编写组件业务逻辑的主战场,各种 hook
类型的方法均需要在 setup
这个作用域下调用,直接来看 RFC
中的例子:
<template>
<button @click="increment">
Count is: {{state.count}}, double is: {{state.double}}
</button>
</template>
<script>
import {reactive, computed} from 'vue'
export default {setup() {
const state = reactive({
count: 0,
double: computed(() => state.count \* 2)
})
function increment() {state.count++}
return {
state,
increment
}
}
}
</script>
其代码结构看起来和 vue2
基本保持一致,不过有以下几点需要注意:
-
setup
本身的调用时机,从目前的源码来看,介于beforeCreate
和created
这两个生命周期之间,言外之意,就是你无法在这里使用this
指向当前组件实例 - 对于
state
的声明,我们使用reactive
这个api
来构造,对于computed state
的声明,使用computed
,两者本质上均是Proxy
对象 - 对于组件方法的声明,我们直接通过声明普通函数的方式进行声明即可,对于
state
的变更,直接通过闭包使用reactive
返回的Proxy
对象即可 -
setup
的返回值被称作render context
,顾名思义,就是模板中可以访问到的各种数据和方法的上下文对象,我在之前的文章中曾提及,模板在解析数据时,会优先考虑从data
对象取值,之后就是这个render context
了 - 除了返回
render context
,还可以返回模板渲染函数,对,就是那个h(...)
,这种形式对应的情况是,当我们没有声明template
属性时
在 vue-next 中,我们直接从 vue
导入 reactive
、computed
以及其他的 api
即可,如果在 vue2
版本上,我们还可以通过使用 composition-api 这个 plugin
来使用这些 api
。
state
声明 state 主要有以下几种形式。
基础类型
基础类型可以通过 ref
这个 api
来声明,如下:
import {ref} from "vue";
export default {setup() {const count = ref(0);
function inc() {count.value++;}
return {count, inc};
}
};
之所以要通过 ref
,是因为 js
中的基础类型传值不是引用传值,因此 vue-next
内部会自动将它封装为一个 ref
对象,其值指向原始值。当然,ref
指向引用类型也是没有问题的,其 value
指向引用类型变量的引用。
引用类型
引用类型除了可以使用 ref
来声明,也可以直接使用 reactive
,如下:
import {reactive} from "vue";
export default {setup() {
const state = reactive({count: 0});
function inc() {state.count++;}
return {state, inc};
// 或者通过 toRefs 方法
// return {...toRefs(state), inc };
}
};
这里使用 toRefs
的原因主要是因为,如果是 reactive 产生的对象,由于我们要只是保存对于该 Proxy 对象的引用,我们无法使用解构来将它 flat
,而 toRefs
会将每一个属性在内部包裹为一个 ref
对象。
props
对于 prop
可以通过如下代码声明及使用:
export default {
props: {count: Number},
setup(props) {watch(() => {console.log(\`count is: \` + props.count)
})
}
}
这里可以发现其实和 vue2
中声明的方式基本一样,但值得注意的是,在 vue-next
中,props
的类型声明不是必须的,如果你使用 typescript
,完全可以改写为如下的版本:
interface PropTypesI {count: number}
export default {setup(props: PropTypesI) {watch(() => {console.log(\`count is: \` + props.count)
})
}
}
除此之外,还可以直接通过 watch
方法来观察某个 prop
的变动,这是为什么呢?答案非常简单,就是 props
本身在源码中,也是一个被 reactive
包裹后的对象,因此它具有响应性,所以在 watch
方法中的回调函数会自动收集依赖,之后当 count
变动时,会自动调用这些回调逻辑。
context
在 setup
那一小节中,我们知道,setup
在调用时,组件实例还未创建,那意味着我们无法使用 this
访问当前实例,那我想通过 this
在 vue2
中访问一些内置属性,怎么办?比如 attrs
或者 emit
。我们可以通过 setup
的第二个参数:
setup(props, context) {do anything...}
这个 context
对象也是一个 Proxy
对象,当我们通过 context.attrs
访问其属性时,本质上代理对象会将访问指向组件的内部实例(即之间文章中提及的 componentInternalInstance
)。
生命周期
每一个 vue2
中的组件生命周期函数,当前都对应一个生命周期 hook
,比如:
import {onMounted, onUpdated, onUnmounted} from "vue";
setup() {onMounted(() => {...});
onUpdated(() => { ...});
onUnmounted(() => { ...});
}
这里值得注意的一点在于,对于 beforeCreate
和 created
生命周期,虽然有响应的 hook
,但是我觉的没有必要单独使用了,因为这些逻辑代码大部分是一些初始化逻辑的代码,直接写在 setup
方法中即可。
如何复用代码
在这个基础上,复用代码的方式也不再像 vue2
中的那样,通过 mixin
或者 HOC
来达到复用代码的目的,这里稍微说一下,这些复用代码方式中比较显著的缺点有:
- 隐藏了数据来源,主要体现在
mixin
中 - 会牺牲一些性能,主要体现在
HOC
中 - 可能会遇到命名冲突问题,主要体现在
mixin
中
在 vue-next
中,复用代码的逻辑本质上是利用这些 hook
来拆分业务逻辑与状态,如果你比较熟悉 react hooks
的话,应该很快就能明白我指的是什么意思。如果我们将逻辑都写在 setup
方法中,很快代码就会变得难以维护,在这方面,我们可以将一些耦合在一起的代码抽离出来,同时以 use
前缀命名,声明一个自定义的 hook
,如下:
export default {setup() {const data = useSearch()
const {sortedData, sort} = useSorting(data)
return {data, sortedData, sort}
}
}
function useSearch() {...fetch data}
function useSort(data) {...sort data}
除了以 inline
的方式来声明,还可以将这些自定义的 hook
声明在单独的文件中,直接通过 import
导入即可,比如:
import useSearch from '@hooks/search'
import useSort from '@hooks/sort'
与 react hooks
对比
vue-next
在这方面借鉴了 react hooks
的设计思想,但是从实现层来讲,它们是不一样的,主要有以下几点:
-
vue-next
不依赖于其调用顺序,而react
依赖 -
vue-next
提供了生命周期方法,而react
刻意模糊生命周期的概念 -
vue-next
基于响应式系统实现,意味它的依赖不需要显示声明(而且是自动的),而react
需要手动声明依赖数组
关注公众号 全栈_101,只谈技术,不谈人生