原文: How Does setState Know What to Do?
How does the React setState know what it’s going to do
You might look at the title and think, how do I do that? React setState updates the status code. So I watched it with curiosity.
What do you think happens when you call setState in a component?
import React from 'react';
import ReactDOM from 'react-dom';
class Button extends React.Component {
constructor(props) {
super(props);
this.state = { clicked: false };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ clicked: true });
}
render() {
if (this.state.clicked) {
return <h1>Thanks</h1>;
}
return (
<button onClick={this.handleClick}>
Click me!
</button>
);
}
}
ReactDOM.render(<Button />, document.getElementById('container'));
Copy the code
Of course, react will re-render the component on the next {clicked: true} state and update the DOM to return the
Thanks
element.
Seems simple enough, right? Wait, please think about it. Is react processing? Or is ReactDOM processing it?
Updating the DOM sounds like the React DOM is handling it. But we’re calling this.setState, and the API comes from react, not the React DOM. And our react.componentis defined in React.
So React.Com ponent. Prototype. SetState () is how to update the DOM.
Just to be clear: just like this blogMost of thetheothertheThe articleAgain, you don’t have to know anything about it, and you can use it just as wellreact
. This series of articles is for those who are curiousreact
Internal principles of some people. So it’s up to you whether you read it or not.
We might think that react.componentcontains some DOM update logic.
But if so, how does this.setState() work in other environments? For example, the Components in React Native also inherit from react.component.react Native. The React Native application calls this.setstate () as above, but React Native uses Android and iOS views instead of the DOM.
For example, you might also be familiar with the React Test Renderer or Shallow Renderer. These all allow you to render a normal component and call this.setState() in it. But none of them apply to DOM.
If you’ve ever used a renderer like React ART, you know that you can use multiple renderers on a page. (For example, ART components work in the React DOM). This prevents global flags or variables from working.
So ** React.componentsomehow delegates status updates to a particular platform. ** Before we understand how this happens, let’s take a closer look at how and why packages are separated.
A common misconception is that the React engine exists in the React package. That’s actually not true.
In fact, since React 0.14, the React package only exposes the API used to define components. Most implementations of React are in “renderers”.
React-dom, react-dom/server, react-native, react-test-renderer, and react-art are some examples of renderers (you can also build your own).
So the React package works on all platforms. All of its exposed content, such as react.component.react.createElement, react.children functions and hooks, are independent of the target platform. Components will import and use them the same way whether they are running React DOM, React DOM Server, or React Native.
In contrast, the Renderer package exposes platform-specific apis, such as reactdom.render, which allows you to mount React hierarchies into DOM nodes. Every renderer provides an API like this. Ideally, most components don’t need to import anything from the Renderer. This makes them lighter and easier to use.
Most people think that react’s “engine” is in every renderer. Many renderers contain copies of the same code — which we call “reconciler.” The build steps turn reconciler’s code into a highly optimized bundle with the Renderer code for better performance. (Copying code is generally not great for package sizes, but most React users only need one renderer at a time, such as React-dom.)
The point here is that the React package only allows you to use the React features, but it is not known how they are implemented. The Renderer package (React-dom, React-Native, etc.) provides an implementation of react functionality and platform-specific logic. Some of this code is shared (” Reconciler “), but this is the implementation detail of the individual renderers.
Now we know why the React and React-DOM packages are updated every time there is a new feature. For example, when React 16.3 added the Context API, React.createcontext () was exposed on the React package.
But React. CreateContext doesn’t actually implement context functionality. For example, the React DOM and React DOM Server implementations need to be different. So createContext() returns some plain objects:
// A bit simplified
function createContext(defaultValue) {
let context = {
_currentValue: defaultValue,
Provider: null.Consumer: null
};
context.Provider = {
? typeof: Symbol.for('react.provider'),
_context: context
};
context.Consumer = {
? typeof: Symbol.for('react.context'),
_context: context,
};
return context;
}
Copy the code
When you use < myContext.provider > or < myContext.consumer > in your code, it’s up to the renderer to decide what to do with them. The React DOM might track context values in one way, but the React DOM Server might do it differently.
So if you update React to 16.3+ but don’t update the React DOM, your renderer will be a renderer that can’t resolve the Provider and Consumer types. This is why the old React – DOM failed to report these types as invalid.
The same caveat applies to React Native. However, unlike the React DOM, the React version update does not force the React Native version to update immediately. He has a release cycle of his own. After a few weeks, the updated renderer will be individually synchronized to the React Native library. Is that why React Native differs from React DOM in terms of availability time
Well, now we know that the React package doesn’t contain what we’re interested in, and that these implementations exist in renderers like React-dom and React-Native. But that doesn’t answer the question of how setState in react.componentknows what it’s doing (working in conjunction with the renderer).
The answer is to set a special field on each class that creates the Renderer. This field is called updater. You can set React DOM, React DOM Server, or React Native only after the class instance is created:
// Inside React DOM
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactDOMUpdater;
// Inside React DOM Server
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactDOMServerUpdater;
// Inside React Native
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactNativeUpdater;
Copy the code
The React DOM Server may want to ignore the status updates and give you a warning, whereas the React DOM and React Native copy a Reconciler code to deal with it
This is why the this.setState definition in the React package can still update the DOM. He gets it by reading this.updater. If it’s the React DOM, let the React DOM schedule and process updates.
Now that we know how the class operates, what about hooks?
When most people see the Hooks proposal API, they often wonder: How does useState ‘know what to do’? Suppose it’s even more magical than this.setstate.
But as we now see, it was always an illusion to understand the implementation of setState. He doesn’t do anything more than apply the call to the corresponding renderer. In fact the useState Hook does the same thing.
In contrast to the setState updater field, the Hooks use the Dispatcher object. When react. useState, react. useEffect, or any other built-in Hook is called, these calls are forwarded to the current dispatcher.
// In React (simplified a bit)
const React = {
// Real property is hidden a bit deeper, see if you can find it!
__currentDispatcher: null,
useState(initialState) {
return React.__currentDispatcher.useState(initialState);
},
useEffect(initialState) {
return React.__currentDispatcher.useEffect(initialState);
},
// ...
};
Copy the code
And before rendering your component, each renderer sets the Dispatcher.
// In React DOM
const prevDispatcher = React.__currentDispatcher;
React.__currentDispatcher = ReactDOMDispatcher;
let result;
try {
result = YourComponent(props);
} finally {
// Restore it back
React.__currentDispatcher = prevDispatcher;
}
Copy the code
For example, the React DOM Server implementation is here, and the Reconciler implementation shared by the React DOM and React Native is here.
That’s why renderer like react-dom needs to access the same React package from which you called Hooks. Otherwise, your component will not know about dispatcher! This may not work as expected when there are multiple React copies in the same component tree. However, this can lead to some unintelligible errors, so hooks force the solution to the package duplication problem.
Although we discourage you from doing this, for advanced tool use cases, you can override dispatcher in this technique. (I lied about the name __currentDispatcher, which is not the real name, but you can find the real name in the React library.) For example, React DevTools will use a special specialized Dispatcher program to reflect on the Hooks tree by capturing a JavaScript stack trace. Don’t repeat this yourself at home.
This also means that Hooks themselves do not depend on React. If more libraries want to reuse these original hooks in the future, the Dispatcher could theoretically be moved into a separate package and exposed as an API with a common name. In practice, we prefer to avoid premature abstraction until we need it.
Both the updater field and the __currentDispatcher object are forms of a programming principle called dependency injection. In these cases, the Renderer injects features like setState into the React package to keep the component more declarative.
When using React, you don’t need to think about how it works. We want React users to spend more time thinking about their code than abstractions like dependency injection. But if you’re wondering how this.setState or useState knows what to do, I hope this article has been helpful.