El uso del useCallback de React es esencialmente solo un envoltorio alrededor de useMemo especializado para funciones para evitar la creación constante de nuevas instancias de funciones dentro de los accesorios de los componentes. Mi pregunta surge cuando necesita pasar un argumento a la devolución de llamada creada a partir de la memorización.
Por ejemplo, una devolución de llamada creada así...
const Button: React.FunctionComponent = props => { const onClick = React.useCallback(() => alert('Clicked!'), []) return <button onClick={onClick}>{props.children}</button> } es un ejemplo simple de una devolución de llamada memorizada y no requiere que se le pasen valores externos para realizar su trabajo. Sin embargo, si quiero crear una devolución de llamada memorizada genérica para un tipo de función React.Dipatch<React.SetStateAction> , entonces necesitaría argumentos... por ejemplo:
const Button: React.FunctionComponent = props => { const [loading, setLoading] = React.useState(false) const genericSetLoadingCb = React.useCallback((x: boolean) => () => setLoading(x), []) return <button onClick={genericSetLoadingCb(!loading)}>{props.children}</button> }En mi cabeza, parece que es exactamente lo mismo que hacer lo siguiente...
const Button: React.FunctionComponent = props => { const [loading, setLoading] = React.useState(false) return <button onClick={() => setLoading(!loading)}>{props.children}</button> } lo que permitiría frustrar el propósito de memorizar la función porque aún estaría creando una nueva función en cada representación, ya que genericSetLoadingCb(false) también devolvería una nueva función en cada representación.
¿Es correcto este entendimiento, o el patrón descrito con argumentos todavía mantiene los beneficios de la memorización?
Consideremos la siguiente función de orden superior genericCb (similar a su genericSetLoadingCb ):
const genericCb = React.useCallback( (param) => (e) => setState({ ...state, [param]: e.target.value }), [] ); genericCb puede ser útil en la siguiente situación donde Input es un componente memorizado creado usando React.memo :
<Input value={state.firstName} onChange={genericCb('firstName')} /> Dado que Input es un componente memorizado, es posible que deseemos que la función generada por genericCb('firstName') siga siendo la misma en los renderizados (para que el componente memorizado no se vuelva a renderizar innecesariamente). A continuación veremos cómo conseguirlo.
Ahora, la forma en que construimos genericCb arriba es que nos aseguramos de que permanezca igual en todas las representaciones (debido al uso de useCallback ).
Sin embargo, cada vez que llame a genericCb para crear una nueva función como esta:
genericCb("firstName")La función devuelta seguirá siendo diferente en cada renderizado . Para garantizar también que la función devuelta se memorice para alguna entrada, también debe usar algún enfoque de memorización:
import memoize from "fast-memoize"; .... const genericCb = React.useCallback( memoize((param) => (e) => setState({ ...state, [param]: e.target.value })), [] ); Ahora, si llama a genericCb("firstName") para generar una función, devolverá la misma función en cada procesamiento, siempre que "firstName" también siga siendo el mismo .
Como se señaló en los comentarios anteriores, la solución que usa useCallback parece generar una advertencia (aunque no en mi proyecto):
React Hook useCallback recibió una función cuyas dependencias se desconocen. Pase una función en línea en su lugar
Parece que la advertencia está ahí porque no pasamos la función en línea a useCallback . La solución que encontré para deshacerme de esta advertencia basada en este hilo de github es usar useMemo para imitar useCallback de esta manera:
// Use this; this doesn't produce the warning anymore const genericCb = React.useMemo( () => memoize( (param) => (e) => setState({ ...state, [param]: e.target.value }) ), [] ); Simplemente usar memoize sin useCallback (o useMemo como en la actualización) no funcionaría, ya que en el siguiente render invocaría memoize desde nuevo como este:
let memoized = memoize(fn) memoized('foo', 3, 'bar') memoized('foo', 3, 'bar') // cache hit memoized = memoize(fn); // without useCallback (or useMemo) this would happen on next render // Now the previous cache is lostParece que hacer lo siguiente es una forma elegante y sencilla de resolver su problema. No creará una nueva función cb si Button solo se está volviendo a representar.
const Button = props => { const [loading, setLoading] = React.useState(false) const cb = React.useCallback(() => { setLoading(!loading) }, [loading]); return <button onClick={cb}>{props.children}</button> }