In the React
A method for native debugging React code
- Download the React code locally and enter the project folder
yarn build
- Create your own project using create-react-app
- Connect the react source code to the project you just created. Put the build source code in the build folder and CD it to the Build folder in the React folder. There is the node_modules folder. Go to that folder. The react folder and the React -dom folder are found. Go to these two folders separately. Run Yarn Link respectively. At this point, two shortcuts are created. React and react to the dom
- CD Go to the directory of your project and run yarn Link react react-dom. Use the React source code for build files in your project. If you make changes to the React source code, refresh the project and it will be included in your project.
scenario
Consider A scenario where the parent component passes an A parameter to the child component, and the child component needs to listen for changes in the A parameter to convert to state.
16 before
We can use before the React componentWillReveiveProps to monitor props transformation
After 16
In React, getDerivedStateFromProps can be used to listen on props. GetDerivedStateFromProps can return either null or an object. If it is an object, the state is updated
GetDerivedStateFromProps Trigger condition
Our goal is to find the trigger condition for getDerivedStateFromProps
We know that a call to setState will trigger getDerivedStateFromProps, and the same value will trigger getDerivedStateFromProps(after version 16.3).
SetState is in 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');
};
Copy the code
ReactNoopUpdateQueue {
/ /... Part of the omitted
enqueueSetState: function (publicInstance, partialState, callback, callerName) {
warnNoop(publicInstance, 'setState'); }}Copy the code
A warning method is executed
function warnNoop(publicInstance, callerName) {{// The instance constructor
var _constructor = publicInstance.constructor;
var componentName = _constructor && (_constructor.displayName || _constructor.name) || 'ReactClass';
// Form a key component name + method name (column such as setState)
var warningKey = componentName + '. ' + callerName;
// If a warning has already been printed, it will not be printed again
if (didWarnStateUpdateForUnmountedComponent[warningKey]) {
return;
}
// Exporting warning logs in the developer tool terminal cannot be called directly with 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; }}Copy the code
ReactNoopUpdateQueue appears to be an abstract class, and the actual method is not implemented here. Also, if we look at the initial updater assignment, the actual updater is passed in when we initialize the Component
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;
}
Copy the code
We print this in the constructor of the component
class App extends Component {
constructor(props) {
super(props);
/ /.. omit
console.log('constructor'.this); }}Copy the code
The method points to the classComponentUpdater in the react-dom.development.js
var classComponentUpdater = {
// Whether to render
isMounted: isMounted,
enqueueSetState: function(inst, payload, callback) {
/ / inst is fiber
inst = inst._reactInternalFiber;
// Get the time
var currentTime = requestCurrentTime();
currentTime = computeExpirationForFiber(currentTime, inst);
// Initializes an identity object based on the update time
var update = createUpdate(currentTime);
update.payload = payload;
void 0! == callback &&null! == callback && (update.callback = callback);// Queue the update task to queue
enqueueUpdate(inst, update);
//
scheduleWork(inst, currentTime);
},
/ /.. omit
}
Copy the code
EnqueueUpdate is to queue the update task
function enqueueUpdate(fiber, update) {
var alternate = fiber.alternate;
// If alternat is empty and the update queue is empty, the update queue is created
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));
}
Copy the code
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, root.expirationTime),
nestedUpdateCount > NESTED_UPDATE_LIMIT &&
((nestedUpdateCount = 0), reactProdInvariant("185")));
}
Copy the code
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);
}
// The setState() process is complete
return;
}
// TODO: Get rid of Sync and use current time?
if (expirationTime === Sync) {
performSyncWork();
} else{ scheduleCallbackWithExpirationTime(root, expirationTime); }}Copy the code
PerformSyncWork is executed after setState, followed by the following order of execution
performSyncWork => performWorkOnRoot => renderRoot => workLoop => performUnitOfWork => beginWork => applyDerivedStateFromProps
The ultimate method is execution
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); }}// Get the changed state
var partialState = getDerivedStateFromProps(nextProps, prevState);
{
// Warn against some bad formats
warnOnUndefinedDerivedState(ctor, partialState);
} // Merge the partial state and the previous state.
// Check whether the format returned by getDerivedStateFromProps is empty or if it is not, the original state will be merged with its return value
var memoizedState = partialState === null || partialState === undefined ? prevState : _assign({}, prevState, partialState);
/ / set the state
// Once the update queue is empty, the derived state is left in the base 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; }}Copy the code
Vue
Vue relies on watch to listen for variable changes, so let’s take a look at the source code to see where watch is triggered.
Watch trigger condition
InitState () in SRC /core/instance
/core/instance/state.js
InitData () registers each vue’s data with objerServer during data initialization
function initData (vm: Component) {
/ /... Omit some code
// observe data
observe(data, true /* asRootData */)}Copy the code
/** * 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) || valueinstanceof 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 ) {/ / create the observer
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
Copy the code
Take a look at the observer constructors, whether array or obj, they will eventually call 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)
// Iterate over each value in the array and call walk
this.observeArray(value)
} else {
this.walk(value)
}
}
Copy the code
Let’s look at the walk method. The walk method calls defineReactive() on object, which overwrites 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 = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
Copy the code
/core/observer/index.js defineReactive rewrites the set and get methods. If we reassign a value to a variable, we will determine whether the new value of the variable is equal to the old value. If not, Dep.notify () is triggered to call back the methods in Watch.
/** * Define a reactive property on an Object. */
export function defineReactive (obj: Object, key: string, val: any, customSetter? :? Function, shallow? : boolean) {
// Dep contains the watcher array
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) {
// If the third value is not passed. Val is fetched directly from obj based on the value of key
val = obj[key]
}
letchildOb = ! shallow && observe(val)Object.defineProperty(obj, key, {
enumerable: true.// Configurable value
configurable: true.get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
// Generate a watcher in dep
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
// focus on set
set: function reactiveSetter (newVal) {
// Get the original value of the variable
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
// If the value is equal, return
if(newVal === value || (newVal ! == newVal && value ! == value)) {return
}
/* eslint-enable no-self-compare */
if(process.env.NODE_ENV ! = ='production' && customSetter) {
// dev environment can directly customize set
customSetter()
}
// Assign the new value
if (setter) {
setter.call(obj, newVal)
} else{ val = newVal } childOb = ! shallow && observe(newVal)// Trigger the watch event
// Dep is an array of wacher
// Notify executes the wacher array update method, which triggers the final Watcher run method, which triggers the watch callback
dep.notify()
}
})
}
Copy the code
Small program
Custom Watch
The data itself of the small program does not support Watch, but we can add it by ourselves. We can write one by referring to the writing method of 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)
}
}
Copy the code
use
We did not compare the old and new values before executing the watch callback, because the variable in data was assigned in wechat, even if the value assigned to the reference variable was the same, it would be judged to be unequal due to different reference addresses. If you want to compare old and new values, you cannot use ===. Instead, you can convert obj or array to A JSON string before comparing.
//index.js
// Get the application instance
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 }); }})Copy the code
reference
- How to read the React source code
- React 16.3 ~ React 16.5 Some important changes
Spread the word
This article is published in Front of Mint Weekly. Welcome to Watch & Star.