Как отслеживать изменения переменных в React и Vue

Node.js внешний интерфейс Vue.js React.js

в реакции

Способы локальной отладки кода React

  • Сначала загрузите код React на локальный сервер, после входа в папку проекта.yarn build
  • Создайте свой собственный проект с приложением create-реагировать
  • Свяжите исходный код реакции с только что созданным проектом, соберите исходный код в папку сборки ранее, а затем перейдите в папку сборки в папке реакции. Внутри есть папка node_modules, войдите в эту папку. Обнаружил, что есть папка react и папка react-dom. в эти две папки соответственно. Звено пряжи выполнить отдельно. На этом этапе создаются два ярлыка. реагировать и реагировать-дом
  • cd в каталог вашего собственного проекта и запустите ссылку на пряжу, реагируйте на реакцию-дом. На этом этапе в вашем проекте используются соответствующие файлы сборки под исходным кодом реакции. Если вы внесете изменения в исходный код реакции, обновите проект, и это будет отражено в вашем проекте.

Сцены

Предположим, есть такой сценарий, родительский компонент передает параметр A дочернему компоненту, а дочернему компоненту необходимо следить за изменением параметра A и преобразовывать его в состояние.

до 16

До React мы могли использоватьcomponentWillReveivePropsконтролироватьpropsпреобразование

после 16

В последней версии React вы можете использовать новыйgetDerivedStateFromPropsРеквизит монитора,getDerivedStateFromPropsможет вернутьсяnullили объект, если это объект, он будет обновленstate

Условие триггера getDerivedStateFromProps

Наша цель найтиgetDerivedStateFromPropsусловие триггера

Мы знаем, что пока мы звонимsetStateвызоветgetDerivedStateFromProps,а такжеpropsэто же значение также сработаетgetDerivedStateFromProps(после версии 16.3)

setStateсуществуетreact.development.jsсреди

Component.prototype.setState = function (partialState, callback) {
  !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : void 0;
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
ReactNoopUpdateQueue {
    //...部分省略
    
    enqueueSetState: function (publicInstance, partialState, callback, callerName) {
    warnNoop(publicInstance, 'setState');
  }
}

Выполняется метод предупреждения

function warnNoop(publicInstance, callerName) {
  {
    // 实例的构造体
    var _constructor = publicInstance.constructor;
    var componentName = _constructor && (_constructor.displayName || _constructor.name) || 'ReactClass';
    // 组成一个key 组件名称+方法名(列如setState)
    var warningKey = componentName + '.' + callerName;
    // 如果已经输出过警告了就不会再输出
    if (didWarnStateUpdateForUnmountedComponent[warningKey]) {
      return;
    }
    // 在开发者工具的终端里输出警告日志 不能直接使用 component.setState来调用 
    warningWithoutStack$1(false, "Can't call %s on a component that is not yet mounted. " + 'This is a no-op, but it might indicate a bug in your application. ' + 'Instead, assign to `this.state` directly or define a `state = {};` ' + 'class property with the desired state in the %s component.', callerName, componentName);
    didWarnStateUpdateForUnmountedComponent[warningKey] = true;
  }
}

похоже на тоReactNoopUpdateQueueЭто абстрактный класс, фактический метод здесь не реализован, и давайте посмотрим на начальныйupdaterгде выполняется присваивание, инициализацияComponent, Настоящийupdater

function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

В конструкторе компонента мы будемthisпечатать

class App extends Component {
  constructor(props) {
    super(props);
    //..省略

    console.log('constructor', this);
  }
}

-w766

метод указывает наreact-dom.development.jsизclassComponentUpdater

var classComponentUpdater = {
  // 是否渲染
  isMounted: isMounted,
  enqueueSetState: function(inst, payload, callback) {
    // inst 是fiber
    inst = inst._reactInternalFiber;
    // 获取时间
    var currentTime = requestCurrentTime();
    currentTime = computeExpirationForFiber(currentTime, inst);
    // 根据更新时间初始化一个标识对象
    var update = createUpdate(currentTime);
    update.payload = payload;
    void 0 !== callback && null !== callback && (update.callback = callback);
    // 排队更新 将更新任务加入队列当中
    enqueueUpdate(inst, update);
    //
    scheduleWork(inst, currentTime);
  },
  // ..省略
}

enqueueUpdate заключается в добавлении задачи обновления в очередь

function enqueueUpdate(fiber, update) {
  var alternate = fiber.alternate;
  // 如果alternat为空并且更新队列为空则创建更新队列
  if (null === alternate) {
    var queue1 = fiber.updateQueue;
    var queue2 = null;
    null === queue1 &&
      (queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState));
  } else

    (queue1 = fiber.updateQueue),
      (queue2 = alternate.updateQueue),
      null === queue1
        ? null === queue2
          ? ((queue1 = fiber.updateQueue = createUpdateQueue(
              fiber.memoizedState
            )),
            (queue2 = alternate.updateQueue = createUpdateQueue(
              alternate.memoizedState
            )))
          : (queue1 = fiber.updateQueue = cloneUpdateQueue(queue2))
        : null === queue2 &&
          (queue2 = alternate.updateQueue = cloneUpdateQueue(queue1));
  null === queue2 || queue1 === queue2
    ? appendUpdateToQueue(queue1, update)
    : null === queue1.lastUpdate || null === queue2.lastUpdate
      ? (appendUpdateToQueue(queue1, update),
        appendUpdateToQueue(queue2, update))
      : (appendUpdateToQueue(queue1, update), (queue2.lastUpdate = update));
}

Посмотрим на график работы

function scheduleWork(fiber, expirationTime) {
  // 获取根 node
  var root = scheduleWorkToRoot(fiber, expirationTime);
  null !== root &&
    (!isWorking &&
      0 !== nextRenderExpirationTime &&
      expirationTime < nextRenderExpirationTime &&
      ((interruptedBy = fiber), resetStack()),
    markPendingPriorityLevel(root, expirationTime),
    (isWorking && !isCommitting$1 && nextRoot === root) ||
      requestWork(root, root.expirationTime),
    nestedUpdateCount > NESTED_UPDATE_LIMIT &&
      ((nestedUpdateCount = 0), reactProdInvariant("185")));
}
function requestWork(root, expirationTime) {
  // 将需要渲染的root进行记录
  addRootToSchedule(root, expirationTime);
  if (isRendering) {
    // Prevent reentrancy. Remaining work will be scheduled at the end of
    // the currently rendering batch.
    return;
  }

  if (isBatchingUpdates) {
    // Flush work at the end of the batch.
    if (isUnbatchingUpdates) {
      // ...unless we're inside unbatchedUpdates, in which case we should
      // flush it now.
      nextFlushedRoot = root;
      nextFlushedExpirationTime = Sync;
      performWorkOnRoot(root, Sync, true);
    }
    // 执行到这边直接return,此时setState()这个过程已经结束
    return;
  }

  // TODO: Get rid of Sync and use current time?
  if (expirationTime === Sync) {
    performSyncWork();
  } else {
    scheduleCallbackWithExpirationTime(root, expirationTime);
  }
}

Это слишком сложно, и некоторые методы еще не поняты, но по брейкпойнту можно сначала проанализировать порядок выполнения.setStateбудет выполнен позжеperformSyncWork, за которым следует последовательность выполнения следующим образом

performSyncWork => performWorkOnRoot => renderRoot => workLoop => performUnitOfWork => beginWork => applyDerivedStateFromProps

Последний метод заключается в выполнении

function applyDerivedStateFromProps(
  workInProgress,
  ctor,
  getDerivedStateFromProps,
  nextProps
) {
  var prevState = workInProgress.memoizedState;
      {
        if (debugRenderPhaseSideEffects || debugRenderPhaseSideEffectsForStrictMode && workInProgress.mode & StrictMode) {
          // Invoke the function an extra time to help detect side-effects.
          getDerivedStateFromProps(nextProps, prevState);
        }
      }
      // 获取改变的state
      var partialState = getDerivedStateFromProps(nextProps, prevState);
      {
        // 对一些错误格式进行警告
        warnOnUndefinedDerivedState(ctor, partialState);
      } // Merge the partial state and the previous state.
      // 判断getDerivedStateFromProps返回的格式是否为空,如果不为空则将由原的state和它的返回值合并
      var memoizedState = partialState === null || partialState === undefined ? prevState : _assign({}, prevState, partialState);
      // 设置state
      // 一旦更新队列为空,将派生状态保留在基础状态当中
      workInProgress.memoizedState = memoizedState; // Once the update queue is empty, persist the derived state onto the
      // base state.
      var updateQueue = workInProgress.updateQueue;

      if (updateQueue !== null && workInProgress.expirationTime === NoWork) {
        updateQueue.baseState = memoizedState;
      }
}

Vue

Изменения переменных мониторинга Vue зависят отwatch, так что давайте сначала посмотрим на исходный код,watchгде срабатывает.

Условие срабатывания часов

существуетsrc/core/instanceимеютinitState()

/core/instance/state.js

Во время инициализации данныхinitData(), зарегистрирует данные каждого vue вobjerserverсередина

function initData (vm: Component) {
  // ...省略部分代码
  
  // observe data
  observe(data, true /* asRootData */)
}
/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 创建observer
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

посмотриobserverМетод построения , будь то массив или объект, они в конечном итоге вызовутthis.walk()

constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      // 遍历array中的每个值,然后调用walk
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

Давайте еще раз посмотрим на метод walk.Метод walk предназначен для выполнения выполнения в объекте.defineReactive()метод, и этот метод фактически переписываетsetа такжеgetметод

/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
}

/core/observer/index.js defineReactiveМетод является ядром. Он переписывает методы set и get. Если мы переназначим переменную, он будет определять, равно ли новое значение переменной старому значению. Если нет, он сработаетdep.notify()Таким образом, вызывая метод в watch.

/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // dep当中存放的是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的值获取
    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
      if (Dep.target) {
        // dep中生成个watcher
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    // 重点看set方法
    set: function reactiveSetter (newVal) {
      // 获取变量原始值
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      // 进行重复值比较 如果相等直接return
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        // dev环境可以直接自定义set
        customSetter()
      }
        
      // 将新的值赋值
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      // 触发watch事件
      // dep当中是一个wacher的数组
      // notify会执行wacher数组的update方法,update方法触发最终的watcher的run方法,触发watch回调
      dep.notify()
    }
  })
}

Апплеты

Пользовательские часы

Данные самого апплета не поддерживают часы, но мы можем добавить их сами, см.VueНаписание метод написания самостоятельно.watcher.js

export function defineReactive (obj, key, callbackObj, val) {
  const property = Object.getOwnPropertyDescriptor(obj, key);
  console.log(property);

  const getter = property && property.get;
  const setter = property && property.set;

  val = obj[key]

  const callback = callbackObj[key];

  Object.defineProperty(obj, key, {
    enumerable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      
      return value
    },
    set: (newVal) => {
      console.log('start set');
      const value = getter ? getter.call(obj) : val

      if (typeof callback === 'function') {
        callback(newVal, val);
      }

      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      console.log('finish set', newVal);
    }
  });
}

export function watch(cxt, callbackObj) {
  const data = cxt.data
  for (const key in data) {
    console.log(key);
    defineReactive(data, key, callbackObj)
  }
}

использовать

Мы не сравнивали старые и новые назначения перед выполнением обратного вызова часов. Причина в том, что переменные в данных назначаются в WeChat. Даже если эталонным переменным присвоено одно и то же значение, суждение не будет равным из-за разных ссылок. адреса. Если вы хотите сравнить старые и новые значения, вы не можете использовать===, вы можете сначала преобразовать obj или массив в строку json, а затем сравнить.

//index.js
//获取应用实例
const app = getApp()

import {watch} from '../../utils/watcher';

Page({
  data: {
    motto: 'hello world',
    userInfo: {},
    hasUserInfo: false,
    canIUse: wx.canIUse('button.open-type.getUserInfo'),
    tableData: []
  },
    onLoad: function () {
    this.initWatcher();
  },
  initWatcher () {
    watch(this, {
      motto(newVal, oldVal) {
        console.log('newVal', newVal, 'oldVal', oldVal);
      },

      userInfo(newVal, oldVal) {
        console.log('newVal', newVal, 'oldVal', oldVal);
      },

      tableData(newVal, oldVal) {
        console.log('newVal', newVal, 'oldVal', oldVal);
      }
    });    
  },
  onClickChangeStringData() {
    this.setData({
      motto: 'hello'
    });
  },
  onClickChangeObjData() {
    this.setData({
      userInfo: {
        name: 'helo'
      }
    });
  },
  onClickChangeArrayDataA() {
    const tableData = [];
    this.setData({
      tableData
    });
  }
})

Ссылаться на

Широкая реклама

Эта статья была опубликована вЕженедельный выпуск Mint Front End, Добро пожаловать в Watch & Star ★, пожалуйста, указывайте источник при перепечатке.

Добро пожаловать, чтобы обсудить, поставить лайк и перейти 。◕‿◕。~