Common APIs
const MyContext = (defaultValue);
Create a Context object. When React renders a component that has subscribed to this Context object, this component will match the closest to it from the component tree.Provider
The current context value is read in .
< value={/* A value */}>
Each Context object returns a Provider React component that allows consuming components to subscribe to changes in context.
Provider receives onevalue
Properties, passed to the consumer component. A Provider can correspond to multiple consumer components. Multiple providers can also be used in nesting, and the inner layer will cover the outer layer of data.
useContext
const store = useContext(MyContext)
Receive a context object () and return the current value of the context. The current context value is the closest to the current component in the upper component
<>
ofvalue
prop decide.
When the value of the Provider changes, all consumer components inside it will be re-rendered
After understanding the API, let’s take a look at a simple example.
Example
const MyContext = (null); function reducer(state, action) { switch () { case 'addCount': { return { ...state, count: + 1 } } case 'addNum': { return { ...state, num: + 1 } } default: return state; } } const MyProvider = ({ children }) => { const [store, dispatch] = useReducer(reducer, { count: 0, num: 0 }) return < value={{store, dispatch}}>{children}</> }; export default () => { return ( <MyProvider> <ChildCount /> <ChildNum /> <Child /> </MyProvider> ); }
export default () => { const { state, dispatch } = (MyContext); ('re-render ChildCount', ) return ( <> <div>count is: {}</div> <button onClick={() => dispatch({ type: 'addCount' })}> AddCount </button> </> ) }
export default () => { const { state, dispatch } = (MyContext); ('re-render ChildNum', ) return ( <> <div>num is: {}</div> <button onClick={() => dispatch({ type: 'addNum' })}> AddNum </button> </> ) }
export default () => { ('re-render Child') return <div>Child</div> }
ClickAddCount
Button, output:
re-render ChildCount 1re-render ChildNum 0
ClickAddNum
Button, output:
re-render ChildCount 1re-render ChildNum 1
We can find thatAll consumer components under
After the change,
re-render
Changecount 、num
Any value,ChildCount,ChildNum
Cityre-render
For the abovere-render
The following solutions can be optimized
optimization
Function memory for subcomponents
We modify all Child components as follows
export default (() => { const { state, dispatch } = (MyContext); ('re-render ChildCount', ) return ( <> <div>count is: {}</div> <button onClick={() => dispatch({ type: 'addCount' })}> AddCount </button> </> ) })
ClickAddCount
After finding out, it still prints out
re-render ChildCount 1
re-render ChildNum 0
Let's get to know each other again
By default, only the incoming props are compared. If it is an internal state update (useState, useContext, etc.), it will still be re-rendered. In the above example, the state returned by useContext is constantly changing, resulting in the update even if the component wrapped by memo is still triggered.
useMemo
We modify all Child components as follows
export default () => { const { state, dispatch } = (MyContext); return useMemo(() => { ('re-render ChildCount', ) return ( <> <div>count is: {}</div> <button onClick={() => dispatch({ type: 'addCount' })}> AddCount </button> </> ) }, [, dispatch]) }
ClickaddCount
After finding out, only printed out
re-render ChildCount 1
ClickaddNum
After finding out, only printed out
re-render ChildNum 1
useMemo can be used to make finer granular caches, and we can manage whether the component is updated in dependency arrays.
We can think about whether there is a way to render on demand without usingMemo. Implement on-demand rendering just like useSelector in react-redux
Hands-on useSelector
Let's think about it first, in the example above, trigger the subcomponentre-render
What is the reason?
That's right, it's becauseThe value is constantly changing, so we need to find a way to make the child components unable to perceive
value
Changes, at the same timevalue
When a certain value changes, consumption can be triggeredvalue
Subcomponents ofre-render
We use the Observer Mode to implement it
1. We useuseMemo
Caches the first value so that the child components cannot perceive the change in value
2. If the value does not change, the subcomponent will notre-render
, at this time we need to change the value,re-render
Subcomponent, we need ahooks(useSelector)
Help us implement subcomponentsre-render
3. When the subcomponent is initialized,useSelector
To help it subscribe to the callback function of state changed and return the latest state (the function gets the two states inside for comparison, and if the different ones, the component will be updated)
4. InCreate a collection of subcomponents that collect state change callbacks and listen inside
state(value)
, if changed, iterates over the collection and executes all callback functions
Based on the above, we have implemented it in turn, useSelector, useDispatch
const MyProvider = ({children}) => { const [state, dispatch] = useReducer(reducer, initState); // ref state const stateRef = useRef(null); = state; // ref subscribe to callback array const subscribersRef = useRef([]); // state changes, traversal execution callback useEffect(() => { (sub => sub()); }, [state]); // Cache value, use ref to get the latest state, subscribe status const value = useMemo( () => ({ dispatch, subscribe: cb => { (cb); return () => { = (item => item !== cb); }; }, getState: () => }), [] ) return < children={children} value={value} />; }
useSelector
export const useSelector = selector => { // Force update const [, forceRender] = useReducer(v => v + 1, 0); const store = useContext(MyContext); // Get the current state const selectedStateRef = useRef(null) = selector(()); // Compare update callback const checkForUpdates = useCallback(() => { // Get the changed state const newState = selector(()); // Compare the two before and after states if (newState !== ) forceRender({}); }, [store]); // Subscribe to state useEffect(() => { const subscription = (checkForUpdates); return () => subscription(); }, [store, checkForUpdates]); // Return the required state return ; }
useDispatch
export const useDispatch = () => { const store = useContext(MyContext); return }
We use the API rewritten above to rewrite the first example
export default () => { return ( <MyProvider> <ChildCount /> <ChildNum /> <Child /> </Provider> ); }
export default () => { const dispatch = useDispatch(); const count = useSelector(state => ); ('re-render ChildCount', count) return ( <> <div>count is: {count}</div> <button onClick={() => dispatch({ type: 'addCount' });}> AddCount </button> </> ) };
export default () => { const dispatch = useDispatch(); const num = useSelector(state => ); ('re-render ChildNum', num) return ( <> <div>num is: {num}</div> <button onClick={() => dispatch({ type: 'addNum' });}> AddNum </button> </> ) }
export default () => { ('re-render Child') return <div>Child</div> }
ClickAddCount
: Only re-render ChildCount 1 was printed
ClickAddNum
: Only re-render ChildNum 1 was printed
Through some thoughts on the use of Context, we simply implemented the useSelector and implemented the on-demand rendering of the Context component.
Summarize
When using the Context API, avoid unnecessaryre-render
, can be useduseMemo
Do fine-grained updates, you can also useuseSelector
Implement on-demand rendering
The above is the detailed content of some optimization suggestions for React using Context. For more information about React Context, please pay attention to my other related articles!