首页后端开发PHPVue3 computed和watch怎么实现

Vue3 computed和watch怎么实现

时间2023-07-10 21:02:02发布访客分类PHP浏览833
导读:这篇文章主要介绍“Vue3 computed和watch怎么实现”,在日常操作中,相信很多人在Vue3 computed和watch怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Vue3 comput...
这篇文章主要介绍“Vue3 computed和watch怎么实现”,在日常操作中,相信很多人在Vue3 computed和watch怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Vue3 computed和watch怎么实现”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

    computed

    computed和watch在面试中经常被问到他们的区别,那么我们就从源码的实现来看看他们的具体实现

    //packages/reactivity/src/computed.ts
    exportfunctioncomputedT>
        (
    getterOrOptions:ComputedGetterT>
        |WritableComputedOptionsT>
    ,
    debugOptions?:DebuggerOptions,
    isSSR=false
    ){
        
    letgetter:ComputedGetterT>
        
    letsetter:ComputedSetterT>
    
    constonlyGetter=isFunction(getterOrOptions)
    if(onlyGetter){
        
    getter=getterOrOptions
    setter=__DEV__
    ?()=>
    {
    
    console.warn('Writeoperationfailed:computedvalueisreadonly')
    }
    
    :NOOP
    }
    else{
    
    getter=getterOrOptions.get
    setter=getterOrOptions.set
    }
        
    //newComputedRefImpl
    constcRef=newComputedRefImpl(getter,setter,onlyGetter||!setter,isSSR)
    if(__DEV__&
        &
        debugOptions&
        &
    !isSSR){
    
    cRef.effect.onTrack=debugOptions.onTrack
    cRef.effect.onTrigger=debugOptions.onTrigger
    }
    
    //返回ComputedRefImpl实例
    returncRefasany
    }
        

    可以看到computed内部只是先处理getter和setter,然后new一个ComputedRefImpl返回,如果你知道ref API的实现,可以发现他们的实现有很多相同之处

    ComputedRefImpl

    //packages/reactivity/src/computed.ts
    exportclassComputedRefImplT>
    {
        
    publicdep?:Dep=undefined//存储effect的集合
    private_value!:T
    publicreadonlyeffect:ReactiveEffectT>
        
    publicreadonly__v_isRef=true
    publicreadonly[ReactiveFlags.IS_READONLY]:boolean=false
    public_dirty=true//是否需要重新更新value
    public_cacheable:boolean
    constructor(
    getter:ComputedGetterT>
        ,
    privatereadonly_setter:ComputedSetterT>
    ,
    isReadonly:boolean,
    isSSR:boolean
    ){
        
    //创建effect
    this.effect=newReactiveEffect(getter,()=>
    {
    
    //调度器执行重新赋值_dirty为true
    if(!this._dirty){
    
    this._dirty=true
    //触发effect
    triggerRefValue(this)
    }
    
    }
    )
    //用于区分effect是否是computed
    this.effect.computed=this
    this.effect.active=this._cacheable=!isSSR
    this[ReactiveFlags.IS_READONLY]=isReadonly
    }
    
    getvalue(){
    
    //thecomputedrefmaygetwrappedbyotherproxiese.g.readonly()#3376
    //computedref可能被其他代理包装,例如readonly()#3376
    //通过toRaw()获取原始值
    constself=toRaw(this)
    //收集effect
    trackRefValue(self)
    //如果是脏的,重新执行effect.run(),并且将_dirty设置为false
    if(self._dirty||!self._cacheable){
    
    self._dirty=false
    //run()方法会执行getter方法值会被缓存到self._value
    self._value=self.effect.run()!
    }
    
    returnself._value
    }
    
    setvalue(newValue:T){
    
    this._setter(newValue)
    }
    
    }
        

    可以看到ComputedRefImplget的get实现基本和ref的get相同(不熟悉ref实现的请看上一章),唯一的区别就是_dirty值的判断,这也是我们常说的computed会缓存value,那么computed是如何知道value需要更新呢?

    可以看到在computed构造函数中,会建立一个getter与其内部响应式数据的关系,这跟我们组件更新函数跟响应式数据建立关系是一样的,所以与getter相关的响应式数据发生修改的时候,就会触发getter effect 对应的scheduler,这里会将_dirty设置为true并去执行收集到的effect(这里通常是执行get里收集到的函数更新的effect),然后就会去执行函数更新函数,里面会再次触发computed的get,此时dirty已经被置为true,就会重新执行getter获取新的值返回,并将该值缓存到_vlaue。

    小结:

    所以computed是有两层的响应式处理的,一层是computed.value和函数的effect之间的关系(与ref的实现相似),一层是computed的getter和响应式数据的关系。

    注意:如果你足够细心就会发现函数更新函数的effect触发和computed getter的effect的触发之间可能存在顺序的问题。假如有一个响应式数据a不仅存在于getter中,还在函数render中早于getter被访问,此时a对应的dep中更新函数的effect就会早于getter的effect被收集,如果此时a被改变,就会先执行更新函数的effect,那么此时render函数访问到computed.value的时候就会发现_dirty依然是false,因为getter的effect还没有被执行,那么此时依然会是旧值。vue3中对此的处理是执行effects的时候会优先执行computed对应的effect(此前章节也有提到):

    //packages/reactivity/src/effect.ts
    exportfunctiontriggerEffects(
    dep:Dep|ReactiveEffect[],
    debuggerEventExtraInfo?:DebuggerEventExtraInfo
    ){
    
    //spreadintoarrayforstabilization
    consteffects=isArray(dep)?dep:[...dep]
    //computed的effect会先执行
    //防止render获取computed值得时候_dirty还没有置为true
    for(consteffectofeffects){
    
    if(effect.computed){
    
    triggerEffect(effect,debuggerEventExtraInfo)
    }
    
    }
    
    for(consteffectofeffects){
    
    if(!effect.computed){
    
    triggerEffect(effect,debuggerEventExtraInfo)
    }
    
    }
    
    }
        

    watch

    watch相对于computed要更简单一些,因为他只用建立getter与响应式数据之间的关系,在响应式数据变化时调用用户传过来的回调并将新旧值传入即可

    //packages/runtime-core/src/apiWatch.ts
    exportfunctionwatchT=any,ImmediateextendsReadonlyboolean>
        =false>
        (
    source:T|WatchSourceT>
        ,
    cb:any,
    options?:WatchOptionsImmediate>
    
    ):WatchStopHandle{
        
    if(__DEV__&
        &
    !isFunction(cb)){
    
    warn(...)
    }
    
    //watch具体实现
    returndoWatch(sourceasany,cb,options)
    }
        
    functiondoWatch(
    source:WatchSource|WatchSource[]|WatchEffect|object,
    cb:WatchCallback|null,
    {
    immediate,deep,flush,onTrack,onTrigger}
    :WatchOptions=EMPTY_OBJ
    ):WatchStopHandle{
        
    if(__DEV__&
        &
    !cb){
    
    ...
    }
        
    constwarnInvalidSource=(s:unknown)=>
    {
    
    warn(...)
    }
        
    constinstance=
    getCurrentScope()===currentInstance?.scope?currentInstance:null
    //constinstance=currentInstance
    letgetter:()=>
    any
    letforceTrigger=false
    letisMultiSource=false
    //根据不同source创建不同的getter函数
    //getter函数与computed的getter函数作用类似
    if(isRef(source)){
        
    getter=()=>
    source.value
    forceTrigger=isShallow(source)
    }
    elseif(isReactive(source)){
        
    //source是reactive对象时自动开启deep=true
    getter=()=>
    source
    deep=true
    }
    elseif(isArray(source)){
        
    isMultiSource=true
    forceTrigger=source.some(s=>
        isReactive(s)||isShallow(s))
    getter=()=>
        
    source.map(s=>
    {
    
    if(isRef(s)){
    
    returns.value
    }
    elseif(isReactive(s)){
    
    returntraverse(s)
    }
    elseif(isFunction(s)){
    
    returncallWithErrorHandling(s,instance,ErrorCodes.WATCH_GETTER)
    }
    else{
        
    __DEV__&
        &
    warnInvalidSource(s)
    }
    
    }
    )
    }
    elseif(isFunction(source)){
    
    if(cb){
        
    //getterwithcb
    getter=()=>
    
    callWithErrorHandling(source,instance,ErrorCodes.WATCH_GETTER)
    }
    else{
        
    //nocb->
        simpleeffect
    getter=()=>
    {
        
    if(instance&
        &
    instance.isUnmounted){
    
    return
    }
    
    if(cleanup){
    
    cleanup()
    }
    
    returncallWithAsyncErrorHandling(
    source,
    instance,
    ErrorCodes.WATCH_CALLBACK,
    [onCleanup]
    )
    }
    
    }
    
    }
    else{
        
    getter=NOOP
    __DEV__&
        &
    warnInvalidSource(source)
    }
        
    //2.xarraymutationwatchcompat
    //兼容vue2
    if(__COMPAT__&
        &
        cb&
        &
    !deep){
        
    constbaseGetter=getter
    getter=()=>
    {
        
    constval=baseGetter()
    if(
    isArray(val)&
        &
    
    checkCompatEnabled(DeprecationTypes.WATCH_ARRAY,instance)
    ){
    
    traverse(val)
    }
    
    returnval
    }
    
    }
        
    //深度监听
    if(cb&
        &
    deep){
        
    constbaseGetter=getter
    //traverse会递归遍历对象的所有属性以达到深度监听的目的
    getter=()=>
    traverse(baseGetter())
    }
        
    letcleanup:()=>
        void
    //watch回调的第三个参数可以用此注册一个cleanup函数会在下一次watchcb调用前执行
    //常用于竞态问题的处理
    letonCleanup:OnCleanup=(fn:()=>
        void)=>
    {
        
    cleanup=effect.onStop=()=>
    {
    
    callWithErrorHandling(fn,instance,ErrorCodes.WATCH_CLEANUP)
    }
    
    }
        
    //inSSRthereisnoneedtosetupanactualeffect,anditshouldbenoop
    //unlessit'seagerorsyncflush
    letssrCleanup:(()=>
        void)[]|undefined
    if(__SSR__&
        &
    isInSSRComponentSetup){
    
    //ssr处理...
    }
        
    //oldValue声明多个source监听则初始化为数组
    letoldValue:any=isMultiSource
    ?newArray((sourceas[]).length).fill(INITIAL_WATCHER_VALUE)
    :INITIAL_WATCHER_VALUE
    //调度器调用时执行
    constjob:SchedulerJob=()=>
    {
    
    if(!effect.active){
    
    return
    }
    
    if(cb){
        
    //watch(source,cb)
    //获取newValue
    constnewValue=effect.run()
    if(
    deep||
    forceTrigger||
    (isMultiSource
    ?(newValueasany[]).some((v,i)=>
        
    hasChanged(v,(oldValueasany[])[i])
    )
    :hasChanged(newValue,oldValue))||
    (__COMPAT__&
        &
        
    isArray(newValue)&
        &
    
    isCompatEnabled(DeprecationTypes.WATCH_ARRAY,instance))
    ){
    
    //cleanupbeforerunningcbagain
    if(cleanup){
    
    //执行onCleanup传过来的函数
    cleanup()
    }
        
    //调用cb参数为newValue、oldValue、onCleanup
    callWithAsyncErrorHandling(cb,instance,ErrorCodes.WATCH_CALLBACK,[
    newValue,
    //passundefinedastheoldvaluewhenit'schangedforthefirsttime
    oldValue===INITIAL_WATCHER_VALUE
    ?undefined
    :isMultiSource&
        &
    oldValue[0]===INITIAL_WATCHER_VALUE
    ?[]
    :oldValue,
    onCleanup
    ])
    //更新oldValue
    oldValue=newValue
    }
    
    }
    else{
    
    //watchEffect
    effect.run()
    }
    
    }
    
    //important:markthejobasawatchercallbacksothatschedulerknows
    //itisallowedtoself-trigger(#1727)
    job.allowRecurse=!!cb
    letscheduler:EffectScheduler
    if(flush==='sync'){
    
    //同步更新即每次响应式数据改变都会回调一次cb通常不使用
    scheduler=jobasany//theschedulerfunctiongetscalleddirectly
    }
    elseif(flush==='post'){
        
    //job放入pendingPostFlushCbs队列中
    //pendingPostFlushCbs队列会在queue队列执行完毕后执行函数更新effect通常会放在queue队列中
    //所以pendingPostFlushCbs队列执行时组件已经更新完毕
    scheduler=()=>
        queuePostRenderEffect(job,instance&
        &
    instance.suspense)
    }
    else{
        
    //default:'pre'
    job.pre=true
    if(instance)job.id=instance.uid
    //默认异步更新关于异步更新会和nextTick放在一起详细讲解
    scheduler=()=>
    queueJob(job)
    }
    
    //创建effecteffect.run的时候建立effect与getter内响应式数据的关系
    consteffect=newReactiveEffect(getter,scheduler)
    if(__DEV__){
    
    effect.onTrack=onTrack
    effect.onTrigger=onTrigger
    }
    
    //initialrun
    if(cb){
    
    if(immediate){
    
    //立马执行一次job
    job()
    }
    else{
    
    //否则执行effect.run()会执行getter获取oldValue
    oldValue=effect.run()
    }
    
    }
    elseif(flush==='post'){
        
    queuePostRenderEffect(
    effect.run.bind(effect),
    instance&
        &
    instance.suspense
    )
    }
    else{
    
    effect.run()
    }
        
    //返回一个取消监听的函数
    constunwatch=()=>
    {
        
    effect.stop()
    if(instance&
        &
    instance.scope){
    
    remove(instance.scope.effects!,effect)
    }
    
    }
        
    if(__SSR__&
        &
    ssrCleanup)ssrCleanup.push(unwatch)
    returnunwatch
    }
        

    到此,关于“Vue3 computed和watch怎么实现”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

    声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!

    vue3computedwatch

    若转载请注明出处: Vue3 computed和watch怎么实现
    本文地址: https://pptw.com/jishu/301632.html
    javascript注释语句怎么写 java注解的作用是什么

    游客 回复需填写必要信息