欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-11-23 11:00:03
react 中 本地调试react代码的方法 yarn build 场景 假设有这样一个场景,父组件传递子组件一个a参数,子组件需要监听a参数的变化转换为state...

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中监听变量变化的方法,希望对大家有所帮助