React和Vue中监听变量变化的方法

React 中

本地调试React代码的方法

yarn build

场景

假设有这样一个场景,父组件传递子组件一个A参数,子组件需要监听A参数的变化转换为state。

16之前

在React以前我们可以使用 componentWillReveiveProps 来监听 props 的变换

16之后

在最新版本的React中可以使用新出的 getDerivedStateFromProps 进行props的监听, 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);
 }
} 

方法指向的是,在 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));
}

我们看scheduleWork下

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 是在哪里触发的。

Watch触发条件

在 src/core/instance 中有 initState()

/core/instance/state.js

在数据初始化时 initData() ,会将每vue的data注册到 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 的构造方法,不管是array还是obj,他们最终都会调用的是 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方法就是将object中的执行 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()
 }
 })
}

小程序

自定义Watch

小程序的data本身是不支持watch的,但是我们可以自行添加,我们参照 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)
 }
}

使用

我们在执行watch回调前没有对新老赋值进行比较,原因是微信当中对data中的变量赋值,即使给引用变量赋值还是相同的值,也会因为引用地址不同,判断不相等。如果想对新老值进行比较就不能使用 === ,可以先对obj或者array转换为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
 });
 }
})

参考

如何阅读React源码

React 16.3 ~ React 16.5 一些比较重要的改动

总结

以上所述是小编给大家介绍的React和Vue中监听变量变化的方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

时间: 2018-11-11

在 React、Vue项目中使用SVG的方法

在一些现代的扁平化设计网站,特别是移动端网站,经常会包含许多简单而清晰的小图标,例如网站图标.用户的默认头像.移动端网页首页底部固定的切换栏等,这些小图标一般都是由美工做好,可能会放到精灵图上,前端再进行裁切展示. 而实际上,这些简单的小图标完全没必要让美工来做,前端完全可以通过 svg使用代码把这些简单的图标画出来,并且,因为这些图标是用代码描述出来的,所以如果想要修改这些图标,例如改变图标的颜色.图标的形状.大小等,都只是改几行代码的事情,非常简单,根本无需美工返工重做. 本文不是阐述如何利

VueJs与ReactJS和AngularJS的异同点

React && Vue React 和 Vue 有许多相似之处,它们都有: 使用 Virtual DOM 提供了响应式(Reactive)和组件化(Composable)的视图组件. 将注意力集中保持在核心库,伴随于此,有配套的路由和负责处理全局状态管理的库. 由于有着众多的相似处,我们会用更多的时间在这一块进行比较.这里我们不只保证技术内容的准确性,同时也兼顾了平衡的考量.我们需要指出 React 比 Vue 更好的地方,像是他们的生态系统和丰富的自定义渲染器. React &

干货!教大家如何选择Vue和React

两者之间的相同之处 同样是基于组件开发的轻量级框架,同样是专注于用户界面的视图view层. 如何选择 1.1 如果喜欢用模板搭建应用(或者有这个想法)选择Vue Vue应用默认的是把markup放在HTML中,数据绑定表达式和Angular一样,采用{{}}的形式,而指令(特殊的HTML属性)用来向模板中添加功能 <div> <p>{{ message }}</p> <button v-on:click="reverseMessage">

Vue.js 2.0 和 React、Augular等其他前端框架大比拼

React React 和 Vue 有许多相似之处,它们都有: 使用 Virtual DOM 提供了响应式(Reactive)和组件化(Composable)的视图组件. 保持注意力集中在核心库,伴随于此,有配套的路由和负责处理全局状态管理的库. 相似的作用域,我们会用更多的时间来讲这一块的比较.不仅我们要保持技术的准确性,同时兼顾平衡.我们指出React比Vue更好的地方,例如,他们的生态系统和丰富的自定义渲染器. React社区在这里非常积极地帮助我们实现这一平衡,特别感谢来自 React

前端框架学习总结之Angular、React与Vue的比较详解

近几年前端的技术发展很快,细分下来,主要可以分成四个方面: 1.开发语言技术,主要是ES6&7,coffeescript,typescript等: 2.开发框架,如Angular,React,Vue.js,Angular2等: 3.开发工具的丰富和前端工程化,像Grunt,Gulp,Webpack,npm,eslint,mocha这些技术: 4.前端开发范围的扩展,如服务端的nodejs,express,koa,meteor,GraphQL;移动端和跨平台的PhoneGap,ionic,Reac

Flask框架学习笔记之使用Flask实现表单开发详解

本文实例讲述了使用Flask实现表单开发.分享给大家供大家参考,具体如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div align="center"> <h1>Use

js中自定义react数据验证组件实例详解

我们在做前端表单提交时,经常会遇到要对表单中的数据进行校验的问题.如果用户提交的数据不合法,例如格式不正确.非数字类型.超过最大长度.是否必填项.最大值和最小值等等,我们需要在相应的地方给出提示信息.如果用户修正了数据,我们还要将提示信息隐藏起来. 有一些现成的插件可以让你非常方便地实现这一功能,如果你使用的是knockout框架,那么你可以借助于Knockout-Validation这一插件.使用起来很简单,例如我下面的这一段代码: ko.validation.locale('zh-CN');

React团队测试并发特性详解

目录 引言 遇到的困境 1. 如何表达渲染结果? 2. 如何测试并发环境? React的应对策略 实现一个渲染器 如何测试并发环境? 总结 引言 React18进入大家视野已经有一段时间了,不知道各位有没有尝试并发特性呢? 当启用并发特性后,React会从同步更新变为异步.带优先级.可中断的更新. 这也为编写单元测试带来了一些难度. 本文来聊聊React团队如何测试并发特性. 遇到的困境 主要有两个问题需要面对. 1. 如何表达渲染结果? React可以对接不同宿主环境的渲染器,大家最熟悉的渲染

React中编写CSS实例详解

目录 正文 内联样式 普通的CSS css modules css in js 样式组件 引入外部变量 默认值 引入全局样式 provider 样式继承 动态添加class 正文 目前,前端最流行的开发方式是组件化,而CSS的设计本身就不是为组件化而生的,所以在目前组件化的框架中都在需要一种合适的CSS解决方案 在组件化开发环境下的CSS,应该满足如下需求: 可以编写局部css: css具备自己的具备作用域,不会随意污染其他组件内的元素 可以编写动态的css: 可以获取当前组件的一些状态,根据状

GraphQL在react中的应用示例详解

目录 什么是 GraphQL GraphQL出现的意义 传统API存在的主要问题: GraphQL 如何解决问题 GraphQL基本语法 标量类型 对象类型 枚举类型 GraphQL 内置指令 什么是 Apollo apollo-server 处理流程 1.解析阶段 2.校验阶段 3.执行阶段 Schema 给server端带来的便利性 创建client 将client注入到react 数据请求 数据缓存 apollo-client 总结 GraphQL 的优缺点 优点 缺点 什么是 Graph

Angular.js之作用域scope'@','=','&'实例详解

什么是scope AngularJS 中,作用域是一个指向应用模型的对象,它是表达式的执行环境.作用域有层次结构,这个层次和相应的 DOM 几乎是一样的.作用域能监控表达式和传递事件. 在 HTML 代码中,一旦一个 ng-app 指令被定义,那么一个作用域就产生了,由 ng-app 所生成的作用域比较特殊,它是一个根作用域($rootScope),它是其他所有$Scope 的最顶层. 除了用 ng-app 指令可以产生一个作用域之外,其他的指令如 ng-controller,ng-repeat

Laravel框架中队列和工作(Queues、Jobs)操作实例详解

在我们的web应用中,经常会遇到这样的情况: 用户在进行了某项操作后,我们需要在后台完成一个耗时且耗费资源的任务,以对应用户的操作. 通常来说,web应用中的操作都是同步的(synchronous),即用户的操作可以立即得到回馈. 但是在以上情况下,同步等待操作结果将是灾难性的.比如用户点击了申请密码重置邮件,倘若我们让用户一直停滞在等待页面,直至邮件发送成功,那么用户体验将非常地不好,因为有时候可能需要很长的时间才能将邮件发送完成. 从另一个角度来说,如果我们服务器处于高负荷的情况,当多个用户

React Fragment介绍与使用详解

目录 前言 Fragments出现动机 React Fragment介绍与使用 <React.Fragment> 与 <>区别 前言 在向 DOM 树批量添加元素时,一个好的实践是创建一个document.createDocumentFragment,先将元素批量添加到 DocumentFragment 上,再把 DocumentFragment 添加到 DOM 树,减少了 DOM操作次数的同时也不会创建一个新元素. 和 DocumentFragment 类似,React 也存在

React中react-redux和路由详解

目录 观察者模式解决组件间通信问题 react-redux connect方法 Provider组件 路由 使用路由 默认路由 路由重定向 获取路由参数 路由导航 观察者模式解决组件间通信问题 使用观察者解决组件间通信,分成两步 在一个组件中,订阅消息 在另一个组件中,发布消息 发布消息之后,订阅的消息回调函数会执行,在函数中,我们修改状态,这样就可以实现组件间通信了.这就是reflux框架的实现. react-redux redux早期被设计成可以在各个框架中使用,因此在不同的框架中使用的时候