React 16.18 es la primera versión estable con soporte para ganchos de reacción . Ahora puede usar ganchos sin temor a que la API cambie drásticamente. Aunque el react
desarrollo de react
recomienda usar la nueva tecnología solo para nuevos componentes, muchos, incluido yo mismo, quisiéramos usarlos para componentes más antiguos que usan clases. Pero como la refactorización manual es un proceso laborioso, intentaremos automatizarlo. Las técnicas descritas en este artículo son adecuadas para automatizar la refactorización no solo de los componentes de react
, sino también de cualquier otro código JavaScript
.
Características Reaccionar ganchos
El artículo de Introducción a React Hooks detalla cuáles son los ganchos y con qué comen. En pocas palabras, esta es una nueva tecnología loca para crear componentes sin state
sin usar clases.
Considere el archivo 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} /> ); } }
Con ganchos, se verá así:
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} /> ); }
Puede discutir durante mucho tiempo cómo este tipo de grabación es más obvio para las personas que no están familiarizadas con la tecnología, pero una cosa está clara de inmediato: el código es más conciso y más fácil de reutilizar. Se pueden encontrar conjuntos interesantes de ganchos personalizados en usehooks.com y streamich.imtqy.com .
A continuación, analizaremos las diferencias de sintaxis hasta el más mínimo detalle y trataremos el proceso de conversión del código del programa, pero antes de eso me gustaría hablar sobre ejemplos de uso de esta forma de notación.
Digresión de letras: uso no estándar de sintaxis de desestructuración
ES2015
le dio al mundo algo tan maravilloso como la reestructuración de matrices . Y ahora, en lugar de extraer cada elemento individualmente:
const letters = ['a', 'b']; const first = letters[0]; const second = letters[1];
Podemos obtener todos los elementos necesarios a la vez:
const letters = ['a', 'b']; const [first, second] = letters;
Tal registro no solo es más conciso, sino que también es menos propenso a errores, ya que elimina la necesidad de recordar índices de elementos y le permite concentrarse en lo que es realmente importante: la inicialización de variables.
Por lo tanto, llegamos a la es2015
que si no fuera por es2015
equipo de es2015
no llegaría a una forma tan inusual de trabajar con el estado.
A continuación, me gustaría considerar varias bibliotecas que utilizan un enfoque similar.
Intenta atrapar
Seis meses antes del anuncio de ganchos en la reacción, se me ocurrió la idea de que la desestructuración se puede usar no solo para obtener datos homogéneos de la matriz, sino también para obtener información sobre un error o el resultado de una función, por analogía con devoluciones de llamada en node.js. Por ejemplo, en lugar de usar la sintaxis try-catch
:
let data; let error; try { data = JSON.parse('xxxx'); } catch (e) { error = e; }
Lo que parece muy engorroso, pero lleva poca información, y nos obliga a usar let
, aunque no planeamos cambiar los valores de las variables. En cambio, puede llamar a la función try-catch , que hará todo lo que necesite, salvándonos de los problemas enumerados anteriormente:
const [error, data] = tryCatch(JSON.parse, 'xxxx');
De esta forma interesante, nos deshicimos de todas las construcciones sintácticas innecesarias, dejando solo lo necesario. Este método tiene las siguientes ventajas:
- la capacidad de especificar cualquier nombre de variable que sea conveniente para nosotros (al usar la desestructuración de objetos, no tendríamos tal privilegio, o más bien, tendría su propio precio engorroso);
- la capacidad de usar constantes para datos que no cambian;
- sintaxis más concisa, falta todo lo que podría eliminarse;
Y, de nuevo, todo esto gracias a la sintaxis de la desestructuración de las matrices. Sin esta sintaxis, usar una biblioteca se vería ridículo:
const result = tryCatch(JSON.parse, 'xxxx'); const error = result[0]; const data = result[1];
Este sigue siendo un código válido, pero pierde significativamente en comparación con la desestructuración. También quiero agregar un ejemplo de la biblioteca try-to-catch , con el advenimiento de async-await
la construcción try-catch
sigue siendo relevante y puede escribirse así:
const [error, data] = await tryToCatch(readFile, path, 'utf8');
Si se me ocurrió la idea de tal uso de la desestructuración, ¿por qué no también los creadores de la reacción, porque de hecho, tenemos algo así como una función que tiene 2 valores de retorno: una tupla de un Haskell?
En esta digresión lírica se puede completar y pasar al tema de la transformación.
Convertir una clase en React Hooks
Para la conversión, utilizaremos el transformador AST de salida , que le permite cambiar solo lo que se necesita y el complemento @ putout / plugin-react-hooks .
Para convertir la clase heredada de Component
en una función usando react-hooks
, se deben seguir los siguientes pasos:
- eliminar
bind
- cambiar el nombre de los métodos privados a public (eliminar "_");
- cambie
this.state
para usar ganchos - cambie
this.setState
para usar ganchos - eliminar
this
de todas partes - convertir
class
para funcionar - en importaciones use
useState
lugar de Component
Conexión
Instale putout
con el @putout/plugin-react-hooks
:
npm i putout @putout/plugin-react-hooks -D
A continuación, cree el archivo .putout.json
:
{ "plugins": [ "react-hooks" ] }
Entonces intente putout
en acción.
Spoiler header 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
encontró 12 lugares que se pueden arreglar, intente:
putout --fix button.js
Ahora button.js
ve así:
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} /> ); }
Implementación de software
Consideremos con más detalle varias de las reglas descritas anteriormente.
Eliminar this
de todas partes
Como no usamos clases, todas las expresiones de la forma this.setEnabled
deben convertirse a setEnabled
.
Para hacer esto, pasaremos por los nodos de ThisExpression , que, a su vez, son hijos de la relación con MemberExpression y se encuentran en el campo de object
, por lo tanto:
{ "type": "MemberExpression", "object": { "type": "ThisExpression", }, "property": { "type": "Identifier", "name": "setEnabled" } }
Considere la implementación de la regla remove-this :
En el código descrito anteriormente, la función de utilidad traverseClass
para encontrar la clase, no es tan importante para una comprensión general, pero aún tiene sentido traerla, para una mayor precisión:
La prueba, a su vez, puede verse así:
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(); });
En las importaciones, use useState
lugar de Component
Considere la implementación de la regla convert-import-component-to-use-state .
Para reemplazar las expresiones:
import React, {Component} from 'react'
en
import React, {useState} from 'react'
Debe procesar el nodo 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" } }
Necesitamos encontrar ImportDeclaration
con source.value = react
, y luego recorrer la matriz de specifiers
en busca de ImportSpecifier
con el campo name = Component
:
Considere la prueba más simple:
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(); });
Y así, examinamos en términos generales la implementación del software de varias reglas, el resto se construye de acuerdo con un esquema similar. Puede familiarizarse con todos los nodos del árbol del archivo analizado button.js en astexplorer . El código fuente de los complementos descritos se puede encontrar en el repositorio .
Conclusión
Hoy analizamos uno de los métodos para la refactorización automática de clases de ganchos de reacción a reacción. Actualmente, el @putout/plugin-react-hooks
solo admite mecanismos básicos, pero se puede mejorar significativamente si la comunidad está interesada e involucrada. Estaré encantado de discutir en los comentarios comentarios, ideas, ejemplos de uso, así como la funcionalidad que falta.