
Buen día, Khabrovsk!
Quiero hablar sobre cómo recientemente aprendí sobre ciertos "ganchos" en React. Aparecieron relativamente recientemente, en la versión [16.8.0] del 6 de febrero de 2019 (que, según las velocidades de desarrollo de FrontEnd, ha sido hace mucho tiempo)
Después de leer la documentación, me concentré en el gancho useReducer e inmediatamente me hice la pregunta: "¿Esto puede reemplazar completamente a Redux?" Pasé varias tardes en experimentos y ahora quiero compartir los resultados y mis conclusiones.
¿Necesito reemplazar Redux con useContext + useReducer?
Para los impacientes: conclusiones inmediatas
Para:
- Puede usar ganchos (useContext + useReducer) en lugar de Redux en aplicaciones pequeñas (donde no hay necesidad de grandes reductores combinados). En este caso, Redux puede ser redundante.
En contra:
- Ya se ha escrito una gran cantidad de código en un grupo de React + Redux, y reescribirlo para ganchos (useContext + useReducer) me parece inapropiado, al menos por ahora.
- Redux es una biblioteca probada, los ganchos son una innovación, sus interfaces y comportamiento pueden cambiar en el futuro.
- Para que el uso de useContext + useReducer sea realmente conveniente, tendrá que escribir algunas bicicletas.
Las conclusiones son la opinión personal del autor y no pretenden ser una verdad incondicional; si no está de acuerdo, me complacerá ver su crítica constructiva en los comentarios.
Tratemos de resolverlo
Comencemos con un ejemplo simple.
(reducer.js)
import React from "react"; export const ContextApp = React.createContext(); export const initialState = { app: { test: 'test_context' } }; export const testReducer = (state, action) => { switch(action.type) { case 'test_update': return { ...state, ...action.payload }; default: return state } };
Hasta ahora, nuestro reductor se ve exactamente igual que en Redux
(app.js)
import React, {useReducer} from 'react' import {ContextApp, initialState, testReducer} from "./reducer.js"; import {IndexComponent} from "./IndexComponent.js" export const App = () => {
(IndexComponent.js)
import React, {useContext} from "react"; import {ContextApp} from "./reducer.js"; export function IndexComponent() {
Este es el ejemplo más simple en el que simplemente actualizar escribir datos nuevos en un reductor plano (sin anidamiento)
En teoría, incluso puedes intentar escribir así:
(reducer.js)
... export const testReducer = (state, data) => { return { ...state, ...data } ...
(IndexComponent.js)
... return (
Si no tenemos una aplicación grande y simple (que rara vez es el caso en realidad), entonces no puede usar el tipo y siempre administrar la actualización del reductor directamente desde la acción. Por cierto, a expensas de las actualizaciones, en este caso solo escribimos nuevos datos en reductor, pero ¿qué pasa si tenemos que cambiar un valor en un árbol con varios niveles de anidamiento?
Más complicado ahora
Veamos el siguiente ejemplo:
(IndexComponent.js)
... return (
(reducer.js)
... export const initialState = { tree_1: { tree_2_1: { tree_3_1: 'tree_3_1', tree_3_2: 'tree_3_2' }, tree_2_2: { tree_3_3: 'tree_3_3', tree_3_4: 'tree_3_4' } } }; export const testReducer = (state, callback) => {
De acuerdo, también descubrimos la actualización del árbol. Aunque en este caso ya es mejor volver a usar los tipos dentro de testReducer y actualizar el árbol de acuerdo con cierto tipo de acción. Todo es como en Redux, solo el paquete resultante es ligeramente más pequeño [8].
Operaciones asincrónicas y despacho
¿Pero está todo bien? ¿Qué sucede si vamos a usar operaciones asincrónicas?
Para hacer esto, tenemos que definir nuestro propio despacho. ¡Intentémoslo!
(action.js)
export const actions = { sendToServer: function ({dataForServer}) {
(IndexComponent.js)
const [state, _dispatch] = useReducer(AppReducer, AppInitialState);
Todo parece estar bien también, pero ahora tenemos un montón de anidamiento de devolución de llamada , lo que no es muy bueno, si solo queremos cambiar el estado sin crear una función de acción, tendremos que escribir una construcción de este tipo:
(IndexComponent.js)
... dispatch( (dispatch) => dispatch(state => { return { {dataForServer: 'data'} } }) ) ...
Resulta algo aterrador, ¿verdad? Para una simple actualización de datos, realmente me gustaría escribir algo como esto:
(IndexComponent.js)
... dispatch({dataForServer: 'data'}) ...
Para hacer esto, debe cambiar el Proxy para la función de envío que creamos anteriormente
(IndexComponent.js)
const [state, _dispatch] = useReducer(AppReducer, AppInitialState);
Ahora podemos pasar tanto una función de acción como un objeto simple para despachar.
Pero! Con una simple transferencia del objeto, debe tener cuidado, puede sentirse tentado a hacer esto:
(IndexComponent.js)
... dispatch({ tree: {
¿Por qué es malo este ejemplo? Por el hecho de que en el momento en que se procesó este envío, el estado podría haberse actualizado a través de otro envío, pero estos cambios aún no han llegado a nuestro componente, y de hecho estamos utilizando una instancia de estado anterior que sobrescribirá todo con datos antiguos.
Por esta razón, dicho método apenas vale la pena, solo para actualizar reductores planos en los que no hay anidamiento y no es necesario contactar el estado para actualizar los objetos anidados. En realidad, los reductores rara vez son perfectamente planos, por lo que le aconsejaría que no utilice este método y que solo actualice los datos a través de acciones.
(action.js)
...
Conclusiones:
- Fue una experiencia interesante, fortalecí mi conocimiento académico y aprendí nuevas características de la reacción.
- No utilizaré este enfoque en la producción (al menos en los próximos seis meses). Por las razones ya descritas anteriormente (esta es una característica nueva, y Redux es una herramienta probada y confiable) + No tengo problemas de rendimiento que perseguir después de los milisegundos que puedes ganar al abandonar el editor [8]
¡Me alegrará saber, en los comentarios, la opinión de colegas de la parte delantera de nuestra Habrosobschestva!
Referencias