Skaalaus reduktorin ja kontekstin avulla
Reduktorien avulla voit yhdistää komponentin tilan päivityslogiikan. Kontekstin avulla voit välittää tietoa syvälle muihin komponentteihin. Voit yhdistää reduktoreita ja konteksteja hallitaksesi monimutkaisen ruudun tilaa.
Tulet oppimaan
- Miten yhdistää reduktori ja konteksti
- Miten välttää tilan ja toimintojen välittäminen propsien kautta
- Miten pitää konteksti ja tilalogiikka erillisessä tiedostossa
Reduktorin yhdistäminen kontekstin kanssa
Tässä esimerkissä reduktoriin tutustumisesta, tilaa hallitaan reduktorilla. Reduktorifunktio sisältää kaiken tilan päivityslogiikan ja se on määritelty tiedoston loppuun:
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 } ];
Reduktori helpottaa pitämään Tapahtumankäsittelijät lyhyinä ja tiiviinä. Kuitenkin, kun sovelluksesi kasvaa, saataat törmätä toiseen haasteeseen. Tällä hetkellä, tasks
tila ja dispatch
funktio ovat vain saatavilla ylätason TaskApp
komponentissa. Jotta muut komponentit voisivat lukea tehtävälistan tai muuttaa sitä, sinun on erikseen välitettävä nykyinen tila ja Tapahtumankäsittelijät, jotka muuttavat sitä propsien kautta.
Esimerkiksi, TaskApp
välittää tehtävälistan ja Tapahtumankäsittelijät TaskList
komponentille:
<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}
/>
And TaskList
passes the event handlers to Task
:
<Task
task={task}
onChange={onChangeTask}
onDelete={onDeleteTask}
/>
Pienessä esimerkissä kuten tässä, tämä toimii hyvin, mutta jos sinulla on kymmeniä tai satoja komponentteja välissä, tilan ja funktioiden välittäminen voi olla melko ärsyttävää!
Tämän takia vaihtoehtoinen tapa on laittaa sekä tasks
tila että dispatch
funktio kontekstiin. Tällöin, minkä tahansa komponentin alla oleva komponentti voi lukea tehtävälistan ja kutsua Tapahtumankäsittelijöitä ilman toistuvaa “prop drillingia”.
Tässä on tapa yhdistää reduktori kontekstiin:
- Luo konteksti.
- Aseta tila ja dispatch -funktio kontekstiin.
- Käytä kontekstia missä tahansa puun sijainnissa.
1. Vaihe: Luo konteksti
useReducer
hookki palauttaa nykyisen tasks
tilan ja dispatch
funktion, joka mahdollistaa sen päivittämisen:
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
Välittääksesi ne puun alle, sinun täytyy luoda kaksi erillistä kontekstia:
TasksContext
tarjoaa nykyisen listan tehtävistä.TasksDispatchContext
tarjoaa funktion, jonka avulla voit lähettää toimintoja.
Exporttaa ne erillisestä tiedostosta, jotta voit myöhemmin importata ne muista tiedostoista:
import { createContext } from 'react'; export const TasksContext = createContext(null); export const TasksDispatchContext = createContext(null);
Tässä, välität null
:n oleetusarvona molemmille konteksteille. Todelliset arvot tarjotaan TaskApp
komponentissa.
2. Vaihe: Aseta tila ja dispatch kontekstiin
Nyt voit importata molemmat kontekstit TaskApp
komponentissa. Ota useReducer()
funktion palauttamat tasks
ja dispatch
ja tarjoa ne kaikille komponenteille jotka ovat TaskApp
komponentin alapuolella:
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>
);
}
Tällä hetkellä välität tiedon sekä propsien että kontekstin kautta:
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 } ];
Seuraavassa vaiheessa, tulet poistamaan propsien välittämisen.
3. Vaihe: Käytä kontekstia missä tahansa sijainnissa puuta
Nyt sinun ei tarvitse välittää listaa tehtävistä tai tapahtumankäsittelijöistä:
<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider value={dispatch}>
<h1>Day off in Kyoto</h1>
<AddTask />
<TaskList />
</TasksDispatchContext.Provider>
</TasksContext.Provider>
Sen sijaan, mikä tahansa komponentti, joka tarvitsee tehtävälistan, voi lukea sen TaskContext
:sta:
export default function TaskList() {
const tasks = useContext(TasksContext);
// ...
Päivittääksesi listan, mikä tahansa komponentti voi lukea dispatch
-funktion kontekstista ja kutsua sitä:
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
komponentin ei tarvitse välittää yhtään Tapahtumankäsittelijää, eikä TaskList
komponentin tarvitse myöskään välittää yhtään Tapahtumankäsittelijää Task
komponentille. Jokainen komponentti lukee kontekstista sen, mitä se tarvitsee:
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> ); }
Tila silti “asuu” ylätason TaskApp
komponentissa, jota hallinnoidaan useReducer
hookilla. Mutta sen tasks
ja dispatch
ovat nyt saatavilla jokaiselle komponentille, kun konteksti importataan ja käytetään.
Koko virityksen siirtäminen yhteen tiedostoon
Sinun ei tarvitse tehdä tätä, mutta voit vielä siistiä komponentteja siirtämällä sekä reduktorin että kontekstin yhteen tiedostoon. Tällä hetkellä, TasksContext.js
sisältää vain kaksi kontekstin määrittelyä:
import { createContext } from 'react';
export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);
Tämä tiedosto tulee olemaan täynnä! Siirrät reduktorin yhden tiedoston sisään. Sitten määrität uuden TasksProvider
komponentin samaan tiedostoon. Tämä komponentti yhdistää kaikki osat yhteen:
- Se hallitsee tilaa reduktorilla.
- Se tarjoaa molemmat kontekstit alla oleville komponenteille.
- Se ottaa
children
propsina, jotta voit välittää JSX:ää sille.
export function TasksProvider({ children }) {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
return (
<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider value={dispatch}>
{children}
</TasksDispatchContext.Provider>
</TasksContext.Provider>
);
}
Tämä postaa kaiken komplikaation ja virityksen pois TaskApp
komponentista:
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> ); }
Voit myös exportata funktioita, jotka käyttävät kontekstia TasksContext.js
:stä:
export function useTasks() {
return useContext(TasksContext);
}
export function useTasksDispatch() {
return useContext(TasksDispatchContext);
}
Kun komponentin tarvitsee lukea kontekstia, se voi tehdä sen näiden funktioiden kautta:
const tasks = useTasks();
const dispatch = useTasksDispatch();
Tämä ei muuta toiminnallisuutta mitenkään, mutta sen avulla voit myöhemmin erottaa näitä konteksteja enemmän tai lisätä logiikkaa funktioihin. Nyt kaikki kontekstin ja reduktorin viritys on TasksContext.js
tiedostossa. Tämä pitää komponentit puhtaina ja selkeinä, keskittyen siihen, mitä ne näyttävät, eikä siihen, mistä ne saavat tiedot:
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> ); }
Voit kuvitella TasksProvider
-komponentin osaksi ruutua, joka osaa käsitellä tehtäviä, useTasks
-funktion tapana lukea niitä ja useTasksDispatch
-funktion tapana päivittää niitä mistä tahansa komponentista.
Kun sovelluksesi kasvaa, sinulla voi olla useita context-reducer-paria kuten tämä. Tämä on tehokas tapa skaalata sovellus ja nostaa tila ylös vähällä työllä, kun haluat lukea dataa syvältä puusta.
Kertaus
- Voit yhdistää reduktorin ja kontekstin, jotta mikä tahansa komponentti voi lukea ja päivittää ylhäällä olevaa tilaa.
- Tarjotaksesi tila ja dispatch -funktiot alla oleville komponenteille:
- Luo kaksi kontekstia (yksi tilalle, toinen dispatch -funktiolle).
- Tarjoa molemmat kontekstit komponentista joka käyttää reduktoria.
- Käytä jompaa kumpaa kontekstia komponenteista jotka tarvitsevat niitä.
- Voit siistiä komponentteja vielä lisää siirtämällä kaiken virityksen yhteen tiedostoon.
- Voit exportata komponentin kuten
TasksProvider
joka tarjoaa kontekstin. - Voit myös exportata mukautettuja hookkeja kuten
useTasks
jauseTasksDispatch
.
- Voit exportata komponentin kuten
- Sinulla voi olla useita konteksti-reduktori -pareja sovelluksessasi.