From 5f08311b4fc14ab969f2b36f01dec0c35b9a6389 Mon Sep 17 00:00:00 2001 From: qinhua <352484005@qq.com> Date: Mon, 8 Nov 2021 20:30:24 +0800 Subject: [PATCH 1/7] docs(cn): sharing-state-between-components --- .../learn/sharing-state-between-components.md | 202 +++++++++--------- 1 file changed, 102 insertions(+), 100 deletions(-) diff --git a/beta/src/pages/learn/sharing-state-between-components.md b/beta/src/pages/learn/sharing-state-between-components.md index 43aa9ceffc..d12bc3924a 100644 --- a/beta/src/pages/learn/sharing-state-between-components.md +++ b/beta/src/pages/learn/sharing-state-between-components.md @@ -1,31 +1,33 @@ --- -title: Sharing State Between Components +title: 在组件间共享状态 +translators: + - qinhua --- -Sometimes, you want the state of two components to always change together. To do it, remove state from both of them, move it to their closest common parent, and then pass it down to them via props. This is known as "lifting state up", and it's one of the most common things you will do writing React code. +有时候,你希望两个组件的状态始终同步更改。要实现这一点,可以将相关状态从这两个组件上移除,并把状态放到它们的公共父级,再通过 props 将状态传递给这两个组件。这被称为“状态提升”,这是编写 React 代码时常做的事。 -- How to share state between components by lifting it up -- What are controlled and uncontrolled components +- 如何使用状态提升在组件之间共享状态 +- 什么是受控组件和非受控组件 -## Lifting state up by example +## 举例说明一下状态提升 {#lifting-state-up-by-example} -In this example, a parent `Accordion` component renders two separate `Panel`s: +在这个例子中,父组件 `Accordion` 渲染了 2 个独立的 `Panel` 组件。 * `Accordion` - `Panel` - `Panel` -Each `Panel` component has a boolean `isActive` state that determines whether its content is visible. +每个 `Panel` 组件都有一个布尔值 `isActive`,用于控制其内容是否可见。 -Press the Show button for both panels: +请点击 2 个面板中的显示按钮: @@ -41,7 +43,7 @@ function Panel({ title, children }) {

{children}

) : ( )} @@ -51,12 +53,12 @@ function Panel({ title, children }) { export default function Accordion() { return ( <> -

Almaty, Kazakhstan

- - With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. +

哈萨克斯坦,阿拉木图

+ + 阿拉木图人口约200万,是哈萨克斯坦最大的城市。它在 1929 年到 1997 年间都是首都。 - - The name comes from алма, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild Malus sieversii is considered a likely candidate for the ancestor of the modern domestic apple. + + 这个名字来自于 алма,哈萨克语中“苹果”的意思,经常被翻译成“苹果之乡”。事实上,阿拉木图的周边地区被认为是苹果的发源地,Malus sieversii 被认为是现今苹果的祖先。 ); @@ -73,23 +75,23 @@ h3, p { margin: 5px 0px; }
-Notice how pressing one panel's button does not affect the other panel--they are independent. +我们发现点击其中一个面板中的按钮并不会影响另外一个——他们是独立的。 -**But now let's say you want to change it so that only one panel is expanded at any given time.** With that design, expanding the second panel should collapse the first one. How would you do that? +**假设现在你想改变这种行为,保证同一时间只展开 1 个面板。** 基于这种设计,展开第 2 个面板时应该折叠第 1 个面板。你想怎么实现呢? -To coordinate these two panels, you need to "lift their state up" to a parent component in three steps: +要协调好这两个面板,我们需要分 3 步将状态“提升”到他们的父组件中。 -1. **Remove** state from the child components. -2. **Pass** hardcoded data from the common parent. -3. **Add** state to the common parent and pass it down together with the event handlers. +1. 从子组件中 **移除** 状态。 +2. 从父组件 **传递** 硬编码数据 +3. 为父组件添加状态并将其与事件处理函数一起向下传递 -This will allow the `Accordion` component to coordinate both `Panel`s and only expand one at a time. +这样,`Accordion` 组件就可以控制 2 个 `Panel` 组件,保证同一时间只能展开一个。 -On the left are two components each owning their own state values. On the right, they are the children of a parent component that owns both their state values. +左侧是两个独立的组件,每个组件都有自己的状态值。右侧是一个包含两个子组件的父组件,它同时拥有两个子组件的状态值。 -### Step 1: Remove state from the child components +### 第 1 步: 从子组件中移除状态 {#remove-state-from-the-child-components} -You will give control of the `Panel`'s `isActive` to its parent component. This means that the parent component will pass `isActive` to `Panel` as a prop instead. Start by **removing this line** from the `Panel` component: +你将把 `Panel` 组件对 `isActive` 的控制权交给他们的父组件。然后再把这个状态通过 `props` 传给子组件。我们先从 `Panel` 组件中 **删除这一行**: ```js const [isActive, setIsActive] = useState(false); @@ -101,17 +103,17 @@ And instead, add `isActive` to the `Panel`'s list of props: function Panel({ title, children, isActive }) { ``` -Now the `Panel`'s parent component can *control* `isActive` by [passing it down as a prop](/learn/passing-props-to-a-component). Conversely, the `Panel` component now has *no control* over the value of `isActive`--it's now up to the parent component! +现在,`Panel` 的父组件就可以通过 [向下传递 prop](/learn/passing-props-to-a-component) 来 *控制* `isActive`。这样就反过来了,`Panel` 组件对 `isActive` 的值 *没有控制权* ——现在完全由父组件决定! -### Step 2: Pass hardcoded data from the common parent +### 第 2 步: 从公共父组件传递硬编码数据 {#pass-hardcoded-data-from-the-common-parent} -To lift state up, you must locate the closest common parent component of *both* of the child components that you want to coordinate: +为了实现状态提升,必须找到 *两个* 子组件最近的公共父组件: -* `Accordion` *(closest common parent)* +* `Accordion` *(最近的公共父组件)* - `Panel` - `Panel` -In this example, it's the `Accordion` component. Since it's above both panels and can control their props, it will become the "source of truth" for which panel is currently active. Make the `Accordion` component pass a hardcoded value of `isActive` (for example, `true`) to both panels: +在这个例子中,公共父组件是 `Accordion`。因为它位于两个面板之上,可以控制它们的 props,所以它将成为当前激活的面板的“真相之源”。通过 `Accordion` 组件将硬编码值 `isActive`(例如 `true` )传递给两个面板: @@ -121,12 +123,12 @@ import { useState } from 'react'; export default function Accordion() { return ( <> -

Almaty, Kazakhstan

- - With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. +

哈萨克斯坦,阿拉木图

+ + 阿拉木图人口约200万,是哈萨克斯坦最大的城市。它在 1929 年到 1997 年间都是首都。 - - The name comes from алма, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild Malus sieversii is considered a likely candidate for the ancestor of the modern domestic apple. + + 这个名字来自于 алма,哈萨克语中“苹果”的意思,经常被翻译成“苹果之乡”。事实上,阿拉木图的周边地区被认为是苹果的发源地,Malus sieversii 被认为是现今苹果的祖先。 ); @@ -140,7 +142,7 @@ function Panel({ title, children, isActive }) {

{children}

) : ( )} @@ -158,23 +160,23 @@ h3, p { margin: 5px 0px; }
-Try editing the hardcoded `isActive` values in the `Accordion` component and see the result on the screen. +尝试修改 `Accordion` 组件中 `isActive` 的值,并在屏幕上查看结果。 -### Step 3: Add state to the common parent +### 第 3 步: 为公共父组件添加状态 {#add-state-to-the-common-parent} -Lifting state up often changes the nature of what you're storing as state. +状态提升通常会改变原状态的数据存储类型。 The parent component passes the state setting function to both child components. -In this case, only one panel should be active at a time. This means that the `Accordion` common parent component needs to keep track of *which* panel is the active one. Instead of a `boolean` value, it could use an number as the index of the active `Panel` for the state variable: +在这个例子中,一次只能激活一个面板。这意味着 `Accordion` 这个父组件需要追踪 *哪个* 面板是活动面板。我们可以用个数字来代表当前活动的 `Panel` 的索引,而不是 `boolean` 值: ```js const [activeIndex, setActiveIndex] = useState(0); ``` -When the `activeIndex` is `0`, the first panel is active, and when it's `1`, it's the second one. +当 `activeIndex` 为 `0` 时,激活第一个面板,为 `1` 时,激活第二个面板。 -Clicking the "Show" button in either `Panel` needs to change the active index in `Accordion`. A `Panel` can't set the `activeIndex` state directly because it's defined inside the `Accordion`. The `Accordion` component needs to *explicitly allow* the `Panel` component to change its state by [passing an event handler down as a prop](/learn/responding-to-events#passing-event-handlers-as-props): +点击 `Panel` 中的“显示”按钮需要更改 `Accordion` 中的活动索引。 `Panel` 中无法直接设置`activeIndex` 的值,因为它是在 `Accordion` 组件中定义的。 `Accordion` 组件需要 *明确地允许* `Panel` 组件通过 [将事件处理程序作为 prop 向下传递](/learn/responding-to-events#passing-event-handlers-as-props) 来更改其状态: ```js <> @@ -193,7 +195,7 @@ Clicking the "Show" button in either `Panel` needs to change the active index in ``` -The ` )} @@ -254,50 +256,50 @@ h3, p { margin: 5px 0px; } -This completes lifting state up! Moving state into the common parent component allowed you to coordinate the two panels. Using the active index instead of two "is shown" flags ensured that only one panel is active at a given time. And passing down the event handler to the child allowed the child to change the parent's state. +这样,我们就完成了对状态的提升!把状态移到公共父组件中可以让你更好的管理这两个面板。使用活动索引代替之前的 `是否显示` 标识确保了一次只能激活一个面板。而通过向下传递事件处理函数可以让子组件修改父组件中的状态。 - + -It is common to call a component with some local state "uncontrolled". For example, the original `Panel` component with an `isActive` state variable is uncontrolled because its parent cannot influence whether the panel is active or not. +通常我们把包含“不受控制”的状态的组件称为“非受控组件”。例如,原本带有 `isActive` 状态变量的 `Panel` 组件是不受控制的,因为其父组件无法改变面板的激活状态。 -In contrast, you might say a component is "controlled" when the important information in it is driven by props rather than its own local state. This lets the parent component fully specify its behavior. The final `Panel` component with the `isActive` prop is controlled by the `Accordion` component. +相反,当组件中的重要信息是由 props 而不是其自身状态驱动时,就可以认为该组件是“受控组件”。这就允许父组件完全指定其行为。最后带有 `isActive` 属性的 `Panel` 组件是由 `Accordion` 组件控制的。 -Uncontrolled components are easier to use within their parents because they require less configuration. But they're less flexible when you want to coordinate them together. Controlled components are maximally flexible, but they require the parent components to fully configure them with props. +非受控组件通常很简单,因为它们不需要太多配置。但是当你想把它们组合在一起使用时,就不那么灵活了。受控组件具有最大的灵活性,但它们需要父组件使用 props 对其进行配置。 -In practice, "controlled" and "uncontrolled" aren't strict technical terms--each component usually has some mix of both local state and props. However, this is a useful way to talk about how components are designed and what capabilities they offer. +事实上,“受控”和“非受控”并不是严格的技术术语——通常每个组件都同时拥有内部状态和 props。然而,这是讨论组件如何设计以及提供什么功能的一种有用方式。 -When writing a component, consider which information in it should be controlled (via props), and which information should be uncontrolled (via state). But you can always change your mind and refactor later. +当编写一个组件时,你应该考虑哪些信息应该受控制(通过 props),哪些信息不应该受控制(通过 state)。不过你后面可以随时更改并重构。 -## A single source of truth for each state +## 每个状态都对应唯一的数据源 {#a-single-source-of-truth-for-each-state} -In a React application, many components will have their own state. Some state may "live" close to the leaf components (components at the bottom of the tree) like inputs. Other state may "live" closer to the top of the app. For example, even client-side routing libraries are usually implemented by storing the current route in the React state, and passing it down by props! +在 React 应用中,很多组件都有自己的状态。一些状态可能“活跃”在叶子组件(如输入框)附近。其它状态可能在应用程序顶部附近“活动”。例如,路由器通常也是通过将当前路由存储在 React 状态中并通过 props 将其传递下去来实现的! -**For each unique piece of state, you will choose the component that "owns" it**. This principle is also known as having a ["single source of truth."](https://en.wikipedia.org/wiki/Single_source_of_truth) It doesn't mean that all state lives in one place--but that for _each_ piece of state, there is a _specific_ component that holds that piece of information. Instead of duplicating shared state between components, you will *lift it up* to their common shared parent, and *pass it down* to the children that need it. +**对于每个唯一的状态,都应该有与它对应的组件**。这一原则也被称为拥有[“可信单一数据源”](https://en.wikipedia.org/wiki/Single_source_of_truth)。它并不意味着所有状态都活跃在一个地方——但对每个状态来说,都有一个特定的组成部分来保存这些信息。与复制组件之间的共享状态不同,你需要 *将其提升* 到公共父级,并 *将其传递* 到需要它的子级中。 -Your app will change as you work on it. It is common that you will move state down or back up while you're still figuring out where each piece of the state "lives". This is all part of the process! +你的应用状态会随着操作而改变。通常,当你在处理每个状态的“活跃”位置时,会进行移除或备份操作。这是整个过程的一部分! -To see what this feels like in practice with a few more components, read [Thinking in React](/learn/thinking-in-react). +想了解在更多组件中的实践,请阅读 [React 思维](/learn/thinking-in-react). -* When you want to coordinate two components, move their state to their common parent. -* Then pass the information down through props from their common parent. -* Finally, pass the event handlers down so that the children can change the parent's state. -* It's useful to consider components as "controlled" (driven by props) or "uncontrolled" (driven by state). +* 当你想要整合两个组件时,将它们的状态移动到共同的父组件中。 +* 然后在父组件中通过 props 把信息传递下去。 +* 最后,向下传递事件处理程序,以便子组件可以改变父组件的状态。 +* 将组件视为“受控”(由属性驱动)或“不受控”(由状态驱动)是很有用的。 -### Synced inputs +### 同步输入状态 {#synced-inputs} -These two inputs are independent. Make them stay in sync: editing one input should update the other input with the same text, and vice versa. +现在有两个独立的输入框。为了让它们的状态保持同步:编辑一个输入框时应使用相同的文本更新另一个输入框,反之亦然。你需要将它们的状态提升到父组件中。 -You'll need to lift their state up into the parent component. +你需要将它们的状态移动到父组件中。 @@ -309,8 +311,8 @@ import { useState } from 'react'; export default function SyncedInputs() { return ( <> - - + + ); } @@ -344,7 +346,7 @@ label { display: block; } -Move the `text` state variable into the parent component along with the `handleChange` handler. Then pass them down as props to both of the `Input` components. This will keep them in sync. +将 `text` 状态变量与 `handleChange` 处理程序一起移动到父组件中。然后将它们作为props 传递给两个 `Input` 组件。这样它们就能保持同步了。 @@ -361,12 +363,12 @@ export default function SyncedInputs() { return ( <> @@ -397,17 +399,17 @@ label { display: block; } -### Filtering a list +### 列表过滤 {#filtering-a-list} -In this example, the `SearchBar` has its own `query` state that controls the text input. Its parent `FilterableList` component displays a `List` of items, but it doesn't take the search query into account. +在这个例子中,`SearchBar` 组件拥有一个用来控制输入框的 `query` 状态,它的父组件中展示了一个 `List` 组件,但是没有考虑搜索条件。 -Use the `filterItems(foods, query)` function to filter the list according to the search query. To test your changes, verify that typing "s" into the input filters down the list to "Sushi", "Shish kebab", and "Dim sum". +使用 `filterItems(foods, query)` 方法来通过搜索条件过滤列表项。为了测试你的修改,请在输入框中输入 “寿” 来验证是否能过滤出 “寿司”。 -Note that `filterItems` is already implemented and imported so you don't need to write it yourself! +可以看到 `filterItems` 已经自动引入了,所以不需要我们自己再引入了。 -You will want to remove the `query` state and the `handleChange` handler from the `SearchBar`, and move them to the `FilterableList`. Then pass them down to `SearchBar` as `query` and `onChange` props. +你需要从 `SearchBar` 组件中移除 `query` 状态和 `handleChange` 处理程序,并把它们移到 `FilterableList` 组件中。然后将 `query` 和 `onChange` 当做 props 向下传递给 `SearchBar` 组件。 @@ -436,7 +438,7 @@ function SearchBar() { return ( -## 每个状态都对应唯一的数据源 {#a-single-source-of-truth-for-each-state} +## 每个状态都对应唯一的数据源 {/*a-single-source-of-truth-for-each-state*/} 在 React 应用中,很多组件都有自己的状态。一些状态可能“活跃”在叶子组件(如输入框)附近。其它状态可能在应用程序顶部附近“活动”。例如,路由器通常也是通过将当前路由存储在 React 状态中并通过 props 将其传递下去来实现的! @@ -293,7 +293,7 @@ h3, p { margin: 5px 0px; } -### 同步输入状态 {#synced-inputs} +### 同步输入状态 {/*synced-inputs*/} 现在有两个独立的输入框。为了让它们的状态保持同步:编辑一个输入框时应使用相同的文本更新另一个输入框,反之亦然。你需要将它们的状态提升到父组件中。 @@ -399,7 +399,7 @@ label { display: block; } -### 列表过滤 {#filtering-a-list} +### 列表过滤 {/*filtering-a-list*/} 在这个例子中,`SearchBar` 组件拥有一个用来控制输入框的 `query` 状态,它的父组件中展示了一个 `List` 组件,但是没有考虑搜索条件。 From d80da02d5f11bbffe4c407d1d631d78289425b3b Mon Sep 17 00:00:00 2001 From: qinhua <352484005@qq.com> Date: Sun, 19 Feb 2023 11:12:49 +0800 Subject: [PATCH 3/7] chore: remove confilct characters --- beta/src/content/learn/sharing-state-between-components.md | 1 - 1 file changed, 1 deletion(-) diff --git a/beta/src/content/learn/sharing-state-between-components.md b/beta/src/content/learn/sharing-state-between-components.md index 0e9686a1f1..9be5434aa6 100644 --- a/beta/src/content/learn/sharing-state-between-components.md +++ b/beta/src/content/learn/sharing-state-between-components.md @@ -328,7 +328,6 @@ h3, p { margin: 5px 0px; } ### 同步输入状态 {/*synced-inputs*/} -======= 现在有两个独立的输入框。为了让它们保持同步:即编辑一个输入框时,另一个输入框也会更新相同的文本,反之亦然。 From c13530080c0869670d899cfe7d2f06158942477d Mon Sep 17 00:00:00 2001 From: qinhua <352484005@qq.com> Date: Sun, 19 Feb 2023 12:00:10 +0800 Subject: [PATCH 4/7] fix: duplicate key in Navigation.tsx --- .../components/MDX/Challenges/Navigation.tsx | 4 +-- .../learn/sharing-state-between-components.md | 26 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/beta/src/components/MDX/Challenges/Navigation.tsx b/beta/src/components/MDX/Challenges/Navigation.tsx index e448828cfb..eb05801113 100644 --- a/beta/src/components/MDX/Challenges/Navigation.tsx +++ b/beta/src/components/MDX/Challenges/Navigation.tsx @@ -65,7 +65,7 @@ export function Navigation({ if (containerRef.current) { const el = containerRef.current; el.scrollLeft = - challengesNavRef.current[scrollPos].current?.offsetLeft || 0; + challengesNavRef.current[scrollPos]?.current?.offsetLeft || 0; } }, [containerRef, challengesNavRef, scrollPos]); @@ -96,7 +96,7 @@ export function Navigation({ 'text-link border-link hover:text-link dark:text-link-dark dark:border-link-dark dark:hover:text-link-dark' )} onClick={() => handleSelectNav(index)} - key={`button-${id}`} + key={`button-${index}`} ref={challengesNavRef.current[index]}> {order}. {name} diff --git a/beta/src/content/learn/sharing-state-between-components.md b/beta/src/content/learn/sharing-state-between-components.md index 9be5434aa6..b2477160d2 100644 --- a/beta/src/content/learn/sharing-state-between-components.md +++ b/beta/src/content/learn/sharing-state-between-components.md @@ -75,7 +75,7 @@ h3, p { margin: 5px 0px; } -我们发现点击其中一个面板中的按钮并不会影响另外一个——他们是独立的。 +我们发现点击其中一个面板中的按钮并不会影响另外一个,他们是独立的。 @@ -94,7 +94,7 @@ h3, p { margin: 5px 0px; } -**假设现在您想修改这种行为,以便在任何时候只展开一个面板。** 在这种设计下,展开第 2 个面板应会折叠第 1 个面板。您该如何做到这一点呢?" +**假设现在您想改变这种行为,以便在任何时候只展开一个面板。** 在这种设计下,展开第 2 个面板应会折叠第 1 个面板。您该如何做到这一点呢?" 要协调好这两个面板,我们需要分 3 步将状态“提升”到他们的父组件中。 @@ -106,15 +106,13 @@ h3, p { margin: 5px 0px; } ### 第 1 步: 从子组件中移除状态 {/*step-1-remove-state-from-the-child-components*/} -### Step 1: Remove state from the child components {/*step-1-remove-state-from-the-child-components*/} - -你将把 `Panel` 组件对 `isActive` 的控制权交给他们的父组件。这意味着,父组件会将 `isActive` 作为 prop 传给子组件 `Panel`。我们可以先从 `Panel` 组件中 **删除这一行** 开始: +你将把 `Panel` 组件对 `isActive` 的控制权交给他们的父组件。这意味着,父组件会将 `isActive` 作为 `prop` 传给子组件 `Panel`。我们先从 `Panel` 组件中 **删除下面这一行**: ```js const [isActive, setIsActive] = useState(false); ``` -And instead, add `isActive` to the `Panel`'s list of props: +然后,把 `isActive` 加入 `Panel` 组件的 `props` 中: ```js function Panel({ title, children, isActive }) { @@ -284,7 +282,7 @@ h3, p { margin: 5px 0px; } -当 `Accordion` 组件中的 `activeIndex` 变为 `1` 时,第二个 `Panel` 组件接受到的属性变为 `isActive = true` +当 `Accordion` 组件中的 `activeIndex` 变为 `1` 时,第二个 `Panel` 组件接受到的属性变为 `isActive = true`,第一个 `Panel` 组件接受到的属性变为 `isActive = false` @@ -296,19 +294,19 @@ h3, p { margin: 5px 0px; } 通常我们把包含“不受控制”状态的组件称为“非受控组件”。例如,最开始带有 `isActive` 状态变量的 `Panel` 组件就是不受控制的,因为其父组件无法控制面板的激活状态。 -相反,当组件中的重要信息是由 props 而不是其自身状态驱动时,就可以认为该组件是“受控组件”。这就允许父组件完全指定其行为。最后带有 `isActive` 属性的 `Panel` 组件是由 `Accordion` 组件控制的。 +相反,当组件中的重要信息是由 `props` 而不是其自身状态驱动时,就可以认为该组件是“受控组件”。这就允许父组件完全指定其行为。最后带有 `isActive` 属性的 `Panel` 组件是由 `Accordion` 组件控制的。 -非受控组件通常很简单,因为它们不需要太多配置。但是当你想把它们组合在一起使用时,就不那么灵活了。受控组件具有最大的灵活性,但它们需要父组件使用 props 对其进行配置。 +非受控组件通常很简单,因为它们不需要太多配置。但是当你想把它们组合在一起使用时,就不那么灵活了。受控组件具有最大的灵活性,但它们需要父组件使用 `props` 对其进行配置。 -在实践中,“受控”和“非受控”并不是严格的技术术语——通常每个组件都同时拥有内部状态和 props。然而,这对于组件该如何设计和提供什么样功能的讨论是有帮助的。 +在实践中,“受控”和“非受控”并不是严格的技术术语——通常每个组件都同时拥有内部状态和 `props`。然而,这对于组件该如何设计和提供什么样功能的讨论是有帮助的。 -当编写一个组件时,你应该考虑哪些信息应该受控制(通过 props),哪些信息不应该受控制(通过 state)。当然,你可以随时改变主意并重构代码。 +当编写一个组件时,你应该考虑哪些信息应该受控制(通过 `props`),哪些信息不应该受控制(通过 `state`)。当然,你可以随时改变主意并重构代码。 ## 每个状态都对应唯一的数据源 {/*a-single-source-of-truth-for-each-state*/} -在 React 应用中,很多组件都有自己的状态。一些状态可能“活跃”在叶子组件(树形结构最底层的组件)附近,例如输入框。另一些状态可能在应用程序顶部“活动”。例如,客户端路由库也是通过将当前路由存储在 React 状态中,利用 props 将状态层层传递下去来实现的! +在 `React` 应用中,很多组件都有自己的状态。一些状态可能“活跃”在叶子组件(树形结构最底层的组件)附近,例如输入框。另一些状态可能在应用程序顶部“活动”。例如,客户端路由库也是通过将当前路由存储在 `React` 状态中,利用 `props` 将状态层层传递下去来实现的! **对于每个独一无二的状态,都应该存在拥有该状态的组件**。这一原则也被称为拥有 [“可信单一数据源”](https://en.wikipedia.org/wiki/Single_source_of_truth)。它并不意味着所有状态都活跃在一个地方——对每个状态来说,都需要一个特定的组件来保存这些状态信息。你应该 *将状态提升* 到公共父级,或 *将状态传递* 到需要它的子级中,而不是在组件之间复制共享的状态。 @@ -319,7 +317,7 @@ h3, p { margin: 5px 0px; } * 当你想要整合两个组件时,将它们的状态移动到共同的父组件中。 -* 然后在父组件中通过 props 把信息传递下去。 +* 然后在父组件中通过 `props` 把信息传递下去。 * 最后,向下传递事件处理程序,以便子组件可以改变父组件的状态。 * 考虑该将组件视为“受控”(由属性驱动)或是“不受控”(由状态驱动)是十分有益的。 @@ -380,7 +378,7 @@ label { display: block; } -将 `text` 状态变量与 `handleChange` 处理程序一起移动到父组件中。然后将它们作为props 传递给两个 `Input` 组件。这样它们就能保持同步了。 +将 `text` 状态变量与 `handleChange` 处理程序一起移动到父组件中。然后将它们作为 `props` 传递给两个 `Input` 组件。这样它们就能保持同步了。 From 795c66b37c01d5e4a41180198ea9ca45b09c8e16 Mon Sep 17 00:00:00 2001 From: qinhua <352484005@qq.com> Date: Sun, 19 Feb 2023 13:52:07 +0800 Subject: [PATCH 5/7] update: reformate md file --- beta/src/components/MDX/Challenges/Navigation.tsx | 4 ++-- beta/src/content/learn/sharing-state-between-components.md | 4 ++-- beta/src/sidebarLearn.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/beta/src/components/MDX/Challenges/Navigation.tsx b/beta/src/components/MDX/Challenges/Navigation.tsx index eb05801113..e448828cfb 100644 --- a/beta/src/components/MDX/Challenges/Navigation.tsx +++ b/beta/src/components/MDX/Challenges/Navigation.tsx @@ -65,7 +65,7 @@ export function Navigation({ if (containerRef.current) { const el = containerRef.current; el.scrollLeft = - challengesNavRef.current[scrollPos]?.current?.offsetLeft || 0; + challengesNavRef.current[scrollPos].current?.offsetLeft || 0; } }, [containerRef, challengesNavRef, scrollPos]); @@ -96,7 +96,7 @@ export function Navigation({ 'text-link border-link hover:text-link dark:text-link-dark dark:border-link-dark dark:hover:text-link-dark' )} onClick={() => handleSelectNav(index)} - key={`button-${index}`} + key={`button-${id}`} ref={challengesNavRef.current[index]}> {order}. {name} diff --git a/beta/src/content/learn/sharing-state-between-components.md b/beta/src/content/learn/sharing-state-between-components.md index b2477160d2..f8b83c2e4a 100644 --- a/beta/src/content/learn/sharing-state-between-components.md +++ b/beta/src/content/learn/sharing-state-between-components.md @@ -325,7 +325,7 @@ h3, p { margin: 5px 0px; } -### 同步输入状态 {/*synced-inputs*/} +#### 同步输入状态 {/*synced-inputs*/} 现在有两个独立的输入框。为了让它们保持同步:即编辑一个输入框时,另一个输入框也会更新相同的文本,反之亦然。 @@ -431,7 +431,7 @@ label { display: block; } -### 列表过滤 {/*filtering-a-list*/} +#### 列表过滤 {/*filtering-a-list*/} 在这个例子中,`SearchBar` 组件拥有一个用来控制输入框的 `query` 状态,它的父组件中展示了一个 `List` 组件,但是没有考虑搜索条件。 diff --git a/beta/src/sidebarLearn.json b/beta/src/sidebarLearn.json index 90fed7e260..ebef19a628 100644 --- a/beta/src/sidebarLearn.json +++ b/beta/src/sidebarLearn.json @@ -139,7 +139,7 @@ "path": "/learn/choosing-the-state-structure" }, { - "title": "组件间共享状态", + "title": "在组件间共享状态", "path": "/learn/sharing-state-between-components" }, { From 99b7118bed860461460835ab33345b97fac9653b Mon Sep 17 00:00:00 2001 From: Baby Chin <352484005@qq.com> Date: Mon, 20 Feb 2023 11:34:47 +0800 Subject: [PATCH 6/7] Update beta/src/content/learn/sharing-state-between-components.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @daochouwangu 确认一下,现在翻译的时候,英文还要加行内代码符号吗? Co-authored-by: TimLi --- beta/src/content/learn/sharing-state-between-components.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beta/src/content/learn/sharing-state-between-components.md b/beta/src/content/learn/sharing-state-between-components.md index f8b83c2e4a..ec0aa8f162 100644 --- a/beta/src/content/learn/sharing-state-between-components.md +++ b/beta/src/content/learn/sharing-state-between-components.md @@ -308,7 +308,7 @@ h3, p { margin: 5px 0px; } 在 `React` 应用中,很多组件都有自己的状态。一些状态可能“活跃”在叶子组件(树形结构最底层的组件)附近,例如输入框。另一些状态可能在应用程序顶部“活动”。例如,客户端路由库也是通过将当前路由存储在 `React` 状态中,利用 `props` 将状态层层传递下去来实现的! -**对于每个独一无二的状态,都应该存在拥有该状态的组件**。这一原则也被称为拥有 [“可信单一数据源”](https://en.wikipedia.org/wiki/Single_source_of_truth)。它并不意味着所有状态都活跃在一个地方——对每个状态来说,都需要一个特定的组件来保存这些状态信息。你应该 *将状态提升* 到公共父级,或 *将状态传递* 到需要它的子级中,而不是在组件之间复制共享的状态。 +**对于每个独特的状态,都应该存在且只存在于一个指定的组件中作为 state **。这一原则也被称为拥有 [“可信单一数据源”](https://en.wikipedia.org/wiki/Single_source_of_truth)。它并不意味着所有状态都存在一个地方——对每个状态来说,都需要一个特定的组件来保存这些状态信息。你应该 *将状态提升* 到公共父级,或 *将状态传递* 到需要它的子级中,而不是在组件之间复制共享的状态。 你的应用会随着你的操作而变化。当你将状态上下移动时,你依然会想要确定每个状态在哪里“活跃”。这都是过程的一部分! From 4264898bfb33381c846e3cb3a930407762c9971f Mon Sep 17 00:00:00 2001 From: Xavi Lee Date: Sat, 11 Mar 2023 08:34:11 +0800 Subject: [PATCH 7/7] Apply suggestions from code review Co-authored-by: TimLi --- .../learn/sharing-state-between-components.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/beta/src/content/learn/sharing-state-between-components.md b/beta/src/content/learn/sharing-state-between-components.md index ec0aa8f162..8190241236 100644 --- a/beta/src/content/learn/sharing-state-between-components.md +++ b/beta/src/content/learn/sharing-state-between-components.md @@ -6,7 +6,7 @@ translators: -有时候,你希望两个组件的状态始终同步更改。要实现这一点,可以将相关状态从这两个组件上移除,并把状态放到它们的公共父级,再通过 props 将状态传递给这两个组件。这被称为“状态提升”,这是编写 React 代码时常做的事。 +有时候,你希望两个组件的状态始终同步更改。要实现这一点,可以将相关 state 从这两个组件上移除,并把 state 放到它们的公共父级,再通过 props 将 state 传递给这两个组件。这被称为“状态提升”,这是编写 React 代码时常做的事。 @@ -98,9 +98,9 @@ h3, p { margin: 5px 0px; } 要协调好这两个面板,我们需要分 3 步将状态“提升”到他们的父组件中。 -1. 从子组件中 **移除** 状态。 +1. 从子组件中 **移除** state 。 2. 从父组件 **传递** 硬编码数据。 -3. 为共同的父组件添加状态,并将其与事件处理函数一起向下传递。 +3. 为共同的父组件添加 state ,并将其与事件处理函数一起向下传递。 这样,`Accordion` 组件就可以控制 2 个 `Panel` 组件,保证同一时间只能展开一个。 @@ -316,10 +316,10 @@ h3, p { margin: 5px 0px; } -* 当你想要整合两个组件时,将它们的状态移动到共同的父组件中。 +* 当你想要整合两个组件时,将它们的 state 移动到共同的父组件中。 * 然后在父组件中通过 `props` 把信息传递下去。 -* 最后,向下传递事件处理程序,以便子组件可以改变父组件的状态。 -* 考虑该将组件视为“受控”(由属性驱动)或是“不受控”(由状态驱动)是十分有益的。 +* 最后,向下传递事件处理程序,以便子组件可以改变父组件的 state 。 +* 考虑该将组件视为“受控”(由 prop 驱动)或是“不受控”(由 state 驱动)是十分有益的。