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 .
- Baixando o repositório
- Instale dependências: npm i
- 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.