مع ظهور useReducer
و useContext
إدارة الدولة التطبيق أصبح أكثر ملاءمة، واختفت الحاجة لاستخدام مسترجع.
عندما تخلت عن Redux لأول مرة لصالح المعيار القياسي ، useReducer
شعرت بنقص في بعض الميزات المفيدة:
- useSelector . يسمح لك بتحسين عرض المكونات المستخدمة
useContext
معmemo
. - إيفاد الوحيد . يبسط تحديث حالة التطبيق نظرًا لأنك لست بحاجة إلى استخدام إرسال منفصل لكل منها
useReducer
. - مخبأ . لا داعي للقلق بشأن التخزين المؤقت للجميع
useReducer
.
ثم قررت محاولة تحسين المعيار useReducer
بإضافة هذه الوظائف الثلاث. تطورت هذه الفكرة إلى مكتبة صغيرة جديدة أسميها Flex Reducer .
حقيقة مثيرة للاهتمام!
لا يتم استخدام Flex Reducer في useReducer
أي useContext
من تنفيذه.
دعونا نرى إيجابيات وسلبيات استخدام معيار useReducer
+ useContext
و Flex Reducer باستخدام تطبيق Todo النموذجي كمثال.
أولاً ، لنقم بإنشاء ملف رئيسي حيث يتم تقديم شجرة React.
// index.js
import TodoApp from "./TodoApp";
const rootElement = document.getElementById("root");
ReactDOM.render(
<TodoApp />,
rootElement
);
ملاحظة : لا مزيد من الجمع بين المخفضات وإنشاء المتجر والموفر. الصيحة! :)
الآن ، دعنا نكتب مكون تطبيق Todo الرئيسي باستخدام 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>
);
}
تبدو جيدا. الآن نفس الشيء ولكن باستخدام 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.
يعد استخدام useReducer
+ useContext
لإدارة حالة التطبيق مناسبًا جدًا. لكن هذا يتطلب العمل "اليدوي" الدقيق مع السياق وذاكرة التخزين المؤقت. يعتني
Flex Reducer بذلك ، ويحسن إمكانية قراءة الكود ، ويسهل كتابة التحسينات منه ، memo
ويقلل من مقدار الوقت. لكن تظهر المشاكل عند العمل مع البيانات البعيدة بشكل إعلاني (كما هو الحال مع رد الفعل-الاستعلام).
انتباه!
Flex Reducer هي مجرد تجربة ولم يتم استخدامها في الإنتاج.
شكرا للقراءة. هي موضع تقدير أي أفكار.
يمكن العثور على مثال عملي لتطبيق Todo هنا .
رابط إلى مستودع Flex Reducer