横向对比vue2,react,vue3响应式

511 阅读3分钟

vue2

入口

src/core/instance/index.js

// 入口 initMixin
initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);

进入initMixin(Vue)方法

src/core/instance/init.js

initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')

进入initState(vm)方法。

  if (opts.data) {

    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }

该方法中检查了methods,props中和data可能的重名,最后执行以下逻辑

// observe data
  observe(data, true /* asRootData */)

进入observe()方法

// 关键逻辑
// 非服务端渲染 非Object.freeze()处理
// 是数组或者普通对象
// Object.prototype.toString.call(new Set()) 返回 "[object Set]"
if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }

在Observer的constructor中,区别了数组和

if (Array.isArray(value)) {
  if (hasProto) {
    protoAugment(value, arrayMethods)
  } else {
    copyAugment(value, arrayMethods, arrayKeys)
  }
  this.observeArray(value)
} else {
// 定义响应式
  this.walk(value)
}
walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
}

正文defineReactive

export function defineReactive(
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 例如传入 obj {message:'hello'} key:message

  // 用于收集 Watcher
  const dep = new Dep();

  const property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return;
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get;
  const setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
  }

  let childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const value = getter ? getter.call(obj) : val;
      // new Watcher 时 设置tagert为自己,获取data中指定值,触发get触发执行此逻辑。再将target置空
      if (Dep.target) {
        // 添加 Watcher
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value;
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return;
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== "production" && customSetter) {
        customSetter();
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return;
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      // 提示 调用watcher中update方法 queueWatcher(this)
      dep.notify();
    }
  });
}

总结,watcher存放了对dom的操作,劫持属性get存储绑定wartcher。set是触发notify,watcher触发预存的操作。


react

react界面对数据的响应,大体是由setState触发的,引起布丁算法对比虚拟dom,然后转换为真实dom。 废话不多说上源码。

正文setState

Component.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

触发了updater定义的入队方法。

ReactPartialRenderer.js

ReactFiberClassComponent.new(old).js

中定义了自己的,后者代码为

 enqueueSetState(inst, payload, callback) {
    const fiber = getInstance(inst);
    const eventTime = requestEventTime();
    const suspenseConfig = requestCurrentSuspenseConfig();
    const lane = requestUpdateLane(fiber, suspenseConfig);

    const update = createUpdate(eventTime, lane, suspenseConfig);
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'setState');
      }
      update.callback = callback;
    }

    enqueueUpdate(fiber, update);
    scheduleUpdateOnFiber(fiber, lane, eventTime);
 }

之后fiber和quene管理相关笔者未深究,后期补上。


vue3

吐槽,终于ts了

vue3定义想用响应数据如下

  setup() {
    // data
    const state = reactive({
      count: 0
  });

reactive.ts

export function reactive(target: object) {
  // isReadonly 属性不设置响应式
  if (target && (target as Target)[ReactiveFlags.isReadonly]) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

createReactiveObject方法去掉一些判断的核心代码

  const observed = new Proxy(
    target,
    collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers
  )
  def(
    target,
    isReadonly ? ReactiveFlags.readonly : ReactiveFlags.reactive,
    observed
  )

这里我们大概先回忆下proxy 劫持的写法

    this._data = new Proxy(data, {
      set(target, prop, newVal) {
        return Reflect.set(...arguments);
      }
    })

历史不支持的数据结构和一些判断也发生了变化

const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet])
const isObservableType = /*#__PURE__*/ makeMap(
  'Object,Array,Map,Set,WeakMap,WeakSet'
)

const canObserve = (value: Target): boolean => {
  return (
    !value[ReactiveFlags.skip] &&
    isObservableType(toRawType(value)) &&
    !Object.isFrozen(value)
  )
}

vue即使没有patch也能响应,其精准的针对了每个变量。 react需要patch去寻找虚拟dom的变化触发ui更新。

未完待续