ReactJS: Hoja de referencia de Hooks





¡Buen dia amigos!



Aquí hay una guía de los principales ganchos de React: useState, useEffect, useLayoutEffect, useContext, useReducer, useCallback, useMemo y UseRef.



Inspiración: Hoja de trucos de React Hooks: Desbloquee soluciones a problemas comunes .



El propósito de esta guía es proporcionar una breve descripción general del propósito y las capacidades de cada gancho. Después de la descripción del gancho, hay un código de ejemplo para su uso y una caja de arena para sus experimentos.



El conjunto completo de ganchos está disponible en este repositorio .



  1. Descargando el repositorio
  2. Instalar dependencias: npm i
  3. Ejecutar: npm start


Los ganchos se encuentran en el directorio "ganchos". El archivo principal es index.js. Para ejecutar un gancho específico, debe descomentar las líneas de importación y renderización correspondientes.



Sin más preámbulos.



useState



useState le permite trabajar con el estado de las variables dentro de un componente funcional.



Estado variable


Para determinar el estado de la variable, llame a useState con el estado inicial como argumento: useState (initialValue).



const DeclareState = () => {
  const [count] = useState(1);
  return <div>  - {count}.</div>;
};


Actualizar el estado de una variable


Para actualizar el estado de una variable, llame a la función de actualización devuelta por useState: const [estado, actualizador] = useState (initialValue).



El código:



const UpdateState = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age + 1);

  return (
    <>
      <p> {age} .</p>
      <button onClick={handleClick}> !</button>
    </>
  );
};


Salvadera:





Estados de múltiples variables


En un componente funcional, puede definir y actualizar los estados de varias variables.



El código:



const MultStates = () => {
  const [age, setAge] = useState(19);
  const [num, setNum] = useState(1);

  const handleAge = () => setAge(age + 1);
  const handleNum = () => setNum(num + 1);

  return (
    <>
      <p> {age} .</p>
      <p>  {num}   .</p>
      <button onClick={handleAge}> !</button>
      <button onClick={handleNum}>   !</button>
    </>
  );
};


Salvadera:





Usar un objeto para determinar el estado de una variable


Además de cadenas y números, los objetos se pueden utilizar como valor inicial. Tenga en cuenta que a useStateUpdater se le debe pasar todo el objeto porque se está reemplazando en lugar de fusionarse con el anterior.



// setState ( ) - useState ( )
// ,    - {name: "Igor"}

setState({ age: 30 });
//   
// {name: "Igor", age: 30} -  

useStateUpdater({ age: 30 });
//   
// {age: 30} -   


El código:



const StateObject = () => {
  const [state, setState] = useState({ age: 19, num: 1 });
  const handleClick = (val) =>
    setState({
      ...state,
      [val]: state[val] + 1,
    });
  const { age, num } = state;

  return (
    <>
      <p> {age} .</p>
      <p>  {num}   .</p>
      <button onClick={() => handleClick('age')}> !</button>
      <button onClick={() => handleClick('num')}>   !</button>
    </>
  );


Salvadera:





Inicializar el estado de una variable usando una función


El valor inicial del estado de una variable se puede determinar mediante una función.



const StateFun = () => {
  const [token] = useState(() => {
    const token = localStorage.getItem("token");
    return token || "default-token";
  });

  return <div> - {token}</div>;
};


Función en lugar de setState


La función de actualización devuelta por useState puede ser más que solo setState.



const [value, updateValue] = useState(0);
//    ,  ,  
updateValue(1);
updateValue((prevVal) => prevVal + 1);


El segundo método es adecuado para casos en los que la actualización depende del estado anterior.



El código:



const CounterState = () => {
  const [count, setCount] = useState(0);

  return (
    <>
      <p>   {count}.</p>
      <button onClick={() => setCount(0)}></button>
      <button onClick={() => setCount((prevVal) => prevVal + 1)}>
         (+)
      </button>
      <button onClick={() => setCount((prevVal) => prevVal - 1)}>
         (-)
      </button>
    </>
  );
};


Salvadera:





useEffect



useEffect acepta una función responsable de efectos (secundarios) adicionales.



Uso básico


El código:



const BasicEffect = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age + 1);

  useEffect(() => {
    document.title = ` ${age} !`;
  });

  return (
    <>
      <p>      .</p>
      <button onClick={handleClick}> !</button>
    </>
  );
};


Salvadera:





Eliminar (cancelar) un efecto


Una práctica común es eliminar el efecto después de un tiempo. Esto se puede hacer usando la función devuelta por el efecto pasado a useEffect. A continuación se muestra un ejemplo con addEventListener.



El código:



const CleanupEffect = () => {
  useEffect(() => {
    const clicked = () => console.log("!");
    window.addEventListener("click", clicked);

    return () => {
      window.removeEventListener("click", clicked);
    };
  }, []);

  return (
    <>
      <p>        .</p>
    </>
  );
};


Salvadera:





Efectos múltiples


Se pueden utilizar múltiples efectos de uso en un componente funcional.



El código:



const MultEffects = () => {
  //   
  useEffect(() => {
    const clicked = () => console.log("!");
    window.addEventListener("click", clicked);

    return () => {
      window.removeEventListener("click", clicked);
    };
  }, []);

  //   
  useEffect(() => {
    console.log(" .");
  });

  return (
    <>
      <p>  .</p>
    </>
  );
};


Salvadera:







Tenga en cuenta que la llamada a useEffect al volver a renderizar se puede omitir pasándole una matriz vacía como segundo argumento.



Dependencias de efectos


El código:



const EffectDependency = () => {
  const [randomInt, setRandomInt] = useState(0);
  const [effectLogs, setEffectLogs] = useState([]);
  const [count, setCount] = useState(1)

  useEffect(() => {
    setEffectLogs((prevEffectLogs) => [
      ...prevEffectLogs,
      `   ${count}.`,
    ]);
    setCount(count + 1)
  }, [randomInt]);

  return (
    <>
      <h3>{randomInt}</h3>
      <button onClick={() => setRandomInt(~~(Math.random() * 10))}>
           !
      </button>
      <ul>
        {effectLogs.map((effect, i) => (
          <li key={i}>{"  ".repeat(i) + effect}</li>
        ))}
      </ul>
    </>
  );
};


Salvadera:







En este caso, estamos pasando la dependencia randomInt a useEffect como segundo argumento, por lo que se llama a la función en el renderizado inicial, así como cuando cambia randomInt.



Efecto de omisión (dependencia de matriz vacía)


En el siguiente ejemplo, a useEffect se le pasa una matriz vacía como una dependencia, por lo que el efecto solo funcionará en la representación inicial.



El código:



const SkipEffect = () => {
  const [randomInt, setRandomInt] = useState(0);
  const [effectLogs, setEffectLogs] = useState([]);
  const [count, setCount] = useState(1);

  useEffect(() => {
    setEffectLogs((prevEffectLogs) => [
      ...prevEffectLogs,
      `   ${count}.`,
    ]);
    setCount(count + 1);
  }, []);

  return (
    <>
      <h3>{randomInt}</h3>
      <button onClick={() => setRandomInt(~~(Math.random() * 10))}>
           !
      </button>
      <ul>
        {effectLogs.map((effect, i) => (
          <li key={i}>{"  ".repeat(i) + effect}</li>
        ))}
      </ul>
    </>
  );
};


Salvadera:







Cuando se hace clic en el botón, no se llama a useEffect.



Efecto de salto (sin dependencias)


Si no hay una matriz de dependencia, el efecto se activará cada vez que se represente la página.



useEffect(() => {
  console.log(
    "        ."
  );
});


useContext



useContext elimina la necesidad de depender del consumidor de contexto. Tiene una interfaz más simple en comparación con MyContext.Consumer y el renderizado de accesorios. A continuación se muestra una comparación del uso del contexto con useContext y Context.Consumer.



//    Context
const ThemeContext = React.createContext("dark")

//   
function Button() {
    return (
        <ThemeContext.Consumer>
            {theme => <button className={thene}> !</button>}
        </ThemeContext.Consumer>
}

//  useContext
import { useContext } from "react"

function ButtonHook() {
    const theme = useContext(ThemeContext)
    return <button className={theme}> !</button>
}


El código:



const ChangeTheme = () => {
  const [mode, setMode] = useState("light");

  const handleClick = () => {
    setMode(mode === "light" ? "dark" : "light");
  };

  const ThemeContext = React.createContext(mode);

  const theme = useContext(ThemeContext);

  return (
    <div
      style={{
        background: theme === "light" ? "#eee" : "#222",
        color: theme === "light" ? "#222" : "#eee",
        display: "grid",
        placeItems: "center",
        minWidth: "320px",
        minHeight: "320px",
        borderRadius: "4px",
      }}
    >
      <p> : {theme}.</p>
      <button onClick={handleClick}>  </button>
    </div>
  );
};


Salvadera:





useLayoutEffect



El comportamiento de useLayoutEffect es similar al de useEffect, con algunas excepciones, de las que hablaremos más adelante.



  useLayoutEffect(() => {
    // 
  }, []);


Uso básico


Aquí hay un ejemplo que usa useEffect, pero con useLayoutEffect.



El código:



  const [randomInt, setRandomInt] = useState(0);
  const [effectLogs, setEffectLogs] = useState([]);
  const [count, setCount] = useState(1);

  useLayoutEffect(() => {
    setEffectLogs((prevEffectLogs) => [
      ...prevEffectLogs,
      `   ${count}.`,
    ]);
    setCount(count + 1);
  }, [randomInt]);

  return (
    <>
      <h3>{randomInt}</h3>
      <button onClick={() => setRandomInt(~~(Math.random() * 10))}>
           !
      </button>
      <ul>
        {effectLogs.map((effect, i) => (
          <li key={i}>{"  ".repeat(i) + effect}</li>
        ))}
      </ul>
    </>
  );
};


Salvadera:





useLayoutEffect y useEffect


La función pasada a useEffect se llama después de que la página se ha renderizado, es decir después de la formación del diseño y renderizado de los elementos. Esto es adecuado para la mayoría de los efectos adicionales que no deberían bloquear el flujo. Sin embargo, si, por ejemplo, desea realizar alguna manipulación del DOM como efecto adicional, useEffect no es la mejor opción. Para evitar que el usuario vea los cambios, se debe utilizar useLayoutEffect. La función pasada a useLayoutEffect se llama antes de que se represente la página.



useReducer



useReducer se puede utilizar como alternativa a useState, sin embargo, su propósito es encapsular lógica compleja para trabajar con estados, cuando el estado depende del valor anterior o hay varios estados.



Uso básico


En el siguiente ejemplo, se usa useReducer en lugar de useState. La llamada useReducer devuelve un valor de estado y una función de envío.



El código:



const initialState = { width: 30 };

const reducer = (state, action) => {
  switch (action) {
    case "plus":
      return { width: Math.min(state.width + 30, 600) };
    case "minus":
      return { width: Math.max(state.width - 30, 30) };
    default:
      throw new Error(" ?");
  }
};

const BasicReducer = () => {
  const [state, dispath] = useReducer(reducer, initialState);
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <div
        style={{
          margin: "0 auto",
          background: color,
          height: "100px",
          width: state.width,
        }}
      ></div>
      <button onClick={() => dispath("plus")}>
          .
      </button>
      <button onClick={() => dispath("minus")}>
          .
      </button>
    </>
  );
};


Salvadera:





Inicialización del estado diferido ("perezoso")


useReducer toma un tercer argumento opcional, una función que devuelve un objeto de estado. Esta función se llama con initialState como segundo argumento.



El código:



const initializeState = () => ({
  width: 90,
});

//  ,  initializeState   
const initialState = { width: 0 };

const reducer = (state, action) => {
  switch (action) {
    case "plus":
      return { width: Math.min(state.width + 30, 600) };
    case "minus":
      return { width: Math.max(state.width - 30, 30) };
    default:
      throw new Error(" ?");
  }
};

const LazyState = () => {
  const [state, dispath] = useReducer(reducer, initialState, initializeState);
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <div
        style={{
          margin: "0 auto",
          background: color,
          height: "100px",
          width: state.width,
        }}
      ></div>
      <button onClick={() => dispath("plus")}>
          .
      </button>
      <button onClick={() => dispath("minus")}>
          .
      </button>
    </>
  );
};


Salvadera:





Simulando el comportamiento de this.setState


useReducer usa un reductor menos estricto que Redux. Por ejemplo, el segundo argumento pasado a un reductor no necesita la propiedad type. Esto nos brinda interesantes oportunidades.



El código:



const initialState = { width: 30 };

const reducer = (state, newState) => ({
  ...state,
  width: newState.width,
});

const NewState = () => {
  const [state, setState] = useReducer(reducer, initialState);
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <div
        style={{
          margin: "0 auto",
          background: color,
          height: "100px",
          width: state.width,
        }}
      ></div>
      <button onClick={() => setState({ width: 300 })}>
          .
      </button>
      <button onClick={() => setState({ width: 30 })}>
          .
      </button>
    </>
  );
};


Salvadera:





useCallback



useCallback devuelve la devolución de llamada guardada (en caché).



Plantilla de inicio


El código:



const CallbackTemplate = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = "some value";
  const doSomething = () => someValue;

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={doSomething} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


Salvadera:







En el ejemplo anterior, el componente Edad se actualiza y se vuelve a renderizar cuando se hace clic en el botón. El componente Guide también se vuelve a representar cuando se pasa una nueva devolución de llamada a los accesorios de doSomething. Aunque Guide usa React.memo para optimizar el rendimiento, aún se vuelve a dibujar. como podemos arreglar esto?



Uso básico


El código:



const BasicCallback = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = "some value";
  const doSomething = useCallback(() => someValue, [someValue]);

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={doSomething} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


Salvadera:







Uso integrado


useCallback se puede utilizar como una función incorporada.



El código:



const InlineCallback = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = "some value";

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={useCallback(() => someValue, [someValue])} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


Salvadera:





useMemo



useMemo devuelve el valor almacenado (en caché).



Plantilla de inicio


El código:



const MemoTemplate = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = { value: "some value" };
  const doSomething = () => someValue;

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={doSomething} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


Salvadera:





Esta plantilla es idéntica a la plantilla useCallback inicial, excepto que someValue es un objeto, no una cadena. El componente Guía también se vuelve a renderizar a pesar de usar React.memo.



Pero ¿por qué sucede esto? Después de todo, los objetos se comparan por referencia y la referencia a someValue cambia con cada renderizado. ¿Algunas ideas?



Uso básico


El valor devuelto por doSomething se puede almacenar usando useMemo. Esto evitará un renderizado innecesario.



El código:



const BasicMemo = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = () => ({ value: "some value" });
  const doSomething = useMemo(() => someValue, []);

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={doSomething} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


Salvadera:





useRef


useRef devuelve un objeto de referencia. Los valores de este objeto están disponibles a través de la propiedad "actual". A esta propiedad se le puede asignar un valor inicial: useRef (initialValue). Un objeto de referencia existe durante la vida de un componente.



Accediendo al DOM


El código:



const DomAccess = () => {
  const textareaEl = useRef(null);
  const handleClick = () => {
    textareaEl.current.value =
      " - ,     . ,    !";
    textareaEl.current.focus();
  };
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <button onClick={handleClick}>
         .
      </button>
      <label htmlFor="msg">
                 .
      </label>
      <textarea ref={textareaEl} id="msg" />
    </>
  );
};


Salvadera:





Variables similares a instancias (genéricas)


Un objeto de referencia puede contener cualquier valor, no solo un puntero a un elemento DOM.



El código:



const StringVal = () => {
  const textareaEl = useRef(null);
  const stringVal = useRef(
    " - ,     . ,    !"
  );
  const handleClick = () => {
    textareaEl.current.value = stringVal.current;
    textareaEl.current.focus();
  };
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <button onClick={handleClick}>
         .
      </button>
      <label htmlFor="msg">
               .
      </label>
      <textarea ref={textareaEl} id="msg" />
    </>
  );
};


Salvadera:





useRef se puede utilizar para almacenar el ID del temporizador para poder detenerlo más tarde.



El código:



const IntervalRef = () => {
  const [time, setTime] = useState(0);
  const setIntervalRef = useRef();

  useEffect(() => {
    const id = setInterval(() => {
      setTime((time) => (time = new Date().toLocaleTimeString()));
    }, 1000);

    setIntervalRef.current = id;

    return () => clearInterval(setIntervalRef.current);
  }, [time]);

  return (
    <>
      <p> :</p>
      <time>{time}</time>
    </>
  );
};


Salvadera:





Espero que hayas disfrutado el artículo. Gracias por su atención.



All Articles