认识
一、认识
setup
是 Vue.js 3.0
新增的组件选项, 主要用于配合 组合式 API
, 为用户提供一个地方, 用于建立组合逻辑、创建响应式数据、创建通用函数、注册生命周期钩子等能力。在组件的整个生命周期中, setup
函数只有在被挂载时执行一次。
1.1 生命周期图示
1.2 使用场景汇总
-
结合单文件组件使用的组合式
API
<script setup>
</script> -
基于选项式
API
中集成基于组合式API
<script>
export default {
setup(){
return {
}
}
}
</script>
二、结合单文件组件使用的组合式 API
<script setup>
</script>
三、基于选项式 API 中集成基于组合式 API
const Component = {
props: {
a: String,
b: Number,
}
setup(props,setupContext){
const { a,b } = props;
const { slots, emit, attrs, expose } = setupContext;
return 函数/对象
}
}
3.1 props
setup
的第一个参数 props
可以取得外部为组件传递的 props
数据对象
3.2 setupContext
setup
的第二个参数 setupContext
保存着与组件接口相关的数据和方法
-
slots
: 组件接收到的插槽 -
emit
: 一个函数, 用来发射自定义事件 -
attrs
: 当为组件传递props
时, 没有显示地声明为props
的属性会存储到attrs
对象中 -
expose
: 一个函数, 用来显示地对外暴露组件数据,
3.3 setup return
setup
的返回值有两种情况:
-
返回一个函数: 该函数作为组件的
render
函数。这种方式常用于组件不是以模版来表达其渲染内容的情况。如果组件以模版来表达其渲染的內容,那么setup
函数不可以再返回函数,否则会与模版编译生成的渲染函数产生冲突。const Component = {
setup(){
return ()=>{
return {
type: 'div',
children: 'hello div'
}
}
}
} -
返回一个对象: 该对象中包含的数据将暴露给模版使用
const Component = {
setup(){
return {
a: 1,
b: 2
}
},
render(){
return {
type: 'div',
children: `a 为: ${this.a}; b 为: ${this.b}`
}
}
}
四、问题
4.1 什么是组合式 API ?
组合式 API
(Composition API
) 是一系列 API
的集合,使我们可以使用函数而不是声明选项的方式书写 Vue
组件。它是一个概括性的术语,涵盖了以下方面的 API
:
-
响应式
API
: 例如ref()
和reactive()
,使我们可以直接创建响应式状态、计算属性和侦听器。 -
生命周期钩子: 例如
onMounted()
和onUnmounted()
,使我们可以在组件各个生命周期阶段添加逻辑。 -
依赖注入: 例如
provide()
和inject()
,使我们可以在使用响应式API
时,利用Vue
的依赖注入系统。
虽然这套 API
的风格是基于函数的组合,但组合式 API
并不是函数式编程。组合式 API
是以 Vue
中数据可变的、细粒度的响应性系统为基础的,而函数式编程通常强调数据不可变。
4.2 为什么要有组合式 API ?
-
更好的逻辑复用: 组合式
API
最基本的优势是它使我们能够通过组合函数来实现更加简洁高效的逻辑复用。在选项式API
中我们主要的逻辑复用机制是mixins
,而组合式API
解决了mixins
的所有缺陷。 -
更灵活的代码组织: 许多用户喜欢选项式
API
的原因是它在默认情况下就能够让人写出有组织的代码: 大部分代码都自然地被放进了对应的选项里。然而,选项式API
在单个组件的逻辑复杂到一定程度时,会面临一些无法忽视的限制。这些限制主要体现在需要处理多个逻辑关注点的组件中。处理相同逻辑关注点的代码被强制拆分在了不同的选项中,位于文件的不同部分。在一个几百行的大组件中,要读懂代码中的一个逻辑关注点,需要在文件中反复上下滚动,这并不理想。另外,如果我们想要将一个逻辑关注点抽取重构到一个可复用的工具函数中,需要从文件的多个不同部分找到所需的正确片段。而如果用组合式API
重构这个组件, 现在与同一个逻辑关注点相关的代码被归为了一组:我们无需再为了一个逻辑关注点在不同的选项块间来回滚动切换。此外,我们现在可以很轻松地将这一组代码移动到一个外部文件中,不再需要为了抽象而重新组织代码,大大降低了重构成本,这在长期维护的大型项目中非常关键。组合式
API
不像选项式API
那样会手把手教你该把代码放在哪里。但反过来,它却让你可以像编写普通的JavaScript
那样来编写组件代码。这意味着你能够,并且应该在写组合式API
的代码时也运用上所有普通JavaScript
代码组织的最佳实践。如果你可以编写组织良好的JavaScript
,你也应该有能力编写组织良好的组合式API
代码。选项式
API
确实允许你在编写组件代码时“少思考”,这是许多用户喜欢它的原因。然而,在减少费神思考的同时,它也将你锁定在规定的代码组织模式中,没有摆脱的余地,这会导致在更大规模的项目中难以进行重构或提高代码质量。在这方面,组合式API
提供了更好的长期可维护性。 -
更好的类型推导: 组合式
API
主要利用基本的变量和函数,它们本身就是类型友好的。用组合式API
重写的代码可以享受到完整的类型推导,不需要书写太多类型标注。大多数时候,用TypeScript
书写的组合式API
代码和用JavaScript
写都差不太多!这也让许多纯JavaScript
用户也能从IDE
中享受到部分类型推导功能。 -
更小的生产包体积: 搭配
<script setup>
使用组合式API
比等价情况下的选项式API
更高效,对代码压缩也更友好。这是由于<script setup>
形式书写的组件模板被编译为了一个内联函数,和<script setup>
中的代码位于同一作用域。不像选项式API
需要依赖this
上下文对象访问属性,被编译的模板可以直接访问<script setup>
中定义的变量,无需从实例中代理。这对代码压缩更友好,因为本地变量的名字可以被压缩,但对象的属性名则不能。
4.3 组合式 API 与 Class API 的关系?
我们不再推荐在 Vue 3
中使用 Class API
,因为组合式 API
提供了很好的 TypeScript
集成,并具有额外的逻辑重用和代码组织优势。
4.4 组合式 API 与 React Hooks 的关系?
组合式 API
提供了和 React Hooks
相同级别的逻辑组织能力,但它们之间有着一些重要的区别。
React Hooks
在组件每次更新时都会重新调用。这就产生了一些即使是经验丰富的 React
开发者也会感到困惑的问题。这也带来了一些性能问题,并且相当影响开发体验。例如:
-
Hooks
有严格的调用顺序,并不可以写在条件分支中。 -
React
组件中定义的变量会被一个钩子函数闭包捕获,若开发者传递了错误的依赖数组,它会变得“过期”。这导致了React
开发者非常依赖ESLint
规则以确保传递了正确的依赖,然而,这些规则往往不够智能,保持正确的代价过高,在一些边缘情况时会遇到令人头疼的、不必要的报错信息。 -
昂贵的计算需要使用
useMemo
,这也需要传入正确的依赖数组。 -
在默认情况下,传递给子组件的事件处理函数会导致子组件进行不必要的更新。子组件默认更新,并需要显式的调用
useCallback
作优化。这个优化同样需要正确的依赖数组,并且几乎在任何时候都需要。忽视这一点会导致默认情况下对应用进行过度渲染,并可能在不知不觉中导致性能问题。 -
要解决变量闭包导致的问题,再结合并发功能,使得很难推理出一段钩子代码是什么时候运行的,并且很不好处理需要在多次渲染间保持引用 (通过
useRef
) 的可变状态。
相比起来,Vue
的组合式 API
:
-
仅调用
setup()
或<script setup>
的代码一次。这使得代码更符合日常JavaScript
的直觉,不需要担心闭包变量的问题。组合式API
也并不限制调用顺序,还可以有条件地进行调用。 -
Vue
的响应性系统运行时会自动收集计算属性和侦听器的依赖,因此无需手动声明依赖。 -
无需手动缓存回调函数来避免不必要的组件更新。
Vue
细粒度的响应性系统能够确保在绝大部分情况下组件仅执行必要的更新。对Vue
开发者来说几乎不怎么需要对子组件更新进行手动优化。
我们承认 React Hooks
的创造性,它是组合式 API
的一个主要灵感来源。然而,它的设计也确实存在上面提到的问题,而 Vue
的响应性模型恰好提供了一种解决这些问题的方法。