O React 16.18 é a primeira versão estável com suporte para ganchos de reação . Agora você pode usar ganchos sem medo de que a API mude drasticamente. Embora a react
desenvolvimento de react
recomende o uso da nova tecnologia apenas para novos componentes, muitos, inclusive eu, gostariam de usá-los para componentes mais antigos que usam classes. Mas como a refatoração manual é um processo trabalhoso, tentaremos automatizá-la. As técnicas descritas neste artigo são adequadas para automatizar a refatoração não apenas de componentes de react
, mas também de qualquer outro código JavaScript
.
Recursos Ganchos de reação
O artigo React Hooks Introduction detalha o que são os ganchos e o que eles comem. Em poucas palavras, esta é uma nova tecnologia maluca para criar componentes livres de state
sem usar classes.
Considere o arquivo button.js
:
import React, {Component} from 'react'; export default Button; class Button extends Component { constructor() { super(); this.state = { enabled: true }; this.toogle = this._toggle.bind(this); } _toggle() { this.setState({ enabled: false, }); } render() { const {enabled} = this.state; return ( <button enabled={enabled} onClick={this.toggle} /> ); } }
Com ganchos, ficará assim:
import React, {useState} from 'react'; export default Button; function Button(props) { const [enabled, setEnabled] = useState(true); function toggle() { setEnabled(false); } return ( <button enabled={enabled} onClick={toggle} /> ); }
Você pode argumentar por um longo tempo como esse tipo de gravação é mais óbvio para pessoas não familiarizadas com a tecnologia, mas uma coisa é imediatamente clara: o código é mais conciso e mais fácil de reutilizar. Conjuntos interessantes de ganchos personalizados podem ser encontrados em usehooks.com e streamich.imtqy.com .
A seguir, analisaremos as diferenças de sintaxe nos mínimos detalhes e entenderemos o processo de conversão de código de programa, mas antes disso eu gostaria de falar sobre exemplos de uso dessa forma de notação.
Digressão lírica: uso não-padrão da sintaxe de desestruturação
ES2015
deu ao mundo uma coisa maravilhosa como a reestruturação de matrizes . E agora, em vez de extrair cada elemento individualmente:
const letters = ['a', 'b']; const first = letters[0]; const second = letters[1];
Podemos obter todos os elementos necessários de uma só vez:
const letters = ['a', 'b']; const [first, second] = letters;
Esse registro não é apenas mais conciso, mas também menos propenso a erros, pois elimina a necessidade de lembrar índices de elementos e permite que você se concentre no que é realmente importante: inicialização de variáveis.
Assim, chegamos à es2015
que, se não fosse pelo es2015
equipe de es2015
não apresentaria uma maneira tão incomum de trabalhar com o estado.
Em seguida, gostaria de considerar várias bibliotecas que usam uma abordagem semelhante.
Tente pegar
Seis meses antes do anúncio de ganchos na reação, tive a ideia de que a desestruturação pode ser usada não apenas para obter dados homogêneos da matriz, mas também para obter informações sobre um erro ou o resultado de uma função, por analogia com retornos de chamada em node.js. Por exemplo, em vez de usar a sintaxe try-catch
:
let data; let error; try { data = JSON.parse('xxxx'); } catch (e) { error = e; }
O que parece muito complicado, mas carrega pouca informação e nos obriga a usar let
, embora não tenhamos planejado alterar os valores das variáveis. Em vez disso, você pode chamar a função try-catch , que fará tudo o que você precisa, economizando nos problemas listados acima:
const [error, data] = tryCatch(JSON.parse, 'xxxx');
Dessa maneira interessante, nos livramos de todas as construções sintáticas desnecessárias, deixando apenas o necessário. Este método tem as seguintes vantagens:
- a capacidade de especificar qualquer nome de variável que seja conveniente para nós (ao usar a destruição de objetos, não teríamos esse privilégio, ou melhor, teria seu próprio preço pesado);
- a capacidade de usar constantes para dados que não são alterados;
- sintaxe mais concisa, tudo o que poderia ser removido está faltando;
E, novamente, tudo isso graças à sintaxe da desestruturação de matrizes. Sem essa sintaxe, o uso de uma biblioteca seria ridículo:
const result = tryCatch(JSON.parse, 'xxxx'); const error = result[0]; const data = result[1];
Esse código ainda é válido, mas perde significativamente em comparação com a desestruturação. Também quero adicionar um exemplo da biblioteca try-to-catch , com o advento do async-await
a construção try-catch
ainda é relevante e pode ser escrita assim:
const [error, data] = await tryToCatch(readFile, path, 'utf8');
Se a idéia de tal uso de desestruturação me ocorreu, por que não os criadores da reação também, porque, de fato, temos algo como uma função que tem 2 valores de retorno: uma tupla de Haskel.
Sobre essa digressão lírica pode ser concluída e passar para a questão da transformação.
Convertendo uma classe em React Hooks
Para a conversão, usaremos o transformador putt AST, que permite alterar apenas o necessário e o plug-in @ putout / plugin-plugin-react-hooks .
Para converter a classe herdada do Component
em uma função usando react-hooks
, as seguintes etapas devem ser realizadas:
- remover
bind
- renomeie métodos privados para públicos (remova "_");
- mude
this.state
para usar ganchos - altere
this.setState
para usar ganchos - remova
this
de qualquer lugar - converter
class
em função - nas importações use
useState
vez de Component
Ligação
Instale o putout
com o @putout/plugin-react-hooks
:
npm i putout @putout/plugin-react-hooks -D
Em seguida, crie o arquivo .putout.json
:
{ "plugins": [ "react-hooks" ] }
Então tente putout
em ação.
Cabeçalho do spoiler coderaiser@cloudcmd:~/example$ putout button.js /home/coderaiser/putout/packages/plugin-react-hooks/button.js 11:8 error bind should not be used react-hooks/remove-bind 14:4 error name of method "_toggle" should not start from under score react-hooks/rename-method-under-score 7:8 error hooks should be used instead of this.state react-hooks/convert-state-to-hooks 15:8 error hooks should be used instead of this.setState react-hooks/convert-state-to-hooks 21:14 error hooks should be used instead of this.state react-hooks/convert-state-to-hooks 7:8 error should be used "state" instead of "this.state" react-hooks/remove-this 11:8 error should be used "toogle" instead of "this.toogle" react-hooks/remove-this 11:22 error should be used "_toggle" instead of "this._toggle" react-hooks/remove-this 15:8 error should be used "setState" instead of "this.setState" react-hooks/remove-this 21:26 error should be used "state" instead of "this.state" react-hooks/remove-this 26:25 error should be used "setEnabled" instead of "this.setEnabled" react-hooks/remove-this 3:0 error class Button should be a function react-hooks/convert-class-to-function 12 errors in 1 files fixable with the `--fix` option
putout
encontrou 12 lugares que podem ser corrigidos, tente:
putout --fix button.js
Agora o button.js
fica assim:
import React, {useState} from 'react'; export default Button; function Button(props) { const [enabled, setEnabled] = useState(true); function toggle() { setEnabled(false); } return ( <button enabled={enabled} onClick={setEnabled} /> ); }
Implementação de software
Vamos considerar com mais detalhes várias das regras descritas acima.
Remova this
de qualquer lugar
Como não usamos classes, todas as expressões no formato this.setEnabled
devem ser convertidas em setEnabled
.
Para fazer isso, passaremos pelos nós de ThisExpression , que, por sua vez, são filhos da relação com MemberExpression e estão localizados no campo de object
, assim:
{ "type": "MemberExpression", "object": { "type": "ThisExpression", }, "property": { "type": "Identifier", "name": "setEnabled" } }
Considere a implementação da regra remove-this :
No código descrito acima, a função de utilitário traverseClass
para encontrar a classe, não é tão importante para uma compreensão geral, mas ainda faz sentido trazê-la, para maior precisão:
O teste, por sua vez, pode ser assim:
const test = require('@putout/test')(__dirname, { 'remove-this': require('.'), }); test('plugin-react-hooks: remove-this: report', (t) => { t.report('this', `should be used "submit" instead of "this.submit"`); t.end(); }); test('plugin-react-hooks: remove-this: transform', (t) => { const from = ` class Hello extends Component { render() { return ( <button onClick={this.setEnabled}/> ); } } `; const to = ` class Hello extends Component { render() { return <button onClick={setEnabled}/>; } } `; t.transformCode(from, to); t.end(); });
Nas importações, use useState
vez de Component
Considere a implementação da regra convert-import-component-to-use-state .
Para substituir as expressões:
import React, {Component} from 'react'
em
import React, {useState} from 'react'
Você deve processar o nó ImportDeclaration :
{ "type": "ImportDeclaration", "specifiers": [{ "type": "ImportDefaultSpecifier", "local": { "type": "Identifier", "name": "React" } }, { "type": "ImportSpecifier", "imported": { "type": "Identifier", "name": "Component" }, "local": { "type": "Identifier", "name": "Component" } }], "source": { "type": "StringLiteral", "value": "react" } }
Precisamos encontrar ImportDeclaration
com source.value = react
e, em seguida, percorrer a matriz de specifiers
em busca de ImportSpecifier
com o campo name = Component
:
Considere o teste mais simples:
const test = require('@putout/test')(__dirname, { 'convert-import-component-to-use-state': require('.'), }); test('plugin-react-hooks: convert-import-component-to-use-state: report', (t) => { t.report('component', 'useState should be used instead of Component'); t.end(); }); test('plugin-react-hooks: convert-import-component-to-use-state: transform', (t) => { t.transformCode(`import {Component} from 'react'`, `import {useState} from 'react'`); t.end(); });
E assim, examinamos em termos gerais a implementação do software de várias regras, o restante é construído de acordo com um esquema semelhante. Você pode se familiarizar com todos os nós da árvore do arquivo button.js
analisados no astexplorer . O código fonte dos plugins descritos pode ser encontrado no repositório .
Conclusão
Hoje analisamos um dos métodos para refatoração automatizada de classes de reação para reagir a ganchos. Atualmente, o @putout/plugin-react-hooks
suporta apenas mecanismos básicos, mas pode ser significativamente aprimorado se a comunidade estiver interessada e envolvida. Ficarei feliz em discutir nos comentários comentários, idéias, exemplos de uso, bem como as funcionalidades ausentes.