React
How to debug React code locally
yarn build
Scene
Suppose there is a scenario where the parent component passes an A parameter for the child component, and the child component needs to listen to the changes in the A parameter to convert it into state.
Before 16
Before React we could use componentWillReveiveProps to listen for the transformation of props
After 16
In the latest version of React, you can use the newly released getDerivedStateFromProps to listen for props. getDerivedStateFromProps can return null or an object. If it is an object, the state will be updated.
getDerivedStateFromProps Triggering Condition
Our goal is to find the trigger condition for getDerivedStateFromProps
We know that as long as you call setState, getDerivedStateFromProps will be triggered, and the value of props is the same, which will also trigger getDerivedStateFromProps (after version 16.3)
setState in
= 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, partialState, callback, 'setState'); }; ReactNoopUpdateQueue { //...Section omitted enqueueSetState: function (publicInstance, partialState, callback, callerName) { warnNoop(publicInstance, 'setState'); } }
Is a warning method executed
function warnNoop(publicInstance, callerName) { { // Example constructor var _constructor = ; var componentName = _constructor && (_constructor.displayName || _constructor.name) || 'ReactClass'; // Make up a key component name + method name (column such as setState) var warningKey = componentName + '.' + callerName; // If the warning has been output, it will not be output again if (didWarnStateUpdateForUnmountedComponent[warningKey]) { return; } // Output warning log in the terminal of the developer tool. It cannot be called directly using it. 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 `` directly or define a `state = {};` ' + 'class property with the desired state in the %s component.', callerName, componentName); didWarnStateUpdateForUnmountedComponent[warningKey] = true; } }
It seems that ReactNoopUpdateQueue is an abstract class, and the actual method is not implemented here. At the same time, let's look at the initial updater assignment. When initializing the Component, the actual updater will be passed in.
function Component(props, context, updater) { = props; = context; // If a component has string refs, we will assign a different object later. = emptyObject; // We initialize the default updater but the real one gets injected by the // renderer. = updater || ReactNoopUpdateQueue; }
We print this in the component construction method
class App extends Component { constructor(props) { super(props); //..Omitted ('constructor', this); } }
The method points to the
ofclassComponentUpdater
var classComponentUpdater = { // Whether to render isMounted: isMounted, enqueueSetState: function(inst, payload, callback) { // inst is fiber inst = inst._reactInternalFiber; // Get time var currentTime = requestCurrentTime(); currentTime = computeExpirationForFiber(currentTime, inst); // Initialize an identification object according to the update time var update = createUpdate(currentTime); = payload; void 0 !== callback && null !== callback && ( = callback); // Queue to update and add update tasks to the queue enqueueUpdate(inst, update); // scheduleWork(inst, currentTime); }, // ..Omitted} enqueueUpdate
It is to add the update task to the queue
function enqueueUpdate(fiber, update) { var alternate = ; // If alternate is empty and the update queue is empty, create an update queue if (null === alternate) { var queue1 = ; var queue2 = null; null === queue1 && (queue1 = = createUpdateQueue()); } else (queue1 = ), (queue2 = ), null === queue1 ? null === queue2 ? ((queue1 = = createUpdateQueue( )), (queue2 = = createUpdateQueue( ))) : (queue1 = = cloneUpdateQueue(queue2)) : null === queue2 && (queue2 = = cloneUpdateQueue(queue1)); null === queue2 || queue1 === queue2 ? appendUpdateToQueue(queue1, update) : null === || null === ? (appendUpdateToQueue(queue1, update), appendUpdateToQueue(queue2, update)) : (appendUpdateToQueue(queue1, update), ( = update)); }
Let's look at scheduleWork
function scheduleWork(fiber, expirationTime) { // Get the root 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, ), nestedUpdateCount > NESTED_UPDATE_LIMIT && ((nestedUpdateCount = 0), reactProdInvariant("185"))); } function requestWork(root, expirationTime) { // Record the root that needs to be rendered 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); } // Execute to this side and return directly. At this time, the process of setState() has ended return; } // TODO: Get rid of Sync and use current time? if (expirationTime === Sync) { performSyncWork(); } else { scheduleCallbackWithExpirationTime(root, expirationTime); } }
Too complicated, some methods have not been understood yet, but according to the breakpoint, you can first sort out the execution order. After setState, performSyncWork will be executed, followed by the following execution order.
performSyncWork => performWorkOnRoot => renderRoot => workLoop => performUnitOfWork => beginWork => applyDerivedStateFromProps
The final method is execution
function applyDerivedStateFromProps( workInProgress, ctor, getDerivedStateFromProps, nextProps ) { var prevState = ; { if (debugRenderPhaseSideEffects || debugRenderPhaseSideEffectsForStrictMode && & StrictMode) { // Invoke the function an extra time to help detect side-effects. getDerivedStateFromProps(nextProps, prevState); } } // Get the changed state var partialState = getDerivedStateFromProps(nextProps, prevState); { // Warn some error formats warnOnUndefinedDerivedState(ctor, partialState); } // Merge the partial state and the previous state. // Determine whether the format returned by getDerivedStateFromProps is empty. If it is not empty, the original state and its return value will be combined var memoizedState = partialState === null || partialState === undefined ? prevState : _assign({}, prevState, partialState); // Set state // Once the update queue is empty, the derived state is retained in the base state = memoizedState; // Once the update queue is empty, persist the derived state onto the // base state. var updateQueue = ; if (updateQueue !== null && === NoWork) { = memoizedState; } }
Vue
Vue listening variable changes rely on watch , so let's first look at where watch is triggered from the source code.
Watch trigger conditions
There is initState() in src/core/instance
/core/instance/
When data is initialized, initData() will register the data of each vue to the objerserver
function initData (vm: Component) { // ...Omit some code // 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() && ((value) || isPlainObject(value)) && (value) && !value._isVue ) { // Create an observer ob = new Observer(value) } if (asRootData && ob) { ++ } return ob }
Let’s take a look at the constructor of the observer. Whether it is array or obj, they will eventually call ()
constructor (value: any) { = value = new Dep() = 0 def(value, '__ob__', this) if ((value)) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) // Iterate through each value in the array and call walk (value) } else { (value) } }
Let's look at the walk method again. The walk method is to execute the defineReactive() method in the object, and this method is actually to override the set and get methods
/** * 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 = (obj) for (let i = 0; i < ; i++) { defineReactive(obj, keys[i]) } } /core/observer/ defineReactive The method is the core,It willsetandgetMethod rewriting,If we reassign the variable,Then it will determine whether the new value of the variable is equal to the old value,If not equal,It will trigger () Thus callbackwatchMethods in。 /** * Define a reactive property on an Object. */ export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { // The watcher array is stored in dep const dep = new Dep() const property = (obj, key) if (property && === false) { return } // cater for pre-defined getter/setters const getter = property && const setter = property && if ((!getter || setter) && === 2) { // If the third value is not passed. Then val is directly obtained from obj according to the value of the key val = obj[key] } let childOb = !shallow && observe(val) (obj, key, { enumerable: true, // Values can be set configurable: true, get: function reactiveGetter () { const value = getter ? (obj) : val if () { // Generate a watcher in dep () if (childOb) { () if ((value)) { dependArray(value) } } } return value }, // Focus on the set method set: function reactiveSetter (newVal) { // Get the original value of the variable const value = getter ? (obj) : val /* eslint-disable no-self-compare */ // Make repeated value comparisons if equality is returned directly if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (.NODE_ENV !== 'production' && customSetter) { // The dev environment can directly customize the set customSetter() } // Assign a new value if (setter) { (obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) // Trigger watch event // Dep is an array of wacher // Notify will execute the update method of the wacher array. The update method triggers the run method of the final watcher and triggers the watch callback () } }) }
Mini Program
Custom Watch
The data of the mini program itself does not support watch, but we can add it ourselves. We will write one by ourselves according to the writing method of Vue.
export function defineReactive (obj, key, callbackObj, val) { const property = (obj, key); (property); const getter = property && ; const setter = property && ; val = obj[key] const callback = callbackObj[key]; (obj, key, { enumerable: true, get: function reactiveGetter () { const value = getter ? (obj) : val return value }, set: (newVal) => { ('start set'); const value = getter ? (obj) : val if (typeof callback === 'function') { callback(newVal, val); } if (setter) { (obj, newVal) } else { val = newVal } ('finish set', newVal); } }); } export function watch(cxt, callbackObj) { const data = for (const key in data) { (key); defineReactive(data, key, callbackObj) } }
use
We did not compare the new and old assignments before executing the watch callback because the variables in data are assigned to WeChat. Even if the reference variables are assigned to the same value, the judgment will be unequal because of the different reference addresses. If you want to compare new and old values, you cannot use === . You can convert obj or array to json strings before comparing.
// //Get the application exampleconst app = getApp() import {watch} from '../../utils/watcher'; Page({ data: { motto: 'hello world', userInfo: {}, hasUserInfo: false, canIUse: (''), tableData: [] }, onLoad: function () { (); }, initWatcher () { watch(this, { motto(newVal, oldVal) { ('newVal', newVal, 'oldVal', oldVal); }, userInfo(newVal, oldVal) { ('newVal', newVal, 'oldVal', oldVal); }, tableData(newVal, oldVal) { ('newVal', newVal, 'oldVal', oldVal); } }); }, onClickChangeStringData() { ({ motto: 'hello' }); }, onClickChangeObjData() { ({ userInfo: { name: 'helo' } }); }, onClickChangeArrayDataA() { const tableData = []; ({ tableData }); } })
refer to
How to read React source code
React 16.3 ~ React 16.5 Some more important changes
Summarize
The above is the method of monitoring variable changes in React and Vue introduced to you by the editor. I hope it will be helpful to you. If you have any questions, please leave me a message and the editor will reply to you in time. Thank you very much for your support for my website!