diff --git a/src/content/learn/thinking-in-react.md b/src/content/learn/thinking-in-react.md index 822891e60..882434958 100644 --- a/src/content/learn/thinking-in-react.md +++ b/src/content/learn/thinking-in-react.md @@ -1,18 +1,18 @@ --- -title: Thinking in React +title: 用 React 的方式去思考 --- -React can change how you think about the designs you look at and the apps you build. When you build a user interface with React, you will first break it apart into pieces called *components*. Then, you will describe the different visual states for each of your components. Finally, you will connect your components together so that the data flows through them. In this tutorial, we’ll guide you through the thought process of building a searchable product data table with React. +React 可以改變你看待設計與建立應用程式的思考方式。當你使用 React 建立使用者介面時,第一步你需要先把它拆解成「元件」。接著,你要描述每個元件不同的畫面狀態。最後,把這些元件連結起來,讓資料可以在它們之間流動。在本教學中,我們將引導你透過 React 打造一個可搜尋的產品資料表格,並了解其中的思考過程。 -## Start with the mockup {/*start-with-the-mockup*/} +## 從 mockup 開始 {/*start-with-the-mockup*/} -Imagine that you already have a JSON API and a mockup from a designer. +想像你已經從設計師那裡拿到了 JSON API 和 mockup。 -The JSON API returns some data that looks like this: +JSON API 回傳的資料如下所示: ```json [ @@ -25,25 +25,25 @@ The JSON API returns some data that looks like this: ] ``` -The mockup looks like this: +Mockup 看起來像這樣: -To implement a UI in React, you will usually follow the same five steps. +在 React 中實作 UI ,通常會依照以下五個步驟進行。 -## Step 1: Break the UI into a component hierarchy {/*step-1-break-the-ui-into-a-component-hierarchy*/} +## 第一步:將 UI 拆解成一層層的元件 {/*step-1-break-the-ui-into-a-component-hierarchy*/} -Start by drawing boxes around every component and subcomponent in the mockup and naming them. If you work with a designer, they may have already named these components in their design tool. Ask them! +首先,把 mockup 中每一個元件與子元件框起來,並為它們命名。如果你是跟設計師合作的話,他們可能已經在設計工具中幫這些元件命名好了。問問他們吧! -Depending on your background, you can think about splitting up a design into components in different ways: +依據你的專業背景,你可以用不同的方式來思考如何將設計拆解成元件: -* **Programming**--use the same techniques for deciding if you should create a new function or object. One such technique is the [single responsibility principle](https://en.wikipedia.org/wiki/Single_responsibility_principle), that is, a component should ideally only do one thing. If it ends up growing, it should be decomposed into smaller subcomponents. -* **CSS**--consider what you would make class selectors for. (However, components are a bit less granular.) -* **Design**--consider how you would organize the design's layers. +* **程式設計**--就像你寫程式時會判斷是否該建立新的函式或物件一樣,也可以用相同的技巧來拆元件。其中一個常見的技巧叫做 [單一職責原則](https://en.wikipedia.org/wiki/Single_responsibility_principle),也就是說,理想的情況下,每個元件應該只做一件事情。如果某個元件隨著開發越來越複雜,它就應該被分解成更小的子元件。 +* **CSS**--思考會在那些地方使用類別選擇器 (不過元件通常並不會拆解得像 CSS 那麼細) +* **Design**--思考你會如何安排設計稿的圖層結構 -If your JSON is well-structured, you'll often find that it naturally maps to the component structure of your UI. That's because UI and data models often have the same information architecture--that is, the same shape. Separate your UI into components, where each component matches one piece of your data model. +假如你的 JSON 架構設計非常棒,通常會發現它可以很自然地對應到 UI 的元件架構。那是因為 UI 與資料模型通常會擁有相同的資訊架構 -- 也就是相同的結構。將 UI 拆成一個個元件,讓每一個元件都能對應到資料模型中的一部分。 -There are five components on this screen: +以下畫面包含了五個元件: @@ -51,19 +51,19 @@ There are five components on this screen: -1. `FilterableProductTable` (grey) contains the entire app. -2. `SearchBar` (blue) receives the user input. -3. `ProductTable` (lavender) displays and filters the list according to the user input. -4. `ProductCategoryRow` (green) displays a heading for each category. -5. `ProductRow` (yellow) displays a row for each product. +1. `FilterableProductTable`(灰色)是整個應用程式的容器。 +2. `SearchBar`(藍色)用來接收使用者的輸入。 +3. `ProductTable`(淡紫色)會根據使用者的輸入顯示並篩選清單。 +4. `ProductCategoryRow`(綠色)用來顯示每個類別的標題。 +5. `ProductRow`(黃色)顯示每筆產品資料的一列。 -If you look at `ProductTable` (lavender), you'll see that the table header (containing the "Name" and "Price" labels) isn't its own component. This is a matter of preference, and you could go either way. For this example, it is a part of `ProductTable` because it appears inside the `ProductTable`'s list. However, if this header grows to be complex (e.g., if you add sorting), you can move it into its own `ProductTableHeader` component. +若你查看 `ProductTable`(淡紫色)時,會發現表格的表頭(包含 "Name" 和 "Price" 標籤 )並不是獨立的元件。這純粹是偏好的問題,你可以選擇任何一種作法。以這裡為例,表頭被視為 `ProductTable` 的一部分,因為它出現在 `ProductTable` 的清單中。不過,當這個表頭變得更加複雜(例如加入了排序功能),你就可以把它抽出成獨立的 `ProductTableHeader` 元件。 -Now that you've identified the components in the mockup, arrange them into a hierarchy. Components that appear within another component in the mockup should appear as a child in the hierarchy: +現在你已經辨識出所有 mockup 裡的元件了,接下來要將它們整理成層級結構。只要在 mockup 中出現在其他元件裡的元件,都應該以子元件的層級呈現: * `FilterableProductTable` * `SearchBar` @@ -71,13 +71,13 @@ Now that you've identified the components in the mockup, arrange them into a hie * `ProductCategoryRow` * `ProductRow` -## Step 2: Build a static version in React {/*step-2-build-a-static-version-in-react*/} +## 第二步:使用 React 建立靜態版本 {/*step-2-build-a-static-version-in-react*/} -Now that you have your component hierarchy, it's time to implement your app. The most straightforward approach is to build a version that renders the UI from your data model without adding any interactivity... yet! It's often easier to build the static version first and add interactivity later. Building a static version requires a lot of typing and no thinking, but adding interactivity requires a lot of thinking and not a lot of typing. +現在你已經有元件的層級結構,是時候開始實作你的應用程式了。最直接的方式是先建立一個根據資料模型來渲染 UI ,且暫時不增加任何互動功能的版本...沒錯!通常都會先完成靜態版本,再慢慢加入互動。建立靜態版本的過程需要大量的打字,但幾乎不太需要思考;而加入互動則相反,它需要大量思考,但打得字並不多。 -To build a static version of your app that renders your data model, you'll want to build [components](/learn/your-first-component) that reuse other components and pass data using [props.](/learn/passing-props-to-a-component) Props are a way of passing data from parent to child. (If you're familiar with the concept of [state](/learn/state-a-components-memory), don't use state at all to build this static version. State is reserved only for interactivity, that is, data that changes over time. Since this is a static version of the app, you don't need it.) +為了建立一個根據資料模型來渲染畫面的靜態版本應用程式,你需要建立可以重複使用的 [元件](/learn/your-first-component) ,並透過 [props.](/learn/passing-props-to-a-component) 來傳遞資料,Props 是一個可以將資料從父元件傳遞到子元件的方法。(如果你已經熟悉 [state](/learn/state-a-components-memory)的概念,請不要在這個靜態版本中使用到 state 。 State 是專門用來處理互動的,也就是那些會隨時間改變的資料。因此在這個靜態版本的應用程式中,你不需要用到它。) -You can either build "top down" by starting with building the components higher up in the hierarchy (like `FilterableProductTable`) or "bottom up" by working from components lower down (like `ProductRow`). In simpler examples, it’s usually easier to go top-down, and on larger projects, it’s easier to go bottom-up. +你可以選擇「由上而下」的方式來開發,從層級較高的元件(像是 `FilterableProductTable`)開始建立,也可以選擇「由下而上」的方式,從層級較低的元件(像是 `ProductRow`)開始。在簡單的例子中,通常由上而下會比較簡單,但在較大型的專案中,則由下而上會比較容易。 @@ -195,85 +195,84 @@ td { -(If this code looks intimidating, go through the [Quick Start](/learn/) first!) +(如果你覺得這段程式碼有點困難,不妨先看看 [快速開始](/learn/) 章節) -After building your components, you'll have a library of reusable components that render your data model. Because this is a static app, the components will only return JSX. The component at the top of the hierarchy (`FilterableProductTable`) will take your data model as a prop. This is called _one-way data flow_ because the data flows down from the top-level component to the ones at the bottom of the tree. +當你建立好元件後,你就會擁有一個可重複使用的元件庫,用來渲染你的資料模型。由於這是一個靜態應用程式,所以元件只會回傳 JSX ,不包含任何互動邏輯。最上層的元件(`FilterableProductTable`)會將資料模型作為 prop 傳入。這種資料從頂層元件一路向下傳遞到樹狀結構底部元件的方式,被稱為 _單向資料流_。 -At this point, you should not be using any state values. That’s for the next step! +在這階段,你不應該使用任何 state 。那是下一步要處理的事! -## Step 3: Find the minimal but complete representation of UI state {/*step-3-find-the-minimal-but-complete-representation-of-ui-state*/} +## 第三步:找到最小但最完整的 UI 狀態 {/*step-3-find-the-minimal-but-complete-representation-of-ui-state*/} -To make the UI interactive, you need to let users change your underlying data model. You will use *state* for this. +為了使 UI 具備互動性,你需要讓使用者可以改變底層的資料模型。這時就會用到 *state* 。 -Think of state as the minimal set of changing data that your app needs to remember. The most important principle for structuring state is to keep it [DRY (Don't Repeat Yourself).](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) Figure out the absolute minimal representation of the state your application needs and compute everything else on-demand. For example, if you're building a shopping list, you can store the items as an array in state. If you want to also display the number of items in the list, don't store the number of items as another state value--instead, read the length of your array. +可以把 state 想成是應用程式中必須記住,且會變動的最小資料集合。在設計 state 時最重要的原則是保持 [DRY (Don't Repeat Yourself,不要自我重複)](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) 。也就是說,你要找出應用程式中真正需要紀錄的最小 state,其他的部分則在需要時再去即時計算出來。舉例來說,假如你在建立一個購物清單,商品項目以陣列方式儲存在 state 裡。如果你還想顯示清單中的項目數量,不要再用額外的 state 來儲存 -- 反之,你應該直接讀取陣列的長度來取得數量。 -Now think of all of the pieces of data in this example application: +現在來思考一下這個應用程式範例中所有的資料: -1. The original list of products -2. The search text the user has entered -3. The value of the checkbox -4. The filtered list of products +1. 原始的產品清單 +2. 使用者輸入的搜尋文字 +3. Checkbox 的勾選狀態 +4. 篩選後的產品清單 -Which of these are state? Identify the ones that are not: +其中哪些資料應該放進 state 呢?試著辨識出其中不是 state 的項目: -* Does it **remain unchanged** over time? If so, it isn't state. -* Is it **passed in from a parent** via props? If so, it isn't state. -* **Can you compute it** based on existing state or props in your component? If so, it *definitely* isn't state! +* 它會隨時間變化 **保持不變** 嗎? 會的話,那它就不是 state。 +* 它是透過 props **從父元件傳進來的** 嗎? 是的話,那它就不是 state。 +* 你能根據元件中現有的 state 或 props 計算出它嗎? 可以的話,那它 *絕對* 不是 state! -What's left is probably state. +剩下的那些資料,很可能就是要放進 state 的了。 -Let's go through them one by one again: +讓我們再一起逐項看過這些資料: -1. The original list of products is **passed in as props, so it's not state.** -2. The search text seems to be state since it changes over time and can't be computed from anything. -3. The value of the checkbox seems to be state since it changes over time and can't be computed from anything. -4. The filtered list of products **isn't state because it can be computed** by taking the original list of products and filtering it according to the search text and value of the checkbox. - -This means only the search text and the value of the checkbox are state! Nicely done! +1. 原始的產品清單會 **透過 props 傳遞,所以它不能放進 state**。 +2. 搜尋文字看起來是 state ,因為它會隨時間變化而改變,且不能從任何東西計算出來。 +3. checkbox 的狀態看起來可以放進 state ,因為它會隨時間變化而改變,且不能從任何東西計算出來。 +4. 篩選後的產品清單 **不能放進 state ,因為它可以透過原始的產品清單被計算出來** ,且篩選本身是根據搜尋文字與 checkbox 狀態而來。 +這代表只有搜尋文字與 checkbox 狀態應該被放進 state 中!做得好! #### Props vs State {/*props-vs-state*/} -There are two types of "model" data in React: props and state. The two are very different: +在 React 中有兩種資料「模型」: props 和 state 。 它們兩者非常的不同: -* [**Props** are like arguments you pass](/learn/passing-props-to-a-component) to a function. They let a parent component pass data to a child component and customize its appearance. For example, a `Form` can pass a `color` prop to a `Button`. -* [**State** is like a component’s memory.](/learn/state-a-components-memory) It lets a component keep track of some information and change it in response to interactions. For example, a `Button` might keep track of `isHovered` state. +* [**Props** 就像你傳給函式的參數](/learn/passing-props-to-a-component) 。它們讓父元件能傳遞資料給子元件,並且客製化子元件的顯示。舉例來說, `Form` 可以傳遞 `color` prop 到 `Button` 。 +* [**State** 就像元件的記憶](/learn/state-a-components-memory) 。它讓元件可以持續追蹤一些資訊,並且根據互動來改變這些資訊。舉例來說, `Button` 可能會持續追蹤 `isHovered` 這個 state 。 -Props and state are different, but they work together. A parent component will often keep some information in state (so that it can change it), and *pass it down* to child components as their props. It's okay if the difference still feels fuzzy on the first read. It takes a bit of practice for it to really stick! +Props 和 state 雖然不同,但它們會一起運作。父元件通常會在 state 中存放一些資訊(這樣才能去改變它),然後再透過 props *往下傳遞* 到子元件。如果第一次閱讀到這裡,對於它們的不同還是感到模糊也沒關係。這需要一些練習,才能真正熟悉! -## Step 4: Identify where your state should live {/*step-4-identify-where-your-state-should-live*/} +## 第四步:辨識 state 應該放在哪裡 {/*step-4-identify-where-your-state-should-live*/} -After identifying your app’s minimal state data, you need to identify which component is responsible for changing this state, or *owns* the state. Remember: React uses one-way data flow, passing data down the component hierarchy from parent to child component. It may not be immediately clear which component should own what state. This can be challenging if you’re new to this concept, but you can figure it out by following these steps! +辨識完應用程式最小的 state 資料後,你需要辨識出哪個元件應該負責改變這個 state ,或者說 *擁有* 這個 state。記住: React 使用單向資料流,資料會透過元件階層,從父元件往下傳遞到子元件。剛開始也許沒辦法立刻清楚哪個元件該擁有哪個 state 。如果你第一次接觸到這個概念將會是一大挑戰,但你可以根據以下步驟來理解! -For each piece of state in your application: +針對你應用程式中的每一個 state: -1. Identify *every* component that renders something based on that state. -2. Find their closest common parent component--a component above them all in the hierarchy. -3. Decide where the state should live: - 1. Often, you can put the state directly into their common parent. - 2. You can also put the state into some component above their common parent. - 3. If you can't find a component where it makes sense to own the state, create a new component solely for holding the state and add it somewhere in the hierarchy above the common parent component. +1. 找出 *所有* 依賴該 state 來渲染畫面的元件。 +2. 找出這些元件最近的共同父元件 -- 也就是在元件層級中,位於它們之上的某個元件。 +3. 決定 state 該放在哪裡: + 1. 通常可以將 state 放在它們的共同父元件中。 + 2. 你也可以將 state 放在它們共同父元件之上的元件。 + 3. 如果沒辦法找到合理的元件來擁有 state,單獨建立一個新元件來處理 state,並且將這個新元件新增在元件階層中高於共同父元件上的位置。 -In the previous step, you found two pieces of state in this application: the search input text, and the value of the checkbox. In this example, they always appear together, so it makes sense to put them into the same place. +在前一個步驟中,你會發現這個應用程式中會有兩個 state:使用者輸入的搜尋文字,和 checkbox 的勾選狀態。在這個範例中,它們總是一起出現,所以將它們放在同一個元件中是合理的。 -Now let's run through our strategy for them: +現在,讓我們來為這些 state 套用我們的策略 : -1. **Identify components that use state:** - * `ProductTable` needs to filter the product list based on that state (search text and checkbox value). - * `SearchBar` needs to display that state (search text and checkbox value). -2. **Find their common parent:** The first parent component both components share is `FilterableProductTable`. -3. **Decide where the state lives**: We'll keep the filter text and checked state values in `FilterableProductTable`. +1. **找出使用 state 的元件:** + * `ProductTable` 需要透過 state(搜尋文字與 checkbox 勾選狀態)來過濾產品清單。 + * `SearchBar` 需要顯示這些 state(搜尋文字與 checkbox 勾選狀態)的內容。 +2. **找出它們的共同父元件:** 兩個 state 最近的共同父元件是 `FilterableProductTable`。 +3. **決定 state 要放在哪裡:** 我們會把搜尋文字與勾選狀態這兩個 state 值保存在 `FilterableProductTable`。 -So the state values will live in `FilterableProductTable`. +所以這兩個 state 值會被放在 `FilterableProductTable` 裡。 -Add state to the component with the [`useState()` Hook.](/reference/react/useState) Hooks are special functions that let you "hook into" React. Add two state variables at the top of `FilterableProductTable` and specify their initial state: +使用 [`useState()` Hook](/reference/react/useState) 將 state 新增進元件裡。 Hook 是一種特殊的函式,可以讓你在 React 中「鉤住」元件的生命週期與行為。在 `FilterableProductTable` 的頂端新增兩個 state 變數,並且為它們設定初始值: ```js function FilterableProductTable({ products }) { @@ -281,7 +280,7 @@ function FilterableProductTable({ products }) { const [inStockOnly, setInStockOnly] = useState(false); ``` -Then, pass `filterText` and `inStockOnly` to `ProductTable` and `SearchBar` as props: +接著,將 `filterText` 和 `inStockOnly` 這兩個值作為 props,傳遞給 `ProductTable` 和 `SearchBar`: ```js
@@ -295,7 +294,7 @@ Then, pass `filterText` and `inStockOnly` to `ProductTable` and `SearchBar` as p
``` -You can start seeing how your application will behave. Edit the `filterText` initial value from `useState('')` to `useState('fruit')` in the sandbox code below. You'll see both the search input text and the table update: +你開始可以看見應用程式是如何運作的了。在下方 sandbox 程式碼中,將 `useState('')` 改成 `useState('fruit')` 來改變 `filterText` 的初始值。你將會看到搜尋輸入的文字與表格兩者都會隨之更新: @@ -437,7 +436,7 @@ td { -Notice that editing the form doesn't work yet. There is a console error in the sandbox above explaining why: +請注意,目前編輯表格的功能還無法運作。在上方 sandbox 中,有一個 console 的錯誤訊息解釋為什麼無法運作: @@ -445,7 +444,7 @@ You provided a \`value\` prop to a form field without an \`onChange\` handler. T -In the sandbox above, `ProductTable` and `SearchBar` read the `filterText` and `inStockOnly` props to render the table, the input, and the checkbox. For example, here is how `SearchBar` populates the input value: +在上方的 sandbox 中,`ProductTable` 和 `SearchBar` 會讀取從 props傳入的 `filterText` 和 `inStockOnly` 來渲染表格、文字輸入框與 checkbox。舉例來說,下面這段程式碼就是 `SearchBar` 顯示值的方式: ```js {1,6} function SearchBar({ filterText, inStockOnly }) { @@ -457,16 +456,16 @@ function SearchBar({ filterText, inStockOnly }) { placeholder="Search..."/> ``` -However, you haven't added any code to respond to the user actions like typing yet. This will be your final step. +不過,你目前還沒有加入任何的程式碼來處理使用者的操作,例如輸入文字。這將會是你接下來的最後一個步驟。 -## Step 5: Add inverse data flow {/*step-5-add-inverse-data-flow*/} +## 第五步:加入反向資料流 {/*step-5-add-inverse-data-flow*/} -Currently your app renders correctly with props and state flowing down the hierarchy. But to change the state according to user input, you will need to support data flowing the other way: the form components deep in the hierarchy need to update the state in `FilterableProductTable`. +目前應用程式已經能透過 props 和 state 隨著階層結構往下流正確地渲染。但為了能根據使用者的輸入來改變 state,就需要支援資料往反方向流動:也就是在階層中最底部的表單元件,要能更新 `FilterableProductTable` 中的 state。 -React makes this data flow explicit, but it requires a little more typing than two-way data binding. If you try to type or check the box in the example above, you'll see that React ignores your input. This is intentional. By writing ``, you've set the `value` prop of the `input` to always be equal to the `filterText` state passed in from `FilterableProductTable`. Since `filterText` state is never set, the input never changes. +React 讓這種資料流變得更加明確,但它比起雙向資料綁定需要多寫一點程式碼。如果你試著在上方的範例中輸入文字或勾選 checkbox,你將發現 React 會忽略你的輸入。這是刻意設計的。當寫下 `` 時,其實是把 `input` 的 `value` prop 設定為 `FilterableProductTable` 提供的 `filterText` state。因此只要 `filterText` 不被更新,輸入框內的文字也就永遠無法改變。 -You want to make it so whenever the user changes the form inputs, the state updates to reflect those changes. The state is owned by `FilterableProductTable`, so only it can call `setFilterText` and `setInStockOnly`. To let `SearchBar` update the `FilterableProductTable`'s state, you need to pass these functions down to `SearchBar`: +你希望無論使用者何時修改表單輸入,state 都能依據這些變化來更新。但因為這些 state 是被 `FilterableProductTable` 所擁有的,所以只有它可以呼叫 `setFilterText` 和 `setInStockOnly`。為了使 `SearchBar` 能夠更新 `FilterableProductTable` 的 state,你需要傳遞將這些函式往下傳遞到 `SearchBar`: ```js {2,3,10,11} function FilterableProductTable({ products }) { @@ -482,7 +481,7 @@ function FilterableProductTable({ products }) { onInStockOnlyChange={setInStockOnly} /> ``` -Inside the `SearchBar`, you will add the `onChange` event handlers and set the parent state from them: +在 `SearchBar` 裡,你將新增 `onChange` 事件處理器,並透過它們來更新父元件的 state: ```js {4,5,13,19} function SearchBar({ @@ -506,7 +505,7 @@ function SearchBar({ onChange={(e) => onInStockOnlyChange(e.target.checked)} ``` -Now the application fully works! +現在應用程式可以完整運作了! @@ -656,8 +655,8 @@ td { -You can learn all about handling events and updating state in the [Adding Interactivity](/learn/adding-interactivity) section. +你可以在[加入互動性](/learn/adding-interactivity)章節中,學到所有關於事件處理與更新 state 的內容。 -## Where to go from here {/*where-to-go-from-here*/} +## 接下來該往哪裡走 {/*where-to-go-from-here*/} -This was a very brief introduction to how to think about building components and applications with React. You can [start a React project](/learn/installation) right now or [dive deeper on all the syntax](/learn/describing-the-ui) used in this tutorial. +這一張只是一個簡單的入門介紹,目的是告訴你如何用 React 思維建立元件與應用程式。現在你可以從[安裝](/learn/installation)章節開始,或深入了解[所有在本章節使用到的語法](/learn/describing-the-ui)。