Mejora del uso Reductor

Con el advenimiento useReducery la useContextadministración del estado de la aplicación, se ha vuelto mucho más conveniente y ha desaparecido la necesidad de usar Redux.



Cuando abandoné Redux por primera vez a favor del estándar, useReducersentí la falta de algunas características útiles:



  • useSelector . Le permite optimizar la representación de los componentes que se utilizan useContextcon memo.
  • El único despacho . Simplifica la actualización del estado de la aplicación, ya que no es necesario utilizar un envío independiente para cada uno useReducer.
  • Caché . No tiene que preocuparse por almacenar en caché a todos useReducer.


Entonces decidí intentar mejorar el estándar useReduceragregando estas 3 funciones. Esta idea se ha convertido en una nueva biblioteca pequeña que llamo Flex Reducer .



¡Dato interesante!



Flex Reducer no se utiliza en useReducerninguna useContextde sus implementaciones.



Veamos los pros y los contras de usar el estándar useReducer+ useContexty Flex Reducer usando una aplicación Todo típica como ejemplo.



Primero, creemos un archivo principal donde se renderiza el árbol de React.



// index.js
import TodoApp from "./TodoApp";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <TodoApp />,
  rootElement
);


Nota : No más reductores combinados, cree tienda y proveedor. ¡Hurra! :)



Ahora, escriba el componente principal de la aplicación Todo usando useReducer.



// TodoApp.js
import { useReducer, createContext, useMemo } from 'react';
import AddTodo from './AddTodo';
import TodoList from './TodoList';

export const AppContext = createContext(null);
const cache = {};

export default function TodoApp() {
  const [state, dispatch] = useReducer(reducer, cache.state || initialState);
  cache.state = state;
  const actions = useMemo(() => ({
    setInput: (value) => {
      dispatch({
        type: 'SET_INPUT', 
        payload: value
      })
    },
    addTodo: ({ id, content }) => {
      dispatch({
        type: 'ADD_TODO',
        payload: { id, content }
      })
    }
  }), []);
  return (
    <AppContext.Provider value=[state, actions]>
      <div className="todo-app">
        <h1>{state.title}</h1>
        <input value={state.input} onChange={e => actions.setInput(e.target.value)} />
        <AddTodo />
        <TodoList />
      </div>
    </AppContext>
  );
}


Se ve bien. Ahora lo mismo pero usando Flex Reducer.



// TodoApp.js
import { useFlexReducer, dispatch } from 'flex-reducer';
import AddTodo from './AddTodo';
import TodoList from './TodoList';

export const setInput = (value) => dispatch({
  type: SET_INPUT,
  payload: value
});
export const addTodo = ({ id, content }) => dispatch({
  type: ADD_TODO,
  payload: { id, content }
});

export default function TodoApp() {
  const [state] = useFlexReducer('app', reducer, initialState);
  return (
    <div className="todo-app">
      <h1>{state.title}</h1>
      <input value={state.input} onChange={e => setInput(e.target.value)} />
      <AddTodo />
      <TodoList />
    </div>
  );
}


, .

:



  • React Context.
  • .
  • actions dispatch.


re-renders Add Todo button.

.



// AddTodo.js
import { useContext, memo } from 'react';
import { appContext } from './TodoApp';

const genId = () => Math.rand();

const AddTodo = memo(({ input, actions }) => {
  function handleAddTodo() {
    if (content) {
      actions.addTodo({ id: genId(), content: input });
      actions.setInput('');
    }
  }
  return (
    <button onClick={handleAddTodo}>
      Add Todo
    </button>
  );
})

export default const MemoizedAddTodo = () => {
  const [state, actions] = useContext(appContext);
  return (
    <AddTodo input={state.input} actions={actions} />
  );
}


useContext AddTodo render memo. -, useContext props.



Flex Reducer.



// AddTodo.js
import { useSelector } from 'flex-reducer';
import { addTodo, setInput } from "./TodoApp";

const genId = () => Math.rand();

export default const AddTodo = React.memo(() => {
  const content = useSelector(state => state.app.input);
  function handleAddTodo() {
    if (content) {
      addTodo({ id: genId(), content });
      setInput('');
    }
  }
  return (
    <button onClick={handleAddTodo}>
      Add Todo
    </button>
  );
})


. -. useSelector, re-render input.



, , Flex Reducer .

remote data, , react-query.



useReducer.



// TodoApp.js
import { useReducer, createContext, useMemo } from 'react';
import { useQuery } from 'react-query';
import AddTodo from './AddTodo';
import TodoList from './TodoList';

export const AppContext = createContext(null);
const cache = {};

export default function TodoApp() {
  const [reducerState, dispatch] = useReducer(reducer, cache.state || initialState);
  cache.state = reducerState;
  const actions = useMemo(() => ({
    setInput: (value) => {
      dispatch({
        type: 'SET_INPUT', 
        payload: value
      })
    },
    addTodo: ({ id, content }) => {
      dispatch({
        type: 'ADD_TODO',
        payload: { id, content }
      })
    }
  }), []);

  const todos = useQuery('todos', fetchTodoList);
  const state = { ...reducerState, todos };

  return (
    <AppContext.Provider value=[state, actions]>
      <div className="todo-app">
        <h1>{state.title}</h1>
        <input value={state.input} onChange={e => actions.setInput(e.target.value)} />
        <AddTodo />
        <TodoList />
      </div>
    </AppContext>
  );
}


. .



Flex Reducer.



// TodoApp.js
import { useFlexReducer, dispatch } from 'flex-reducer';
import { useQuery } from 'react-query';
import AddTodo from './AddTodo';
import TodoList from './TodoList';

export const setInput = (value) => dispatch({
  type: SET_INPUT,
  payload: value
});
export const addTodo = ({ id, content }) => dispatch({
  type: ADD_TODO,
  payload: { id, content }
});
export const setTodos = (todos) => dispatch({
  type: SET_TODOS,
  payload: todos
});

export default function TodoApp() {
  const [state] = useFlexReducer('app', reducer, initialState);
  const todos = useQuery('todos', fetchTodoList);
  React.useEffect(() => {
    setTodos(todos);
  }, [todos]);

  return (
    <div className="todo-app">
      <h1>{state.title}</h1>
      <input value={state.input} onChange={e => setInput(e.target.value)} />
      <AddTodo />
      <TodoList />
    </div>
  );
}


todos query.





Usar useReducer+ useContextpara administrar el estado de la aplicación es bastante conveniente. Pero esto requiere un trabajo "manual" cuidadoso con el contexto y la caché.

Flex Reducer se encarga de esto, mejora la legibilidad del código, facilita la escritura de optimizaciones memoy reduce la cantidad de cuándo. Pero aparecen problemas cuando se trabaja con datos remotos de forma declarativa (como con react-query).



¡Atención!



Flex Reducer es solo un experimento y no se ha utilizado en producción.



Gracias por leer. Se agradece cualquier pensamiento.



Aquí puede encontrar un ejemplo funcional de la aplicación Todo .

Enlace al repositorio de Flex Reducer




All Articles