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中监听变量变化的方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!