Hola Habr
Ha pasado exactamente un año desde que comencé a estudiar React. Durante este tiempo, logré lanzar varias pequeñas aplicaciones móviles escritas en React Native y participar en el desarrollo de una aplicación web usando ReactJS. Resumiendo y mirando hacia atrás a todos los rastrillos que pude pisar, tuve la idea de expresar mi experiencia en forma de artículo. Haré una reserva de que antes de comenzar el estudio de la reacción, tenía 3 años de experiencia en desarrollo en c ++, python, así como la opinión de que no hay nada complicado en el desarrollo front-end y que no será difícil entenderlo todo. Por lo tanto, en los primeros meses dejé de leer la literatura educativa y, básicamente, solo ejemplos de códigos listos para Google. En consecuencia, un desarrollador ejemplar que estudie la documentación en primer lugar, muy probablemente, no encontrará nada nuevo para él aquí, pero sigo pensando que algunas personas cuando estudian nuevas tecnologías prefieren el camino de la práctica a la teoría. Entonces, si este artículo salva a alguien de un rastrillo, entonces intenté no en vano.
Consejo 1. Trabajando con formularios
La situación clásica: hay un formulario con varios campos en los que el usuario ingresa datos, luego hace clic en el botón y los datos ingresados se envían a una API externa / se guardan en estado / se muestran en la pantalla - subraye lo necesario.
Opción 1. Cómo no hacer
En React, puede crear un enlace a un nodo DOM o componente React.
this.myRef = React.createRef();
Usando el atributo ref, el enlace creado se puede adjuntar al componente / nodo requerido.
<input id="data" type="text" ref={this.myRef} />
Por lo tanto, el problema anterior se puede resolver creando una referencia para cada campo del formulario, y en el cuerpo de la función llamada cuando se hace clic en el botón, obtenga datos del formulario contactando los enlaces necesarios.
class BadForm extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); this.onClickHandler = this.onClickHandler.bind(this); } onClickHandler() { const data = this.myRef.current.value; alert(data); } render() { return ( <> <form> <label htmlFor="data">Bad form:</label> <input id="data" type="text" ref={this.myRef} /> <input type="button" value="OK" onClick={this.onClickHandler} /> </form> </> ); } }
Cómo el mono interno puede tratar de justificar esta decisión:- Lo principal que funciona es que aún tienes 100500 tareas y
no se miran programas de TV; los boletos no están cerrados. Déjalo así, luego cambia - Vea qué poco código se necesita para procesar el formulario. Ref declarado y acceder a los datos donde quieras.
- Si almacena el valor en estado, cada vez que cambie los datos de entrada, se volverá a procesar toda la aplicación y solo necesitará los datos finales. Entonces, este método también resulta bueno para la optimización, solo déjalo así.
Por qué el mono está equivocado:El ejemplo anterior es el antipatrón clásico en React, que viola el concepto de un flujo de datos unidireccional. En este caso, su aplicación no podrá responder a los cambios de datos durante la entrada, ya que no están almacenados en estado.
Opción 2. Solución clásica.
Para cada campo de formulario, se crea una variable en un estado en el que se almacenará el resultado de entrada. Al atributo de valor se le asigna esta variable. Al atributo onhange se le asigna una función en la que el valor de la variable en estado se cambia a través de setState (). Por lo tanto, todos los datos se toman del estado, y cuando los datos cambian, el estado cambia y la aplicación se procesa nuevamente.
class GoodForm extends React.Component { constructor(props) { super(props); this.state = { data: '' }; this.onChangeData = this.onChangeData.bind(this); this.onClickHandler = this.onClickHandler.bind(this); } onChangeData(event) { this.setState({ data: event.target.value }); } onClickHandler(event) { const { data } = this.state; alert(data); } render() { const { data } = this.state; return ( <> <form> <label htmlFor="data">Good form:</label> <input id="data" type="text" value={data} onChange={this.onChangeData} /> <input type="button" value="OK" onClick={this.onClickHandler} /> </form> </> ); } }
Opción 3. Avanzado. Cuando las formas se vuelven muchas
La segunda opción tiene una serie de inconvenientes: una gran cantidad de código estándar, para cada campo es necesario declarar el método onhange y agregar una variable al estado. Cuando se trata de validar los datos ingresados y mostrar mensajes de error, la cantidad de código aumenta aún más. Para facilitar el trabajo con formularios, existe una excelente biblioteca de
Formik que se ocupa de los problemas relacionados con el mantenimiento de formularios y también facilita la adición de un esquema de validación.
import React from 'react'; import { Formik } from 'formik'; import * as Yup from 'yup'; const SigninSchema = Yup.object().shape({ data: Yup.string() .min(2, 'Too Short!') .max(50, 'Too Long!') .required('Data required'), }); export default () => ( <div> <Formik initialValues={{ data: '' }} validationSchema={SigninSchema} onSubmit={(values) => { alert(values.data); }} render={(props) => ( <form onSubmit={props.handleSubmit}> <label>Formik form:</label> <input type="text" onChange={props.handleChange} onBlur={props.handleBlur} value={props.values.data} name="data" /> {props.errors.data && props.touched.data ? ( <div>{props.errors.data}</div> ) : null} <button type="submit">Ok</button> </form> )} /> </div> );
Consejo 2. Evita la mutación
Considere una simple aplicación de lista de tareas. En el constructor, definimos en estado la variable en la que se almacenará la lista de tareas. En el método render (), muestre el formulario a través del cual agregaremos casos a la lista. Ahora considere cómo podemos cambiar de estado.
Opción incorrecta que conduce a la mutación de la matriz: this.state.data.push(item);
En este caso, la matriz realmente ha cambiado, pero React no sabe nada al respecto, lo que significa que no se llamará al método render () y nuestros cambios no se mostrarán. El hecho es que en JavaScript, al crear una nueva matriz u objeto, el enlace se guarda en la variable y no en el objeto en sí. Por lo tanto, al agregar un nuevo elemento a la matriz de datos, cambiamos la matriz en sí, pero no el enlace, lo que significa que el valor de los datos almacenados en el estado no cambiará.
Las mutaciones en JavaScript se pueden encontrar en todo momento. Para evitar mutaciones de datos, use el operador de propagación para las matrices, los métodos filter () y map (), y para los objetos, use el operador de propagación o el método asignar ().
const newData = [...data, item]; const copy = Object.assign({}, obj);
Volviendo a nuestra aplicación, vale la pena decir que la opción correcta para cambiar el estado es usar el método setState (). No intente cambiar el estado directamente en otro lugar que no sea el constructor, ya que esto contradice la ideología React.
¡No hagas eso! this.state.data = [...data, item];
También evite la mutación estatal. Incluso si usa setState (), las mutaciones pueden provocar errores al intentar optimizar. Por ejemplo, si pasa un objeto mutado a través de accesorios a un elemento secundario puro, este componente no podrá comprender que los accesorios recibidos han cambiado y no se volverán a renderizar.
¡No hagas eso! this.state.data.push(item); this.setState({ data: this.state.data });
La opción correcta: const { data } = this.state; const newData = [...data, item]; this.setState({ data: newData });
Pero incluso la opción anterior puede provocar errores sutiles. El hecho es que nadie garantiza que durante el tiempo transcurrido entre la recepción de la variable de datos del estado y la escritura de su nuevo valor en el estado, el estado en sí no cambiará. Por lo tanto, corre el riesgo de perder algunos de los cambios realizados. Por lo tanto, en el caso de que necesite actualizar el valor de una variable en estado utilizando su valor anterior, hágalo de la siguiente manera:
La opción correcta, si la siguiente condición depende de la corriente: this.setState((state) => { return {data: [...state.data, item]}; });
Consejo 3. Emular una aplicación de varias páginas
Su aplicación se está desarrollando y, en algún momento, se da cuenta de que necesita varias páginas. ¿Pero qué hacer, porque React es una aplicación de una sola página? En este punto, la siguiente idea loca puede venir a su mente. Decide que mantendrá el identificador de la página actual en el estado global de su aplicación, por ejemplo, utilizando la tienda redux. Para mostrar la página deseada, utilizará la representación condicional y cambiará de página, llamando a la acción con la carga útil deseada, cambiando así los valores en la tienda redux.
App.js import React from 'react'; import { connect } from 'react-redux'; import './App.css'; import Page1 from './Page1'; import Page2 from './Page2'; const mapStateToProps = (state) => ({ page: state.page }); function AppCon(props) { if (props.page === 'Page1') { return ( <div className="App"> <Page1 /> </div> ); } return ( <div className="App"> <Page2 /> </div> ); } const App = connect(mapStateToProps)(AppCon); export default App;
Página1.js import React from 'react'; import { connect } from 'react-redux'; import { setPage } from './redux/actions'; function mapDispatchToProps(dispatch) { return { setPageHandle: (page) => dispatch(setPage(page)), }; } function Page1Con(props) { return ( <> <h3> Page 1 </h3> <input type="button" value="Go to page2" onClick={() => props.setPageHandle('Page2')} /> </> ); } const Page1 = connect(null, mapDispatchToProps)(Page1Con); export default Page1;
¿Por qué es esto malo?
- Esta solución es un ejemplo de una bicicleta primitiva. Si sabe cómo fabricar una bicicleta de este tipo de manera competente y entiende lo que está buscando, entonces no es mi responsabilidad aconsejarle. De lo contrario, su código será implícito, confuso y demasiado complejo.
- No podrá utilizar el botón Atrás en el navegador, ya que no se guardará el historial de visitas.
¿Cómo resolver esto?
Solo usa
react-router . Este es un gran paquete que puede convertir fácilmente su aplicación en una de varias páginas.
Consejo 4. Dónde colocar solicitudes de API
En algún momento, necesitaba agregar una solicitud a una API externa en su aplicación. Y aquí se pregunta: ¿en qué parte de su aplicación necesita ejecutar la solicitud?
En este momento, al montar un componente React, su ciclo de vida es el siguiente:
- constructor ()
- getDerivedStateFromProps () estático
- render ()
- componentDidMount ()
Analicemos todas las opciones en orden.
En el método constructor (), la
documentación no recomienda hacer otra cosa que:
- Inicializando el estado interno a través de la asignación del objeto this.state.
- Enlaces de controladores de eventos a una instancia.
Las llamadas a la API no están incluidas en esta lista, así que continuemos.
El método getDerivedStateFromProps () de acuerdo con la
documentación existe para situaciones raras cuando el estado depende de cambios en los accesorios. De nuevo, no es nuestro caso.
El error más común es la ubicación del código que ejecuta las solicitudes de API en el método render (). Esto lleva al hecho de que tan pronto como su solicitud se ejecute con éxito, lo más probable es que guarde el resultado en el estado del componente, y esto conducirá a una nueva llamada al método render (), en el que su solicitud a la API se ejecutará nuevamente. Por lo tanto, su componente terminará en una representación interminable, y esto claramente no es lo que necesita.
Por lo tanto, el método componentDidMount () es el lugar ideal para acceder a la API externa.
Conclusión
Se pueden encontrar ejemplos de código en
github .