手机版

深刻理解vue.js中钩子的相关知识

时间:2021-08-22 来源:互联网 编辑:宝哥软件园 浏览:

背景

最近我们研究了vue3.0的最新发展,发现变化很大。总的来说,vue已经开始向hooks靠拢,vue的作者本人也表示,vue3.0的特性从hooks身上吸收了很多灵感。所以,在vue3.0正式发布之前,花点时间研究一下钩子相关的东西。

源地址:vue-hooks-poc

为什么要用钩子?

类别-组件/vue-选项:

跨组件代码难以复用大型组件,维护困难,粒度难以控制,细粒度划分中组件嵌套太深——影响性能类组件,这是不可控的,逻辑是分散的,不容易理解mixins有副作用,逻辑是相互嵌套的,数据源是未知的,不能相互消费。当一个模板依赖多个mixin时,很容易出现数据源不清晰或者命名冲突的情况,开发mixin时,逻辑和逻辑是相互依赖的。这些都是开发中非常痛苦的地方,所以在vue3.0中引入钩子相关的特性是明智的。

vue挂钩

在探索vue-hooks之前,我们应该大致回顾一下vue的响应系统:首先,在初始化vue组件时,会响应性地处理挂载在数据上的属性(会挂载依赖关系管理器),然后在将模板编译成v-dom的过程中,会实例化一个Watcher observer来观察整个对齐的vnode,同时会访问这些依赖属性,这会触发依赖关系管理器收集依赖关系(与Watcher observer建立关联)。当依赖属性改变时,相应的Watcher观察者将被通知重新评估(setter-notify-watcher-run),相应的模板被重新渲染(re-render)。

注意:默认情况下,vue在内部将重新渲染过程放置在子任务队列中,当前渲染将在最后一个渲染刷新阶段进行评估。

带着书

导出函数withouks(render){ return { data(){ return { _ state : } } },created() {this。_ EffectStore={ this }。_ refsStore={ }此。_computedStore={}},render(h){ callIndex=0 current instance=thistmounting=!这个。_ vnode const ret=render (h,this。$ attrs,这个。$ props)current instance=null ret } } } with hooks为vue组件提供了hooks jsx的开发模式,使用如下:

使用手册导出默认值((h)={ 0 }.return span/span})很容易看到,使用hooks仍然会返回vue组件的一个配置项选项,后续的hooks相关属性会挂载在本地提供的选项上。

首先,分析vue-hooks需要使用的几个全局变量:

CurrentInstance:缓存当前vue实例is mounting:render是否是第一次呈现isMounting=!这个。_vnode

这里,_vnode与$vnode非常不同,后者代表父组件(vm。_vnode.parent)

_vnode被初始化为null,并将在安装阶段被分配给当前组件的v-dom

IsMounting不仅控制内部数据的初始化阶段,还可以防止重复重新呈现。

CallIndex:属性索引,在选项上挂载属性时用作唯一的索引标识符。vue选项上声明的几个局部变量:

_state:放置响应数据_refsStore:放置非响应数据并返回引用类型_effectStore:存储副作用逻辑和清理逻辑_computedStore:存储计算属性。最后,回调函数使用传入的attrs和$props作为输入参数,在呈现当前组件后,重置全局变量以呈现下一个组件。

使用的数据

const data=useData(initial)导出函数useData(initial){ const id=callIndexconst state=current instance。$数据。_ state if(IsMounting){ current instance。$ set (state,id,initial)} return state [id]}我们知道,如果我们想要响应性地监控一个数据的变化,我们需要在vue中经过一些处理,场景是有限的。当使用useData声明变量时,响应数据将装载在内部数据上。_state。但是,缺点是没有提供更新程序,当返回到外部的数据发生变化时,可能会失去响应性的监控。

useState

const [data,setData]=useState(initial)导出函数useState(initial){ ensureCurrentInstance()} const id=callIndexconst state=currentInstance。$数据。_ state const updater=NewVaLue={ state[id]=NewVaLue } if(IsMounting){ current instance。$set (state,id,initial)} return [state [id,updater]} usestate是hooks的核心API之一,它通过闭包在内部提供了一个updater。使用更新程序可以相应地更新数据,并且在数据更改后会触发重新渲染。下一个渲染过程不会再次用$ set初始化。

useRef

const data=useRef(initial)//data={ current : initial }导出函数useRef(initial){ ensureCurrentInstance()} const id=callIndexconst { _ refs store : refs }=currentInstancereturn is mounting?(refs[id]={ current : initial }): refs[id]}使用useRef初始化将返回一个携带电流的引用,该引用指向初始化的值。第一次用useRef的时候,一直看不懂它的应用场景,但是上手之后还是有一些感触的。

例如,有以下代码:

导出默认值withouks(h={ const[count,setCount]=useState(0)const num=useRef(count)const log=()={ let sum=count 1 setCount(sum)num . current=sum console . log(count,num . current);}返回(按钮onclick={log} {count} {num。current}/button)})点击按钮将值设置为1,同时打印相应的变量。输出结果是:

0 11 22 33 44 5可以看到num.current总是最新的值,而count获取最后一个渲染值。

实际上,将num提升到全局范围可以达到同样的效果。

因此,我们可以预见useRef的使用场景:

在多次重新渲染期间保存最新值。该值不需要以响应的方式处理,也不会污染其他范围。

UseEffect(function()={//side effect logic return()={//cleaning logic } },[deps])导出函数use effect (raweffect,deps){ ensureCurrentInstance()} const id=callindesif(IsMounting){ const clean up=()={ const { current }=clean upif(current){ current()()const effect={ const { current }=effect(current){ clean up . current=current . call(this)effect . current=null } } current。_effectStore[id]={effect,cleanup,deps}currentInstance。$ on(' hook : mount ',effect)currentInstance。$ on(' hook : desired ',cleanup)if(!deps | | deps . length 0){ current instance。$on('hook:updated ',effect)} } else { const record=current instance。_effectStore[id]const { effect,cleanup,deps : prev deps=[]}=record record . deps=dep SIF(!deps || deps.some((d,i)=d!==prevdeps [I])) {cleanup()效果。current=raweffect}}} useeffect也是hooks中最重要的API之一,负责副作用处理和清理逻辑。这里的副作用可以理解为可以根据依赖关系有选择地执行的操作,不需要每次重新渲染都执行,比如dom操作和网络请求。这些操作可能会导致一些副作用,比如需要清除dom侦听器、空引用等等。

从执行顺序来看,初始化时声明清理函数和副作用函数,效果的当前指向当前副作用逻辑。副作用函数在装载阶段被调用一次,返回值被保存为清理逻辑。同时根据依赖关系判断是否在更新阶段再次调用副作用函数。第一次渲染时,会根据deps依赖关系判断是否需要再次调用副作用函数。如果需要再次执行,会先清除上次渲染产生的副作用,将副作用函数的当前指向最新的副作用逻辑,等待更新的阶段调用。

使用安装的

use mounted(function(){ })export function use mounted(fn){ use effect(fn,[])}当使用效果依赖于传递[],副作用函数只在mounted阶段调用。

使用过的

使用销毁(function () {})导出函数使用销毁(fn){使用效果(()=fn,[])}使用效果取决于pass []并且有一个返回函数,该函数将作为清理逻辑被调用销毁。

使用更新

使用更新(fn,deps)导出函数useUpdated(fn,deps){ const is mount=useRef(true)useEffect(()={ if(is mount。当前){已安装。current=false } else { return fn()},deps)}如果依赖的列表固定不变,传入的使用效果会在安装好的和更新阶段各执行一次,这里借助useRef声明一个持久化的变量,来跳过安装好的阶段。

使用观察

导出函数使用Watch(getter,cb,options){ ensureCurrentInstance()(if(is mounting){ currentInstance } .$watch(getter、cb、options)}}使用方式同$手表。这里加了一个是否初次渲染判断,防止重新渲染产生多余看守人观察者。

使用计算的

const data=useData({ count :1 })const getCount=useComputed(()=data。计数)导出函数useComputed(getter){ ensureCurrentInstance()} const id=callIndexconst store=currentInstance ._ computed store if(IsMounting){ store[id]=getter()当前实例.$watch(getter,val={store[id]=val},{ sync : true })}返回存储[id]}useComputed首先会计算一次依赖值并缓存,调用$手表来观察依赖属性变化,并更新对应的缓存值。

实际上,vue底层对计算对处理要稍微复杂一些,在初始化计算时,采用懒惰:真(异步)的方式来监听依赖变化,即依赖属性变化时不会立刻求值,而是控制肮脏的变量变化;并将计算属性对应的键绑定到组件实例上,同时修改为访问器属性,等到访问该计算属性的时候,再依据肮脏的来判断是否求值。

这里直接调用看会在属性变化时,立即获取最新值,而不是等到渲染齐平阶段去求值。

钩住

导出函数钩子(Vue){ Vue。mixin({ BeforeCREAte(){ const { hooks,data }=this .$optionsif (hooks) {this ._ EffectStore={ this } ._ refsStore={ }此_computedStore={}//改写数据函数,注入_状态属性这个$期权。data=function(){ const ret=data?数据。拨打(此): { ret ._ state={ } ret ret } } },beforeMount() {const { hooks,render }=this .$optionsif(钩子渲染){//改写组件的提出函数这个$期权。render=function(h){ callIndex=0当前实例=thistmounting=!这个_vnode//默认传入小道具属性const hookProps=hooks(这个$props)//_self指示本身组件实例Object.assign(此. self,hook props)const ret=render。调用(this,h)当前实例=nullreturn ret } } })借助有了书,我们可以发挥钩住的作用,但牺牲来很多某视频剪辑软件的特性,比如道具、道具、组件等。

某视频剪辑软件挂钩暴露了一个钩住函数,开发者在入口Vue.use(钩子)之后,可以将内部逻辑混入所有的子组件。这样,我们就可以在表面(同表面)组件中使用钩住啦。

为了便于理解,这里简单实现了一个功能,将动态计算元素节点尺寸封装成独立的挂钩:

模板节类=' demo ' p { { resize } }/p/节/模板脚本导入{ hooks,useRef,useData,useState,useEffect,useMounted,useWatch } from './hooks ';函数uselesize(El){ const node=useRef(null);const [resize,setResize]=useState({ });useEffect(function(){ if(El){ node。元素的实例?El :文件。query selector(El);} else { node。curr net=文档。身体;} const Observer=new resize Observer(条目={ entries。foreach)({ content rect })={ setResize(content rect);});});观察者。观察(节点。curr net);return()={ observer。未观察(节点。curr net);观察者。disconnect();};},[]);返回调整大小;}导出默认值{ props: { msg: String },//这里和设置函数很接近了,都是接受道具,最后返回依赖的属性钩子(道具){ const data=user esize();返回{ resize : JSON。stringify(data)};}};/scriptstylehtml,body { height : 100%;}/样式使用效果是,元素尺寸变更时,将变更信息输出至文档中,同时在组件销毁时,注销调整大小监听器。

钩住返回的属性,会合并进组件的自身实例中,这样模版绑定的变量就可以引用了。

钩住存在什么问题?

在实际应用中,发现hooks可以解决mixin带来的很多问题,同时可以更抽象地开发组件。但同时也带来了更高的门槛。例如,useEffect在使用时必须忠于依赖性,否则将需要几分钟才能导致渲染的无限循环。与react-hooks相比,vue可以借鉴函数抽象和重用的能力,同时可以充分发挥自身的响应跟踪优势。我们可以看到给出的视图,尤其是与react-hooks的比较:

总体来说,更符合JavaScript直觉;可以有条件调用,不受调用顺序限制。不会在后续更新中产生大量的内联函数,影响引擎优化或导致GC压力;没有必要总是使用useCallback来缓存对子组件的回调以防止过度更新;不需要担心传递不正确的依赖数组给useEffect/useMemo/useCallback,导致回调中使用过期值—— Vue。依赖项跟踪是完全自动的。

感觉

为了在vue3.0发布后更快地上手新功能,我研究了与hooks相关的源代码,发现收获比我想象的要多,对比新发布的RFC,我突然意识到。不幸的是,由于工作原因,许多开发项目依赖vue-property-decorator进行ts适配。看来三个版本出来后会有很大的变化。

最后,钩子真香(逃)

以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。

版权声明:深刻理解vue.js中钩子的相关知识是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。