background
Hooks can use useContext for state management as follows: Create a Context object using createContext to consume useContext in child components
export const AppContext = React.createContext();
Copy the code
Context.Provider created using createContext in the parent component. This component allows the consuming component to subscribe to changes in the Context. When a Provider’s value changes, all of its internal consuming components are rerendered
// parent.jsx
const [position, setPosition] = useState({
left: 0.top: 0});const change = () = > {
setPosition({
left: position.left++,
top: 0})}return (
<AppContext.Provider value={position}>
<button onClick={change}>change<button>
<Child />
</AppContext.Provider>
)
Copy the code
Consume Context value changes in child components
// child.jsx
const store = useContext(AppContext);
return <span>top: {store.top}; random: {Math.random()}</span>
Copy the code
In the above code, the child can consume the values defined in the parent. The problem with this is that in the Child component we rely on the value of store.top, but when the parent component changes the left value, the random value also changes, indicating that the component has been rendered multiple times. So, is there a way that this subcomponent depends on some value in the Context, and if that value changes then the component will be rerendered, and if any other value changes, that doesn’t cause the subcomponent to be rerendered. The general idea is as follows:
const store = useStore(['left']);
return <span>child</span>
Copy the code
Above we only need the value of left in store, and when the value of left changes, the component starts to re-render. Changes to top in store do not result in rerendering of the component
useReact.memo
The memo can be used to check changes to props. If the props are not changed, the component is not rendered, and the Child component can be split into two components
const InnerChild = React.memo(({ top }) = > (
<span>
top: {top}; random: {Math.random()}
</span>
));
function Child() {
const store = useContext(AppContext);
return <InnerChild top={store.top} />;
}
Copy the code
Use React. Memo to wrap the component, because the parent only changes the value of left, so top never changes, and the current component is never rerendered
useuseMemo
Caching components
You pass in the creation of functions and an array of dependencies as arguments to useMemo, which recalculates memoized values only when a dependency changes. Then you can change the child component above to the following:
// child
const store = useContext(AppContext);
return useMemo(() = > <span>random: {Math.random()}</span>, [store.top]);
Copy the code
The useMemo return value will change only when store.top changes. However, there is a drawback to this approach. When a child component needs to maintain a large amount of state, useMemo dependencies need to be written a lot, which can lead to omissions and state updates, and the DOM tree is not updated.
Break upContext
This idea is borrowed from the publish subscriber model, where publishers publish data and only update the components on which they depend. Here’s how to use it
const { Provider, useModal } = createModel((initState) = > {
const [count, setCount] = React.useState(0);
const [value, setValue] = React.useState('value');
const inc = function () {
setCount(count + 1);
};
const dec = function () {
setCount(count - 1);
};
return { count, inc, dec, value, setValue };
});
Copy the code
Then use the Provider in the parent component to provide the value
function Parent() {
return (
<Provider>
<Child />
<Count />
</Provider>)}Copy the code
The Provider provides two components that depend on different values in the Provider, as follows:
const Child = () = > {
const { count, inc, dec } = useModel(['count']);
return (
<div>
{Math.random()}
<Button onClick={dec}>-</Button>
<span>{count}</span>
<Button onClick={inc}>+</Button>
</div>
);
};
const Counter = () = > {
const { value, setValue } = useModel(['value']);
return (
<div>
{Math.random()}
<input value={value} onChange={e= > setValue(e.target.value)} />
</div>
);
};
Copy the code
In the code above, the Count Child only expects the component to re-render when the value changes, while the Child component only expects the component to re-render when the Count value changes. First, let’s talk about the idea of this approach. In a nutshell, it is the publishing subscriber mode. Provider is the publisher, and Child and Count are the subscribers. When the Provider value changes, all subscribers need to be notified to update it. After receiving the update notification, the subscriber determines whether to update according to the previous value.
Implement the publish subscriber pattern
The first step is to implement a simple publish subscriber pattern
class Subs {
constructor(state) {
this.state = state;
this.observers = [];
}
add(observer) {
this.observers.push(observer)
}
notify() {
this.observers.forEach(observer= > observer())
}
delete(observer) {
const index = this.observers.findIndex(item= > item === observer);
if (index > -1) {
this.observers.splice(index, 1); }}}Copy the code
The add method is used to add subscribers to the subscription list, and notify notifies all subscribers of updates
Provider
The Provider needs to be wrapped so that when the supplied value changes, all subscribers need to be notified to trigger the update
function createModel(model) {
const Context1 = createContext(null);
const Context2 = createContext(null);
const Provider = ({ initState, children }) = > {
const containerRef = useRef();
if(! containerRef.current) { containerRef.current =new Subs(initState);
}
const sealedInitState = useMemo(() = > initState, []);
const state = model(sealedInitState);
useEffect(() = > {
containerRef.current.notify();
})
return (
<Context1 value={state}>
<Context2 value={containerRef.current}>
{children}
</Context2>
</Context1>)}return {
Provider
}
}
Copy the code
The code is relatively simple. It creates two contexts. If the sub-component needs to use useModel([‘count’]) in this way, it will actually consume the Context2 value, and if useModel() consumes Context1 directly.
useModel
This function pushes the update function to the Provider’s subscription list when the child component is created.
const useModel = (deps = []) = > {
const sealedDeps = useMemo(() = > deps, []);
if (sealedDeps.length === 0) {
return useContext(Context1);
}
const container = useContext(Context2);
const [state, setState] = useState(container.state);
const prevDepsRef = useRef([]);
useEffect(() = > {
const observer = () = > {
const prev = prevDepsRef.current;
const curr = getAttr(container.state, sealedDeps);
if(! isEuqal(prev, curr)) { setState(container.state) } prev.current = curr; } container.add(observer)return () = > {
container.delete(observer)
}
}, [])
return state;
}
Copy the code
In simple terms, when useModel is called, the publisher collects the dependency, and when the value is updated, the observer function is triggered. This function compares whether the value has changed before and after the change, and resets the value of state if it has changed. Finally, return the value of state
This is done by loading updated components on demand
react-tracked
A react-Tracked component can also be updated on demand using react-Tracked. For details, see react-Tracked
Welcome you to pay attention to my github, more like ღ(´ · ᴗ · ‘) than heart