הגדלה עם מפחית ו-הקשר

מפחית מאפשרים לך לאחד את לוגיקת עדכון הstate של רכיב. ההקשר האפשרי להעביר מידע אחר למטה לרכיבים. אתה יכול להפחית את השלב והקשר יחד כדי לנהל מצב של כמות מורכבת.

You will learn

  • איך מפחית לשלב עם הקשר
  • איך מעבר state ומשלוח דרך props
  • איך לשמור על הקשר והיגיון מצב בקובץ נפרד

שילוב של מפחית עם הקשר

בדוגמה זו מהמבוא למפחיתים, הstate מנוהל על ידי מפחית. פונקציית המפחית מכילה את כל הלוגיקה של עדכון ה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 עוזר לשמור על מטפלים קצרים ומציתיים. עם זאת, ככל שהאפליקציה שלך תגדל, אתה יכול להיתקל בקושי נוסף. כרגע, מצב ‘משימות’ ופונקציית ‘שיגור’ זמינים רק ברכיב ‘TaskApp’ ברמה העליונה. כדי לאפשר לרכיבים אחרים לקרוא את רשימת המשימות או לשנות אותה, עליך במפורש עבירה למטה

לדוגמה, TaskApp מעביר רשימה של משימות ורופאים לTaskList:

<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}
/>

ו-‘TaskList’ מעביר את המטפלים ל-‘Task’:

<Task
task={task}
onChange={onChangeTask}
onDelete={onDeleteTask}
/>

בדוגמה קטנה כמו זו, זה עובד היטב, אבל אם יש לך כמה מאות רכיבים באמצע, העברת כל הstates והפונקציות יכולות להיות די מתסכל!

ללא תקווה, כחלופה להעברתם דרך props, אולי תרצה להכניס גם את המצב משימות וגם את הפונקציית שיגור להקשר. **בדרך זו, כל רכיב מתחת ל-TaskApp באפשרות לקרוא את “המשימות של פעולות”.

הנה איך אתה יכול לשלב מפחית עם הקשר:

  1. צור את ההקשר.
  2. הצב מצב ושליחה בהקשר.
  3. השתמש בהקשר בכל מקום בעץ.

שלב 1: צור את ההקשר

ה- ‘useReducer’ Hook מחזיר את ה’משימות’ הנוכחיות ואת הפונקציה ‘שיגור’ המאפשרת לך לעדכן:

const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);

כדי להעביר אותם בעץ, תיצור שני הקשרים נפרדים:

  • TasksContext מספק את רשימת המשימות הנוכחית.
  • TasksDispatchContext מספק את הפונקציה המאפשרת לרכיבים לשלוח פעולות.

ייצא אותם מקובץ נפרד כדי שתוכל לייבא אותם מאוחר יותר מקבצים אחרים:

import { createContext } from 'react';

export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);

כאן, אתה מעביר את ‘null’ כערך ברירת המחדל לשני ההקשרים. הערכים בפועל יסופקו על ידי רכיב ‘TaskApp’.

שלב 2: הכנס מצב ושליחה להקשר

עכשיו אתה יכול לייבא את שני ההקשרים ברכיב ‘TaskApp’ שלך. קח את ה’משימות’ וה’שליחות’ שהוא חזרו על ידי ‘useReducer()’ ו-ספק אותם לעץ המלא למטה:

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 והן בהקשר:

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 }
];

בהבא, תסיר את העברת הprops.

שלב 3: השתמש בהקשר בכל מקום בעץ

כעת אינך צריך להעביר את רשימת המשימות או את מטפלי האירועים בעץ:

<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider value={dispatch}>
<h1>Day off in Kyoto</h1>
<AddTask />
<TaskList />
</TasksDispatchContext.Provider>
</TasksContext.Provider>

במקום יכול זאת, כל רכיב שזקוק לרשימת המשימות לקרוא אותה מתוך ה-TaskContext:

export default function TaskList() {
const tasks = useContext(TasksContext);
// ...

כדי לעדכן את רשימת המשימות, כל רכיב יכול לקרוא את פונקציית ‘שיגור’ מהקשר ולקרוא לה:

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 אינו מעביר אף מטפל באירועים, ו-TaskList אינו מעביר אף מטפל באירועים לרכיב Task. כל רכיב קורא את ההקשר שהוא צריך:

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. אבל משימות ומשלוח שלו זמינים כעת לכל רכיב למטה בעץ על ידי ייבוא ​​ושימוש בהקשרים אלו.

העברת כל החיווט לקובץ בודד

אתה לא חייב לעשות זאת, אבל אתה יכול עוד יותר לבטל את הרכיבים על ידי העברת המפחית והקשר לקובץ בודד. נכון לעכשיו, TasksContext.js מכיל רק שתי הצהרות הקשר:

import { createContext } from 'react';

export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);

הקובץ הזה עומד להיות צפוף! אתה תעביר את המפחית לאותו קובץ. לאחר תכריז על רכיב TasksProvider חדש באותו קובץ. רכיב זה יקשר את כל החלקים יחד:

  1. היא תנהל את הstate עם מפחית.
  2. זה יספק את שני ההקשרים לרכיבים למטה.
  3. זה ייקח ילדים כprops כך אפשר להעביר אליו 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:

export function useTasks() {
return useContext(TasksContext);
}

export function useTasksDispatch() {
return useContext(TasksDispatchContext);
}

כאשר רכיב צריך לקרוא את ההקשר, הוא יכול לעשות זאת באמצעות הפונקציות הבאות:

const tasks = useTasks();
const dispatch = useTasksDispatch();

זה לא משנה את ההתנהגות בשום צורה, אבל זה יכול לך מאוחר יותר לפצל את ההקשרים האלה או להוסיף קצת היגיון לפונקציות האלה. עכשיו כל ההקשר והחייווט נמצא ב-‘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’ כחלק מהמסך שיודע להתמודד עם משימות, על ‘useTasks’ כדרך לקרוא, ועל ‘useTasksDispatch’ כדרך לעדכן אותם מכל רכיב למטה בעץ.

Note

פונקציות כמו useTasks ו-useTasksDispatch נקראות Custom Hooks. הפונקציה שלך נחשבת Hook מותאמת אישית השם שלה מתחיל ב-use. זה יכול לך להשתמש ב-Hooks אחרים, כמו ‘useContext’, בתוכו.

ככל שהאפליקציה שלך תגדל, אפשר שיהיו לך הרבה זוגות צמצום הקשר כמו זה. זו דרך רבת עוצמתית להרחיב את קנה המידה של האפליקציה שלך והרם את state למעלה ללא יותר מדי עבודה בכל פעם שאתה רוצה לגשת לנתונים עמוק בעץ.

Recap

  • אתה יכול לשלב מפחית עם הקשר כדי לאפשר לכל רכיב לקרוא ולעדכן מצב מעליו.
  • כדי לספק מצב ופונקציית השיגור לרכיבים הבאים:
    1. צור שני הקשרים (עבור מצב ופונקציות שיגור).
  1. ספק את שני ההקשרים מהרכיב שמשתמש ב-reducer.
  2. השתמש בכל אחד מההקשרים מרכיבים שצריכים לקרוא אותם.
  • אתה יכול לשחרר עוד יותר את הרכיבים על ידי העברת כל החיווט לקובץ אחד.
  • אתה יכול לייצא רכיב כמו TasksProvider שמספק הקשר.
    • אתה יכול גם לייצא Hooks מותאמים אישית כמו useTasks ו-useTasksDispatch כדי לקרוא אותו.
  • אתה יכול לקבל הרבה זוגות צמצום הקשר כמו זה באפליקציה שלך.