ReactJS: Folha de Dicas dos Ganchos





Bom dia amigos!



Aqui está um guia para os principais ganchos do React: useState, useEffect, useLayoutEffect, useContext, useReducer, useCallback, useMemo e UseRef.



Inspiração: Reagir Hooks folha de fraude: soluções de desbloqueio para problemas comuns .



O objetivo deste guia é fornecer uma breve visão geral do objetivo e das capacidades de cada gancho. Após a descrição do gancho, há um código de exemplo para seu uso e uma caixa de areia para seus experimentos.



O conjunto completo de ganchos está disponível neste repositório .



  1. Baixando o repositório
  2. Instale dependências: npm i
  3. Executar: npm start


Os ganchos estão localizados no diretório "ganchos". O arquivo principal é index.js. Para executar um gancho específico, você precisa descomentar as linhas de importação e renderização correspondentes.



Sem mais preâmbulos.



useState



useState permite que você trabalhe com o estado das variáveis ​​dentro de um componente funcional.



Estado variável


Para determinar o estado da variável, chame useState com o estado inicial como argumento: useState (initialValue).



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


Atualizar o estado de uma variável


Para atualizar o estado de uma variável, chame a função de atualização retornada por useState: const [state, updater] = useState (initialValue).



O código:



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

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


Caixa de areia:





Vários estados de variáveis


Em um componente funcional, você pode definir e atualizar os estados de várias variáveis.



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


Caixa de areia:





Usando um objeto para determinar o estado de uma variável


Além de strings e números, objetos podem ser usados ​​como um valor inicial. Observe que useStateUpdater deve ser passado o objeto inteiro porque ele está sendo substituído em vez de mesclado com o anterior.



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

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

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


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


Caixa de areia:





Inicializando o estado de uma variável usando uma função


O valor inicial do estado de uma variável pode ser determinado por uma função.



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

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


Função em vez de setState


A função de atualização retornada por useState pode ser mais do que apenas setState.



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


O segundo método é adequado para casos em que a atualização depende do estado anterior.



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


Caixa de areia:





useEffect



useEffect aceita uma função responsável por efeitos adicionais (colaterais).



Uso básico


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


Caixa de areia:





Removendo (cancelando) um efeito


Uma prática comum é remover o efeito depois de um tempo. Isso pode ser feito usando a função retornada pelo efeito passado para useEffect. Abaixo está um exemplo com addEventListener.



O código:



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

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

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


Caixa de areia:





Efeitos múltiplos


Vários useEffects podem ser usados ​​em um componente funcional.



O código:



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

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

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

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


Caixa de areia:







Observe que uma chamada para useEffect na re-renderização pode ser ignorada passando uma matriz vazia como o segundo argumento.



Dependências de efeito


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


Caixa de areia:







Nesse caso, estamos passando a dependência randomInt para useEffect como o segundo argumento, então a função é chamada na renderização inicial, bem como sempre que randomInt muda.



Efeito de salto (dependência de matriz vazia)


No exemplo a seguir, useEffect como um array vazio como dependência, então o efeito só funcionará na renderização inicial.



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


Caixa de areia:







Quando o botão é clicado, useEffect não é chamado.



Efeito de salto (sem dependências)


Na ausência de uma matriz de dependências, o efeito será acionado sempre que a página for renderizada.



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


useContext



useContext elimina a necessidade de confiar no consumidor de contexto. Tem uma interface mais simples em comparação com MyContext.Consumer e renderização de adereços. Abaixo está uma comparação do uso de contexto usando useContext e 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>
}


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


Caixa de areia:





useLayoutEffect



O comportamento de useLayoutEffect é semelhante ao de useEffect, com algumas exceções, sobre as quais falaremos mais tarde.



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


Uso básico


Aqui está um exemplo usando useEffect, mas com useLayoutEffect.



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


Caixa de areia:





useLayoutEffect e useEffect


A função passada para useEffect é chamada depois que a página foi renderizada, ou seja, após a formação do layout e renderização dos elementos. Isso é adequado para a maioria dos efeitos adicionais que não devem bloquear o fluxo. No entanto, se, por exemplo, você deseja fazer alguma manipulação DOM como um efeito adicional, useEffect não é a melhor escolha. Para evitar que o usuário veja as alterações, useLayoutEffect. A função passada para useLayoutEffect é chamada antes que a página seja renderizada.



useReducer



useReducer pode ser usado como alternativa a useState, porém, sua finalidade é encapsular lógicas complexas para trabalhar com estados, quando o estado depende do valor anterior ou existem vários estados.



Uso básico


No exemplo abaixo, useReducer é usado em vez de useState. A chamada useReducer retorna um valor de estado e uma função de despacho.



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


Caixa de areia:





Inicialização de estado diferido ("preguiçoso")


useReducer recebe um terceiro argumento opcional, uma função que retorna um objeto de estado. Esta função é chamada com initialState como o segundo argumento.



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


Caixa de areia:





Simulando o comportamento de this.setState


useReducer usa um redutor menos estrito do que Redux. Por exemplo, o segundo argumento passado para um redutor não precisa da propriedade type. Isso nos oferece oportunidades interessantes.



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


Caixa de areia:





useCallback



useCallback retorna o retorno de chamada salvo (em cache).



Modelo inicial


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


Caixa de areia:







No exemplo acima, o componente Age é atualizado e renderizado novamente quando o botão é clicado. O componente Guia também é renderizado novamente quando um novo retorno de chamada é passado para os props doSomething. Mesmo que Guide use React.memo para otimizar o desempenho, ele ainda é redesenhado. Como podemos consertar isso?



Uso básico


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


Caixa de areia:







UseCallback integrado


useCallback pode ser usado como uma função interna.



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


Caixa de areia:





useMemo



useMemo retorna o valor armazenado (em cache).



Modelo inicial


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


Caixa de areia:





Este modelo é idêntico ao modelo inicial useCallback, exceto que someValue é um objeto e não uma string. O componente Guia também é renderizado novamente, apesar de usar React.memo.



Mas por que isso está acontecendo? Afinal, os objetos são comparados por referência, e a referência a algum valor muda a cada renderização. Alguma ideia?



Uso básico


O valor retornado por doSomething pode ser armazenado usando useMemo. Isso evitará renderizações desnecessárias.



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


Caixa de areia:





useRef


useRef retorna um objeto ref. Os valores deste objeto estão disponíveis através da propriedade "atual". Esta propriedade pode receber um valor inicial: useRef (initialValue). Um objeto ref existe para a vida de um componente.



Acessando o DOM


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


Caixa de areia:





Variáveis ​​semelhantes a instâncias (genéricas)


Um objeto ref pode conter qualquer valor, não apenas um ponteiro para um elemento DOM.



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


Caixa de areia:





useRef pode ser usado para armazenar um identificador de cronômetro para parada posterior.



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


Caixa de areia:





Eu espero que você tenha gostado do artigo. Obrigado pela atenção.



All Articles