Vue Composition API(VCA) 在实现上也其实只是把 Vue 自己就有的响应式系统更显式地暴露出来而已。
这不是函数式,只是 API 暴露为函数。
3.0 Template 编译出来的性能会比手写 jsx 快好几倍。
——尤雨溪Vue2 传统的 data,computed,watch,methods 写法,咱们称之为「选项式api(Options API )」
Vue3 使用 Composition API (VCA)能够根据逻辑功能来组织代码,一个功能相关的 api 会放在一块儿。html
到目前为止,前端
Vue:Mixins(混入)、HOC(高阶组件)、做用域插槽、Vue Composition API(VCA/组合式API)。vue
React:Mixins、HOC、Render Props、Hook。react
咱们能够看到都是一段愈来愈好的成长史,这里就再也不举例赘述,本文重心在 VCA,VCA 更偏向于「组合」的概念。webpack
在 Vue 中,有了抽象封装组件的概念,解决了在页面上模块越多,越显臃肿的问题。但即便进行组件封装,在应用愈来愈大的时候,会发现页面的逻辑功能点愈来愈多,
data/computed/watch/methods
中会被不断塞入逻辑功能,因此要将逻辑再进行抽离组合、复用,这就是 VCA。
举个简单的例子:es6
咱们要实现 3 个逻辑web
为了阅读质量,省略了部分代码,但不影响咱们了解 VCAsegmentfault
// 逻辑功能(1) const getTableDataApi = id => { const mockData = { 1: [ { id: 11, name: '张三1' }, { id: 12, name: '李四1' }, { id: 13, name: '王五1' } ], 2: [ { id: 21, name: '张三2' }, { id: 22, name: '李四2' }, { id: 23, name: '王五2' } ] }; return new Promise(resolve => { setTimeout(() => { resolve(mockData[id] || []); }, 1000); }); }; export default { name: 'VCADemo', components: { Modal }, data() { return { // 逻辑功能(1) id: 1, table: [], // 逻辑功能(2) search: '', // 逻辑功能(3) modalShow: false, form: { id: '', name: '' } }; }, computed: { // 逻辑功能(2) getTableDataBySearch() { return this.table.filter(item => item.name.indexOf(this.search) !== -1); } }, watch: { // 逻辑功能(1) id: 'getTableData' }, mounted() { // 逻辑功能(1) this.getTableData(); }, methods: { // 逻辑功能(1) async getTableData() { const res = await getTableDataApi(this.id); this.table = res; }, // 逻辑功能(3) handleAdd() { this.modalShow = true; }, // 逻辑功能(3) handlePost() { const { id, name } = this.form; this.table.push({ id, name }); this.modalShow = false; } } };
这里只是举例简单的逻辑。若是项目复杂了,逻辑增多了。涉及到一个逻辑的改动,咱们就可能须要修改分布在不一样位置的相同功能点,提高了维护成本。api
让咱们来关注逻辑,抽离逻辑,先看主体的代码结构数组
import useTable from './composables/useTable'; import useSearch from './composables/useSearch'; import useAdd from './composables/useAdd'; export default defineComponent({ name: 'VCADemo', components: { Modal }, setup() { // 逻辑功能(1) const { id, table, getTable } = useTable(id); // 逻辑功能(2) const { search, getTableBySearch } = useSearch(table); // 逻辑功能(3) const { modalShow, form, handleAdd, handlePost } = useAdd(table); return { id, table, getTable, search, getTableBySearch, modalShow, form, handleAdd, handlePost }; } });
setup 接收两个参数:props,context。能够返回一个对象,对象的各个属性都是被 proxy
的,进行监听追踪,将在模板上进行响应式渲染。
咱们来关注其中一个逻辑,useTable
,通常来讲咱们会用 use
开头进行命名,有那味了~
// VCADemo/composables/useTable.ts // 逻辑功能(1)相关 import { ref, onMounted, watch, Ref } from 'vue'; import { ITable } from '../index.type'; const getTableApi = (id: number): Promise<ITable[]> => { const mockData: { [key: number]: ITable[] } = { 1: [ { id: '11', name: '张三1' }, { id: '12', name: '李四1' }, { id: '13', name: '王五1' } ], 2: [ { id: '21', name: '张三2' }, { id: '22', name: '李四2' }, { id: '23', name: '王五2' } ] }; return new Promise(resolve => { setTimeout(() => { resolve(mockData[id] || []); }, 1000); }); }; export default function useTable() { const id = ref<number>(1); const table = ref<ITable[]>([]); const getTable = async () => { table.value = await getTableApi(id.value); }; onMounted(getTable); watch(id, getTable); return { id, table, getTable }; }
咱们把相关逻辑独立抽离,并「组合」在一块儿了,能够看到在 vue 包暴露不少独立函数提供咱们使用,已经再也不 OO 了,嗅到了一股 FP 的气息~
上面这个例子先说明了 VCA 的带来的好处,Vue3 的核心固然是 VCA,Vue3 不只仅是 VCA,让咱们带着好奇往下看~
| 选项式 API(Vue2)| Hook inside setup(Vue3)|
| -------- | ----- |
| beforeCreate | Not needed* |
| created | Not needed* |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeUnmount | onBeforeUnmount |
| unmounted | onUnmounted |
| errorCaptured | onErrorCaptured |
| renderTracked | onRenderTracked |
| renderTriggered | onRenderTriggered |
Hook inside setup,顾名思义,VCA 建议在 setup
这个大方法里面写咱们的各类逻辑功能点。
传送,将组件的 DOM 元素挂载在任意指定的一个 DOM 元素,与 React Portals 的概念是一致的。
一个典型的例子,咱们在组件调用了 Modal 弹框组件,咱们但愿的弹框是这样子的,绝对居中,层级最高,如:
组件的结构是这样子的
<Home> <Modal /> </Home>
可是若是在父组件 Home 有相似这样的样式,如 transform
:
就会影响到 Modal 的位置,即便 Modal 用了 position:fixed
来定位,如:
这就是为何咱们须要用 Teleport 组件来帮助咱们 “跳出” 容器,避免受到父组件的一些约束控制,把组件的 DOM 元素挂载到 body 下,如:
<Teleport to="body"> <div v-if="show"> ...Modal 组件的 DOM 结构... </div> </Teleport>
注意:即便 Modal 跳出了容器,也保持 “父子组件关系”,只是 DOM 元素的位置被移动了而已 。
咱们都知道在 Vue2 也有异步组件的概念,但总体上来讲不算完整~,Vue3 提供了 defineAsyncComponent
方法与 Suspense
内置组件,咱们能够用它们来作一个优雅的异步组件加载方案。
直接看代码:
HOCLazy/index.tsx
import { defineAsyncComponent, defineComponent } from 'vue'; import MySuspense from './MySuspense.vue'; export default function HOCLazy(chunk: any, isComponent: boolean = false) { const wrappedComponent = defineAsyncComponent(chunk); return defineComponent({ name: 'HOCLazy', setup() { const props = { isComponent, wrappedComponent }; return () => <MySuspense {...props} />; } }); }
解释:HOCLazy 接收了两个参数,chunk
就是咱们常常采用的组件异步加载方式如:chunk=()=>import(xxx.vue)
,isComponent
表示当前的“组件”是一个 组件级 or 页面级,经过判断 isComponent
来分别对应不一样的 “loading” 操做。
HOCLazy/MySuspense.vue
<template> <Suspense> <template #default> <component :is="wrappedComponent" v-bind="$attrs" /> </template> <template #fallback> <div> <Teleport to="body" :disabled="isComponent"> <div v-if="delayShow" class="loading" :class="{component:isComponent}"> <!-- 组件和页面有两种不同的loading方式,这里再也不详细封装 --> <div> {{isComponent?'组件级':'页面级'}}Loading ...</div> </div> </Teleport> </div> </template> </Suspense> </template> <script lang="ts"> import { defineComponent, defineAsyncComponent, ref, onMounted } from 'vue'; export default defineComponent({ name: 'HOCLazy', props: ['isComponent', 'wrappedComponent'], setup(props) { const delayShow = ref<boolean>(false); onMounted(() => { setTimeout(() => { delayShow.value = true; // delay 本身拿捏,也能够以 props 的方式传入 }, 300); }); return { ...props, delayShow }; } }); </script> <style lang="less" scoped> .loading { // 组件级样式 &.component { } // 页面级样式 } </style>
解释:
fallback
咱们这里能够理解成一个 loading 的占位符,在异步组件还没显示以前的后备内容。v-bind="$attrs"
来保证咱们传递给目标组件的 props 不会消失。Teleport :disabled="isComponent"
来控制是否跳出。delayShow
,若是咱们没有这个延迟,在网络环境良好的状况下,loading 每次都会一闪而过,会有一种“反优化”的感受。调用 HOCLazy:
为了更好的看出效果,咱们封装了 slow 方法来延迟组件加载:
utils/slow.ts
const slow = (comp: any, delay: number = 1000): Promise<any> => { return new Promise(resolve => { setTimeout(() => resolve(comp), delay); }); }; export default slow;
调用(组件级)
<template> <LazyComp1 str="hello~" /> </template> const LazyComp1 = HOCLazy( () => slow(import('@/components/LazyComp1.vue'), 1000), true ); // ... components: { LazyComp1 }, // ...
看个效果:
其实这与 React 中的
React.lazy + React.Suspense
的概念是一致的,以前写过的一篇文章
《React丨用户体验丨hook版 lazy loading》,小伙伴能够看看作下对比~
ref 和 reactive 的存在都是了追踪值变化(响应式),ref 有个「包装」的概念,它用来包装原始值类型,如 string 和 number ,咱们都知道不是引用类型是没法追踪后续的变化的。ref 返回的是一个包含 .value
属性的对象。
setup(props, context) { const count = ref<number>(1); // 赋值 count.value = 2; // 读取 console.log('count.value :>> ', count.value); return { count }; }
在 template 中 ref 包装对象会被自动展开(Ref Unwrapping),也就是咱们在模板里不用再 .value
<template> {{count}} </template>
与 Vue2 中的 Vue.observable()
是一个概念。
用来返回一个响应式对象,如:
const obj = reactive({ count: 0 }) // 改变 obj.count++
注意:它用来返回一个响应式对象,自己就是对象,因此不须要包装。咱们使用它的属性,不须要加 .value
来获取。
官网:由于 props 是响应式的,你不能使用 ES6 解构,由于它会消除 prop 的响应性。
让咱们关注 setup
方法的 props 的相关操做:
<template> {{name}} <button @click="handleClick">点我</button> </template> // ... props: { name: { type: String, default: ' ' } }, setup(props) { const { name } = props; const handleClick = () => { console.log('name :>> ', name); }; return { handleClick }; } // ...
注意:props 无需经过 setup 函数 return,也能够在 template 进行绑定对应的值
咱们都知道解构是 es6 一种便捷的手段,编译成 es5 ,如:
// es6 syntax const { name } = props; // to es5 syntax var name = props.name;
假设父组件更改了 props.name 值,当咱们再点击了 button 输出的 name 就仍是以前的值,不会跟着变化,这实际上是一个基础的 js 的知识点。
为了方便咱们对它进行包装,toRefs
能够理解成批量包装 props 对象,如:
const { name } = toRefs(props); const handleClick = () => { // 由于是包装对象,因此读取的时候要用.value console.log('name :>> ', name.value); };
能够理解这一切都是由于咱们要用解构,toRefs
所采起的解决方案。
toRef 的用法,就是多了一个参数,容许咱们针对一个 key 进行包装,如:
const name = toRef(props,'name'); console.log('name :>> ', name.value);
Vue3 的 watch 方法与 Vue2 的概念相似,watchEffect 会让咱们有些疑惑。其实 watchEffect 与 watch 大致相似,区别在于:
watch 能够作到的
对于 Vue2 的 watch 方法,Vue3 的 "watch" 多了一个「清除反作用」 的概念,咱们着重关注这点。
这里拿 watchEffect
来举例:
watchEffect:它当即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变动时从新运行该函数。
watchEffect 方法简单结构
watchEffect(onInvalidate => { // 执行反作用 // do something... onInvalidate(() => { // 执行/清理失效回调 // do something... }) })
执行失效回调,有两个时机
一个例子:咱们要经过 id 发起请求获取「水果」的详情,咱们监听 id,当 id 切换过于频繁(还没等上个异步数据返回成功)。可能会致使最后 id=1
的数据覆盖了id=2
的数据,这并非咱们但愿的。
咱们来模拟并解决这个场景:
模拟接口 getFruitsById
interface IFruit { id: number; name: string; imgs: string; } const list: { [key: number]: IFruit } = { 1: { id: 1, name: '苹果', imgs: 'https://xxx.apple.jpg' }, 2: { id: 2, name: '香蕉', imgs: 'https://xxx.banana.jpg' } }; const getFruitsById = ( id: number, delay: number = 3000 ): [Promise<IFruit>, () => void] => { let _reject: (reason?: any) => void; const _promise: Promise<IFruit> = new Promise((resolve, reject) => { _reject = reject; setTimeout(() => { resolve(list[id]); }, delay); }); return [ _promise, () => _reject({ message: 'abort~' }) ]; };
这里封装了“取消请求”的方法,利用 reject 来完成这一动做。
在 setup 方法
setup() { const id = ref<number>(1); const detail = ref<IFruit | {}>({}); watchEffect(async onInvalidate => { onInvalidate(() => { cancel && cancel(); }); // 模拟id=2的时候请求时间 1s,id=1的时候请求时间 2s const [p, cancel] = getFruitsById(id.value, id.value === 2 ? 1000 : 2000); const res = await p; detail.value = res; }); // 模拟频繁切换id,获取香蕉的时候,获取苹果的结果尚未回来,取消苹果的请求,保证数据不会被覆盖 id.value = 2; // 最后 detail 值为 { "id": 2, "name": "香蕉", "imgs": "https://xxx.banana.jpg" } }
若是没有执行 cancel()
,那么 detail 的数据将会是 { "id": 1, "name": "苹果", "imgs": "https://xxx.apple.jpg" }
,由于 id=1 数据比较“晚接收到”。
这就是在异步场景下常见的例子,清理失效的回调,保证当前反作用有效,不会被覆盖。感兴趣的小伙伴能够继续深究。
咱们都知道在封装组件的时候,只能有一个 root 。在 Vue3 容许咱们有多个 root ,也就是片断,可是在一些操做值得咱们注意。
当 inheritAttrs=true[默认]
时,组件会自动在 root 继承合并 class ,如:
子组件
<template> <div class="fragment"> <div>div1</div> <div>div2</div> </div> </template>
父组件调用,新增了一个 class
<MyFragment class="extend-class" />
子组件会被渲染成
<div class="fragment extend-class"> <div> div1 </div> <div> div2 </div> </div>
若是咱们使用了 片断 ,就须要显式的去指定绑定 attrs ,如子组件:
<template> <div v-bind="$attrs">div1</div> <div>div2</div> </template>
在 Vue2 咱们会对 props 里的数据进行规定类型,默认值,非空等一些验证,能够理解 emits 作了相似的事情,把 emit 规范起来,如:
// 也能够直接用数组,不作验证 // emits: ['on-update', 'on-other'], emits: { // 赋值 null 不验证 'on-other': null, // 验证 'on-update'(val: number) { if (val === 1) { return true; } // 自定义报错 console.error('val must be 1'); return false; } }, setup(props, ctx) { const handleEmitUpdate = () => { // 验证 val 不为 1,控制台报错 ctx.emit('on-update', 2); }; const handleEmitOther = () => { ctx.emit('on-other'); }; return { handleEmitUpdate, handleEmitOther }; }
在 setup 中,emit 已经再也不用 this.$emit
了,而是 setup 的第二个参数 context
上下文来获取 emit 。
我的仍是挺喜欢 v-model 的更新的,能够提高封装组件的体验感~
在Vue2,假设我须要封装一个弹框组件 Modal,用show
变量来控制弹框的显示隐藏,这确定是一个父子组件都要维护的值。由于单向数据流,因此须要在 Modal 组件 emit 一个事件,父组件监听事件接收并修改这个show
值。
为了方便咱们会有一些语法糖,如 v-model,可是在 Vue2 一个组件上只能有一个 v-model ,由于语法糖的背后是value
和@input
的组成, 若是还有多个相似这样的 “双向修改数据”,咱们就须要用语法糖.sync
同步修饰符。
Vue3 把这两个语法糖统一了,因此咱们如今能够在一个组件上使用 多个 v-model 语法糖,举个例子:
先从父组件看
<VModel v-model="show" v-model:model1="check" v-model:model2.hello="textVal" />
hello为自定义修饰符
咱们在一个组件上用了 3 个 v-model 语法糖,分别是
| v-model 语法糖| 对应的 prop | 对应的 event | 自定义修饰符对应的 prop |
| -------- | ----- | ----- | ----- |
|v-model(default)| modelValue | update:modelValue | 无 |
| v-model:model1 | model1 | update:model1 | 无 |
|v-model:model2 | model2 | update:model2 | model2Modifiers |
这样子咱们就更清晰的在子组件咱们要进行一些什么封装了,如:
VModel.vue
// ... props: { modelValue: { type: Boolean, default: false }, model1: { type: Boolean, default: false }, model2: { type: String, default: '' }, model2Modifiers: { type: Object, default: () => ({}) } }, emits: ['update:modelValue', 'update:model1', 'update:model2'], // ...
<template> <input type="text" placeholder="请输入帐号" v-if="show" /> <input type="text" placeholder="请输入邮箱" v-else /> <button @click="show=!show">Toggle</button> </template>
相似这样的 v-if/v-else,在 Vue2 中,会尽量高效地渲染元素,一般会复用已有元素而不是从头开始渲染,因此当咱们在第一个 input 中输入,而后切换第二个
input 。第一个 input 的值将会被保留复用。
有些场景下咱们不要复用它们,须要添加一个惟一的 key ,如:
<template> <input type="text" placeholder="请输入帐号" v-if="show" key="account" /> <input type="text" placeholder="请输入邮箱" v-else key="email" /> <button @click="show=!show">Toggle</button> </template>
可是在 Vue3 咱们不用显式的去添加 key ,这两个 input 元素也是彻底独立的,由于 Vue3 会对 v-if/v-else 自动生成惟一的 key。
在 Vue2 咱们对于一些全局的配置多是这样子的,例如咱们使用了一个插件
Vue.use({ /* ... */ }); const app1 = new Vue({ el: '#app-1' }); const app2 = new Vue({ el: '#app-2' });
可是这样子这会影响两个根实例,也就是说,会变得不可控。
在 Vue3 引入一个新的 API createApp
方法,返回一个实例:
import { createApp } from 'vue'; const app = createApp({ /* ... */ });
而后咱们就能够在这个实例上挂载全局相关方法,并只对当前实例生效,如:
app .component(/* ... */) .directive(/* ... */ ) .mixin(/* ... */ ) .use(/* ... */ ) .mount('#app');
须要注意的是,在 Vue2 咱们用了 Vue.prototype.$http=()=>{}
这样的写法,来对 “根Vue” 的 prototype 进行挂载方法,使得咱们在子组件,能够经过原型链的方式找到 $http
方法,即 this.$http
。
而在 Vue3 咱们相似这样的挂载须要用一个新的属性 globalProperties
:
app.config.globalProperties.$http = () => {}
在 setup 内部使用 $http
:
setup() { const { ctx: { $http } } = getCurrentInstance(); }
Vue2 响应式的基本原理,就是经过 Object.defineProperty
,但这个方式存在缺陷。使得 Vue 不得不经过一些手段来 hack,如:
push
,pop
等方法。而在 Vue3 中优先使用了 Proxy 来处理,它代理的是整个对象而不是对象的属性,可对于整个对象进行操做。不只提高了性能,也没有上面所说的缺陷。
简单举两个例子:
const targetObj = { id: '1', name: 'zhagnsan' }; const proxyObj = new Proxy(targetObj, { get: function (target, propKey, receiver) { console.log(`getting key:${propKey}`); return Reflect.get(...arguments); }, set: function (target, propKey, value, receiver) { console.log(`setting key:${propKey},value:${value}`); return Reflect.set(...arguments); } }); proxyObj.age = 18; // setting key:age,value:18
如上,用 Proxy
咱们对 proxyObj
对象动态添加的属性也会被拦截到。
Reflect
对象是ES6 为了操做对象而提供的新 API。它有几个内置的方法,就如上面的 get
/ set
,这里能够理解成咱们用 Reflect
更加方便,不然咱们须要如:
get: function (target, propKey, receiver) { console.log(`getting ${propKey}!`); return target[propKey]; },
const targetArr = [1, 2]; const proxyArr = new Proxy(targetArr, { set: function (target, propKey, value, receiver) { console.log(`setting key:${propKey},value:${value}`); return Reflect.set(...arguments); } }); proxyArr.push('3'); // setting key:2,value:3 // setting key:length,value:3
咱们都知道 Vue 有虚拟dom的概念,它能为咱们在数据改变时高效的渲染页面。
Vue3 优化了 vdom 的更新性能,简单举个例子
Template
<div class="div"> <div>content</div> <div>{{message}}</div> </div>
Compiler 后,没有静态提高
function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", { class: "div" }, [ _createVNode("div", null, "content"), _createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */) ])) }
Compiler 后,有静态提高
const _hoisted_1 = { class: "div" } const _hoisted_2 = /*#__PURE__*/_createVNode("div", null, "content", -1 /* HOISTED */) function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", _hoisted_1, [ _hoisted_2, _createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */) ])) }
静态提高包含「静态节点」和「静态属性」的提高,也就是说,咱们把一些静态的不会变的节点用变量缓存起来,提供下次 re-render 直接调用。
若是没有作这个动做,当 render
从新执行时,即便标签是静态的,也会被从新建立,这就会产生性能消耗。
3.0 的一个主要设计目标是加强对 TypeScript 的支持。本来咱们指望经过 Class API 来达成这个目标,可是通过讨论和原型开发,咱们认为 Class 并非解决这个问题的正确路线,基于 Class 的 API 依然存在类型问题。——尤雨溪基于函数的 API 自然 与 TS 完美结合。
在 TS 下,咱们须要用 Vue 暴露的方法 defineComponent,它单纯为了类型推导而存在的。
import { defineComponent } from 'vue'; export default defineComponent({ props: { val1: String, val2: { type: String, default: '' }, }, setup(props, context) { props.val1; } })
当咱们在 setup 方法访问 props 时候,咱们能够看到被推导后的类型,
string | undefined
string
,如图:咱们关注一下 props 定义的类型,若是是一个复杂对象,咱们就要用 PropType 来进行强转声明,如:
interface IObj { id: number; name: string; } obj: { type: Object as PropType<IObj>, default: (): IObj => ({ id: 1, name: '张三' }) },
或 联合类型
type: { type: String as PropType<'success' | 'error' | 'warning'>, default: 'warning' },
tree-sharking 即在构建工具构建后消除程序中无用的代码,来减小包的体积。
基于函数的 API 每个函数均可以用 import { method1,method2 } from "xxx";
,这就对 tree-sharking 很是友好,并且函数名同变量名均可以被压缩,对象去不能够。举个例子,咱们封装了一个工具,工具提供了两个方法,用 method1
,method2
来代替。
咱们把它们封装成一个对象,而且暴露出去,如:
// utils const obj = { method1() {}, method2() {} }; export default obj;
// 调用 import util from '@/utils'; util.method1();
通过webpack打包压缩以后为:
a={method1:function(){},method2:function(){}};a.method1();
咱们不用对象的形式,而用函数的形式来看看:
// utils export function method1() {} export function method2() {}
// 调用 import { method1 } from '@/utils'; method1();
通过webpack打包压缩以后为:
function a(){}a();
用这个例子咱们就能够了解 Vue3 为何能更好的 tree-sharking ,由于它用的是基于函数形式的API,如:
import { defineComponent, reactive, ref, watchEffect, watch, onMounted, toRefs, toRef } from 'vue';
咱们上面的代码都是在 setup 内部实现,可是目前 Vue3 还保留了 Vue2 的 options api 写法,就是能够“并存”,如:
// ... setup() { const val = ref<string>(''); const fn = () => {}; return { val, fn }; }, mounted() { // 在 mounted 生命周期能够访问到 setup return 出来的对象 console.log(this.val); this.fn(); }, // ...
结合 react ,咱们知道 “函数式”,hook 是将来的一个趋势。
因此我的建议仍是采用都在 setup
内部写逻辑的方式,由于 Vue3 能够彻底提供 Vue2 的所有能力。
我的以为无论是 React Hook 仍是 Vue3 的 VCA,咱们均可以看到如今的前端框架趋势,“更函数式”,让逻辑复用更灵活。hook 的模式新增了 React / Vue 的抽象层级,「组件级 + 函数级」,可让咱们处理逻辑时分的更细,更好维护。
Vue3 One Piece,nice !
最后,前端精本精祝您圣诞快乐🎄~ (据说公众号关注「前端精」会更快乐哦~