《Vue进阶教程》第十二课:实现一对多

发布于:2024-12-20 ⋅ 阅读:(13) ⋅ 点赞:(0)

  往期内容:

《Vue进阶教程》第二课:为什么提出组合式API

《Vue进阶教程》第三课:Vue响应式原理

《Vue进阶教程》第四课:reactive()函数详解

《Vue进阶教程》第五课:ref()函数详解(重点)

《Vue进阶教程》第六课:computed()函数详解(上)

《Vue进阶教程》第七课:computed()函数详解(下)

《Vue进阶教程》第八课:watch()函数的基本使用

《Vue进阶教程》第九课:watch()函数的高级使用

《Vue进阶教程》第十课:其它函数

经过前面的努力, 我们初步了解最基本的响应式原理, 并且实现了一个Demo

接下来, 我们要一步步完善响应式系统~😀

1 实现一对多

所谓一对多: 一个属性对应多个副作用函数

🤔思考

如果一个属性存在多个与之对应的副作用函数

理论上当属性改变时, 属性关联的每一个副作用函数都应该重新执行

1) 手动方式

示例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">xiaopang</div>

    <script>
      function isObject(value) {
        return typeof value === 'object' && value !== null
      }
      /**
       * 创建响应式数据
       *  @param [object]: 普通对象
       *  @return [Proxy]: 代理对象
       */
      function reactive(data) {
        if (!isObject(data)) return

        return new Proxy(data, {
          get(target, key) {
            return target[key]
          },
          set(target, key, value) {
            target[key] = value
            effect() // 调用副作用函数, 调用effect函数会重新获取新的数据
            effect1()
            return true
          },
        })
      }

      // 定义一个响应式数据 : 触发者
      const state = reactive({ name: 'xiaopang' })

      // 定义一个副作用函数 : 响应者
      function effect() {
        console.log('effect被执行了...')
        app.innerHTML = state.name
      }

      function effect1() {
        console.log('effect1被执行了...')
        state.name
      }

      // 当state.name改变时, 重新执行对应副作用函数effect
      setTimeout(() => {
        state.name = 'xxp'
      }, 1000)
    </script>
  </body>
</html>

很显然, 我们可以通过手动的方式, 依次执行每一个关联的副作用函数.

2) 自动方式

我们可以创建一个存储所有副作用函数的空间, 这个空间叫做副作用桶

首先想到的是数组, 数组的每个元素都是一个副作用函数

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">xiaopang</div>

    <script>
      // 定义一个副作用函数桶, 存放所有的副作用函数. 每个元素都是一个副作用函数
      const bucket = [] // 新增

      function isObject(value) {
        return typeof value === 'object' && value !== null
      }
      /**
       * 创建响应式数据
       *  @param [object]: 普通对象
       *  @return [Proxy]: 代理对象
       */
      function reactive(data) {
        if (!isObject(data)) return

        return new Proxy(data, {
          get(target, key) {
            return target[key]
          },
          set(target, key, value) {
            target[key] = value
            // 从副作用函数桶中依次取出每一个元素(副作用函数)执行
            bucket.forEach((fn) => fn()) // 修改
            return true
          },
        })
      }

      // 定义一个响应式数据 : 触发者
      const state = reactive({ name: 'xiaopang' })

      // 定义一个副作用函数 : 响应者
      function effect() {
        console.log('effect被执行了...')
        app.innerHTML = state.name
      }
      bucket.push(effect)
      bucket.push(effect)

      function effect1() {
        console.log('effect1被执行了...')
        state.name
      }
      bucket.push(effect1)

      // 当state.name改变时, 重新执行对应副作用函数effect
      setTimeout(() => {
        state.name = 'xxp'
      }, 1000)
    </script>
  </body>
</html>

但是数组元素是也可重复的, 为了效率, 我们还要去重, 而Set集合是默认去重的

因此, 考虑用Set实现, Set集合中存放的每个元素也是副作用函数

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">xiaopang</div>

    <script>
      // 定义一个副作用函数桶, 存放所有的副作用函数. 每个元素都是一个副作用函数
      const bucket = new Set() // 修改

      function isObject(value) {
        return typeof value === 'object' && value !== null
      }
      /**
       * 创建响应式数据
       *  @param [object]: 普通对象
       *  @return [Proxy]: 代理对象
       */
      function reactive(data) {
        if (!isObject(data)) return

        return new Proxy(data, {
          get(target, key) {
            return target[key]
          },
          set(target, key, value) {
            target[key] = value
            // 从副作用函数桶中依次取出每一个元素(副作用函数)执行
            bucket.forEach((fn) => fn())
            return true
          },
        })
      }

      // 定义一个响应式数据 : 触发者
      const state = reactive({ name: 'xiaopang' })

      // 定义一个副作用函数 : 响应者
      function effect() {
        console.log('effect被执行了...')
        app.innerHTML = state.name
      }
      bucket.add(effect)

      function effect1() {
        console.log('effect1被执行了...')
        state.name
      }
      bucket.add(effect1)

      const effect2 = () => {
        console.log(state.name)
      }
      bucket.add(effect2)

      // 当state.name改变时, 重新执行对应副作用函数effect
      setTimeout(() => {
        state.name = 'xxp'
      }, 1000)
    </script>
  </body>
</html>