ReactJS: Feuille de triche Hooks





Bonne journée, mes amis!



Voici un guide des principaux hooks React: useState, useEffect, useLayoutEffect, useContext, useReducer, useCallback, useMemo et UseRef.



Inspiration: Aide-mémoire de React Hooks: débloquez des solutions aux problèmes courants .



Le but de ce guide est de fournir un bref aperçu de l'objectif et des capacités de chaque crochet. Après la description du hook, il y a un exemple de code pour son utilisation et un bac à sable pour vos expériences.



L'ensemble complet des hooks est disponible dans ce référentiel .



  1. Téléchargement du référentiel
  2. Installer les dépendances: npm i
  3. Exécuter: npm start


Les hooks sont situés dans le répertoire "hooks". Le fichier principal est index.js. Pour exécuter un hook spécifique, vous devez décommenter les lignes d'importation et de rendu correspondantes.



Sans autre préface.



useState



useState vous permet de travailler avec l'état des variables à l'intérieur d'un composant fonctionnel.



État variable


Pour déterminer l'état de la variable, appelez useState avec l'état initial comme argument: useState (initialValue).



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


Mettre à jour l'état d'une variable


Pour mettre à jour l'état d'une variable, appelez la fonction de mise à jour renvoyée par useState: const [state, updater] = useState (initialValue).



Le code:



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

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


Bac à sable:





États variables multiples


Dans un composant fonctionnel, vous pouvez définir et mettre à jour les états de plusieurs variables.



Le code:



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>
    </>
  );
};


Bac à sable:





Utilisation d'un objet pour déterminer l'état d'une variable


En plus des chaînes et des nombres, les objets peuvent être utilisés comme valeur initiale. Notez que useStateUpdater doit recevoir l'objet entier car il est remplacé plutôt que fusionné avec le précédent.



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

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

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


Le code:



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>
    </>
  );


Bac à sable:





Initialiser l'état d'une variable à l'aide d'une fonction


La valeur initiale de l'état d'une variable peut être déterminée par une fonction.



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

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


Fonction au lieu de setState


La fonction de mise à jour renvoyée par useState peut être plus que setState.



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


La deuxième méthode convient aux cas où la mise à jour dépend de l'état précédent.



Le code:



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>
    </>
  );
};


Bac à sable:





useEffect



useEffect accepte une fonction responsable des effets (secondaires) supplémentaires.



Utilisation basique


Le code:



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

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

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


Bac à sable:





Supprimer (annuler) un effet


Une pratique courante consiste à supprimer l'effet après un certain temps. Cela peut être fait en utilisant la fonction renvoyée par l'effet passé à useEffect. Voici un exemple avec addEventListener.



Le code:



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

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

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


Bac à sable:





Effets multiples


Plusieurs useEffects peuvent être utilisés dans un composant fonctionnel.



Le code:



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

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

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

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


Bac à sable:







Notez que l'appel à useEffect lors du re-rendu peut être ignoré en lui passant un tableau vide comme deuxième argument.



Dépendances d'effet


Le code:



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>
    </>
  );
};


Bac à sable:







Dans ce cas, nous transmettons la dépendance randomInt à useEffect comme deuxième argument, donc la fonction est appelée sur le rendu initial, ainsi que chaque fois que randomInt change.



Effet de saut (dépendance de tableau vide)


Dans l'exemple ci-dessous, useEffect reçoit un tableau vide en tant que dépendance, de sorte que l'effet ne fonctionnera que sur le rendu initial.



Le code:



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>
    </>
  );
};


Bac à sable:







Lorsque vous cliquez sur le bouton, useEffect n'est pas appelé.



Effet de saut (pas de dépendances)


En l'absence de tableau de dépendances, l'effet sera déclenché à chaque rendu de la page.



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


useContext



useContext supprime le besoin de s'appuyer sur le consommateur de contexte. Il a une interface plus simple par rapport à MyContext.Consumer et le rendu des accessoires. Vous trouverez ci-dessous une comparaison de l'utilisation du contexte à l'aide de useContext et 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>
}


Le code:



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>
  );
};


Bac à sable:





useLayoutEffect



Le comportement de useLayoutEffect est similaire à celui de useEffect, à quelques exceptions près, dont nous parlerons plus tard.



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


Utilisation basique


Voici un exemple utilisant useEffect, mais avec useLayoutEffect.



Le code:



  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>
    </>
  );
};


Bac à sable:





useLayoutEffect et useEffect


La fonction passée à useEffect est appelée après le rendu de la page, c'est-à-dire après la formation de la mise en page et le rendu des éléments. Cela convient à la plupart des effets supplémentaires qui ne devraient pas bloquer le flux. Cependant, si, par exemple, vous souhaitez effectuer des manipulations DOM comme effet supplémentaire, useEffect n'est pas le meilleur choix. Pour empêcher l'utilisateur de voir les modifications, useLayoutEffect doit être utilisé. La fonction passée à useLayoutEffect est appelée avant le rendu de la page.



useReducer



useReducer peut être utilisé comme alternative à useState, cependant, son but est d'encapsuler une logique complexe pour travailler avec des états, lorsque l'état dépend de la valeur précédente ou qu'il y a plusieurs états.



Utilisation basique


Dans l'exemple ci-dessous, useReducer est utilisé à la place de useState. L'appel useReducer renvoie une valeur d'état et une fonction de répartition.



Le code:



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>
    </>
  );
};


Bac à sable:





Initialisation de l'état différé ("paresseux")


useReducer prend un troisième argument facultatif, une fonction qui renvoie un objet d'état. Cette fonction est appelée avec initialState comme deuxième argument.



Le code:



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>
    </>
  );
};


Bac à sable:





Simuler le comportement de this.setState


useReducer utilise un réducteur moins strict que Redux. Par exemple, le deuxième argument passé à un réducteur n'a pas besoin de la propriété type. Cela nous offre des opportunités intéressantes.



Le code:



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>
    </>
  );
};


Bac à sable:





useCallback



useCallback renvoie le rappel enregistré (mis en cache).



Modèle de démarrage


Le code:



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>
  );
});


Bac à sable:







Dans l'exemple ci-dessus, le composant Age est mis à jour et rendu à nouveau lorsque l'utilisateur clique sur le bouton. Le composant Guide est également rendu à nouveau lorsqu'un nouveau rappel est passé aux accessoires doSomething. Même si Guide utilise React.memo pour optimiser les performances, il est toujours redessiné. Comment pouvons-nous régler ceci?



Utilisation basique


Le code:



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>
  );
});


Bac à sable:







Utilisation intégrée


useCallback peut être utilisé comme fonction intégrée.



Le code:



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>
  );
});


Bac à sable:





useMemo



useMemo renvoie la valeur stockée (mise en cache).



Modèle de démarrage


Le code:



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>
  );
});


Bac à sable:





Ce modèle est identique au modèle useCallback de départ, sauf que someValue est un objet et non une chaîne. Le composant Guide est également re-rendu malgré l'utilisation de React.memo.



Mais pourquoi cela se produit-il? Après tout, les objets sont comparés par référence et la référence à someValue change à chaque rendu. Des idées?



Utilisation basique


La valeur renvoyée par doSomething peut être stockée à l'aide de useMemo. Cela évitera un rendu inutile.



Le code:



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>
  );
});


Bac à sable:





useRef


useRef renvoie un objet ref. Les valeurs de cet objet sont disponibles via la propriété "current". Cette propriété peut se voir attribuer une valeur initiale: useRef (initialValue). Un objet ref existe pour la durée de vie d'un composant.



Accéder au DOM


Le code:



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" />
    </>
  );
};


Bac à sable:





Variables de type instance (génériques)


Un objet ref peut contenir n'importe quelle valeur, pas seulement un pointeur vers un élément DOM.



Le code:



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" />
    </>
  );
};


Bac à sable:





useRef peut être utilisé pour stocker l'ID du minuteur afin qu'il puisse être arrêté plus tard.



Le code:



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>
    </>
  );
};


Bac à sable:





J'espère que vous avez apprécié l'article. Merci de votre attention.



All Articles