本文带大家深入理解组合式 API 的设计详情,同时加入我们的实践经验总结。
背景
简介
Vue3.x
引入了组合式 API,它是一组附加的、基于功能的 API,用于灵活的组合组件逻辑。主要通过 setup
的生命周期进行组件的初始化,参考以下组合式 API 的基本使用方法:<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>
设计分析
options
的方式来组织,相同逻辑的代码分散到不同的 options 里,不符合就近原则
,因此通过逻辑上就近的原则来考虑来组织代码更加合理;Mixin 混合
组件逻辑的方式,存在来源不清晰的问题;this
上下文的方式暴露属性,这与原生 JS 中的 this
大相径庭,而这也对当前 Vue 与 TypeScript 的整合造成了极大的困难。vue-class-component
通过修饰符将组件以类的方式进行编写。在设计 Vue3.x
阶段,Vue 团队尝试提供内置的 Class API 来解决类型的问题,不过经过反复的讨论发现这种方式必须依赖修饰符,这种方式的不稳定性和不确定性导致其成为了一个有风险的基础建设。reactive和refs函数替代了原有的data,computed函数代替了computed属性,watchEffect函数代替了watch属性
。乍一看所有逻辑都混合在一起写在了 setup()
中,代码组织还不如使用 options,但是如果我们真正考虑代码组织的最终目标 —— 更加容易的理解代码,我们会发现仅仅知道一个复杂的组件有哪些选项并不能帮助我们阅读理解代码,从而理解整个组件的代码逻辑。当开发者们阅读其他人编写的组件代码的时候,比起「组件使用了哪些选项?」,他们更在意的是「组件想要做什么?」。name
和 gender
,两个方法 getName
和 getGender
,在调用时可以获取到 people
对象中的 name
和 gender
属性,代码如下所示:const component = {
data() {
return {
people: {
name: 'maxuxiao',
gender: 'male'
},
name: '',
gender: ''
};
},
methods: {
getName() {
this.name = this.people.name;
},
getGender() {
this.gender = this.people.gender;
}
}
};
setup
函数的复杂度,避免 setup 代码量越来越多,return 的对象越来越复杂情况这种面条代码
的产生。我们期望的是 setup 函数现在只是简单地作为调用所有组合函数的入口,参考以下功能分组的方式:const component = {
setup() {
const people = {
name: 'maxuxiao',
gender: 'male'
};
return {
...useName(people),
...useGender(people)
};
}
};
const component2 = {
setup() {
const people = {
name: 'chenmingming',
gender: 'male'
};
return {
...useName(people),
...useGender(people)
};
}
};
// 处理 name 相关的业务
function useName(people) {
const name = Vue.ref('');
const getName = () => {
name.value = people.name;
};
return {
name,
getName
};
}
// 处理 gender 相关的业务
function useGender(people) {
const gender = Vue.ref('');
const getGender = () => {
gender.value = people.gender;
};
return {
gender,
getGender
};
}
setup
函数负责将它们组合起来。通过这种方式也达到了组合式 API 设计的另外一个核心目的:让相同的代码逻辑在不同组件中低成本的抽取和复用。不过对 Vue3.x
来说,组合式 API 并不是默认的方案,它被定义为高级特性,意在解决大型应用程序中的复杂组件的编写。组件逻辑复用和代码组织
的机制主要做法有:来源不清晰、容易冲突、类型推导不明确
等问题。HOC 高阶组件
可以看作 React
对装饰模式的一种实现,高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。高阶组件(HOC)是 React 中的高级技术,用来重用组件逻辑。但高阶组件本身并不是 React API。它只是一种模式,这种模式是由 React 自身的组合性质必然产生的。
function visible(WrappedComponent) {
return class extends Component {
render() {
const { visible, ...props } = this.props;
if (visible === false) return null;
return ;
}
}
}
业界观点
props
、computed
、watch
、methods
、生命周期
等进行分组,存在一定约束,就算是低级别的程序员,也不会写出太杂乱无章的代码。但组合式 API,没有这些约束,容易导致『意大利面条式』代码。项目实践
props
、computed
、watch
、methods
、生命周期
都放在 setup 中,这显然是不合适的做法,也进一步应证了『意大利面条』的说法。非业务组件
;共享逻辑的组件
,包含 mixin、HOC 高阶组件、slot 等场景;多种Options或生命周期钩子函数
;总结
缺乏经验的开发者对组合式API的滥用会使得代码更加晦涩难懂
。Options API 通过约定我们该在哪个位置做什么事,一定程度上也强制我们进行了代码分隔。而没有正确进行逻辑分隔的组合式 API 会使 setup
中的代码量越来越多,导致「意大利面条代码」情况的出现。总的来说,组合式 API 在提升了代码质量上限的同时,也降低了下限。