Scaling Up with Reducer and Context
Reducers একটি কম্পোনেন্টের state আপডেট লজিক সংক্ষেপণ করতে সাহায্য করে। Context আপনাকে অন্যান্য কম্পোনেন্টের গভীরে তথ্য পাঠানোর সুযোগ দেয়। আপনি reducers এবং context দুটি একসাথে সংমিলিত করে একটি জটিল স্ক্রিনের state ব্যবস্থাপনা করতে পারেন ।
যা যা আপনি শিখবেন
- কিভাবে reducer কে context এর সাথে সংযুক্ত করতে হয় ।
- কিভাবে state এবং dispatch কে props এর মাধ্যমে পাঠানো থেকে বিরত থাকা যায় ।
- কিভাবে context এবং state এর যুক্তিকে ভিন্ন ফাইলে রাখা যায় ।
Context এর সাথে reducer এর সংযুক্তি
Reducers এর সাথে পরিচিতি এই উদাহরণে, state কে reducer ব্যবস্থাপনা করেছে । Reducer ফাংশনটি সকল state হালানাগাদ যুক্তিসমূহ ধারন করে এবং একে ফাইলের একদম শেষে ডিক্লেয়ার করা হয় ।
import { useReducer } from 'react'; import AddTask from './AddTask.js'; import TaskList from './TaskList.js'; export default function TaskApp() { const [tasks, dispatch] = useReducer( tasksReducer, initialTasks ); function handleAddTask(text) { dispatch({ type: 'added', id: nextId++, text: text, }); } function handleChangeTask(task) { dispatch({ type: 'changed', task: task }); } function handleDeleteTask(taskId) { dispatch({ type: 'deleted', id: taskId }); } return ( <> <h1>Day off in Kyoto</h1> <AddTask onAddTask={handleAddTask} /> <TaskList tasks={tasks} onChangeTask={handleChangeTask} onDeleteTask={handleDeleteTask} /> </> ); } function tasksReducer(tasks, action) { switch (action.type) { case 'added': { return [...tasks, { id: action.id, text: action.text, done: false }]; } case 'changed': { return tasks.map(t => { if (t.id === action.task.id) { return action.task; } else { return t; } }); } case 'deleted': { return tasks.filter(t => t.id !== action.id); } default: { throw Error('Unknown action: ' + action.type); } } } let nextId = 3; const initialTasks = [ { id: 0, text: 'Philosopher’s Path', done: true }, { id: 1, text: 'Visit the temple', done: false }, { id: 2, text: 'Drink matcha', done: false } ];
একটি Reducer ইভেন্ট হ্যান্ডলারগুলি ছোট এবং সংক্ষিপ্ত রাখতে সাহায্য করে । তবে, আপনার অ্যাপ্লিকেশন বাড়তে শুরু করলে, আপনি আরও একটি সমস্যায় পরে যেতে পারেন । বর্তমানে, tasks
state এবং dispatch
ফাংশনটি শুধুমাত্র শীর্ষ-স্তরের TaskApp
কম্পোনেন্টে উপলব্ধ রয়েছে। অন্য কম্পোনেন্টগুলিকে টাস্কের তালিকা পড়তে অথবা তা পরিবর্তন করতে দিতে হলে, আপনাকে বর্তমান state এবং তা পরিবর্তন করার ইভেন্ট হ্যান্ডলারগুলি স্পষ্টভাবে props হিসেবে পাঠাতে হবে।
উদাহরণস্বরূপ, TaskApp
টাস্কের তালিকা এবং ইভেন্ট হ্যান্ডলারগুলি TaskList
এ পাঠিয়ে দেয়:
<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}
/>
এবং TaskList
ইভেন্ট হ্যান্ডলারগুলোকে Task
এ পাঠিয়ে দেয়ঃ
<Task
task={task}
onChange={onChangeTask}
onDelete={onDeleteTask}
/>
একটি ছোট উদাহরনে এটি ভালো কাজ করে, কিন্তু যদি এর মাঝে আপনার দশ বা শতাধিক কম্পোনেন্ট থাকে, তাহলে সকল state এবং ফাংশনগুলিকে পাঠানো অনেক বিরক্তিকর হতে পারে ।
এই কারনে, props এর মাধমে পাঠানোর বিকল্প হিসেবে, আপনি সমস্ত tasks
স্টেট এবং dispatch
ফাংশনকে context এর মধ্যে রাখতে পারেন । এইভাবে, TaskApp
এর নীচে যেকোনো কম্পোনেন্ট রুটে আপনি “prop drilling” এর পুনরাবৃত্তি ছাড়াই task পড়তে এবং একশনগুলিকে dispatch করতে পারবেন ।
যেভাবে আপনি reducer এবং context এর সংযুক্তি করতে পারেনঃ
- Context তৈরি করুন।
- state এবং dispatch কে Context এর ভেতরে রাখুন।
- Context কে যেকোনো কম্পোনেন্ট রুটে ব্যবহার করুন।
ধাপ ১: Context তৈরি করুন
useReducer
হুক আপনাকে বর্তমান tasks
এবং তা আপডেট করার জন্য dispatch
ফাংশনকে রিটার্ন করে ।
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
তাদেরকে ট্রি-এর নিচে পাঠানোর জন্য আপনি দুটি ভিন্ন context তৈরি করবেন ।
TasksContext
বর্তমান tasks তালিকা প্রদান করে ।TasksDispatchContext
একটি ফাংশন প্রদান করে যা কম্পোনেন্টগুলিকে একশনগুলি dispatch করতে দেয় ।
এদেরকে একটি আলাদা ফাইলে এক্সপোর্ট করুন যাতে আপনি পরবর্তিতে অন্য ফাইলে ইম্পোর্ট করতে পারেন:
import { createContext } from 'react'; export const TasksContext = createContext(null); export const TasksDispatchContext = createContext(null);
এখানে আপনি null
কে ডিফল্ট মান হিসেবে দুটি context এ পাঠাচ্ছেন । আসল মানগুলি TaskApp
এর মাধ্যমে সরাসরি প্রদান হবে ।
ধাপ ২: State এবং dispatch কে context এর ভেতরে রাখুন
এখন আপনি দুটো context কে TaskApp
কম্পোনেন্টে ইম্পোর্ট করতে পারেন । useReducer()
এর রিটার্ন করা tasks
এবং dispatch
কে গ্রহণ করুন এবং এদেরকে নিচের সম্পূর্ন ট্রিতে প্রদান করুন:
import { TasksContext, TasksDispatchContext } from './TasksContext.js';
export default function TaskApp() {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
// ...
return (
<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider value={dispatch}>
...
</TasksDispatchContext.Provider>
</TasksContext.Provider>
);
}
এখন, আপনি তথ্যকে props এবং context উভয়ের মাধ্যমে পাঠাতে পারবেনঃ
import { useReducer } from 'react'; import AddTask from './AddTask.js'; import TaskList from './TaskList.js'; import { TasksContext, TasksDispatchContext } from './TasksContext.js'; export default function TaskApp() { const [tasks, dispatch] = useReducer( tasksReducer, initialTasks ); function handleAddTask(text) { dispatch({ type: 'added', id: nextId++, text: text, }); } function handleChangeTask(task) { dispatch({ type: 'changed', task: task }); } function handleDeleteTask(taskId) { dispatch({ type: 'deleted', id: taskId }); } return ( <TasksContext.Provider value={tasks}> <TasksDispatchContext.Provider value={dispatch}> <h1>Day off in Kyoto</h1> <AddTask onAddTask={handleAddTask} /> <TaskList tasks={tasks} onChangeTask={handleChangeTask} onDeleteTask={handleDeleteTask} /> </TasksDispatchContext.Provider> </TasksContext.Provider> ); } function tasksReducer(tasks, action) { switch (action.type) { case 'added': { return [...tasks, { id: action.id, text: action.text, done: false }]; } case 'changed': { return tasks.map(t => { if (t.id === action.task.id) { return action.task; } else { return t; } }); } case 'deleted': { return tasks.filter(t => t.id !== action.id); } default: { throw Error('Unknown action: ' + action.type); } } } let nextId = 3; const initialTasks = [ { id: 0, text: 'Philosopher’s Path', done: true }, { id: 1, text: 'Visit the temple', done: false }, { id: 2, text: 'Drink matcha', done: false } ];
পরবর্তী ধাপে, আপনি prop পাঠানো মুছে ফেলবেন ।
ধাপ ৩: ট্রি এর যেকোনো জায়গায় context ব্যবহার করুন
এখন আপনাকে আর task এর তালিকা অথবা event handlers কে ট্রি এর নিচে পাঠাতে হবেনা :
<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider value={dispatch}>
<h1>Day off in Kyoto</h1>
<AddTask />
<TaskList />
</TasksDispatchContext.Provider>
</TasksContext.Provider>
এর পরিবর্তে যেকোনো কম্পোনেন্ট যার task তালিকা দরকার হবে সে তা TaskContext
থেকে পড়তে পারবে ।
export default function TaskList() {
const tasks = useContext(TasksContext);
// ...
Task তালিকা হালনাগাদ করার জন্য যেকোনো কম্পোনেন্ট dispatch
ফাংশনকে context থেকে পড়তে পারেন এবং call করতে পারেন ।
export default function AddTask() {
const [text, setText] = useState('');
const dispatch = useContext(TasksDispatchContext);
// ...
return (
// ...
<button onClick={() => {
setText('');
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}}>Add</button>
// ...
TaskApp
কম্পোনেন্ট কোনো event handlers কে নিচে পাঠায় না এবং TaskList
কোনো event handlers কে Task
কম্পোনেন্টেও পাঠায় না । প্রতিটা কম্পোনেন্ট তার প্রয়োজনীয় context কে পড়েঃ
import { useState, useContext } from 'react'; import { TasksContext, TasksDispatchContext } from './TasksContext.js'; export default function TaskList() { const tasks = useContext(TasksContext); return ( <ul> {tasks.map(task => ( <li key={task.id}> <Task task={task} /> </li> ))} </ul> ); } function Task({ task }) { const [isEditing, setIsEditing] = useState(false); const dispatch = useContext(TasksDispatchContext); let taskContent; if (isEditing) { taskContent = ( <> <input value={task.text} onChange={e => { dispatch({ type: 'changed', task: { ...task, text: e.target.value } }); }} /> <button onClick={() => setIsEditing(false)}> Save </button> </> ); } else { taskContent = ( <> {task.text} <button onClick={() => setIsEditing(true)}> Edit </button> </> ); } return ( <label> <input type="checkbox" checked={task.done} onChange={e => { dispatch({ type: 'changed', task: { ...task, done: e.target.checked } }); }} /> {taskContent} <button onClick={() => { dispatch({ type: 'deleted', id: task.id }); }}> Delete </button> </label> ); }
State টি এখনো টপ-লেভেল TaskApp
কম্পোনেন্টেই অবস্থান করছে, useReducer
এর ব্যবস্থাপনায়। কিন্তু এর tasks
এবং dispatch
এখন ট্রিয়ের নিচের প্রতিটি কম্পোনেন্ট পাওয়া যাবে ইম্পোর্টিং এবং এই context গুলিকে ব্যবহারের মাধ্যমে।
সকল সংযোগসমূহকে একটি ফাইলে সরানো
আপনার এটি করার দরকার নেই, কিন্তু আপনি কম্পোনেন্টগুলিকে আরো সাজানোর জন্য reducer এবং context উভয়কেই একটি ফাইলে সরিয়ে নিতে পারেন। বর্তমানে, TaskContext.js
এ কেবল দুটি context ডিক্লেয়ারেশন রয়েছেঃ
import { createContext } from 'react';
export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);
এই ফাইলটিতে এখন জটলা বেঁধে যাবে! আপনি reducer কে একই ফাইলে সরাবেন। এরপর আপনি একটি নতুন TaskProvider
কম্পোনেন্ট একই ফাইলে ডিক্লেয়ার করবেন। এই কম্পোনেন্ট সকল অংশকে একীভূত করবে।
- এটি state কে reducer দিয়ে পরিচালনা করবে ।
- এটি উভয় context কে নিচের কম্পোনেন্টে পাঠাবে ।
- এটি
children
কে prop হিসেবে নেয় যাতে আপনি এতে JSX পাঠাতে পারেন।
export function TasksProvider({ children }) {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
return (
<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider value={dispatch}>
{children}
</TasksDispatchContext.Provider>
</TasksContext.Provider>
);
}
এটি আপনার TaskApp
থেকে সকল জটিলতা এবং সংযোগকে সরিয়ে দেয় :
import AddTask from './AddTask.js'; import TaskList from './TaskList.js'; import { TasksProvider } from './TasksContext.js'; export default function TaskApp() { return ( <TasksProvider> <h1>Day off in Kyoto</h1> <AddTask /> <TaskList /> </TasksProvider> ); }
আপনি ফাংশনগুলি এক্সপোর্টও করতে পারেন যেটা TasksContext.js
এর context কে ব্যবহার করেন ঃ
export function useTasks() {
return useContext(TasksContext);
}
export function useTasksDispatch() {
return useContext(TasksDispatchContext);
}
যখন একটি কম্পোনেন্ট এর context পড়ার প্রয়োজন হয়, এটি ফাংশনের মাধ্যমে তা করতে পারে ঃ
const tasks = useTasks();
const dispatch = useTasksDispatch();
এটি আচরণকে কোনোভাবেই পরিবর্তন করেনা, কিন্তু এটি আপনাকে পরবর্তীতে এই context গুলিকে ভাগ করতে দেয় অথবা কিছু যুক্তি যোগ করতে দেয় এই ফাংশগুলিতে। এখন সকল context এবং reducer সংযোগসমূহ TasksContext.js
এ আছে। এটি কম্পোনেন্টগুলিকে পরিচ্ছন্ন এবং গোছানো রাখে,কোথায় থেকে ডেটা পাচ্ছে তা নয় বরং তারা কি প্রদর্শন করে তাতে মনোযোগ দেয়ঃ
import { useState } from 'react'; import { useTasks, useTasksDispatch } from './TasksContext.js'; export default function TaskList() { const tasks = useTasks(); return ( <ul> {tasks.map(task => ( <li key={task.id}> <Task task={task} /> </li> ))} </ul> ); } function Task({ task }) { const [isEditing, setIsEditing] = useState(false); const dispatch = useTasksDispatch(); let taskContent; if (isEditing) { taskContent = ( <> <input value={task.text} onChange={e => { dispatch({ type: 'changed', task: { ...task, text: e.target.value } }); }} /> <button onClick={() => setIsEditing(false)}> Save </button> </> ); } else { taskContent = ( <> {task.text} <button onClick={() => setIsEditing(true)}> Edit </button> </> ); } return ( <label> <input type="checkbox" checked={task.done} onChange={e => { dispatch({ type: 'changed', task: { ...task, done: e.target.checked } }); }} /> {taskContent} <button onClick={() => { dispatch({ type: 'deleted', id: task.id }); }}> Delete </button> </label> ); }
আপনি TasksProvider
কে স্ক্রীন এর একটি অংশ হিসেবে চিন্তা করতে পারেন যে জানে কিভাবে tasks এর সাথে আচরন করতে হয়, useTasks
এদেরকে পড়ার একটি উপায় এবং useDispatch
তাদেরকে ট্রি এর নিচের যেকোন কম্পোনেন্ট থেকে আপডেট করার একটি উপায়।
যখন আপনার অ্যাপ্লিকেশন বাড়তে থাকে, আপনার এরকম অনেক context-reducer জোড়া থাকতে পারে। এটি আপনার অ্যাপ্লিকেশন স্কেল করার একটি শক্তিশালী উপায় এবং আপনি যখনই ট্রির গভীরে ডেটা অ্যাক্সেস করতে চান তখন অধিক কাজ না করেই স্টেট উঠাতে পারে।
পুনরালোচনা
-
আপনি Reducer সঙ্গে context যোগ করে যেকোনো কোম্পোনেন্টকে এর উপরের state পড়তে এবং আপডেট করতে দিতে পারেন।
-
state এবং dispatch ফাংশনকে নীচের কোম্পোনেন্টগুলিকে প্রদান করতে:
- দুটি কনটেক্সট তৈরি করুন (state এবং dispatch ফাংশনের জন্য)।
- যে কোম্পোনেন্ট রিডিউসারটি ব্যবহার করে, তার থেকে উভয় কনটেক্সট প্রদান করুন।
- যে কোম্পোনেন্ট এর তাদের পড়ার দরকার সেগুলি থেকে যেকোনো কনটেক্সট ব্যবহার করুন।
-
আপনি কম্পোনেন্টগুলিকে আরো গোছাতে পারেন এদেরকে একটি ফাইলে সরিয়ে ফেলার মাধ্যমে।
- আপনি
TasksProvider
এর মতো কম্পোনেন্টকে এক্সপোর্ট করতে পারেন যারা context প্রদান করেন। - আপনি
useTasks
এবংuseTasksDispatch
এর মতো কাস্টম হুকগিলে এক্সপোর্ট করতে পারেন পড়ার জন্য। - আপনি অনেকগুলি context-reducer জোড়া রাখতে পারেন আপনার app এ।
- আপনি