This article was first published on the public account “Xiao Li’s Front-end cabin”, welcome to follow ~
preface
UseEffect is usually used for side effects caused by status updates.
useEffect(effect, deps);
Copy the code
It takes two arguments:
effect
Effect is a side effect function that is executed automatically every time a component is rendered.
deps
Sometimes, if you don’t want useEffect() to be executed every time you render, you can use its second argument, deps, to specify the dependencies of the side effect function with an array and only re-render if the dependencies change.
This article mainly shares some high-level effect hooks based on useEffect encapsulation.
Simple encapsulation
Let’s start with the simplest example: run effect useEffectOnce:
function useEffectOnece(effect) {
useEffect(effect, []); / / deps is empty
}
Copy the code
The mount and unload lifecycle functions are also executed only once in the component lifecycle, so we can implement useMount and useUnmount based on useEffectOnce.
function useMount(fn) {
useEffectOnce(() = > {
fn();
});
};
Copy the code
function useUnmount(fn) {
const fnRef = useRef(fn);
fnRef.current = fn; // Always ensure that the function is the latest
useEffectOnce(() = > () = > fnRef.current());
};
Copy the code
useAsyncEffect
UseEffect itself does not support async functions, so we cannot write:
useEffect(async() = > {awaitgetData(); } []);Copy the code
UseEffect should return a destruct function. If async is used as the first argument, the return value will be a Promise. React will return an error when the destruct function is called: Function. The apply is undefined.
We can modify useAsyncEffect to support Async.
Normal version
function useAsyncEffect(effect, deps) {
useEffect(() = > {
const e = effect();
async function execute() {
await e;
}
execute();
}, deps);
}
Copy the code
Advanced edition: Determine the generator scenario
function useAsyncEffect(effect, deps) {
// Determine if it is a generator
function isGenerator(val) {
return typeof val[Symbol.asyncIterator] === 'function';
}
useEffect(() = > {
const e = effect();
let cancelled = false;
async function execute() {
// Add a generator judgment branch
if (isGenerator(e)) {
// Iterate indefinitely until you get the final result of the generator
while (true) {
const result = await e.next();
// During an iteration, components are unloaded or the iteration is complete
if (cancelled || result.done) {
break; }}}else {
await e;
}
}
execute();
return () = > {
cancelled = true;
};
}, deps);
}
Copy the code
useUpdateEffect
Some scenes we don’t want to effect the first time we render them. For example, when searching, the search method is called only when the keyword changes, and we can encapsulate useUpdateEffect, which ignores the first useEffect execution and only executes when relying on updates.
// Is used to record whether the current render is the first render
function useFirstMountState() {
const isFirst = useRef(true);
if (isFirst.current) {
isFirst.current = false;
return true;
}
return isFirst.current;
}
function useUpdateEffect(effect, deps) {
const isFirstMount = useFirstMountState(); // First render
useEffect(() = > {
if(! isFirstMount) {// If it is not the first time, the effect function is executed
return effect();
}
}, deps);
};
Copy the code
useCustomCompareEffect
The useEffect deps comparison uses the object. is method to compare whether the values or references are exactly the same.
function areHookInputsEqual(
nextDeps: Array<mixed>,
prevDeps: Array<mixed> | null.) {
if (prevDeps === null) {
return false;
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
Copy the code
We can add the ability to custom compare DEPs to useEffect:
function useCustomCompareEffect(effect, deps, depsEqual) {
const ref = useRef(undefined); // Manually maintain deps
// Use custom methods to determine the difference, then modify the deps
if(! ref.current || ! depsEqual(deps, ref.current)) { ref.current = deps; } useEffect(effect, ref.current); };Copy the code
Continue to encapsulate two commonly used shallow comparison and deep comparison effects based on usecustomcompareEffects.
useShallowCompareEffect
Deps was compared by shallow comparison
import { equal as isShallowEqual } from 'fast-shallow-equal';
/ / light
const shallowEqualDepsList = (prevDeps, nextDeps) = >
prevDeps.every((dep, index) = > isShallowEqual(dep, nextDeps[index]));
function useShallowCompareEffect(effect, deps) {
useCustomCompareEffect(effect, deps, shallowEqualDepsList);
};
Copy the code
useDeepCompareEffect
Deps was compared by deep comparison
import isDeepEqual from 'fast-deep-equal/react';
function useDeepCompareEffect(effect, deps) {
useCustomCompareEffect(effect, deps, isDeepEqual);
};
Copy the code
The idea of throttling and anti-shaking can also be used with useEffect:
useDebounceEffect
Added anti-shake capability to useEffect
Main ideas:
- Deps changes, effect is triggered normally, and anti-shake starts timing
- Deps changes frequently for anti-shake treatment, so it is needed
flag
recorddelay
Whether to end - After the component is uninstalled, cancel the anti-shock function call
// Use the Hook to handle the anti-shake function
function useDebounceFn(fn, options) {
// Ensure that every fn fetched in debounce is the latest
const fnRef = useRef(fn);
fnRef.current = fn;
constwait = options? .wait ??1000;
const debounced = useMemo(
() = >
debounce(
((. args) = > {
returnfnRef.current(... args); }), wait, options, ), [], ); useUnmount(() = > {
debounced.cancel();
});
return {
run: debounced,
cancel: debounced.cancel,
flush: debounced.flush,
};
}
function useDebounceEffect(effect, deps, debounceOptions) {
const [flag, setFlag] = useState({}); // Record whether the delay is over
const { run, cancel } = useDebounceFn(() = > {
setFlag({});
}, debounceOptions);
// If dePS changes normally, effect is executed and the anti-shake timer starts
useEffect(() = > {
return run();
}, deps);
// Cancel the anti-shock function call after the component is uninstalled
useUnmount(cancel);
// Use useUpdateEffect to execute effect only when the flag changes
useUpdateEffect(effect, [flag]);
}
Copy the code
useThrottleEffect
Adds throttling capability for useEffect
// 'useThrottleEffect' is similar to 'useDebounceEffect' and will not be stated here
function useThrottleEffect(effect, deps, throttleOptions) {
const [flag, setFlag] = useState({});
const { run, cancel } = useThrottleFn(() = > {
setFlag({});
}, options);
useEffect(() = > {
return run();
}, deps);
useUnmount(cancel);
useUpdateEffect(effect, [flag]);
}
Copy the code
conclusion
See react-use and ahooks for the above implementation. We usually should use this kind of tool hook to improve the efficiency of development.
Thanks for your support ❤️
If this article has been helpful to you, please give me a like.
My public account “Xiao Li’s front-end cabin” has just been created, and is still improving. Pay attention to me and become stronger together! 🚀