diff --git a/src/content/learn/scaling-up-with-reducer-and-context.md b/src/content/learn/scaling-up-with-reducer-and-context.md index fe1762d8e..a31d5245d 100644 --- a/src/content/learn/scaling-up-with-reducer-and-context.md +++ b/src/content/learn/scaling-up-with-reducer-and-context.md @@ -1,24 +1,24 @@ --- -title: Scaling Up with Reducer and Context +title: 使用化簡器和 Context 擴充應用程式 --- -Reducers let you consolidate a component's state update logic. Context lets you pass information deep down to other components. You can combine reducers and context together to manage state of a complex screen. +化簡器(reducers)可以整合元件的狀態更新邏輯。Context 能將資訊深入向下傳遞到其他的元件。你可以混用化簡器和 context 來管理複雜畫面的狀態。 -* How to combine a reducer with context -* How to avoid passing state and dispatch through props -* How to keep context and state logic in a separate file +* 如何混用化簡器和 context +* 如何避免藉由屬性(props)傳送狀態和派發 action +* 如何維持不同檔案中的 context 和狀態邏輯 -## Combining a reducer with context {/*combining-a-reducer-with-context*/} +## 混用化簡器和 context {/*combining-a-reducer-with-context*/} -In this example from [the introduction to reducers](/learn/extracting-state-logic-into-a-reducer), the state is managed by a reducer. The reducer function contains all of the state update logic and is declared at the bottom of this file: +在[介紹化簡器](/learn/extracting-state-logic-into-a-reducer)的例子中,狀態是由化簡器所管理。化簡器函式包含所有的狀態邏輯,並被宣告於檔案的下方: @@ -57,7 +57,7 @@ export default function TaskApp() { return ( <> -

Day off in Kyoto

+

放假去京都

@@ -92,16 +92,16 @@ function tasksReducer(tasks, action) { return tasks.filter(t => t.id !== action.id); } default: { - throw Error('Unknown action: ' + action.type); + throw Error('未知的 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 } + { id: 0, text: '哲學之道', done: true }, + { id: 1, text: '參觀寺廟', done: false }, + { id: 2, text: '喝抹茶', done: false } ]; ``` @@ -113,14 +113,14 @@ export default function AddTask({ onAddTask }) { return ( <> setText(e.target.value)} /> + }}>新增 ) } @@ -164,7 +164,7 @@ function Task({ task, onChange, onDelete }) { }); }} /> ); @@ -173,7 +173,7 @@ function Task({ task, onChange, onDelete }) { <> {task.text} ); @@ -192,7 +192,7 @@ function Task({ task, onChange, onDelete }) { /> {taskContent} ); @@ -207,9 +207,9 @@ ul, li { margin: 0; padding: 0; }
-A reducer helps keep the event handlers short and concise. However, as your app grows, you might run into another difficulty. **Currently, the `tasks` state and the `dispatch` function are only available in the top-level `TaskApp` component.** To let other components read the list of tasks or change it, you have to explicitly [pass down](/learn/passing-props-to-a-component) the current state and the event handlers that change it as props. +化簡器可以讓事件處理函式保持簡潔。不過隨著應用程式成長,可能會遭遇另一種困境。**現在 `task` 狀態和 `dispatch` 函式只在 `TaskApp` 元件的頂層才能使用。** 要讓其他元件讀取任務清單或改變任務,需要另外[向下傳遞](/learn/passing-props-to-a-component)當前狀態並讓事件處理函式作為屬性來改變任務。 -For example, `TaskApp` passes a list of tasks and the event handlers to `TaskList`: +舉例來說,`TaskApp` 將一個任務清單和一些事件處理函式傳入 `TaskList`: ```js ``` -And `TaskList` passes the event handlers to `Task`: +`TaskList` 將這些事件處理函式傳給 `Task`: ```js ``` -In a small example like this, this works well, but if you have tens or hundreds of components in the middle, passing down all state and functions can be quite frustrating! +在這樣的小範例中,它可以作用得很好,但如果你有數以百計的元件在中間,要把所有的狀態和函式都向下傳遞,會很令人挫折! -This is why, as an alternative to passing them through props, you might want to put both the `tasks` state and the `dispatch` function [into context.](/learn/passing-data-deeply-with-context) **This way, any component below `TaskApp` in the tree can read the tasks and dispatch actions without the repetitive "prop drilling".** +這就是為什麼,作為傳遞屬性的替代方案,你可能會想把 `tasks` 狀態和 `dispatch` 函式都[放進 context](/learn/passing-data-deeply-with-context)。 +**這樣一來,`TaskApp` 樹底下的任何元件都能讀取到任務及派發 action,而不用重複「層層傳遞屬性(prop drilling)」。** -Here is how you can combine a reducer with context: +以下是混用化簡器和 context 的方法: -1. **Create** the context. -2. **Put** state and dispatch into context. -3. **Use** context anywhere in the tree. +1. **創建** context。 +2. 將狀態和派發函式 **放入** context。 +3. 在樹的任何地方 **使用** context。 -### Step 1: Create the context {/*step-1-create-the-context*/} +### 步驟一:創建 context {/*step-1-create-the-context*/} -The `useReducer` Hook returns the current `tasks` and the `dispatch` function that lets you update them: +`useReducer` Hook 會回傳當前的 `tasks` 和用來更新 `tasks` 的 `dispatch` 函式: ```js const [tasks, dispatch] = useReducer(tasksReducer, initialTasks); ``` -To pass them down the tree, you will [create](/learn/passing-data-deeply-with-context#step-2-use-the-context) two separate contexts: +為了把狀態跟函式往樹的下方傳遞,必須[創建](/learn/passing-data-deeply-with-context#step-2-use-the-context)兩個分別的 context: -- `TasksContext` provides the current list of tasks. -- `TasksDispatchContext` provides the function that lets components dispatch actions. +- `TasksContext` 提供目前的任務清單。 +- `TasksDispatchContext` 提供讓元件派發 action 的函式。 -Export them from a separate file so that you can later import them from other files: +從一個分別的檔案導出(export)這兩個 context,之後才能在其他檔案引入: @@ -291,7 +292,7 @@ export default function TaskApp() { return ( <> -

Day off in Kyoto

+

放假去京都

@@ -326,16 +327,16 @@ function tasksReducer(tasks, action) { return tasks.filter(t => t.id !== action.id); } default: { - throw Error('Unknown action: ' + action.type); + throw Error('未知的 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 } + { id: 0, text: '哲學之道', done: true }, + { id: 1, text: '參觀寺廟', done: false }, + { id: 2, text: '喝抹茶', done: false } ]; ``` @@ -354,14 +355,14 @@ export default function AddTask({ onAddTask }) { return ( <> setText(e.target.value)} /> + }}>新增 ) } @@ -405,7 +406,7 @@ function Task({ task, onChange, onDelete }) { }); }} /> ); @@ -414,7 +415,7 @@ function Task({ task, onChange, onDelete }) { <> {task.text} ); @@ -433,7 +434,7 @@ function Task({ task, onChange, onDelete }) { /> {taskContent} ); @@ -448,11 +449,11 @@ ul, li { margin: 0; padding: 0; }
-Here, you're passing `null` as the default value to both contexts. The actual values will be provided by the `TaskApp` component. +以上傳入 `null` 作為兩個 context 的預設值。確切的數值會由 `TaskApp` 元件提供。 -### Step 2: Put state and dispatch into context {/*step-2-put-state-and-dispatch-into-context*/} +### 步驟二:將狀態和派發函式放入 context {/*step-2-put-state-and-dispatch-into-context*/} -Now you can import both contexts in your `TaskApp` component. Take the `tasks` and `dispatch` returned by `useReducer()` and [provide them](/learn/passing-data-deeply-with-context#step-3-provide-the-context) to the entire tree below: +現在你可以引入兩個 context 到 `TaskApp` 元件了。取得 `useReducer()` 回傳的 `tasks` 和 `dispatch`,並[提供](/learn/passing-data-deeply-with-context#step-3-provide-the-context)給下面的整棵樹: ```js {4,7-8} import { TasksContext, TasksDispatchContext } from './TasksContext.js'; @@ -470,7 +471,7 @@ export default function TaskApp() { } ``` -For now, you pass the information both via props and in context: +目前為止,你同時藉由屬性和 context 傳遞這些資訊: @@ -511,7 +512,7 @@ export default function TaskApp() { return ( -

Day off in Kyoto

+

放假去京都

@@ -547,16 +548,16 @@ function tasksReducer(tasks, action) { return tasks.filter(t => t.id !== action.id); } default: { - throw Error('Unknown action: ' + action.type); + throw Error('未知的 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 } + { id: 0, text: '哲學之道', done: true }, + { id: 1, text: '參觀寺廟', done: false }, + { id: 2, text: '喝抹茶', done: false } ]; ``` @@ -575,14 +576,14 @@ export default function AddTask({ onAddTask }) { return ( <> setText(e.target.value)} /> + }}>新增 ) } @@ -626,7 +627,7 @@ function Task({ task, onChange, onDelete }) { }); }} /> ); @@ -635,7 +636,7 @@ function Task({ task, onChange, onDelete }) { <> {task.text} ); @@ -654,7 +655,7 @@ function Task({ task, onChange, onDelete }) { /> {taskContent} ); @@ -669,23 +670,23 @@ ul, li { margin: 0; padding: 0; }
-In the next step, you will remove prop passing. +在下個步驟中,將會移除屬性傳遞: -### Step 3: Use context anywhere in the tree {/*step-3-use-context-anywhere-in-the-tree*/} +### 步驟三:在樹的任何地方使用 context {/*step-3-use-context-anywhere-in-the-tree*/} -Now you don't need to pass the list of tasks or the event handlers down the tree: +現在不用向下傳遞任務清單或事件處理函式了: ```js {4-5} -

Day off in Kyoto

+

放假去京都

``` -Instead, any component that needs the task list can read it from the `TasksContext`: +任何需要任務清單的元件,可以從 `TasksContext` 讀取: ```js {2} export default function TaskList() { @@ -693,7 +694,7 @@ export default function TaskList() { // ... ``` -To update the task list, any component can read the `dispatch` function from context and call it: +想要更新任務清單的話,任何元件都可以讀取 context 中的 `dispatch` 函式並呼叫它: ```js {3,9-13} export default function AddTask() { @@ -709,11 +710,11 @@ export default function AddTask() { id: nextId++, text: text, }); - }}>Add + }}>新增 // ... ``` -**The `TaskApp` component does not pass any event handlers down, and the `TaskList` does not pass any event handlers to the `Task` component either.** Each component reads the context that it needs: +**`TaskApp` 元件不會將任何事件處理函式向下傳遞,`TaskList` 也不會向下傳遞任何事件處理函式給 `Task` 元件。** 每個元件都只會讀取所需的 context: @@ -732,7 +733,7 @@ export default function TaskApp() { return ( -

Day off in Kyoto

+

放假去京都

@@ -762,15 +763,15 @@ function tasksReducer(tasks, action) { return tasks.filter(t => t.id !== action.id); } default: { - throw Error('Unknown action: ' + action.type); + throw Error('未知的 action:' + action.type); } } } 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 } + { id: 0, text: '哲學之道', done: true }, + { id: 1, text: '參觀寺廟', done: false }, + { id: 2, text: '喝抹茶', done: false } ]; ``` @@ -791,7 +792,7 @@ export default function AddTask() { return ( <> setText(e.target.value)} /> @@ -802,7 +803,7 @@ export default function AddTask() { id: nextId++, text: text, }); - }}>Add + }}>新增 ); } @@ -846,7 +847,7 @@ function Task({ task }) { }); }} /> ); @@ -855,7 +856,7 @@ function Task({ task }) { <> {task.text} ); @@ -882,7 +883,7 @@ function Task({ task }) { id: task.id }); }}> - Delete + 刪除 ); @@ -897,11 +898,11 @@ ul, li { margin: 0; padding: 0; }
-**The state still "lives" in the top-level `TaskApp` component, managed with `useReducer`.** But its `tasks` and `dispatch` are now available to every component below in the tree by importing and using these contexts. +**狀態仍然「活在」`TaskApp` 元件的頂層,並由 `useReducer` 管理。** 但 `tasks` 和 `dispatch` 現在可以被樹底下的所有元件藉由引入和使用這些 context 來取得。 -## Moving all wiring into a single file {/*moving-all-wiring-into-a-single-file*/} +## 將程式碼移到獨立的檔案 {/*moving-all-wiring-into-a-single-file*/} -You don't have to do this, but you could further declutter the components by moving both reducer and context into a single file. Currently, `TasksContext.js` contains only two context declarations: +雖然不一定要這麼做,但可以將化簡器和 context 移到獨立的檔案,以進一步整理這些元件。現在 `TasksContext.js` 只包含兩個 context 的宣告: ```js import { createContext } from 'react'; @@ -910,11 +911,11 @@ export const TasksContext = createContext(null); export const TasksDispatchContext = createContext(null); ``` -This file is about to get crowded! You'll move the reducer into that same file. Then you'll declare a new `TasksProvider` component in the same file. This component will tie all the pieces together: +這個檔案逐漸變得擁擠!你將會移動化簡器到同一個檔案,接著你會宣告一個新的 `TasksProvider` 元件在同一個檔案裡。這個元件會將這些片段綁在一起: -1. It will manage the state with a reducer. -2. It will provide both contexts to components below. -3. It will [take `children` as a prop](/learn/passing-props-to-a-component#passing-jsx-as-children) so you can pass JSX to it. +1. 元件會用化簡器管理狀態。 +2. 元件會提供兩個 context 給下面的這些元件。 +3. 元件會[以 `children` 作為屬性](/learn/passing-props-to-a-component#passing-jsx-as-children),因此你能傳入 JSX 給它。 ```js export function TasksProvider({ children }) { @@ -930,7 +931,7 @@ export function TasksProvider({ children }) { } ``` -**This removes all the complexity and wiring from your `TaskApp` component:** +**這會移除 `TaskApp` 元件中所有複雜的程式碼和串接:** @@ -942,7 +943,7 @@ import { TasksProvider } from './TasksContext.js'; export default function TaskApp() { return ( -

Day off in Kyoto

+

放假去京都

@@ -993,15 +994,15 @@ function tasksReducer(tasks, action) { return tasks.filter(t => t.id !== action.id); } default: { - throw Error('Unknown action: ' + action.type); + throw Error('未知的 action:' + action.type); } } } 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 } + { id: 0, text: '哲學之道', done: true }, + { id: 1, text: '參觀寺廟', done: false }, + { id: 2, text: '喝抹茶', done: false } ]; ``` @@ -1015,7 +1016,7 @@ export default function AddTask() { return ( <> setText(e.target.value)} /> @@ -1026,7 +1027,7 @@ export default function AddTask() { id: nextId++, text: text, }); - }}>Add + }}>新增 ); } @@ -1070,7 +1071,7 @@ function Task({ task }) { }); }} /> ); @@ -1079,7 +1080,7 @@ function Task({ task }) { <> {task.text} ); @@ -1106,7 +1107,7 @@ function Task({ task }) { id: task.id }); }}> - Delete + 刪除 ); @@ -1121,7 +1122,7 @@ ul, li { margin: 0; padding: 0; }
-You can also export functions that _use_ the context from `TasksContext.js`: +也可以從 `TasksContext.js` 導出 _使用_ 到 context 的這些函式: ```js export function useTasks() { @@ -1133,14 +1134,14 @@ export function useTasksDispatch() { } ``` -When a component needs to read context, it can do it through these functions: +當元件需要讀取 context 時,可以透過這些函式: ```js const tasks = useTasks(); const dispatch = useTasksDispatch(); ``` -This doesn't change the behavior in any way, but it lets you later split these contexts further or add some logic to these functions. **Now all of the context and reducer wiring is in `TasksContext.js`. This keeps the components clean and uncluttered, focused on what they display rather than where they get the data:** +這不會以任何方式改變行為,但會使之後進一步拆分這些 context 或增加一些函式中的邏輯。 **現在所有的 context 和化簡器串接都在 `TaskContext.js` 裡了。這能使元件保持乾淨及簡潔,並專注於要呈現的是什麼,而不是從何處獲取資料:** @@ -1152,7 +1153,7 @@ import { TasksProvider } from './TasksContext.js'; export default function TaskApp() { return ( -

Day off in Kyoto

+

放假去京都

@@ -1212,15 +1213,15 @@ function tasksReducer(tasks, action) { return tasks.filter(t => t.id !== action.id); } default: { - throw Error('Unknown action: ' + action.type); + throw Error('未知的 action:' + action.type); } } } 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 } + { id: 0, text: '哲學之道', done: true }, + { id: 1, text: '參觀寺廟', done: false }, + { id: 2, text: '喝抹茶', done: false } ]; ``` @@ -1234,7 +1235,7 @@ export default function AddTask() { return ( <> setText(e.target.value)} /> @@ -1245,7 +1246,7 @@ export default function AddTask() { id: nextId++, text: text, }); - }}>Add + }}>新增 ); } @@ -1289,7 +1290,7 @@ function Task({ task }) { }); }} /> ); @@ -1298,7 +1299,7 @@ function Task({ task }) { <> {task.text} ); @@ -1325,7 +1326,7 @@ function Task({ task }) { id: task.id }); }}> - Delete + 刪除 ); @@ -1340,26 +1341,26 @@ ul, li { margin: 0; padding: 0; }
-You can think of `TasksProvider` as a part of the screen that knows how to deal with tasks, `useTasks` as a way to read them, and `useTasksDispatch` as a way to update them from any component below in the tree. +你可以將 `TasksProvider` 考慮為畫面的一部份,它知道怎麼處理任務;把 `useTasks` 視為讀取任務的方式;並把 `useTasksDispatch` 視為在樹下任一元件更新任務的方式。 -Functions like `useTasks` and `useTasksDispatch` are called *[Custom Hooks.](/learn/reusing-logic-with-custom-hooks)* Your function is considered a custom Hook if its name starts with `use`. This lets you use other Hooks, like `useContext`, inside it. +像 `useTasks` 和 `useTasksDispatch` 這樣的函式被稱為 *[自定義(Custom) Hooks](/learn/reusing-logic-with-custom-hooks)*。當函式的命名以 `use` 開頭,就會被認為是自定義 Hook。這能讓你在像 `useContext` 這樣的其他 Hooks 中使用你的 Hook。 -As your app grows, you may have many context-reducer pairs like this. This is a powerful way to scale your app and [lift state up](/learn/sharing-state-between-components) without too much work whenever you want to access the data deep in the tree. +隨著應用程式成長,你可能會有很多像這樣的 context 與化簡器的配對。這是擴充應用程式的有效方式,且在需要從樹的深層取得資料時,不需要太多的工作,就能[抬升狀態](/learn/sharing-state-between-components)。 -- You can combine reducer with context to let any component read and update state above it. -- To provide state and the dispatch function to components below: - 1. Create two contexts (for state and for dispatch functions). - 2. Provide both contexts from the component that uses the reducer. - 3. Use either context from components that need to read them. -- You can further declutter the components by moving all wiring into one file. - - You can export a component like `TasksProvider` that provides context. - - You can also export custom Hooks like `useTasks` and `useTasksDispatch` to read it. -- You can have many context-reducer pairs like this in your app. +- 可以混用化簡器和 context,讓任何元件讀取和更新元件上的狀態。 +- 想提供狀態和派發函式給下方的元件的話: + 1. 創建兩個 context(給狀態和派發函式)。 + 2. 由使用化簡器的元件提供兩個 context。 + 3. 在需要讀取的元件中使用 context。 +- 可以將相關程式碼移動到獨立檔案,來進一步整理元件。 + - 可以導出像是 `TaskProvider` 這樣的元件來提供 context。 + - 也可以導出像是 `useTasks` 和 `useTasksDispatch` 這樣的自定義 Hooks 來讀取 context。 + - 在應用中可以有許多像這樣的化簡器與 context 的配對。