实际上,vue底层对computed对处理要稍微复杂一些,在初始化computed时,采用lazy:true(异步)的方式来监听依赖变化,即依赖属性变化时不会立刻求值,而是控制dirty变量变化;并将计算属性对应的key绑定到组件实例上,同时修改为访问器属性,等到访问该计算属性的时候,再依据dirty来判断是否求值。
这里直接调用watch会在属性变化时,立即获取最新值,而不是等到render flush阶段去求值。
hooks
export function hooks (Vue) { Vue.mixin({ beforeCreate() { const { hooks, data } = this.$options if (hooks) { this._effectStore = {} this._refsStore = {} this._computedStore = {} // 改写data函数,注入_state属性 this.$options.data = function () { const ret = data ? data.call(this) : {} ret._state = {} return ret } } }, beforeMount() { const { hooks, render } = this.$options if (hooks && render) { // 改写组件的render函数 this.$options.render = function(h) { callIndex = 0 currentInstance = this isMounting = !this._vnode // 默认传入props属性 const hookProps = hooks(this.$props) // _self指示本身组件实例 Object.assign(this._self, hookProps) const ret = render.call(this, h) currentInstance = null return ret } } } }) }
借助withHooks,我们可以发挥hooks的作用,但牺牲来很多vue的特性,比如props,attrs,components等。
vue-hooks暴露了一个hooks函数,开发者在入口Vue.use(hooks)之后,可以将内部逻辑混入所有的子组件。这样,我们就可以在SFC组件中使用hooks啦。
为了便于理解,这里简单实现了一个功能,将动态计算元素节点尺寸封装成独立的hooks:
<template> <section> <p>{{resize}}</p> </section> </template> <script> import { hooks, useRef, useData, useState, useEffect, useMounted, useWatch } from '../hooks'; function useResize(el) { const node = useRef(null); const [resize, setResize] = useState({}); useEffect( function() { if (el) { node.currnet = el instanceof Element ? el : document.querySelector(el); } else { node.currnet = document.body; } const Observer = new ResizeObserver(entries => { entries.forEach(({ contentRect }) => { setResize(contentRect); }); }); Observer.observe(node.currnet); return () => { Observer.unobserve(node.currnet); Observer.disconnect(); }; }, [] ); return resize; } export default { props: { msg: String }, // 这里和setup函数很接近了,都是接受props,最后返回依赖的属性 hooks(props) { const data = useResize(); return { resize: JSON.stringify(data) }; } }; </script> <style> html, body { height: 100%; } </style>
使用效果是,元素尺寸变更时,将变更信息输出至文档中,同时在组件销毁时,注销resize监听器。
hooks返回的属性,会合并进组件的自身实例中,这样模版绑定的变量就可以引用了。
hooks存在什么问题?
在实际应用过程中发现,hooks的出现确实能解决mixin带来的诸多问题,同时也能更加抽象化的开发组件。但与此同时也带来了更高的门槛,比如useEffect在使用时一定要对依赖忠诚,否则引起render的死循环也是分分钟的事情。
与react-hooks相比,vue可以借鉴函数抽象及复用的能力,同时也可以发挥自身响应式追踪的优势。我们可以看尤在与react-hooks对比中给出的看法:
整体上更符合 JavaScript 的直觉;
不受调用顺序的限制,可以有条件地被调用;
不会在后续更新时不断产生大量的内联函数而影响引擎优化或是导致 GC 压力;
不需要总是使用 useCallback 来缓存传给子组件的回调以防止过度更新;
不需要担心传了错误的依赖数组给 useEffect/useMemo/useCallback 从而导致回调中使用了过期的值 —— Vue 的依赖追踪是全自动的。
感受
为了能够在vue3.0发布后更快的上手新特性,便研读了一下hooks相关的源码,发现比想象中收获的要多,而且与新发布的RFC对比来看,恍然大悟。可惜工作原因,开发项目中很多依赖了vue-property-decorator来做ts适配,看来三版本出来后要大改了。
最后,hooks真香(逃)