Meningkatkan useReducer

Dengan munculnya useReducerdan useContextpengelolaan status aplikasi, ini menjadi jauh lebih nyaman, dan kebutuhan untuk menggunakan Redux telah menghilang.



Ketika saya meninggalkan Redux untuk pertama kalinya dan memilih yang standar, useReducersaya merasa kekurangan beberapa fitur yang berguna:



  • useSelector . Memungkinkan Anda untuk mengoptimalkan rendering komponen yang digunakan useContextdengan memo.
  • Satu-satunya pengiriman . Menyederhanakan pembaruan status aplikasi karena Anda tidak perlu menggunakan pengiriman terpisah untuk masing-masing useReducer.
  • Cache . Anda tidak perlu khawatir tentang menyimpan semua orang ke cache useReducer.


Kemudian saya memutuskan untuk mencoba meningkatkan standar useReducerdengan menambahkan 3 fungsi ini. Ide ini telah berkembang menjadi perpustakaan kecil baru yang saya sebut Flex Reducer .



Fakta yang menarik!



Flex Reducer tidak digunakan dalam useReducersetiap useContextpelaksanaannya.



Mari kita lihat pro dan kontra penggunaan Standar useReducer+ useContextdan Peredam Flex menggunakan Aplikasi Todo yang khas sebagai contoh.



Pertama, mari buat file utama tempat pohon React dirender.



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

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


Catatan : Tidak ada lagi gabungkan reduksi, buat penyimpanan dan Penyedia. Hore! :)



Sekarang, mari kita tulis komponen utama Aplikasi Todo menggunakan 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>
  );
}


Kelihatan bagus. Sekarang hal yang sama tetapi menggunakan Peredam Flex.



// 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.





Menggunakan useReducer+ useContextuntuk mengelola status aplikasi cukup nyaman. Tetapi ini membutuhkan pekerjaan "manual" yang cermat dengan konteks dan cache.

Flex Reducer menangani ini, meningkatkan keterbacaan kode, membuatnya lebih mudah untuk menulis pengoptimalan, memodan mengurangi jumlah kapan. Tapi masalah muncul saat bekerja dengan data jarak jauh secara deklaratif (seperti dengan react-query).



Perhatian!



Peredam Flex hanyalah sebuah eksperimen dan belum digunakan dalam produksi.



Terima kasih sudah membaca. Pikiran apa pun dihargai.



Contoh yang berfungsi dari aplikasi Todo dapat ditemukan di sini .

Tautkan ke gudang Flex Reducer




All Articles