第七章 手写watch 实现

1.创建watch函数

在Vue3中,watch是一个函数,这个函数接收三个参数,前两个是必须的参数,第三个是一个配置参数。

  1. 第一个参数是需要监听的响应式数据,对于复杂类型的数据可以直接传变量本身或者传入一个函数,该函数返回需要监听的数据,如果是简单类型数据,则必须传入一个函数然后函数返回该数据。
  2. 第二个参数是一个回调函数,该回调函数会在监听数据发生变化后执行,同时该函数会被注入两个参数,第一个参数为变化后的新值,第二个参数为变化前的旧值。
  3. 第三个参数是一个配置对象,可以传入一个deep属性,如果为true,则监听的数据变化后,会递归遍历数据,将所有数据都监听一遍。
js 复制代码
  export function watch(source,callback,options = {} as any) {
    return doWatch(source,callback,options)
}

2.doWatch函数实现

设置doWatch目的是为了与watch相似的API ,watchEffect准备,便后后续代码书写

  1. 第一步,判断传入的source是否为函数,如果是函数则调用该函数获取到响应式数据并重新赋值给source,如果不是,则将该函数直接作为getter用于后续创建effect
  2. 判断当前source是否还是一个对象,如果是对象则说明第一步中传入的函数返回的是一个对象,监听整个对象,或者说传入的并不是函数而直接是一个对象
  3. 通过第二步判断,我们需要开始创建一个getter函数,同时该函数内部需要递归调用某个函数来实现对象属性的访问,递归的过程需要监控当前递归的层级与当前watch的配置问题,如果watch配置为深度监听,就需要完全遍历对象。
  4. 创建effect,并传入getter函数作为effect的run函数,同时将callback作为effect的回调函数
js 复制代码
  function doWatch(source,callback,{ deep }) {
    let getter;
    // 判断传入是否是一个函数
    if(isFunction(source)) {
        // 判断当前函数返回值是否是简单数据
        const result = source()  
        if(isObject(result)) {
            source = result
        } else {
            getter = source
        }
    } 

    //判断当前source是否为对象
    if(isObject(source)) {
        const reactiveGetter = (source) => traverse(source,deep === false ? 1 : undefined)
        getter = () => reactiveGetter(source)
    }

    // 执行的工具函数
    const job = () => {
        // 执行逻辑
        callback()
    }
    // 创建依赖对象
    const effect = new ReactiveEffect(getter,job)

}

3.实现traverse函数

  1. 遍历对象,需要判断当前层级是否为1,如果为1,说明是第一层,需要将该对象中的所有属性都监听一遍,否则就递归遍历对象
    同时为了防止对象中有循环引用,需要设置一个seen集合,用于判断当前对象是否已经遍历过,如果已经遍历过则直接返回
js 复制代码
  
function traverse(source, depth,currentDepth = 0,seen = new Set()) {
    // 判断是否为对象
   if(!isObject(source)) return source
    if(depth) {
        if(currentDepth >= depth) {
            return source
        }
        currentDepth++
    }
    // 判断是否循环引用
    if(seen.has(source)) return source
    for(let key in source) {
        traverse(source[key],depth,currentDepth,seen)
    }
    seen.add(source)
    return source
}

4 .注入新值与旧值**

在effect创建之前,我们创建一个oldValue变量用于存放老值

然后我们主动调用一次effect.run()获取到当前数据

在job调用时我们再次手动调用effect.run获取到新值,然后通过callback函数将新值与老值传入

js 复制代码
  function doWatch(source,callback,{ deep }) {
    ...
  // 保存老值
    let oldValue;

    // 执行的工具函数
    const job = () => {
        // 获取新值
        const newValue = effect.run()
        // 执行逻辑
        callback(newValue,oldValue)
        oldValue = newValue // 替换老值
    }
    // 创建依赖对象
    const effect = new ReactiveEffect(getter,job)
    oldValue = effect.run()
  }

5.判断source数据类型

watch只能够监听响应式数据,因此我们需要在getter赋值时来判断当前数据是否是响应式,如果不是响应时则不做处理

还有一种可能就是如果传入的第一个参数是一个函数,第二个参数不是函数或者不传,这就会使用watchEffectAPI,因此我们还需要再判断

js 复制代码
  # apiWatch.ts
  function doWatch(source,callback,{ deep }) {
    ...
    // 判断是否是响应式对象
        if(isReactive(source)) {
            getter = () => reactiveGetter(source)
        } else if(isRef(source)) {
            getter = () => source.value
        }
    ...
      const job = () => {
        if(callback) {
            // 获取新值
            const newValue = effect.run()
            // 执行逻辑
            callback(newValue,oldValue)
            oldValue = newValue // 替换老值
        } else {
            // watchEffect
        }
    }
    ...
    // 判断当前是否传入回调函数
    if(callback) {

    } else {
        // watchEffect  处理    
    }
  }

6.watch立即执行与watchEffect

  1. watch函数的第三个参数可以传入配置项,其中就有一个配置是immediate,该配置的作用是当监听器创建时立即执行回调函数
js 复制代码
        // 判断如果是需要立即执行,则先执行一次job函数,将新旧值传入
        if(immediate) {
            job()
        } else  {
            oldValue = effect.run()
         }
  1. watchEffect函数与watch函数很相似,函数接收两个参数,第一个参数传入一个回调函数,第二个参数传入一个配置,当第一个函数依赖的数据变化,就会重新执行该函数,本质上就是一个effect。
js 复制代码
  export function watchEffect(getter, options = {}) {
    return doWatch(getter,null,options as any)
}

7.doWatch返回值

  1. 返回一个stop函数,用于停止监听
    在这个函数函数中调用effect的stop方法,用于停止监听
js 复制代码
  function doWatch(source,callback,{ deep }) {
    ...
    // 创建依赖对象
    const effect = new ReactiveEffect(getter,job)
    // 判断是否需要立即执行
    if(immediate) {
        job()
    } else  {
        oldValue = effect.run()
     }
    // 返回一个stop函数
    return () => {
        effect.stop()
    }
  }
相关推荐
别拿曾经看以后~4 分钟前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
我要洋人死7 分钟前
导航栏及下拉菜单的实现
前端·css·css3
川石课堂软件测试9 分钟前
性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台
运维·javascript·深度学习·jmeter·docker·容器·grafana
科技探秘人18 分钟前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人19 分钟前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR24 分钟前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香26 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q24985969329 分钟前
前端预览word、excel、ppt
前端·word·excel
小华同学ai34 分钟前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
problc39 分钟前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter