preface
AHooks is an alibaba open source React Hooks library, many of which are implemented smarty. UsePersistFn is the focus of this article.
Here is the usePersistFn document
usePersistFn
What problem does usePersistFn solve?
In some scenarios, you might need to remember a callback using useCallback, but since the internal function must be recreated frequently, the memory is not very good, causing the child component to repeat render. For super complex subcomponents, rerendering can have an impact on performance. With usePersistFn, the function address is guaranteed to never change.
Write a specific Demo to describe the above scenario
function Child(props) {
console.log("child render");
return <button onClick={props.showCount}>showCount</button>;
}
const ChildMemo = memo(Child);
function App() {
const [count, setCount] = useState(0);
const showCount = useCallback(() = > {
console.log("showCount"); } []);return (
<div className="App">
<button onClick={()= >SetCount ((val) => val + 1)}> triggers parent component rendering</button>
<h2>count:{count}</h2>
<ChildMemo showCount={showCount} />
</div>
);
}
Copy the code
To reduce rendering of the Child component, we use memo with useCallback, and useCallback’s second argument passes an empty array, so showCount is created only once, so that when the parent component rerenders, ChildMemo is not rerendered. The goal of reducing the number of unnecessary renders is achieved.
But when we want to access the count variable in showCount
const showCount = useCallback(() = >{+console.log(count); } []);Copy the code
The value of count is not updated. This is a result of the React-hooks implementation, which can be described as a closure trap. Add count to the dependency.
const showCount = useCallback(() = >{+console.log(count);
}, [count]);
Copy the code
The problem is resolved and the latest count can be accessed, but each time the count changes, showCount is recreated to produce a new function, invalidating the memo.
React has a temporary solution to this problem: use useRef.
The react. HTML. Cn/docs/hooks -…
function Child(props) {
console.log("child render");
return <button onClick={props.showCount}>showCount</button>;
}
const ChildMemo = memo(Child);
function App() {
const [count, setCount] = useState(0);
const countRef = useRef();
useEffect(() = > {
countRef.current = count;
}, [count]);
const showCount = useCallback(() = > {
console.log(countRef.current);
}, [countRef]);
return (
<div className="App">
<button onClick={()= >SetCount ((val) => val + 1)}> triggers parent component rendering</button>
<h2>count:{count}</h2>
<ChildMemo showCount={showCount} />
</div>
);
}
Copy the code
We could use custom hooks, which is how React officially uses useRef
function usePersistFn(fn, deps) {
const fnRef = useRef();
useEffect(() = > {
fnRef.current = fn;
}, [fn, ...deps]);
return useCallback(() = > {
return fnRef.current();
}, [fnRef]);
}
Copy the code
Such use
function App() {
const [count, setCount] = useState(0);
const showCountWithPersist = usePersistFn(() = > {
console.log(count);
}, [count]);
return (
<div className="App">
<button onClick={()= >SetCount ((val) => val + 1)}> triggers parent component rendering</button>
<h2>count:{count}</h2>
<ChildMemo showCount={showCountWithPersist} />
</div>
);
}
Copy the code
Codesandbox.io /s/rough-daw…
However, every time we use it, we need to pass the dependency, which is troublesome. We can optimize it so that we do not need to pass the dependency.
The root reason we pass in the dependency is because we want the dependency to change and need to reassign fn to fnref.current
useEffect(() = > {
fnRef.current = fn;
}, [fn, ...deps]);
Copy the code
As long as we don’t check whether the dependency changes, we reassign fn to fnref.current every time the function executes, regardless of whether the dependency changes or not, then we don’t need the user to pass the dependency
function usePersistFn(fn) {
const fnRef = useRef();
+ fnRef.current = fn; UseEffect is removed from this line
return useCallback(() = > {
return fnRef.current();
}, [fnRef]);
}
Copy the code
This section of code codesandbox. IO/s/unruffled…
Ahooks usePersistFn (github.com/alibaba/hoo…) This is the way to do it without passing dependencies.
The only difference is that it replaces useCallback with useRef, but the end result is the same: the returned reference is guaranteed to be the same when usePersistFn is called multiple times.
- Ahooks (ahooks.js.org/zh-CN/hooks…).
function usePersistFn(fn) {
const fnRef = useRef(fn);
fnRef.current = fn;
const persistFn = useRef();
if(! persistFn.current) { persistFn.current =function (. args) {
return fnRef.current.apply(this, args);
};
}
return persistFn.crrent;
}
Copy the code
The ref to persistFn is first created and then rendered for the first time. Persistfn.current returns true, and the anonymous function is assigned to persistfn.current.
Return fnref.current.apply (this, args); Use apply only to ensure that this points. Anonymous functions can also be replaced with arrow functions, so there is no need to apply, for example:
persistFn.current = (. args) = > fnRef.current(args);
Copy the code
fromReact
Official advice
We recommend passing dispatches in the context rather than calling callbacks separately in props(properties). For completeness and as an escape hatch, only the following methods are mentioned here. Also note that this pattern can cause problems in concurrent mode. We plan to provide more customizable alternatives in the future, but the safest solution for now is to always invalidate callbacks if a value depends on change.
The main points are as follows
- When faced with the need for props
The child/sun
This is used when components are passed layer by layerContext
Cooperate withdispatch
The problem is that there’s a lot of garbage rendering going on right now when you use Context directly, and there’s a lot of things you need to be aware of in order to reduce garbage rendering, like the following
-
Break up Context with different granularity
const App = () = > { // ... return ( <ContextA.Provider value={valueA}> <ContextB.Provider value={valueB}> <ContextC.Provider value={valueC}>.</ContextC.Provider> </ContextB.Provider> </ContextA.Provider> ); }; Copy the code
-
Pay attention to the order of the Context, keep the invariant in the outer layer, the variable in the inner layer.
conclusion
- Use caution
useRef
In order to achieveClosure through
The effect inReact18
Concurrency mode (Concurrent Mode
) Unexpected results may occur. - Not recommended
Context
If not, use it with care to avoid additional rendering behavior. The following principles should be maintained- Break up
Context
- Focus on
Context
The order, let the constant in the outer layer, the variable in the inner layer. - In the current
React Context
The lack ofcontext selectors
In the case of this mechanism, it is recommended to use a state management library instead of the Context, after all, most state management libraries have itselectors
Mechanisms to optimize performance.
- Break up
Reference
- zhuanlan.zhihu.com/p/313983390
- The react. HTML. Cn/docs/hooks -…