Amélioration de l'utilisation

Avec l'avènement useReduceret la useContextgestion de l'état des applications, il est devenu beaucoup plus pratique et le besoin d'utiliser Redux a disparu.



Lorsque j'ai abandonné Redux pour la première fois au profit du standard, useReducerj'ai ressenti un manque de fonctionnalités utiles:



  • useSelector . Vous permet d'optimiser le rendu des composants utilisés useContextavec memo.
  • La seule expédition . Simplifie la mise à jour de l'état de l'application car vous n'avez pas besoin d'utiliser une distribution distincte pour chacune useReducer.
  • Cache . Vous n'avez pas à vous soucier de la mise en cache de tout le monde useReducer.


Puis j'ai décidé d'essayer d'améliorer le standard useReduceren ajoutant ces 3 fonctions. Cette idée a évolué vers une nouvelle petite bibliothèque que j'appelle Flex Reducer .



Fait intéressant!



Flex Reducer n'est utilisé dans useReduceraucune useContextde ses implémentations.



Voyons les avantages et les inconvénients de l'utilisation du standard useReducer+ useContextet du Flex Reducer en utilisant une application Todo typique à titre d'exemple.



Tout d'abord, créons un fichier principal dans lequel l'arborescence React est rendue.



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

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


Remarque : Plus besoin de combiner les réducteurs, de créer un magasin et un fournisseur. Hourra! :)



Maintenant, écrivons le composant principal de l'application Todo en utilisant 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>
  );
}


Cela semble bon. Maintenant la même chose mais en utilisant 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.





Utiliser useReducer+ useContextpour gérer l'état de l'application est assez pratique. Mais cela nécessite un travail «manuel» soigneux avec le contexte et le cache.

Flex Reducer s'occupe de cela, améliore la lisibilité du code, facilite l'écriture des optimisations memoet réduit la durée. Mais des problèmes apparaissent lors de l'utilisation déclarative de données distantes (comme avec react-query).



Attention!



Flex Reducer n'est qu'une expérience et n'a pas été utilisé en production.



Merci d'avoir lu. Toutes les pensées sont appréciées.



Un exemple fonctionnel de l'application Todo peut être trouvé ici .

Lien vers le référentiel Flex Reducer




All Articles