# 第 4 章 响应系统的作用和实现

# 1. 响应式数据与副作用函数

示例:

const obj = { text: 'hello world' };

// 副作用函数
function effect() {
  document.body.innerText = obj.text;
}

obj.text = 'hello 张三';

obj.text 的值变化后,如果 effect() 能自动重新执行,则 obj 就是响应式数据

# 2. 响应式数据的基本实现

说明:

  • 读取属性时,将副作用函数存储到桶中
  • 设置属性时,将副作用函数从桶中取出来执行

微型响应系统:

const bucket = new Set();

const data = { text: 'hello world' };

const obj = new Proxy(data, {
  get(target, key) {

    bucket.add(effect);

    return target[key];
  },

  set(target, key, newValue) {
    target[key] = newValue;

    bucket.forEach((fn) => fn());

    return true;
  }
});

测试:

function effect() {
  document.body.innerText = obj.text;
}

effect();

setTimeout(() => {
  obj.text = 'hello 张三';
}, 2000);

# 3. 设计一个完善的响应式系统

原理:

桶的结构:

  target1
    prop1
      effectFn1
      effectFn2
    prop2
      effectFn1

  target2
    prop1
      effectFn1

桶的实现:

  bucket(WeekMap)
    key: target
    val: depsMap(Map)
  
      depsMap(Map)
        key: prop
        val: effectFns(Set)

          effectFns(set)
            val: effectFn

代码:

let activeEffect;

function effect(fn) {
  activeEffect = fn;
  fn();
}

const data = { 
  name: '张三',
  age: 18
};

const bucket = new WeakMap();

const obj = new Proxy(data, {
  get(target, key) {
    track(target, key);

    return target[key];
  },

  set(target, key, newVal) {
    target[key] = newVal;

    trigger(target, key);
  }
});

function track(target, key) {
  if (!activeEffect) {
    return target[key];
  }

  let depsMap = bucket.get(target);

  if (!depsMap) {
    depsMap = new Map();
    bucket.set(target, depsMap);
  }

  let deps = depsMap.get(key);

  if (!deps) {
    deps = new Set();
    depsMap.set(key, deps);
  }

  deps.add(activeEffect);

  activeEffect = null;
}

function trigger(target, key) {
  const depsMap = bucket.get(target);

  if (!depsMap) {
    return;
  }

  const effects = depsMap.get(key);

  if (!effects) {
    return;
  }

  effects.forEach((fn) => fn());
}

测试:

document.body.innerHTML = `
  <div id="box1"></div>
  <div id="box2"></div>
`;

effect(function() {
  console.log('读取 obj.name');
  document.getElementById('box1').innerText = obj.name;
});

effect(function() {
  console.log('读取 obj.age');
  document.getElementById('box2').innerText = obj.age;
});

setTimeout(() => {
  obj.name = '李四';
}, 2000);

setTimeout(() => {
  obj.age = 19;
}, 4000);

# 4. 分支切换与 cleanup

本章目录