Redux-react or Mobx-React principles require you to understand the principles of the old and new context, so after a bit of research, Here we implement a simplified react Context.
New context API documentationThe official example is 👇
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
return( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } function Toolbar(props) { return ( <> <ThemedButton /> <ThemedColor /> </> ); Class ThemedButton extends React.Component {static contextType = ThemeContext;} class ThemedButton extends React.Component {static contextType = ThemeContext; render() { return <Button theme={this.context} />; }} // The second way to get the context, Function ThemedColor(){return (< themecontext.consumer > {value =>(<div> {value} <div/>)} </ThemeContext.Consumer> ) }Copy the code
The basic idea
A class component may have many instances, but the static attribute does not change. All instances can be fetched.
Implement the Provider and Consumer components
The createContext method exports an object that contains two high-level components: Provider and Consumer. On each update, the Provider group goes to the static getDerivedStateFromProps method, and then mounts the value of the props to the static property so that the value changes in real time. The Consumer and Provider share the same space. So every time render takes the static property value on the Provider, the two components are joined together. The second way to get the context is to make the Consumer’s children a function that passes parameters to the component to be returned for updating purposes
function createContext(defaultValue) {
class Provider extends React.Component {
static value = defaultValue;
constructor(props) {
super(props);
Provider.value = props.value;
}
static getDerivedStateFromProps(nextProps, prevState) {
Provider.value = nextProps.value;
return prevState;
}
render() {
return this.props.children; }}class Consumer extends React.Component {
render() {
return this.props.children(Provider.value); }}return { Provider, Consumer }
}
Copy the code
Static property to get the context value
static contextType = MyContext;
class MyClass extends React.Component {
static contextType = MyContext;
render() {
this.context = MyClass.contextType.Provider.value;/* This line is the mock implementation */
let value = this.context;
return <>
{value}
</>}}Copy the code
extension
The above can only run the basic demo, there are still many problems to be solved
If there are n nested levels between provider and consumer, shouldComponentUpdate is false, the bottom consumer should still receive updates.
In fact, neither the previous demo nor the older Context Legacy Context can do this, and this is one of the criticisms of the older Context, so what is the dark magic of the new Context that can do this?
The answer is that the context change causes another check
The following is somewhat cryptic, referring to Fiber’s workings: Thanks to Fiber’s linked list mechanism, all children in a Provider’s consumers register in the event pool and provide a propagateContextChange method. The Provider change triggers the updateContextProvider function, and each consumer child’s Fiber node executes the propagateContextChange method (see question 2 if the bit operation passes), In propagateContextChange, look for the matching Consumer node in the subtree rooted in the current Fiber node, and mark the update.
Therefore, shouldComponentUpdate causes the parent of the Consumer component to not be tagged, but the Provider propagateContextChange causes the Consumer component to be tagged again, So that it can be rendered.
2. What does the second argument accepted by createContext do?
Note that createContext accepts the second argument
export function createContext<T> (defaultValue: T, calculateChangedBits: ? (a: T, b: T) = >number,
): ReactContext<T> {
if (calculateChangedBits === undefined) {
calculateChangedBits = null;
}
const context: ReactContext<T> = {
? typeof: REACT_CONTEXT_TYPE, // This type identifier is very special, and the first problem is related to it
_calculateChangedBits: calculateChangedBits,
_currentValue: defaultValue,
_currentValue2: defaultValue,
_threadCount: 0.Provider: (null: any),
Consumer: (null: any),
};
context.Provider = {
? typeof: REACT_PROVIDER_TYPE,
_context: context,
};
context.Consumer = context;
return context;
}
Copy the code
The second parameter is actually a function to determine if the context is updated, and we can use it to customize the update granularity to avoid unnecessary updates.
This is something that involves some bit operations, so I won’t go into it, but I’ll probably add it later.
CalculateChangedBits observedBits are a pair of apis, this thing may be unstable now, use with caution
Some reference articles
Different React context
ObservedBits: Secret function of React Context
React Tips — Context API (Performance Considerations) needs to be read
I see a long way to go, I will continue to search, front-end engineers are always on the road to learning