Como os ganchos são úteis?
Antes de dizer o que e por que fiquei desapontado, quero declarar oficialmente que, na verdade, sou fã de ganchos .
Costumo ouvir que ganchos são criados para substituir componentes de classe. Infelizmente, o post "Introdução aos Ganchos" no site oficial do React anuncia essa inovação, francamente, lamentável: os
Ganchos são uma inovação no React 16.8 que permite que você use o estado e outros recursos do React sem escrever classes.
A mensagem que vejo aqui é mais ou menos assim: "As aulas não são legais!". Não o suficiente para motivá-lo a usar ganchos. Na minha opinião, os ganchos nos permitem resolver a funcionalidade de corte transversal de forma mais elegante do que as abordagens anteriores: mixins , componentes de ordem superior e adereços de renderização .
As funções de registro e autenticação são comuns a todos os componentes e os ganchos permitem que tais funções reutilizáveis sejam anexadas aos componentes.
O que há de errado com os componentes da classe?
Há alguma beleza incompreensível em um componente sem estado (ou seja, um componente sem um estado interno) que recebe adereços como entrada e retorna um elemento React. Esta é uma função pura, ou seja, uma função sem efeitos colaterais.
export const Heading: React.FC<HeadingProps> = ({ level, className, tabIndex, children, ...rest }) => {
const Tag = `h${level}` as Taggable;
return (
<Tag className={cs(className)} {...rest} tabIndex={tabIndex}>
{children}
</Tag>
);
};
Infelizmente, a falta de efeitos colaterais limita o uso de componentes sem estado. Afinal, a manipulação do estado é essencial. No React, isso significa que os efeitos colaterais são adicionados aos beans de classe que têm estado. Eles também são chamados de componentes de contêiner. Eles executam efeitos colaterais e passam acessórios para funções puras - componentes sem estado.
Existem alguns problemas bem conhecidos com eventos de ciclo de vida baseados em classe. Muitas pessoas estão infelizes por terem de repetir a lógica
componentDidMounte os métodos componentDidUpdate.
async componentDidMount() {
const response = await get(`/users`);
this.setState({ users: response.data });
};
async componentDidUpdate(prevProps) {
if (prevProps.resource !== this.props.resource) {
const response = await get(`/users`);
this.setState({ users: response.data });
}
};
Mais cedo ou mais tarde, todos os desenvolvedores se depararão com esse problema.
Este código de efeito colateral pode ser executado em um único componente usando um gancho de efeito.
const UsersContainer: React.FC = () => {
const [ users, setUsers ] = useState([]);
const [ showDetails, setShowDetails ] = useState(false);
const fetchUsers = async () => {
const response = await get('/users');
setUsers(response.data);
};
useEffect( () => {
fetchUsers(users)
}, [ users ]
);
// etc.
O Hook
useEffecttorna a vida muito mais fácil, mas priva daquela função pura - componente sem estado - que usamos antes. Essa é a primeira coisa que me decepcionou.
Outro paradigma JavaScript para saber
Tenho 49 anos e sou fã do React. Depois de desenvolver um aplicativo ember com este observador e loucura de propriedade computada, sempre terei um sentimento caloroso por fluxo de dados unidirecional.
O problema com ganchos
useEffecte similares é que eles não são usados em nenhum outro lugar do cenário JavaScript. Ele é incomum e geralmente estranho. Vejo apenas uma maneira de domá-lo - usar este gancho na prática e sofrer. E nenhum exemplo de contadores me induzirá a codificar abnegadamente a noite toda. Sou freelancer e utilizo não só o React mas também outras bibliotecas, e já estou cansadosiga todas essas inovações. Assim que penso que preciso instalar o plugin eslint, que me colocará no caminho certo, este novo paradigma começa a me desgastar.
Matrizes de dependência são um inferno
O gancho useEffect pode receber um segundo argumento opcional chamado array de dependência , que permite que você chame de volta o efeito quando você precisar dele. Para determinar se uma mudança ocorreu , o React compara os valores entre si usando o método Object.is . Se algum elemento mudou desde o último ciclo de renderização, o efeito será aplicado aos novos valores.
A comparação é ótima para lidar com tipos de dados primitivos. Mas se um dos elementos for um objeto ou uma matriz, podem surgir problemas. Object.is compara objetos e arrays por referência e você não pode fazer nada a respeito. O algoritmo de comparação personalizado não pode ser aplicado.
Validar objetos por referência é um obstáculo conhecido. Vamos dar uma olhada em uma versão simplificada de um problema que encontrei recentemente.
const useFetch = (config: ApiOptions) => {
const [data, setData] = useState(null);
useEffect(() => {
const { url, skip, take } = config;
const resource = `${url}?$skip=${skip}&take=${take}`;
axios({ url: resource }).then(response => setData(response.data));
}, [config]); // <-- will fetch on each render
return data;
};
const App: React.FC = () => {
const data = useFetch({ url: "/users", take: 10, skip: 0 });
return <div>{data.map(d => <div>{d})}</div>;
};
Na linha 14 , um
useFetchnovo objeto será passado para a função para cada renderização, a menos que façamos isso de forma que o mesmo objeto seja usado todas as vezes. Nesse cenário, você gostaria de verificar os campos do objeto, não a referência a ele.
Eu entendo por que o React não faz comparações profundas de objetos como esta solução . Portanto, você precisa usar o gancho com cuidado, caso contrário, podem surgir problemas sérios com o desempenho do aplicativo. Estou constantemente me perguntando o que pode ser feito sobre isso e já encontrei várias opções . Para objetos mais dinâmicos, você terá que procurar mais soluções alternativas.
Existe um plugin eslint para corrigir erros automaticamenteencontrado durante a validação do código. É adequado para qualquer editor de texto. Para ser honesto, estou incomodado com todos esses novos recursos que exigem a instalação de um plugin externo para testá-los.
A própria existência de plug-ins como use-deep-object-compare e use-memo-one sugere que realmente há um problema (ou pelo menos uma bagunça).
React depende da ordem em que os ganchos são chamados
Os primeiros ganchos personalizados eram várias implementações de uma função
useFetchpara fazer solicitações a uma API remota. A maioria deles não resolve o problema de fazer solicitações remotas de API de um manipulador de eventos, porque os ganchos só podem ser usados no início de um componente funcional.
Mas e se houver links para sites paginados nos dados e quisermos executar o efeito novamente quando o usuário clicar no link? Aqui está um caso de uso simples
useFetch:
const useFetch = (config: ApiOptions): [User[], boolean] => {
const [data, setData] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const { skip, take } = config;
api({ skip, take }).then(response => {
setData(response);
setLoading(false);
});
}, [config]);
return [data, loading];
};
const App: React.FC = () => {
const [currentPage, setCurrentPage] = useState<ApiOptions>({
take: 10,
skip: 0
});
const [users, loading] = useFetch(currentPage);
if (loading) {
return <div>loading....</div>;
}
return (
<>
{users.map((u: User) => (
<div>{u.name}</div>
))}
<ul>
{[...Array(4).keys()].map((n: number) => (
<li>
<button onClick={() => console.log(' ?')}>{n + 1}</button>
</li>
))}
</ul>
</>
);
};
Na linha 23, o gancho
useFetchserá chamado uma vez na primeira renderização. Nas linhas 35 a 38, renderizamos os botões de paginação. Mas como chamaríamos o gancho useFetchdo manipulador de eventos para esses botões?
As regras de
ganchos afirmam claramente: Não use ganchos dentro de loops, condicionais ou funções aninhadas; em vez disso, sempre use ganchos apenas no nível superior das funções React.
Os ganchos são chamados na mesma ordem sempre que o componente é renderizado. Existem vários motivos para isso, os quais você pode aprender neste excelente post.
Você não pode fazer isso:
<button onClick={() => useFetch({ skip: n + 1 * 10, take: 10 })}>
{n + 1}
</button>
Chamar um gancho
useFetchde um manipulador de eventos viola as regras dos ganchos, porque a ordem de sua chamada muda a cada renderização.
Retornar uma função executável de um gancho
Estou familiarizado com duas soluções para esse problema. Eles seguem a mesma abordagem e gosto de ambos. O plug - in react-async-hook retorna uma função do gancho
execute:
import { useAsyncCallback } from 'react-async-hook';
const AppButton = ({ onClick, children }) => {
const asyncOnClick = useAsyncCallback(onClick);
return (
<button onClick={asyncOnClick.execute} disabled={asyncOnClick.loading}>
{asyncOnClick.loading ? '...' : children}
</button>
);
};
const CreateTodoButton = () => (
<AppButton
onClick={async () => {
await createTodoAPI('new todo text');
}}
>
Create Todo
</AppButton>
);
Chamar o gancho
useAsyncCallbackretornará um objeto com as propriedades esperadas de carga, erro e resultado, bem como uma função executeque pode ser chamada de um manipulador de eventos.
React-hooks-async é um plugin com uma abordagem semelhante. Ele usa uma função
useAsyncTask.
Aqui está um exemplo completo com uma versão simplificada
useAsyncTask:
const createTask = (func, forceUpdateRef) => {
const task = {
start: async (...args) => {
task.loading = true;
task.result = null;
forceUpdateRef.current(func);
try {
task.result = await func(...args);
} catch (e) {
task.error = e;
}
task.loading = false;
forceUpdateRef.current(func);
},
loading: false,
result: null,
error: undefined
};
return task;
};
export const useAsyncTask = (func) => {
const forceUpdate = useForceUpdate();
const forceUpdateRef = useRef(forceUpdate);
const task = useMemo(() => createTask(func, forceUpdateRef), [func]);
useEffect(() => {
forceUpdateRef.current = f => {
if (f === func) {
forceUpdate({});
}
};
const cleanup = () => {
forceUpdateRef.current = () => null;
};
return cleanup;
}, [func, forceUpdate]);
return useMemo(
() => ({
start: task.start,
loading: task.loading,
error: task.error,
result: task.result
}),
[task.start, task.loading, task.error, task.result]
);
};
A função createTask retorna um objeto de tarefa no seguinte formato.
interface Task {
start: (...args: any[]) => Promise<void>;
loading: boolean;
result: null;
error: undefined;
}
O trabalho tem estados
, e , o que nós esperamos. Mas a função também retorna uma função startque pode ser chamada posteriormente. Um trabalho criado com uma função createTasknão afeta a atualização. A atualização é acionada por funções forceUpdatee forceUpdateRefem useAsyncTask.
Agora temos uma função
startque pode ser chamada de um manipulador de eventos ou de outro trecho de código, não necessariamente do início de um componente funcional.
Mas perdemos a capacidade de chamar o gancho na primeira execução do componente funcional. É bom que o plug - in react-hooks-async contenha uma função
useAsyncRun- isso torna as coisas mais fáceis:
export const useAsyncRun = (
asyncTask: ReturnType<typeof useAsyncTask>,
...args: any[]
) => {
const { start } = asyncTask;
useEffect(() => {
start(...args);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [asyncTask.start, ...args]);
useEffect(() => {
const cleanup = () => {
//
};
return cleanup;
});
};
A função
startserá executada quando qualquer um dos argumentos mudar args. Agora, o código com ganchos se parece com isto:
const App: React.FC = () => {
const asyncTask = useFetch(initialPage);
useAsyncRun(asyncTask);
const { start, loading, result: users } = asyncTask;
if (loading) {
return <div>loading....</div>;
}
return (
<>
{(users || []).map((u: User) => (
<div>{u.name}</div>
))}
<ul>
{[...Array(4).keys()].map((n: number) => (
<li key={n}>
<button onClick={() => start({ skip: 10 * n, take: 10 })}>
{n + 1}
</button>
</li>
))}
</ul>
</>
);
};
De acordo com as regras dos ganchos, usamos um gancho
useFetchno início de um componente funcional. A função useAsyncRunchama a API no início e startusamos a função no manipulador onClickpara os botões de paginação.
Agora, o gancho
useFetchpode ser usado para a finalidade pretendida, mas, infelizmente, você tem que ir por meio de soluções alternativas. Também usamos um fecho, o que, devo admitir, me assusta um pouco.
Controle de ganchos em programas de aplicativos
Nos aplicativos, tudo deve funcionar como planejado. Se você planeja rastrear problemas relacionados a componentes E interações do usuário com componentes específicos, você pode usar LogRocket .
LogRocket é uma espécie de gravador de vídeo de aplicativo da web que grava quase tudo o que acontece no site. O plug-in LogRocket para React permite que você encontre sessões de usuário durante as quais o usuário clicou em um componente específico de seu aplicativo. Você entenderá como os usuários interagem com os componentes e por que alguns componentes não renderizam nada.
LogRocket registra todas as ações e estados da loja Redux. É um conjunto de ferramentas para seu aplicativo que permite registrar solicitações / respostas com cabeçalhos e corpos. Eles escrevem HTML e CSS na página, fornecendo renderização pixel a pixel até mesmo para os aplicativos de página única mais complexos.
LogRocket oferece uma abordagem moderna para depurar aplicativos React - experimente gratuitamente .
Conclusão
Acho que o exemplo c
useFetchexplica melhor por que estou frustrado com ganchos.
Alcançar o resultado desejado acabou não sendo tão fácil quanto eu esperava, mas ainda entendo por que é tão importante usar ganchos em uma ordem específica. Infelizmente, nossos recursos são severamente limitados devido ao fato de que os ganchos só podem ser chamados no início de um componente funcional, e teremos que procurar mais soluções alternativas. A solução é
useFetchbastante complicada. Além disso, ao usar ganchos, você não pode prescindir de fechos. Os fechamentos são surpresas contínuas que deixaram muitas cicatrizes em minha alma.
Fechamentos (como aqueles passados para
useEffecteuseCallback) pode obter versões mais antigas de adereços e valores de estado. Isso acontece, por exemplo, quando uma das variáveis capturadas está faltando na matriz de entrada por algum motivo - podem surgir dificuldades.
O estado obsoleto que ocorre após a execução do código em um encerramento é um dos problemas que o hook linter foi projetado para resolver. Existem muitas perguntas no Stack Overflow sobre ganchos obsoletos
useEffecte similares. Eu envolvi funções useCallbacke torci os arrays de dependência desta maneira e daquela forma para me livrar do estado obsoleto ou da repetição infinita de um problema de renderização. Não pode ser de outra forma, mas é um pouco chato. Este é um problema real que você precisa resolver para provar seu valor.
No início deste post, disse que no geral gosto de ganchos. Mas eles parecem muito complicados. Não há nada igual no cenário JavaScript atual. Chamar ganchos toda vez que um componente funcional é renderizado cria problemas que os mixins não criam. A necessidade de um linter para usar esse padrão não é muito confiável e os fechamentos são um problema.
Espero ter entendido mal essa abordagem. Se sim, escreva sobre isso nos comentários.
Consulte Mais informação: