第七章 手写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()
    }
  }
相关推荐
panxianren4 小时前
Bun 1.4 的 bun test --parallel 将 4 秒的测试套件缩短到约 1 秒
java·javascript
陈随易5 小时前
bm2,Node.js 与 Bun 项目部署新选择
前端·后端·程序员
可乐鸡翅yeah_7 小时前
FFmpeg 生成 HLS 独立分片 independent‑segments 参数到底有什么用
javascript·ffmpeg·音视频·safari·m3u8
福兮说7 小时前
canvas 旋转图片的六个坑:四角被裁、Math.ceil 多出 1px 黑边、JPG 角发黑、翻转方向反了
前端·javascript·图像处理·canvas
Ai-_Man8 小时前
您您这可以把Grok的多个会话比如说。左侧的多个会话一次性导出吗?不是单条会话里面的多次会对话。AI导出鸭
javascript·人工智能·ai·小程序·电脑
传人once8 小时前
多图片上传预览功能如何写
前端·css·html·jquery·html5
Eric.469 小时前
Wan‑Animate+MiniMax-H3 本地部署 AI 漫剧流水线:8G 显存动作迁移与动态分镜工程实战
前端·人工智能·comfyui·ai漫剧
liangshanbo12159 小时前
JavaScript 异步核心主线
javascript·事件循环·异步操作
传人once10 小时前
悬停旋转放大和位移效果如何写
javascript·css·动画
库拉镜像AI牛牛10 小时前
短剧内容自动化生产:知漫剧工作室落地教程
大数据·服务器·前端·人工智能·语音识别