React和Vue中监听变量变化的方法
React中
本地调试React代码的方法
yarnbuild
场景
假设有这样一个场景,父组件传递子组件一个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){ !(typeofpartialState==='object'||typeofpartialState==='function'||partialState==null)?invariant(false,'setState(...):takesanobjectofstatevariablestoupdateorafunctionwhichreturnsanobjectofstatevariables.'):void0; this.updater.enqueueSetState(this,partialState,callback,'setState'); }; ReactNoopUpdateQueue{ //...部分省略 enqueueSetState:function(publicInstance,partialState,callback,callerName){ warnNoop(publicInstance,'setState'); } }
执行的是一个警告方法
functionwarnNoop(publicInstance,callerName){ { //实例的构造体 var_constructor=publicInstance.constructor; varcomponentName=_constructor&&(_constructor.displayName||_constructor.name)||'ReactClass'; //组成一个key组件名称+方法名(列如setState) varwarningKey=componentName+'.'+callerName; //如果已经输出过警告了就不会再输出 if(didWarnStateUpdateForUnmountedComponent[warningKey]){ return; } //在开发者工具的终端里输出警告日志不能直接使用component.setState来调用 warningWithoutStack$1(false,"Can'tcall%sonacomponentthatisnotyetmounted."+'Thisisano-op,butitmightindicateabuginyourapplication.'+'Instead,assignto`this.state`directlyordefinea`state={};`'+'classpropertywiththedesiredstateinthe%scomponent.',callerName,componentName); didWarnStateUpdateForUnmountedComponent[warningKey]=true; } }
看来ReactNoopUpdateQueue是一个抽象类,实际的方法并不是在这里实现的,同时我们看下最初updater赋值的地方,初始化Component时,会传入实际的updater
functionComponent(props,context,updater){ this.props=props; this.context=context; //Ifacomponenthasstringrefs,wewillassignadifferentobjectlater. this.refs=emptyObject; //Weinitializethedefaultupdaterbuttherealonegetsinjectedbythe //renderer. this.updater=updater||ReactNoopUpdateQueue; }
我们在组件的构造方法当中将this进行打印
classAppextendsComponent{ constructor(props){ super(props); //..省略 console.log('constructor',this); } }
方法指向的是,在react-dom.development.js的classComponentUpdater
varclassComponentUpdater={ //是否渲染 isMounted:isMounted, enqueueSetState:function(inst,payload,callback){ //inst是fiber inst=inst._reactInternalFiber; //获取时间 varcurrentTime=requestCurrentTime(); currentTime=computeExpirationForFiber(currentTime,inst); //根据更新时间初始化一个标识对象 varupdate=createUpdate(currentTime); update.payload=payload; void0!==callback&&null!==callback&&(update.callback=callback); //排队更新将更新任务加入队列当中 enqueueUpdate(inst,update); // scheduleWork(inst,currentTime); }, //..省略 } enqueueUpdate
就是将更新任务加入队列当中
functionenqueueUpdate(fiber,update){ varalternate=fiber.alternate; //如果alternat为空并且更新队列为空则创建更新队列 if(null===alternate){ varqueue1=fiber.updateQueue; varqueue2=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下
functionscheduleWork(fiber,expirationTime){ //获取根node varroot=scheduleWorkToRoot(fiber,expirationTime); null!==root&& (!isWorking&& 0!==nextRenderExpirationTime&& expirationTimeNESTED_UPDATE_LIMIT&& ((nestedUpdateCount=0),reactProdInvariant("185"))); } functionrequestWork(root,expirationTime){ //将需要渲染的root进行记录 addRootToSchedule(root,expirationTime); if(isRendering){ //Preventreentrancy.Remainingworkwillbescheduledattheendof //thecurrentlyrenderingbatch. return; } if(isBatchingUpdates){ //Flushworkattheendofthebatch. if(isUnbatchingUpdates){ //...unlesswe'reinsideunbatchedUpdates,inwhichcaseweshould //flushitnow. nextFlushedRoot=root; nextFlushedExpirationTime=Sync; performWorkOnRoot(root,Sync,true); } //执行到这边直接return,此时setState()这个过程已经结束 return; } //TODO:GetridofSyncandusecurrenttime? if(expirationTime===Sync){ performSyncWork(); }else{ scheduleCallbackWithExpirationTime(root,expirationTime); } }
太过复杂,一些方法其实还没有看懂,但是根据断点可以把执行顺序先理一下,在setState之后会执行performSyncWork,随后是如下的一个执行顺序
performSyncWork=>performWorkOnRoot=>renderRoot=>workLoop=>performUnitOfWork=>beginWork=>applyDerivedStateFromProps
最终方法是执行
functionapplyDerivedStateFromProps( workInProgress, ctor, getDerivedStateFromProps, nextProps ){ varprevState=workInProgress.memoizedState; { if(debugRenderPhaseSideEffects||debugRenderPhaseSideEffectsForStrictMode&&workInProgress.mode&StrictMode){ //Invokethefunctionanextratimetohelpdetectside-effects. getDerivedStateFromProps(nextProps,prevState); } } //获取改变的state varpartialState=getDerivedStateFromProps(nextProps,prevState); { //对一些错误格式进行警告 warnOnUndefinedDerivedState(ctor,partialState); }//Mergethepartialstateandthepreviousstate. //判断getDerivedStateFromProps返回的格式是否为空,如果不为空则将由原的state和它的返回值合并 varmemoizedState=partialState===null||partialState===undefined?prevState:_assign({},prevState,partialState); //设置state //一旦更新队列为空,将派生状态保留在基础状态当中 workInProgress.memoizedState=memoizedState;//Oncetheupdatequeueisempty,persistthederivedstateontothe //basestate. varupdateQueue=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中
functioninitData(vm:Component){ //...省略部分代码 //observedata observe(data,true/*asRootData*/) } /** *Attempttocreateanobserverinstanceforavalue, *returnsthenewobserverifsuccessfullyobserved, *ortheexistingobserverifthevaluealreadyhasone. */ exportfunctionobserve(value:any,asRootData:?boolean):Observer|void{ if(!isObject(value)||valueinstanceofVNode){ return } letob:Observer|void if(hasOwn(value,'__ob__')&&value.__ob__instanceofObserver){ ob=value.__ob__ }elseif( shouldObserve&& !isServerRendering()&& (Array.isArray(value)||isPlainObject(value))&& Object.isExtensible(value)&& !value._isVue ){ //创建observer ob=newObserver(value) } if(asRootData&&ob){ ob.vmCount++ } returnob }
来看下observer的构造方法,不管是array还是obj,他们最终都会调用的是this.walk()
constructor(value:any){ this.value=value this.dep=newDep() this.vmCount=0 def(value,'__ob__',this) if(Array.isArray(value)){ constaugment=hasProto ?protoAugment :copyAugment augment(value,arrayMethods,arrayKeys) //遍历array中的每个值,然后调用walk this.observeArray(value) }else{ this.walk(value) } }
我们再来看下walk方法,walk方法就是将object中的执行defineReactive()方法,而这个方法实际就是改写set和get方法
/** *Walkthrougheachpropertyandconverttheminto *getter/setters.Thismethodshouldonlybecalledwhen *valuetypeisObject. */ walk(obj:Object){ constkeys=Object.keys(obj) for(leti=0;i小程序
自定义Watch
小程序的data本身是不支持watch的,但是我们可以自行添加,我们参照Vue的写法自己写一个。
watcher.js
exportfunctiondefineReactive(obj,key,callbackObj,val){ constproperty=Object.getOwnPropertyDescriptor(obj,key); console.log(property); constgetter=property&&property.get; constsetter=property&&property.set; val=obj[key] constcallback=callbackObj[key]; Object.defineProperty(obj,key,{ enumerable:true, get:functionreactiveGetter(){ constvalue=getter?getter.call(obj):val returnvalue }, set:(newVal)=>{ console.log('startset'); constvalue=getter?getter.call(obj):val if(typeofcallback==='function'){ callback(newVal,val); } if(setter){ setter.call(obj,newVal) }else{ val=newVal } console.log('finishset',newVal); } }); } exportfunctionwatch(cxt,callbackObj){ constdata=cxt.data for(constkeyindata){ console.log(key); defineReactive(data,key,callbackObj) } }使用
我们在执行watch回调前没有对新老赋值进行比较,原因是微信当中对data中的变量赋值,即使给引用变量赋值还是相同的值,也会因为引用地址不同,判断不相等。如果想对新老值进行比较就不能使用===,可以先对obj或者array转换为json字符串再比较。
//index.js //获取应用实例 constapp=getApp() import{watch}from'../../utils/watcher'; Page({ data:{ motto:'helloworld', 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(){ consttableData=[]; this.setData({ tableData }); } })参考
如何阅读React源码
React16.3~React16.5一些比较重要的改动
总结
以上所述是小编给大家介绍的React和Vue中监听变量变化的方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!