Vue原始碼學習(十三):實現watch(一):方法,物件

2023-10-29 06:01:42

好傢伙,

 程式碼出了點bug,暫時只能實現這兩種形式

 

完整程式碼已開源https://github.com/Fattiger4399/analytic-vue.git

Vue:watch的多種使用方法

watch有非常多種使用方式,我們要對其進行分類討論處理

 

1.初始化:

//initState.js

if (opts.watch) {
        initWatch(vm);
    }

 

initWatch()方法

function initWatch(vm) {
    //1 獲取watch
    let watch = vm.$options.watch
    console.log(watch)
    //2 遍歷  { a,b,c}
    for (let key in watch) {
        //2.1獲取 他的屬性對應的值 (判斷)
        let handler = watch[key] //陣列 ,物件 ,字元,函數
        if (Array.isArray(handler)) {//陣列  []
            handler.forEach(item=>{
                createrWatcher(vm,key,item) 
            })
        } else {//物件 ,字元,函數
           //3建立一個方法來處理
           createrWatcher(vm,key,handler)
        }
    }
}

 

createrWatcher()

//格式化處理
//vm 範例
//exprOrfn key
//hendler key對應的值
//options 自定義設定項 vue自己的為空,使用者定義的才有
function createrWatcher(vm,exprOrfn,handler,options){
   //3.1 處理handler
   if(typeof handler ==='object'){
       options = handler; //使用者的設定專案
       handler = handler.handler;//這個是一個函數
   }
   if(typeof handler ==='string'){// 'aa'
       handler = vm[handler] //將範例行的方法作為 handler 方法代理和data 一樣
   }
   //其他是 函數
   //watch 最終處理 $watch 這個方法
//    console.log(vm,"||vm")
//    console.log(exprOrfn,"||exprOrfn")
//    console.log(handler,"||handler")
//    console.log(options,"||options")

   return vm.$watch(vm,exprOrfn,handler,options)
}

 

原型上掛$watch方法

export function stateMixin(vm) {
    console.log(vm,6666)
    //列隊 :1就是vue自己的nextTick  2使用者自己的
    vm.prototype.$nextTick = function (cb) { //nextTick: 資料更新之後獲取到最新的DOM
        //  console.log(cb)
        nextTick(cb)
    },
    vm.prototype.$watch =function(Vue,exprOrfn,handler,options={}){ //上面格式化處理
        //   console.log(exprOrfn,handler,options)
          //實現watch 方法 就是new  watcher //渲染走 渲染watcher $watch 走 watcher  user false
         //  watch 核心 watcher
         let watcher = new Watcher(Vue,exprOrfn,handler,{...options,user:true})
          
         if(options.immediate){
            handler.call(Vue) //如果有這個immediate 立即執行
         }
    }
    
}

 

 

2.watcher.js

watcher類

class Watcher {
    //vm 範例
    //exprOrfn vm._updata(vm._render()) 
    constructor(vm, exprOrfn, cb, options) {
        // 1.建立類第一步將選項放在範例上
        this.vm = vm;
        this.exprOrfn = exprOrfn;
        this.cb = cb;
        this.options = options;
        // 2. 每一元件只有一個watcher 他是為標識
        this.id = id++
        this.user = !!options.user
        // 3.判斷表示式是不是一個函數
        this.deps = []  //watcher 記錄有多少dep 依賴
        this.depsId = new Set()
        if (typeof exprOrfn === 'function') {
            this.getter = exprOrfn
        }else{ //{a,b,c}  字串 變成函數 
            this.getter =function(){ //屬性 c.c.c
              let path = exprOrfn.split('.')
              let obj = vm
              for(let i = 0;i<path.length;i++){
                obj  = obj[path[i]]
              }
              return obj //
            }
        }
        // 4.執行渲染頁面
        this.value =  this.get() //儲存watch 初始值

    }
    addDep(dep) {
        //去重  判斷一下 如果dep 相同我們是不用去處理的
        let id = dep.id
        //  console.log(dep.id)
        if (!this.depsId.has(id)) {
            this.deps.push(dep)
            this.depsId.add(id)
            //同時將watcher 放到 dep中
            // console.log(666)
            dep.addSub(this)

        }
        // 現在只需要記住  一個watcher 有多個dep,一個dep 有多個watcher
        //為後面的 component 
    }
    run() { //old new
       let value =  this.get() //new
       let oldValue = this.value //old
       this.value = value
       //執行 hendler (cb) 這個使用者wathcer
       if(this.user){
        this.cb.call(this.vm,value,oldValue)
       }
    }
    get() {
        // Dep.target = watcher

        pushTarget(this) //當前的範例新增
      const value = this.getter()// 渲染頁面  render()   with(wm){_v(msg,_s(name))} ,取值(執行get這個方法) 走劫持方法
        popTarget(); //刪除當前的範例 這兩個方法放在 dep 中
        return value
    }
    //問題:要把屬性和watcher 繫結在一起   去html頁面
    // (1)是不是頁面中呼叫的屬性要和watcher 關聯起來
    //方法
    //(1)建立一個dep 模組
    updata() { //三次
        //注意:不要資料更新後每次都呼叫 get 方法 ,get 方法回重新渲染
        //快取
        // this.get() //重新渲染
        queueWatcher(this)
    }
}

 

3.看看效果

<body>
    <div id="app">{{a}}</div>
    <script src='./dist/vue.js'></script>
    <script>
        let vm = new Vue({
            el: "#app",
            data: {
                a: 1,
                b:[1,2],
                c:{c:{c:100}}
            },
            methods:{
                aa(){
                    console.log(2000)
                }
            },
            //watch 基本使用方式
            //1 屬性 :方法(函數)
            //2 屬性 :陣列
            //3 屬性 :物件
            //4 屬性 :字串
            watch: {
                  'c.c.c'(newValue,oldValue){
                      console.log(newValue,oldValue,'||this isnewValue,oldValue from c.c.c')
                  },
                
                a:{
                    handler(){ //
                        console.log('a資料更新')
                    },
                   immediate:true
                    
                },
                // a:'aa',
            }
        })
        vm.a = 100
        // vm.b[1] = 2
        console.log(vm.c.c.c=2000)
    </script>
</body>