【JS】Vue3丨从 5 个维度来讲 Vue3 变化

一些概念

Vue 和 React 的逻辑复用手段

到目前为止,

Vue:Mixins(混入)、HOC(高阶组件)、作用域插槽、Vue Composition API(VCA/组合式API)。

React:Mixins、HOC、Render Props、Hook。

我们可以看到都是一段越来越好的成长史,这里就不再举例赘述,本文重心在 VCA,VCA 更偏向于「组合」的概念。

5个维度来讲 Vue3

1. 框架

一个例子先来了解 VCA

举个简单的例子:

我们要实现 3 个逻辑

  1. 根据 id 获取表格的数据
  2. 可对表格数据进行搜索过滤
  3. 弹框新增数据到表格中

Vue2 options api 的处理

为了阅读质量,省略了部分代码,但不影响我们了解 VCA

// 逻辑功能(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;

}

}

};

这里只是举例简单的逻辑。如果项目复杂了,逻辑增多了。涉及到一个逻辑的改动,我们就可能需要修改分布在不同位置的相同功能点,提升了维护成本。

Vue3 composion 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,让我们带着好奇往下看~

生命周期,Vue2 vs Vue3

| 选项式 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 这个大方法里面写我们的各种逻辑功能点。

Teleport 组件

传送,将组件的 DOM 元素挂载在任意指定的一个 DOM 元素,与 React Portals 的概念是一致的。

一个典型的例子,我们在组件调用了 Modal 弹框组件,我们希望的弹框是这样子的,绝对居中,层级最高,如:

【JS】Vue3丨从 5 个维度来讲 Vue3 变化

组件的结构是这样子的

<Home>

<Modal />

</Home>

但是如果在父组件 Home 有类似这样的样式,如 transform

【JS】Vue3丨从 5 个维度来讲 Vue3 变化

就会影响到 Modal 的位置,即使 Modal 用了 position:fixed 来定位,如:

【JS】Vue3丨从 5 个维度来讲 Vue3 变化

这就是为什么我们需要用 Teleport 组件来帮助我们 “跳出” 容器,避免受到父组件的一些约束控制,把组件的 DOM 元素挂载到 body 下,如:

<Teleport to="body">

<div v-if="show">

...Modal 组件的 DOM 结构...

</div>

</Teleport>

注意:即使 Modal 跳出了容器,也保持 “父子组件关系”,只是 DOM 元素的位置被移动了而已 。

异步组件(defineAsyncComponent)

我们都知道在 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>

解释:

  1. Suspense 组件有两个插槽,具名插槽 fallback 我们这里可以理解成一个 loading 的占位符,在异步组件还没显示之前的后备内容。
  2. 这里还用了 Vue 的动态组件 component 来灵活的传入一个异步组件,v-bind="$attrs" 来保证我们传递给目标组件的 props 不会消失。
  3. fallback 中我们利用了判断 isComponent 来展示不同的 loading ,因为我们希望页面级的 loading 是“全局”的,组件级是在原来的文档流,这里用了 Teleport :disabled="isComponent" 来控制是否跳出。
  4. 细心的小伙伴会发现这里做了一个延迟显示 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

},

// ...

看个效果:

【JS】Vue3丨从 5 个维度来讲 Vue3 变化

ref,reactive,toRef,toRefs 的区别使用

ref(reference)

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>

reactive

与 Vue2 中的 Vue.observable() 是一个概念。
用来返回一个响应式对象,如:

const obj = reactive({

count: 0

})

// 改变

obj.count++

注意:它用来返回一个响应式对象,本身就是对象,所以不需要包装。我们使用它的属性,不需要加 .value 来获取。

toRefs

让我们关注 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

toRef 的用法,就是多了一个参数,允许我们针对一个 key 进行包装,如:

const name = toRef(props,'name');

console.log('name :>> ', name.value);

watchEffect vs watch

Vue3 的 watch 方法与 Vue2 的概念类似,watchEffect 会让我们有些疑惑。其实 watchEffect 与 watch 大体类似,区别在于:

watch 可以做到的

  • 懒执行副作用
  • 更具体地说明什么状态应该触发侦听器重新运行
  • 访问侦听状态变化前后的值

对于 Vue2 的 watch 方法,Vue3 的 "watch" 多了一个「清除副作用」 的概念,我们着重关注这点。

这里拿 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 数据比较“晚接收到”。

这就是在异步场景下常见的例子,清理失效的回调,保证当前副作用有效,不会被覆盖。感兴趣的小伙伴可以继续深究。

fragment(片段)

我们都知道在封装组件的时候,只能有一个 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>

emits

在 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

个人还是挺喜欢 v-model 的更新的,可以提升封装组件的体验感~

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'],

// ...

key attribute

<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。

全局 API

在 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();

}

2. 底层优化

Proxy 代理

Vue2 响应式的基本原理,就是通过 Object.defineProperty,但这个方式存在缺陷。使得 Vue 不得不通过一些手段来 hack,如:

  • Vue.$set() 动态添加新的响应式属性
  • 无法监听数组变化,Vue 底层需要对数组的一些操作方法,进行再封装。如 pushpop 等方法。

而在 Vue3 中优先使用了 Proxy 来处理,它代理的是整个对象而不是对象的属性,可对于整个对象进行操作。不仅提升了性能,也没有上面所说的缺陷。

简单举两个例子:

  1. 动态添加响应式属性

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];

},

  1. 对数组的操作进行拦截

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

静态提升(hoistStatic) vdom

我们都知道 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. 与 TS

defineComponent

在 TS 下,我们需要用 Vue 暴露的方法 defineComponent,它单纯为了类型推导而存在的。

props 推导

import { defineComponent } from 'vue';

export default defineComponent({

props: {

val1: String,

val2: { type: String, default: '' },

},

setup(props, context) {

props.val1;

}

})

当我们在 setup 方法访问 props 时候,我们可以看到被推导后的类型,

  • val1 我们没有设置默认值,所以它为 string | undefined

  • 而 val2 的值有值,所以是 string,如图:

【JS】Vue3丨从 5 个维度来讲 Vue3 变化

PropType

我们关注一下 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'

},

4. build丨更好的 tree-sharking(摇树优化)

基于函数的 API 每一个函数都可以用 import { method1,method2 } from "xxx";,这就对 tree-sharking 非常友好,而且函数名同变量名都可以被压缩,对象去不可以。举个例子,我们封装了一个工具,工具提供了两个方法,用 method1method2 来代替。

我们把它们封装成一个对象,并且暴露出去,如:

// 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';

5. options api 与 composition api 取舍

我们上面的代码都是在 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 !

最后,前端精本精祝您圣诞快乐🎄~ (听说公众号关注「前端精」会更快乐哦~

以上是 【JS】Vue3丨从 5 个维度来讲 Vue3 变化 的全部内容, 来源链接: utcz.com/a/89098.html

回到顶部