vue3介绍

逻辑复用

在做了一个又一个的xx管理系统后,好像生活变得更枯燥乏味了,逐渐的在一次又一次的复制粘贴中迷失自我。不甘如此的17开寻求新的出口,既然都是在 ctrl+c ctrl+v 那位了不 cv 的更上流一些呢?

前端的逻辑复用一直是个讨论的热点话题,毕竟程序员都那么懒,懒到连复制粘贴都不想,所以就想尽办法的抽取封装通用的业务逻辑。那什么是业务逻辑呢,从代码层面来讲,就是一些数据( Data )和一些事件、方法( Method )的组合。

比如一个常见的列表页面,他可能会有以下这些数据、事件和方法:

  1. searchOptions 用于搜索查询过滤列表的参数
  2. list 列表所展示所需要用到的数组数据
  3. pagination 存储分页信息的数据
  4. getList 用于拉取列表数据的接口调用
  5. onChangePage onSearch onFilter 翻页搜索过滤等事件

所以像状态管理(redux,vuex等)和一些工具方法(表单校验,防抖节流等)都不能算是逻辑复用的方案。

接下来我们先看看当前比较主流的逻辑复用方案都有哪些。

探索

Mixin

Mixin 是一种借鉴 OOP 的逻辑复用方式,直觉上来讲,它很像多重继承,但实际上它是组合,将多个方法和属性合并到一个对象中,像 jQuery 中的 $.extend $.fn.extend ,早期的 React 和 Vue2.x 都是通过这种方式来实现逻辑复用的。

React 中的 Mixin

const Mixin1 = {
getMessage() {
return 'hello world';
}
};
const Mixin2 = {
componentDidMount() {
console.log('Mixin2.componentDidMount()');
}
};

const MyComponent = React.createClass({
mixins: [Mixin1, Mixin2],
render() {
return (<div>{this.getMessage()}</div>);
}
});

Vue 中的 Mixin

const Mixin1 = {
data() {
return {
list: []
}
},
methods: {
getList() {
return fetchData(this.searchOptions).then(res => {
this.list = res.data;
return res;
})
}
}
}

const Mixin2 = {
created() {
console.log('Mixin2.created()');
}
}

const MyComponent = {
mixins: [Mixin1, Mixin2],
data() {
return {
searchOptions: {
name: ''
}
}
},
mounted() {
this.getList();
}
};

为了防止 Minix 的使用不当,React 和 Vue 又对其进行了一些限制和合并策略上的调整,比如在 React 中:

  1. 如果 mixins 中有相同的属性会直接抛出异常。
  2. constructor 、render 和 shouldComponentUpdate 也是不允许重复的。
  3. 如果有重复的 compoentDidMount 生命周期,会先执行 mixins 中的生命周期,然后再执行组件内的 compoentDidMount;

在 Vue 中:

  1. 相同的 data 会保留组件自己的 data
  2. 相同的 method 会被封装为一个数组,在调用的时候则会依次执行 mixins 中的 method 组件内的 method
  3. 相同的生命周期会合并到一个钩子中,依次执行 mixins 中的钩子,最后执行组件内的钩子

Mixin虽然实现了业务逻辑的复用,但是却有着一些致命的缺陷:

  • 引入了不清晰的依赖关系
    Mixin1 可能还会依赖 Mixin2 , Mixin2 可能还需要组件提供指定的方法,如果某个 Mixin 中的方法修改了,依赖它的 Mixin 可能会无法正常工作。这种复杂的依赖关系在 JavaScript 这种动态类型的语言中,会变成一种心智负担。

  • 导致命名空间的冲突
    多个 Mixin 之间可能会存在相同的属性和方法,造成冲突。

  • 滚雪球般的复杂度
    Mixin 是侵入式的,改变了原组,和组件之间高度耦合,尤其当依赖多个 Mixin 时,代码将变得难以预测,管理代码的复杂度也会直线上升。

所以 React 很早就宣布放弃了这种方式,当然 Vue 也快了。

HOC(Higher-Order Components)

有时候一些英文单词的缩写看起来很唬人, HOC 便是其中之一了。它的中文名叫高阶组件,直白的解释一下,它和高阶函数一样,只不过高阶组件的参数是 Component 返回的也还是 Component ,既: const EnhancedComponent = higherOrderComponent(WrappedComponent);。在 React 放弃 Mixin 之后,HOC 就成为了逻辑复用的推荐方案。

HOC 实现逻辑复用的原理将需要复用的代码抽离到函数中,然后通过 props 将其传递到下层组件当中,实际上就是对下层组件进行了一次包装,其实这就是装饰器模式。所以 HOC 并不算是 React 的一部分,它是一种基于 React 的组合特性而形成的设计模式。我们常用的 react-redux 中的 connect 就是一个高阶组件,它通过 props 将 store 中的 state 以 prop 的方式传递给下层组件。HOC很好的解决的了 Mixin 的缺陷。

React 中的 HOC :

function List({ data }) {
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}

function withLoadData(WrappedComponent) {
return class extends React.Component {

state = {
data: []
};

componentWillMount() {
setTimeout(() => {
this.setState({
data: [
{ id: 1, name: '11' },
{ id: 2, name: '22' }
]
});
}, 1000);
}

render() {
return <WrappedComponent {...this.props} {...this.state} />
}
}
}

export default withLoadData(List);

这是一个很简单的例子, List 组件只负责展示数据,没有任何多余的逻辑,而 withLoadData 则负责加载数据,当然你还可以在写一个 withLoading 来控制组件的 laoding 状态。

在 Vue 很少有提及 HOC 这个概念,可能主要是因为 Vue 是以模板为主要方式,没有 jsx 那样灵活,而且 Vue 的 props 需要显式的定义出来(可以通过 $arrts 绕做),相对于 React 也多了一个事件的概念(对应 React 中的回调,如 onInput 等,可以用 $listeners 绕过 )。当然你要是想在 Vue 是使用高阶组件也不是不可以,在 React 中 f(Component): NewComponent ,那在 Vue 中就是 f(Options): NewOptions。但是还是极力不推荐在 Vue 中使用 HOC 。同样以上面的场景为例,Vue 中的 HOC:

const List = {
name: 'List',
props: {
data: {
type: Array,
required: true,
},
},
render() {
return (
<ul>
{this.data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
},
};

function withLoadData(WrappedOptions) {
return {
name: 'withLoadData',
component: { WrappedOptions },
template: `<Wrapped-Options v-on="$listeners" v-bind="{data:data, ...$attrs}" />`
data() {
return {
data: [],
};
},
created() {
setTimeout(() => {
this.data = [
{ id: 1, name: '11' },
{ id: 2, name: '22' },
];
}, 1000);
}
};
}

export default withLoadData(List);

同样的,HOC 自身也有一些令人不爽的小问题:

  • 扩展性限制,并不能完全替代 Mixin ,例如无法获取子组件的 state 等
  • Ref 被隔断
  • 静态方法丢失
  • Wrapper Hell ,一层函数嵌套不够,那就多嵌套几层
  • 性能有一定的影响

hook的诞生

介绍完了上面三种逻辑复用的方式,终于轮到 hook 出场了。

假想

如前面所提到的,所谓业务逻辑 就是一些数据( Data )和一些事件、方法( Method )的组合。如果抛开框架的束缚,我们能想到最直接、最简单的复用方式就是函数了。我们可以将这些有关联性的 Data 和 Method 都写在一起,然后放在一个函数中。但是有一个问题,如何保证这些 Data 能和 View 进行关联呢?

Class Component 与 Vue 的问题

在 React 中,我们可以在类组件中定义 state ,当显式的调用 this.setState 时,state 会发生改变,然后通过 vDOM diff 查找需要更新的区域,最后在更新真实的 DOM 树。如果用我们提到的方法,就遇到了一个问题,如何将这些 Data 和 Method 放进这个组件内,还有 this 指向也会是个不小的阻碍。

在 Vue 中也会遇到同样的问题,我们需要想办法将这些 Data 放进 datacomputed 这个两个属性中,将 Method 放进 methods 属性中。而且 Vue 也需要考虑 this 指向的问题。

Function Component缺失的功能

如果是函数式组件,那就更不好办,函数式组件是没有状态,没有生命周期,他只能接受 props 作为参数,只有当 props 变更时,才会更新对应的 DOM 。所以就更没有办法将 Data 放进函数式组件当中了。

Function Component + Hook

不过 React 团队还是想出了办法让函数组件也能拥有自己的状态,且状态更新对应的视图也做出更新。那就是通过指定的方法在函数式组件内部创建和更新 state :

import React, { useState } from 'react';

function Count() {
const [count, setCount] = useState(0);

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

useState 返回的结果是数组,数组的第一项是 state ,第二项是更新 state 函数,useState 函数的第一个参数就是默认值,也支持回调函数。在上述的例子中 countsetCount 分别对应逻辑中的 Data 和 Method 。这样便解决了函数式组件没有自己的状态的问题,为了让函数式组件能有和 class 组件类似的生命周期, react 还提供了 useState 方法。下面就介绍一下 react 中两个常用的 hook 。

React Hook

在介绍 React Hook 之前,需要知道以两点规则(限制):

  • hook 只能在函数式组件中使用
  • 不要在循环,条件或嵌套函数中调用 Hook

useState

const [count, setCount] = useState(0);

useState 让函数式组件也能拥有了自己的状态。当需要在组件内部拥有自己的状态时,便可以通过调用 useState 来创建 state变量 ,不过你要遵循上面的两个条件。当你需要多个 state变量 的时候你就需要调用多次,这时候你可能就会遇到控制 state 粒度的问题了。

在传统的 class 组件中,我们是不用关心这个问题的,以 TodoList 组件为例,在 class 组件中,我们会这样写:

class TodoList extends React.Component {
state = {
todoList: [],
loading: false,
filterText: '',
filterType: 'all',
};

getTodoList() {
this.setState({
loading: true
});
fetch('/todos')
.then((res) => res.json())
.then((data) => {
this.setState({
todoList: data,
loading: false,
});
})
.catch(() => {
this.setState({
loading: false,
});
});
}
}

但是在使用了 State Hook 之后,我们就要考虑这个问题了:

function TodoList() {
const [todoList, setTodoList] = useState([]);
const [loading, setLoading] = useState(false);
const [filterText, setFilterText] = useState('');
const [filterType, setFilterType] = useState('all');

const getTodoList = () => {
setLoading(true);
fetch('/todos')
.then((res) => res.json())
.then((data) => {
setLoading(false);
setTodoList(data);
})
.catch(() => {
setLoading(false);
});
}
}

这时候我们已经发现了两个问题了:

  1. 函数开始很长一串的声明 state 变量的代码,这还仅仅是一个简单的组件
  2. 当需要同时更新多个 state 的时候,无法像 class 组件那样批量更新,而需要依次的去调用对应的 setXxx 方法。

Q: 连续多次调用不同的 setXxx 会触发几次更新呢?

那可不可以将所有的 state 变量都通过一个 useState 生成呢?当然可以:

const [state, setState] = useState({
todoList: [],
loading: false,
filterText: '',
filterType: 'all',
});

但是事情并不是像我们想象的那么美好,在 class 组件中, setState 会把更新的字段自动合并到 this.state 对象中。而在 State Hook 中,setXxx 做的是替换操作,所以每当我们需要更新某个 state 的时候,就需要这么写: setState({ ...state, todoList: data, loading: false }) 看起来好像也没什么,但是这样做又引入了新的问题,粒度过粗,代码的可复用性就会降低,也有些违背了 hook api 的初衷 。

所以,在使用 State Hook 的时候,结合业务场景,尽量遵循下面两点:

  1. 将完全不相关的 state 拆分为多组 state
  2. 如果某些 state 是相互关联的,或者需要一起发生改变,就可以把它们合并为一组 state

useEffect

已经通过 useState 让函数式组件拥有了自己的状态,那再提供个 hook 让函数式组件也能拥有自己的生命周期,这不算过分吧。这就是 useEffect 的作用就是,但是实际上官方文档中对它的描述却让人有些模糊: Effect Hook 可以让你在函数组件中执行副作用操作

OK, 先不着急。先看一下 useEffect 怎么使用:

useEffect(() => {

/**
* part1
* dosomething
*/

return () => {
/**
* part2
* dosomething
*/
}

}, [ /* part3 依赖的state */])

useEffect 的第一个参数是函数,我们可以在这个函数内发起网络请求、绑定事件、订阅消息、创建定时器、查找操作 DOM 等,这也就是所谓的副作用,对应的就是上述代码中的 part1 。还可以在函数内部再返回一个函数,在该函数内清除上一次副作用遗留下来的状态,比如解绑事件、取消订阅、消除定时器等等。它是可选的,你也可以不返回任何值,这取决你的业务场景,比如你只是在 part1 部分获取数据、记录一些日志,那就完全没必要再多一写一个 return 了。对应上述代码中的 part2 。第三个参数是一个数组,将 part1part2 部分使用到的 stateprop 放进这个数组内,接下来我们先分析 useEffect 的执行过程,然后解释为什么需要 part3

  1. 组件初次 render 的时候执行 part1
  2. 组件更新之前执行 part2
  3. 组件更新完成后 执行 part1

所以我们可以把 useEffect 当做是 componentDidMountcomponentDidUpdatecomponentWillUnmount 的组合。 part1 对应 componentDidMountpart3 对应 componentDidUpdatecomponentWillUnmount

useEffect 和 useState 一样可以在一个组件内使用多次。这样我们就可以更好的将有关联的代码组织在一起了,但是随之而来就产生了一个问题,来看下面一段代码:

function App() {
const [count, setCount] = useState(0);
const [width, setWidth] = useState(document.body.clientWidth);

const onResize = () => {
setWidth(document.body.clientWidth)
}

useEffect(() => {
window.addEventListener('resize', onResize, false);
return () => {
window.removeEventListener('resize', onResize, false);
}
});

useEffect(() => {
document.title = count;
});

return (
<div>
页面名称: {count}
页面宽度: {width}
<button onClick={() => { setCount(count + 1) }}>Click Me</button>
</div>
);
}

在上面的代码中,第一个 useEffect 在组件初始时监听 window 的 resize 事件,在组件更新或销毁的时候取消事件绑定;第二个 useEffect 在组件初始和更新的时候修改页面的 title 。

每当 state 或 prop 都会触发函数式组件的更新,所以只要触发了 resize 事件,第二个 useEffect 都会再执行一遍。所以在某些情况下,每次渲染后都 effect 可能会导致性能问题。那 useEffect 的第二参数 DependencyList 便是用来控制何时执行 effect :

  1. 在不传 DependencyList 的情况下,每次渲染、更新都会执行 part1 和 part2
  2. 在传 DependencyList 的情况下,只有当 DependencyList 中的值发生变化才会执行 part1 和 part2
  3. 如果传了空数组,只有在初次渲染和卸载的时候才会执行 part1 和 part2
    根据上面的规则,我可以对上面的代码做一些优化:
    useEffect(() => {
    window.addEventListener('resize', onResize, false);
    return () => {
    window.removeEventListener('resize', onResize, false);
    }
    }, []);

    useEffect(() => {
    document.title = count;
    }, [count]);

自定义 hook

仅仅让有函数式组件拥有 state 和生命周期这远远不够,我们要讨论的是逻辑复用,这时候我们就可以用到自定义 hook 。自定义 hook 就是以 use 开头的函数,我们可以在该函数能使用 useStateuseEffect 等等这些 React 提供的 hook ,还可以在函数内部返回一些 Date 或者 Method 。

Vue Composition API

从框架使用者角度而言, Vue3 最大的改动就是从 Options API 表更为 Composition API 。Options API 是一种非常直观的分隔代码的方法, data, computed, watch, methods, lifecycle, 一切看看起来井井有条。但是它也有致命的缺点,比如你在刚写 Vue 的时候是否经常疑惑 this 的指向;当一个组件的代码有数百行,一个简单的功能所对应的代码可能会被分割在了不同的地方,从而变得难以阅读或调试;无法更便捷的使用类型推到,所以在 Vue2 中使用 ts 相对于 ng 和 react 要更麻烦一些;而且这种配置式的 API 注定想要复用逻辑,就只能使用 mixin 的方式。

而且 Vue3 的 Composition API 将上述的问题都统统解决了。为了更直观的对比,我们就一个计数器的例子来展示说明。

<div>
<span>count is {{ count }}</span><br />
<span>plusOne is {{ plusOne }}</span><br />
<button @click="increment">count++</button>
</div>

Vue2 Options API

export default {
data() {
return { count: 0 };
},
computed: {
plusOne() {
return this.count + 1;
},
},
methods: {
increment() {
this.count += 1;
},
},
watch: {
count(val, oldVal) {
console.log(`oldVal is ${oldVal}`);
},
},
mounted() {
console.log('mounted');
},
};

Vue3 Composition API

import { defineComponent, ref, computed, watch, onMounted, } from 'vue';

export default defineComponent({
setup() {
const count = ref(0);

const plusOne = computed(() => count.value + 1);

const increment = () => {
count.value += 1;
};

watch(count, (val, oldVal) => {
console.log(`oldVal is ${oldVal}`);
});

onMounted(() => {
console.log('onMounted');
});

return { count, plusOne, increment };
},
});

从上面的例子中我们可以发现 Composition API 已经完全移除了 data, computed, methods, watch, 生命周期等等这些配置项(其实为了兼容 Vue2 并没有移除),而是提供了 ref/reactive , computed , watch , onMounted 这些方法来代替。新增了 setup 方法,创建响应式数据,业务逻辑也全都移到了 setup 之中。如果需要在模板中访问数据调用方法,只需在 setup 中将相应的数据/方法 return 出去即可。

底层主要是从 flow 切换到 typescript ,用 Proxy 取代了 Object.defineProperty ,重构了 Virtual DOM 。

Composition API 在一定程度借鉴了 react hook ,但是实现方式是完全不同的,并且确实解决了 Vue 中逻辑复用和代码组织的问题,同时提供了更好的类型推导。而且 Composition API 相较于 React Hook 使用起来心智负担要很多,所以没必要对 Composition API 有过多的质疑甚至是否定,没人能逃得出真香定论。

setup

setup 是新增的一个组件选项,也是使用 Composition API 的入口点。创建组件实例,然后初始化 props ,紧接着就调用setup 函数。从生命周期钩子的角度来看,它会在 beforeCreate之前被调用。setup 中返回的对象会被合并到模板的渲染上下文中。也可以在 setup 返回一个渲染函数。

它和一些响应式 API ,生命周期钩子组合,代替了之前的 data , computed , methods , watch , 生命周期等。在 setup 中将无法在使用 this 。那当我们需要在 setup 使用到 props 或者一些组件内置的方法(比如 this.$emit )时该怎么办呢? setup 提供了两个参数,分别是 props 和 context 。

setup(props, ctx) {

}

props 就是由父组件传递给子组件的 props 。它和 react 中函数式组件的 props 类似,但是不要解构 props 因为那样是让 props 失去响应式特性。

context 则代替了之前的 this 。比如 ctx.attrs , ctx.emit 等等。

Q: 为什么将 props 设计为第一个参数,而不是 context 呢?为什么不将 props 和 context 合并呢?

响应式 API

  • reactive
    以一个普通对象为参数,传入的对象会经过 Proxy 包装,返回该对象的响应式代理。它与 Vue2 的 Vue.observable() 用法完和作用完全相同。为了避免和 RXJS 的 observable 混淆,改名为 reactive 。
    const state = reactive({ name: 'qq', age: 18 });
  • ref
    reactive 可以代理对象,但是如果我们只需要用到一个简单的值类型时,比如上面例子的中 count ,该怎么办呢?一种办法是将 count 包装一次层,用 reactive 创建创建响应式数据 :
    const state = reactive({ count: 0 });
    另一种方法就是用 ref :
    const countRef = ref(0);
    console.log(countRef.value); // 0
    ref 返回的也是一个响应式对象,拥有一个指向内部值的单一属性 .value 。为了让我们能方便一些,在模板中使用 ref 的值时是不需要通过有 .value 来获取的。
    <span>{{countRef}}<span>
    Q: 那什么时候使用 reactive 什么时候使用 ref 呢?
  • computed
    computed 函数代替了之前的 computed 选项:
    const count = ref(1);
    const user = reactive({ name: 'qq', age: 18 });

    const plusOne = computed({
    get: () => count.value + 1,
    set: (val) => {
    count.value = val - 1;
    },
    });

    const userInfo = computed(() => `我是${user.name},今年${user.age}岁`);
    不过和 Vue2 不同的是, computed 返回的值与 ref 一样是一个 ref 对象。原因是一样的,属性计算就可能会返回一个值类型 plusOne , 也可能会返回一个引用类型 userInfo ,为了保证值类型不丢失内部逻辑关系,那就要像 ref 一样包装一层,为了统一起来, computed 一律返回一个 ref 对象。
  • watch
    watch 与之前的 watch 选项完全相同,当需要监听指定值变化并执行一些逻辑的时候可以使用 watch 。
    const count = ref(1);
    const bar = ref('bar');
    const user = reactive({ name: 'qq', age: 18 });

    watch(
    () => user,
    (val, oldVal) => {
    console.log('user', val, oldVal);
    },
    { deep: true, immediate: true },
    );

    watch(count, (val, oldVal) => {
    console.log('count', val, oldVal);
    });

    watch([count, bar], ([currentCount, currentBar], [oldCount, oldBar]) => {
    console.log('count & bar current: ', currentCount, currentBar);
    console.log('count & bar old: ', oldCount, oldBar);
    });

    onMounted(() => {
    setTimeout(() => {
    user.name = 'pp';
    count.value += 1;
    bar.value = 'foo';
    }, 3 * 1000);
    });
    与之前不能的是, watch 现在可以监听多个值了, watch 会返回一个用于停止监听的函数,显式的调用该函数,就可以停止监听:
    const stop = watch(count, (val, oldVal) => { });

    // 停止监听
    stop();
    Q: 在 Vue2 中如何同时监听多个值呢?
  • watchEffect
    watchEffect 与 wacth 的功能类似,不过它不需要像 watch 那样指明被依赖的数据。
    const count = ref(1);

    watchEffect(() => {
    document.title = `count is ${count.value}`;
    });

    onMounted(() => {
    setTimeout(() => {
    count.value += 1;
    }, 3 * 1000);
    });
    看着是不是和 React.useEffect 很像。那可不可以和 React.useEffect 一样清除副作用呢?当然可以:
    const count = ref(1);

    const onResize = () => {
    count.value++;
    };

    watchEffect((onInvalidate) => {
    window.addEventListener('resize', onResize, false);
    onInvalidate(() => {
    window.removeEventListener('resize', onResize, false);
    });
    });
    那清除副作用的函数 清除副作用会在何时调用呢?
  • readonly
    用于创建一个只读的代理对象,参数可以是普通对象、代理对象、或者 ref
    const count = ref(0);
    const readonlyCount = readonly(count);

    const config = readonly({
    baseURL: '/api',
    timeout: 15 * 1000,
    });

    const user = reactive({ name: 'qq', age: 18 });
    const readonlyUser = readonly(user);

    onMounted(() => {
    count.value++;
    console.log('count.value:', count.value);
    console.log('readonlyCount.value:', count.value);

    readonlyUser.name = 'pp'; // 无法修改并且发出警告
    });

    Lifecycle Hooks

    Vue3 中新增了一些周期钩子,这钩子只能在 setup 期间同步使用(或者在自定义 hook 中使用,但是自定义 hook 最中还是要在 setup 中使用)。为它们依赖于内部的全局状态来定位当前组件实例(正在调用 setup() 的组件实例), 不在当前组件下调用这些函数会抛出一个错误。
  • beforeCreate -> 使用 setup()
  • created -> 使用 setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured

新增

  • onRenderTracked
  • onRenderTriggered

可以用这个钩子进行调试优化。

响应式系统工具集

  • isRef()
    检查一个值是否为一个 ref 对象,其实是通过 __v_isRef 来判断的
  • toRef()
    为一个 reactive 对象的属性创建一个 ref 。 toRef() 则可以解决这个问题
    const user = reactive({ name: 'qq', age: 18 });
    const userNameRef = toRef(user, 'name'); // userNameRef.value qq
  • toRefs()
    把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref ,和响应式对象 property 一一对应。
    const user = reactive({ name: 'qq', age: 18 });
    const userRef = toRefs(user);

    onMounted(() => {
    setTimeout(() => {
    user.name = 'pp';
    userRef.age.value = 16;

    console.log(userRef.name.value); // pp
    console.log(user.age); //16
    }, 3000);
    });
  • unref()
    如果参数是一个 ref 则返回它的 value,否则返回参数本身。它是 val = isRef(val) ? val.value : val 的语法糖。
  • isProxy
    检查一个对象是否是由 reactive 或者 readonly 方法创建的代理。
  • isReactive
    检查一个对象是否是由 reactive 创建的响应式代理。如果这个代理是由 readonly 创建的,但是又被 reactive 创建的另一个代理包裹了一层,那么同样也会返回 true。
  • isReadonly
    检查一个对象是否是由 readonly 创建的只读代理。

体验 Composition API

  • npm init vite-app vue-vite
  • vue add vue-next
  • npm i @vue/composition-api
请我喝杯咖啡
请我喝杯咖啡