encapsulate useCreation with useMemo and useRef
useCreation : is a substitute for useMemo
or useRef
. In other words, the useCreation
hook enhances useMemo
and useRef
so that the hook can replace the two hooks. (from ahooks-useCreation)
-
useMemo
is not necessarily the latest value, butuseCreation
ensures that the value you get must be the latest value - for the creation of complex constants,
useRef
is prone to potential performance risks, butuseCreation
can avoid
the performance hazard here refers to:
// Every time you re render, the process of instantiating the Subject is executed, even if the instance is immediately thrown away
const a = useRef(new Subject())
//Performance hazards can be avoided through the factory function
const b = useCreation(() => new Subject(), [])
undefined
next let's look at how to encapsulate a useCreation
. First, we need to understand the following three points:
- the first point: determine the parameters first. The parameters of
useCreation
are the same as those ofuseMemo
. The first parameter is a function, and the second parameter is a variable array - second point: our values should be saved in
useRef
, so that the values can be cached, thus reducing extraneous refreshes - the third point: the judgment of updating value, how to determine whether to update the value in
useRef
by the second parameter.
.
if we understand three things, we can implement a useCreation
ourselves.
import { useRef } from 'react';
import type { DependencyList } from 'react';
const depsAreSame = (oldDeps: DependencyList, deps: DependencyList):boolean => {
if(oldDeps === deps) return true
for(let i = 0; i < oldDeps.length; i++) {
// Determine whether two values are the same value
if(!Object.is(oldDeps[i], deps[i])) return false
}
return true
}
const useCreation = <T>(fn:() => T, deps: DependencyList)=> {
const { current } = useRef({
deps,
obj: undefined as undefined | T ,
initialized: false
})
if(current.initialized === false || !depsAreSame(current.deps, deps)) {
current.deps = deps;
current.obj = fn();
current.initialized = true;
}
return current.obj as T
}
export default useCreation;
undefined
in useRef
, the updated value is judged by initialized
and depsAreSame
, in which depsAreSame
compares the deps
(old value) stored in useRef
with the newly passed deps
(new value) to determine whether the data of the two arrays are consistent and whether to update
verify useCreation
Let's write a small example to verify whether useCreation
meets our requirements:
import React, { useState } from 'react';
import { Button } from 'antd-mobile';
import { useCreation } from '@/components';
const Index: React.FC<any> = () => {
const [_, setFlag] = useState<boolean>(false)
const getNowData = () => {
return Math.random()
}
const nowData = useCreation(() => getNowData(), []);
return (
<div style={{padding: 50}}>
<div>Normal functions: {getNowData()}</div>
<div>UseCreation Wrapped: {nowData}</div>
<Button color='primary' onClick={() => {setFlag(v => !v)}}> Render</Button>
</div>
)
}
export default Index;
undefined
We can see that when we make irrelevant state
changes, the normal function is refreshed, but useCreation
is not refreshed, thus enhancing the performance of rendering ~
useEffect
useEffect
I believe that what you have already used cannot be ripe again. We can use useEffect
to simulate the componentDidMount
and componentWillUnmount
functions of class
.
useMount
Needless to say, this hook simplifies the second parameter of useEffect
:
import { useEffect } from 'react';
const useMount = (fn: () => void) => {
useEffect(() => {
fn?.();
}, []);
};
export default useMount;
undefined
useUnmount
one thing to note about this is to use useRef
to ensure that the passed function is in the latest state, so you can use
with useLatest mentioned above.
import { useEffect, useRef } from 'react';
const useUnmount = (fn: () => void) => {
const ref = useRef(fn);
ref.current = fn;
useEffect(
() => () => {
ref.current()
},
[],
);
};
export default useUnmount;
undefined
combine useMount
and useUnmount
to make a small example
import { Button, Toast } from 'antd-mobile';
import React,{ useState } from 'react';
import { useMount, useUnmount } from '@/components';
const Child = () => {
useMount(() => {
Toast.show('First render')
});
useUnmount(() => {
Toast.show('Component uninstalled')
})
return <div>Hello, I'm Star</div>
}
const Index:React.FC<any> = (props)=> {
const [flag, setFlag] = useState<boolean>(false)
return (
<div style={{padding: 50}}>
<Button color='primary' onClick={() => {setFlag(v => !v)}}>changing-over {flag ? 'unmount' : 'mount'}</Button>
{flag && <Child />}
</div>
);
}
export default Index;
undefined
useUpdate
useUpdate : force updates
sometimes we need a component to force updates, so we can use this hook:
import { useCallback, useState } from 'react';
const useUpdate = () => {
const [, setState] = useState({});
return useCallback(() => setState({}), []);
};
export default useUpdate;
//Example:
import { Button } from 'antd-mobile';
import React from 'react';
import { useUpdate } from '@/components';
const Index:React.FC<any> = (props)=> {
const update = useUpdate();
return (
<div style={{padding: 50}}>
<div>Time:{Date.now()}</div>
<Button color='primary' onClick={update}>Update time</Button>
</div>
);
}
export default Index;
undefined