侧边栏壁纸
博主头像
woku博主等级

成功的路上并不拥挤

  • 累计撰写 50 篇文章
  • 累计创建 13 个标签
  • 累计收到 3 条评论
vue

watch认识与实现

woku
2022-03-07 / 0 评论 / 0 点赞 / 190 阅读 / 5,841 字

watch与computed区别

watchcomputed
关注点数据更新(给数据增加侦听器,当数据更新时,侦听器函数执行)用在模板中;复用/抽离模板中复杂的逻辑操作
特点数据更新时,需要完成某些逻辑当函数内的依赖改变后,重新调用方法得出新值

watch的使用

    <script>
        var rootComp = {
            data() {
                return {
                    a: 1,
                    b: 2,
                    type: 'plus'
                }
            },
            template: `
               <p>运算的结果{{ result }}</p>
               <p>
                数字A:<span>{{ a }}</span>
                数字B:<span>{{ b }}</span>
               </p>
               <button @click="change('plus')">+</button>
               <button @click="change('minus')">-</button>
               <button @click="change('mul')">*</button>
               <button @click="change('div')">/</button>
            `,
            methods: {
                change(type) {
                    this.type = type
                }
            },
            computed: {
                result() {
                    switch (this.type) {
                        case 'plus':
                            return this.a + this.b;
                        case 'minus':
                            return this.a - this.b;
                        case 'mul':
                            return this.a * this.b;
                        case 'div':
                            return this.a / this.b
                    }
                }
            },
            watch: {
                // 侦听器的名字要和computed名字或数据的名字保持一致
                result(newV, oldV) {
                    console.log(newV, oldV)
                },
                type(newV, oldV) {
                    console.log(newV, oldV)
                }
            }
        }
        var app = Vue.createApp(rootComp)
        var vm = app.mount('#app')
    </script>
  • 给计算属性result增加侦听器,当result改变的时候,执行方法(第一次不会执行)

  • 也可以给组件数据增加侦听器

    newV 表示更新后的新值

    oldV 表示更新前的旧值

补充:

computed在列表循环中也可以这样使用

  <script>
        var rootComp = {
            data() {
                return {
                   list: [{
                       id: 0,
                       title: '学习Vue',
                       isComplete: 0
                   },{
                       id: 1,
                       title: '学习JS',
                       isComplete: 1
                   },{
                       id: 2,
                       title: '学习webpack',
                       isComplete: 1
                   }]
                }
            },
            template: `
               <ul v-for="item of list" :key="item.id">
                <li>
                    <p>名称{{item.title}}</p>
                    <p>是否已完成:{{ isComplete(item) }}</p>
                </li>
               </ul>
            `,
            computed: {
                isComplete() {
                    return function(item) {
                        return item.isComplete ? '已完成' : '未完成'
                    }
                }
            }
        }
        var app = Vue.createApp(rootComp)
        var vm = app.mount('#app')
    </script>

watch实现

创建vm实例(main.js)

import Vue from './modules/vue'
const vm = new Vue({
    // 数据
    data() {
        return {
            a: 1,
            b: 2
        }
    },
    // 计算属性
    computed: {
        total() {
            console.log('computed')
            return this.a + this.b
        }
    },
    // 侦听器
    watch: {
        a (newV, oldV) {
            console.log(newV, oldV)
        },
        b (newV, oldV) {
            console.log(newV, oldV)
        },
        total(newV, oldV) {
            console.log('total', newV, oldV)
        }

    }
})
console.log(vm)
vm.a = 100
console.log(vm.total)
console.log(vm.total)
console.log(vm.total)
vm.b = 200
console.log(vm.total)
console.log(vm.total)
console.log(vm.total)

vue类

class Vue {
    constructor(options) {
        const {
            data,
            computed,
            watch
        } = options
        this.$data = data()
        this._init(this, computed, watch)
    }
    _init(vm, computed, watch) {
        
    }
}

实现响应式与暴露回调接口

  • vm.$data上面的数据挂载到vm实例上

设置vm实例上数据时,也要设置vm.$data上对应数据

获取vm实例上数据时,返回的是vm.$data上对应数据

  • 在上面vue类的_init方法中,初始化数据

  • reactive方法创建响应式数据

import reactive from "./reactive"
class Vue {
    constructor(options) {
        const {
            data,
            computed,
            watch
        } = options
        this.$data = data()
        this._init(this, computed, watch)
    }
    _init(vm, computed, watch) {
        this.initData(vm)
    }
    initData(vm) {
        reactive(vm, () => {}, (key, newV, oldV) => {
            if (newV === oldV) return
            console.log(newV, oldV)
        })
    }
}

reactive.js

function reactive(vm, __get__, __set__) {
  var _data = vm.$data
  for (let key in _data) {
    Object.defineProperty(vm, key, {
      get() {
        __get__(key, _data[key])
        return _data[key]
      },
      set(newV) {
        _data[key] = newV
        // 暴露__set__方法,更新computed和执行watch
        __set__(key, newV, oldV)
      }
    })
  }
}

export default reactive

实现计算属性特性

  • initComputed 将所有的计算属性收集起来,放到computedData中,结构如下:
{

   Key:  计算属性名称,

   value:最终的值,

   get: 方法(数据更新时候要调用)

   dep: 所依赖的数据 

}

  • 把计算属性的值挂载到vm实例上(计算属性缓存)

  • 当数据更新时,看computedData数据里,如果是有依赖的,重新执行方法,更新计算属性值

Vue类改动:

import reactive from "./reactive"
import Computed from "./computed"
class Vue {
    constructor(options) {
        const {
            data,
            computed,
            watch
        } = options
        this.$data = data()
        this._init(this, computed, watch)
    }
    _init(vm, computed, watch) {
        this.initData(vm)
        const computedIns = this.initComputed(vm, computed)
        this.$computed = computedIns.update.bind(computedIns)
    }
    initData(vm) {
        reactive(vm, () => {}, (key, newV, oldV) => {
            if (newV === oldV) return
            this.$computed(key)
        })
    }
    initComputed(vm, computed) {
        const computedIns = new Computed()
        for (let key in computed) {
            // 收集计算属性 {key: '', value: '', get: '', dep: ''}
            computedIns.addComputed(vm, computed, key)
        }
        return computedIns
    }
}

computed.js

class Computed {
    constructor() {
        this.computedData = []
    }
    addComputed(vm, computed, key) {
        const descriptor = Object.getOwnPropertyDescriptor(computed, key),
              descriptorFn = descriptor.value.get ? descriptor.value.get : descriptor.value,
              value = descriptorFn.call(vm),
              get = descriptorFn.bind(vm),
              dep = this._collectDep(descriptorFn)
        this._addComputedProp({
            key,
            value,
            get,
            dep
        })
        const dataItem = this.computedData.find(x => x.key === key)
        Object.defineProperty(vm, key, {
            get() {
                return dataItem.value
            },
            set() {
                dataItem.value = dataItem.get()
            }
        })

    }
    update(key, watch) {
        this.computedData.map(item => {
            let dep = item.dep
            const _key = dep.find(x => x === key)
            if (_key) {
                const oldV = item.value
                item.value = item.get()
                // 当计算属性更新时候,要去看这个计算属性是否有侦听器
                watch(item.key, item.value, oldV)
            }
        })
    }
    _addComputedProp(item) {
        this.computedData.push(item)
    }
    _collectDep(fn) {
        const matchs = fn.toString().match(/this\.(.+?)/g)
        let depArr = []
        if (matchs) {
            depArr = matchs.map(x => x.split('.')[1])
        }
        return depArr
    }
}

export default Computed

实现侦听器特性

  • initWatch 将所有的侦听器收集,放到watchers中,结构如下:
{

   key:  侦听器名称(要监听的数据),

   fn:  执行的函数

}
  • 当数据或计算属性更新时,根据key在watchers里面找,重新执行这个key对应的fn

vue类完整代码

import reactive from "./reactive"
import Computed from "./computed"
import Watcher from './watcher'
class Vue {
    constructor(options) {
        const {
            data,
            computed,
            watch
        } = options
        this.$data = data()
        this._init(this, computed, watch)
    }
    _init(vm, computed, watch) {
        this.initData(vm)
        const computedIns = this.initComputed(vm, computed)
        const watcherIns = this.initWatch(vm, watch)
        this.$computed = computedIns.update.bind(computedIns)
        this.$watch = watcherIns.invoke.bind(watcherIns)
    }
    initData(vm) {
        reactive(vm, () => {}, (key, newV, oldV) => {
            if (newV === oldV) return
            this.$computed(key, this.$watch)
            this.$watch(key, newV, oldV)
        })
    }
    initComputed(vm, computed) {
        const computedIns = new Computed()
        for (let key in computed) {
            computedIns.addComputed(vm, computed, key)
        }
        return computedIns
    }
    initWatch(vm, watch) {
        const watcherIns = new Watcher()
        for (let key in watch) {
            watcherIns.addWatcher(vm, watch, key)
        }
        return watcherIns
    }

}
export default Vue

watcher类

class Watcher {
    constructor() {
        this.watchers = []
    }
    addWatcher(vm, watcher, key) {
        this._addWatchProp({
            key,
            fn: watcher[key].bind(vm)
        })
    }
    invoke(key, newV, oldV) {
        this.watchers.map(item => {
            if (item.key === key) {
                item.fn(newV, oldV)
            }
        })
    }
    _addWatchProp (prop) {
        this.watchers.push(prop)
    }
}

export default Watcher
0

评论区