diff --git a/content/docs/thinking-in-react.md b/content/docs/thinking-in-react.md index 53caa2b208..e2abbe84df 100644 --- a/content/docs/thinking-in-react.md +++ b/content/docs/thinking-in-react.md @@ -1,6 +1,6 @@ --- id: thinking-in-react -title: Thinking in React +title: React 哲学 permalink: docs/thinking-in-react.html redirect_from: - 'blog/2013/11/05/thinking-in-react.html' @@ -8,17 +8,17 @@ redirect_from: prev: composition-vs-inheritance.html --- -React is, in our opinion, the premier way to build big, fast Web apps with JavaScript. It has scaled very well for us at Facebook and Instagram. +我们认为,React 是用 JavaScript 构建快速响应的大型 Web 应用程序的首选方式。它在 Facebook 和 Instagram 上表现优秀。 -One of the many great parts of React is how it makes you think about apps as you build them. In this document, we'll walk you through the thought process of building a searchable product data table using React. +React 最棒的部分之一是引导我们思考如何构建一个应用。在这篇文档中,我们将会通过 React 构建一个可搜索的产品数据表格来更深刻地领会 React 哲学。 -## Start With A Mock +## 从原型图开始 -Imagine that we already have a JSON API and a mock from our designer. The mock looks like this: +假设我们已经有了一个返回 JSON 的 API,以及设计师提供的组件原型图。如下所示: ![Mockup](../images/blog/thinking-in-react-mock.png) -Our JSON API returns some data that looks like this: +该 JSON API 会返回以下数据: ``` [ @@ -31,27 +31,27 @@ Our JSON API returns some data that looks like this: ]; ``` -## Step 1: Break The UI Into A Component Hierarchy +## 第一步:将设计好的 UI 划分为组件层级 -The first thing you'll want to do is to draw boxes around every component (and subcomponent) in the mock and give them all names. If you're working with a designer, they may have already done this, so go talk to them! Their Photoshop layer names may end up being the names of your React components! +首先,你需要在原型图上用方框圈出每一个组件(包括它们的子组件),并且以合适的名称命名。如果你是和设计师一起完成此任务,那么他们可能已经做过类似的工作,所以请和他们进行交流!他们的 Photoshop 的图层名称可能最终就是你编写的 React 组件的名称! -But how do you know what should be its own component? Just 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. +但你如何确定应该将哪些部分划分到一个组件中呢?你可以将组件当作一种函数或者是对象来考虑,根据[单一功能原则](https://en.wikipedia.org/wiki/Single_responsibility_principle)来判定组件的范围。也就是说,一个组件原则上只能负责一个功能。如果它需要负责更多的功能,这时候就应该考虑将它拆分成更小的组件。 -Since you're often displaying a JSON data model to a user, you'll find that if your model was built correctly, your UI (and therefore your component structure) will map nicely. That's because UI and data models tend to adhere to the same *information architecture*, which means the work of separating your UI into components is often trivial. Just break it up into components that represent exactly one piece of your data model. +在实践中,因为你经常是在向用户展示 JSON 数据模型,所以如果你的模型设计得恰当,UI(或者说组件结构)便会与数据模型一一对应,这是因为 UI 和数据模型都会倾向于遵守相同的*信息结构*。因此,将用户界面划分为组件的工作量就可以忽略不计了。只需使组件完全对应地展现数据模型的某部分即可。 ![Component diagram](../images/blog/thinking-in-react-components.png) -You'll see here that we have five components in our simple app. We've italicized the data each component represents. +你会看到我们的简单应用中包含五个组件。我们已经将每个组件展示的数据标注为了斜体。 - 1. **`FilterableProductTable` (orange):** contains the entirety of the example - 2. **`SearchBar` (blue):** receives all *user input* - 3. **`ProductTable` (green):** displays and filters the *data collection* based on *user input* - 4. **`ProductCategoryRow` (turquoise):** displays a heading for each *category* - 5. **`ProductRow` (red):** displays a row for each *product* + 1. **`FilterableProductTable` (橙色):** 是整个示例应用的整体 + 2. **`SearchBar` (蓝色):** 接受所有的*用户输入* + 3. **`ProductTable` (绿色):** 展示*数据内容*并根据*用户输入*筛选结果 + 4. **`ProductCategoryRow` (天蓝色):** 为每一个*产品类别*展示标题 + 5. **`ProductRow` (红色):** 每一行展示一个*产品* -If you look at `ProductTable`, 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 there's an argument to be made either way. For this example, we left it as part of `ProductTable` because it is part of rendering the *data collection* which is `ProductTable`'s responsibility. However, if this header grows to be complex (i.e. if we were to add affordances for sorting), it would certainly make sense to make this its own `ProductTableHeader` component. +你可能注意到,`ProductTable` 的表头(包含 "Name" 和 "Price" 的那一部分)并未单独成为一个组件。这仅仅是一种偏好选择,如何处理这一问题也一直存在争论。就这个示例而言,因为表头只起到了渲染*数据集合*的作用——这与 `ProductTable` 是一致的,所以我们仍然将其保留为 `ProductTable` 的一部分。但是,如果表头过于复杂(比如我们需为其添加排序功能),那么将它作为一个独立的 `ProductTableHeader` 组件就显得很有必要了。 -Now that we've identified the components in our mock, let's arrange them into a hierarchy. This is easy. Components that appear within another component in the mock should appear as a child in the hierarchy: +现在我们已经确定了原型图中应该包含的组件,接下来我们将把它们描述为更加清晰的层级。原型图中被其他组件包含的子组件,在层级上应该作为其子节点。 * `FilterableProductTable` * `SearchBar` @@ -59,90 +59,90 @@ Now that we've identified the components in our mock, let's arrange them into a * `ProductCategoryRow` * `ProductRow` -## Step 2: Build A Static Version in React +## 第二步:用 React 创建一个静态版本 -

See the Pen Thinking In React: Step 2 on CodePen.

+

参阅 CodePen 上的 React 哲学:第二步

-Now that you have your component hierarchy, it's time to implement your app. The easiest way is to build a version that takes your data model and renders the UI but has no interactivity. It's best to decouple these processes because building a static version requires a lot of typing and no thinking, and adding interactivity requires a lot of thinking and not a lot of typing. We'll see why. +现在我们已经确定了组件层级,可以编写对应的应用了。最容易的方式,是先用已有的数据模型渲染一个不包含交互功能的 UI。最好将渲染 UI 和添加交互这两个过程分开。这是因为,编写一个应用的静态版本时,往往要编写大量代码,而不需要考虑太多交互细节;添加交互功能时则要考虑大量细节,而不需要编写太多代码。所以,将这两个过程分开进行更为合适。我们会在接下来的代码中体会到其中的区别。 -To build a static version of your app that renders your data model, you'll want to build components that reuse other components and pass data using *props*. *props* are a way of passing data from parent to child. If you're familiar with the concept of *state*, **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. +在构建应用的静态版本时,我们需要创建一些会重用其他组件的组件,然后通过 *props* 传入所需的数据。*props* 是父组件向子组件传递数据的方式。即使你已经熟悉了 *state* 的概念,也**完全不应该使用 state** 构建静态版本。state 代表了随时间会产生变化的数据,应当仅在实现交互时使用。所以构建应用的静态版本时,你不会用到它。 -You can build top-down or bottom-up. That is, you can either start with building the components higher up in the hierarchy (i.e. starting with `FilterableProductTable`) or with the ones lower in it (`ProductRow`). In simpler examples, it's usually easier to go top-down, and on larger projects, it's easier to go bottom-up and write tests as you build. +你可以自上而下或者自下而上构建应用:自上而下意味着首先编写层级较高的组件(比如 `FilterableProductTable`),自下而上意味着从最基本的组件开始编写(比如 `ProductRow`)。当你的应用比较简单时,使用自上而下的方式更方便;对于较为大型的项目来说,自下而上地构建,并同时为低层组件编写测试是更加简单的方式。 -At the end of this step, you'll have a library of reusable components that render your data model. The components will only have `render()` methods since this is a static version of your app. The component at the top of the hierarchy (`FilterableProductTable`) will take your data model as a prop. If you make a change to your underlying data model and call `ReactDOM.render()` again, the UI will be updated. It's easy to see how your UI is updated and where to make changes since there's nothing complicated going on. React's **one-way data flow** (also called *one-way binding*) keeps everything modular and fast. +到此为止,你应该已经有了一个可重用的组件库来渲染你的数据模型。由于我们构建的是静态版本,所以这些组件目前只需提供 `render()` 方法用于渲染。最顶层的组件 `FilterableProductTable` 通过 props 接受你的数据模型。如果你的数据模型发生了改变,再次调用 `ReactDOM.render()`,UI 就会相应地被更新。数据模型变化、调用 `render()` 方法、UI 相应变化,这个过程并不复杂,因此很容易看清楚 UI 是如何被更新的,以及是在哪里被更新的。React **单向数据流**(也叫*单向绑定*)的思想使得组件模块化,易于快速开发。 -Simply refer to the [React docs](/docs/) if you need help executing this step. +如果你在完成这一步骤时遇到了困难,可以参阅 [React 文档](/docs/)。 -### A Brief Interlude: Props vs State +### 补充说明: 有关 props 和 state -There are two types of "model" data in React: props and state. It's important to understand the distinction between the two; skim [the official React docs](/docs/interactivity-and-dynamic-uis.html) if you aren't sure what the difference is. +在 React 中,有两类“模型”数据:props 和 state。清楚地理解两者的区别是十分重要的;如果你不太有把握,可以参阅 [React 官方文档](/docs/interactivity-and-dynamic-uis.html)。 -## Step 3: Identify The Minimal (but complete) Representation Of UI State +## 第三步:确定 UI state 的最小(且完整)表示 -To make your UI interactive, you need to be able to trigger changes to your underlying data model. React makes this easy with **state**. +想要使你的 UI 具备交互功能,需要有触发基础数据模型改变的能力。React 通过 **state** 来完成这个任务。 -To build your app correctly, you first need to think of the minimal set of mutable state that your app needs. The key here is [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 you need on-demand. For example, if you're building a TODO list, just keep an array of the TODO items around; don't keep a separate state variable for the count. Instead, when you want to render the TODO count, simply take the length of the TODO items array. +为了正确地构建应用,你首先需要找出应用所需的 state 的最小表示,并根据需要计算出其他所有数据。其中的关键正是 [DRY: *Don't Repeat Yourself*](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)。只保留应用所需的可变 state 的最小集合,其他数据均由它们计算产生。比如,你要编写一个任务清单应用,你只需要保存一个包含所有事项的数组,而无需额外保存一个单独的 state 变量(用于存储任务个数)。当你需要展示任务个数时,只需要利用该数组的 length 属性即可。 -Think of all of the pieces of data in our example application. We have: +我们的示例应用拥有如下数据: - * The original list of products - * The search text the user has entered - * The value of the checkbox - * The filtered list of products + * 包含所有产品的原始列表 + * 用户输入的搜索词 + * 复选框是否选中的值 + * 经过搜索筛选的产品列表 -Let's go through each one and figure out which one is state. Simply ask three questions about each piece of data: +通过问自己以下三个问题,你可以逐个检查相应数据是否属于 state: - 1. Is it passed in from a parent via props? If so, it probably isn't state. - 2. Does it remain unchanged over time? If so, it probably isn't state. - 3. Can you compute it based on any other state or props in your component? If so, it isn't state. + 1. 该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。 + 2. 该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。 + 3. 你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。 -The original list of products is passed in as props, so that's not state. The search text and the checkbox seem to be state since they change over time and can't be computed from anything. And finally, the filtered list of products isn't state because it can be computed by combining the original list of products with the search text and value of the checkbox. +包含所有产品的原始列表是经由 props 传入的,所以它不是 state;搜索词和复选框的值应该是 state,因为它们随时间会发生改变且无法由其他数据计算而来;经过搜索筛选的产品列表不是 state,因为它的结果可以由产品的原始列表根据搜索词和复选框的选择计算出来。 -So finally, our state is: +综上所述,属于 state 的有: - * The search text the user has entered - * The value of the checkbox + * 用户输入的搜索词 + * 复选框是否选中的值 -## Step 4: Identify Where Your State Should Live +## 第四步:确定 state 放置的位置 -

See the Pen Thinking In React: Step 4 on CodePen.

+

参阅 CodePen 上的 React 哲学:第四步

-OK, so we've identified what the minimal set of app state is. Next, we need to identify which component mutates, or *owns*, this state. +我们已经确定了应用所需的 state 的最小集合。接下来,我们需要确定哪个组件能够改变这些 state,或者说*拥有*这些 state。 -Remember: React is all about one-way data flow down the component hierarchy. It may not be immediately clear which component should own what state. **This is often the most challenging part for newcomers to understand,** so follow these steps to figure it out: +注意:React 中的数据流是单向的,并顺着组件层级从上往下传递。哪个组件应该拥有某个 state 这件事,**对初学者来说往往是最难理解的部分**。尽管这可能在一开始不是那么清晰,但你可以尝试通过以下步骤来判断: -For each piece of state in your application: +对于应用中的每一个 state: - * Identify every component that renders something based on that state. - * Find a common owner component (a single component above all the components that need the state in the hierarchy). - * Either the common owner or another component higher up in the hierarchy should own the state. - * If you can't find a component where it makes sense to own the state, create a new component simply for holding the state and add it somewhere in the hierarchy above the common owner component. + * 找到根据这个 state 进行渲染的所有组件。 + * 找到他们的共同所有者(common owner)组件(在组件层级上高于所有需要该 state 的组件)。 + * 该共同所有者组件或者比它层级更高的组件应该拥有该 state。 + * 如果你找不到一个合适的位置来存放该 state,就可以直接创建一个新的组件来存放该 state,并将这一新组件置于高于共同所有者组件层级的位置。 -Let's run through this strategy for our application: +根据以上策略重新考虑我们的示例应用: - * `ProductTable` needs to filter the product list based on state and `SearchBar` needs to display the search text and checked state. - * The common owner component is `FilterableProductTable`. - * It conceptually makes sense for the filter text and checked value to live in `FilterableProductTable` + * `ProductTable` 需要根据 state 筛选产品列表。`SearchBar` 需要展示搜索词和复选框的状态。 + * 他们的共同所有者是 `FilterableProductTable`。 + * 因此,搜索词和复选框的值应该很自然地存放在 `FilterableProductTable` 组件中。 -Cool, so we've decided that our state lives in `FilterableProductTable`. First, add an instance property `this.state = {filterText: '', inStockOnly: false}` to `FilterableProductTable`'s `constructor` to reflect the initial state of your application. Then, pass `filterText` and `inStockOnly` to `ProductTable` and `SearchBar` as a prop. Finally, use these props to filter the rows in `ProductTable` and set the values of the form fields in `SearchBar`. +很好,我们已经决定把这些 state 存放在 `FilterableProductTable` 组件中。首先,将实例属性 `this.state = {filterText: '', inStockOnly: false}` 添加到 `FilterableProductTable` 的 `constructor` 中,设置应用的初始 state;接着,将 `filterText` 和 `inStockOnly` 作为 props 传入 `ProductTable` 和 `SearchBar`;最后,用这些 props 筛选 `ProductTable` 中的产品信息,并设置 `SearchBar` 的表单值。 -You can start seeing how your application will behave: set `filterText` to `"ball"` and refresh your app. You'll see that the data table is updated correctly. +你现在可以看到应用的变化了:将 `filterText` 设置为 `"ball"` 并刷新应用,你能发现表格中的数据已经更新了。 -## Step 5: Add Inverse Data Flow +## 第五步:添加反向数据流 -

See the Pen Thinking In React: Step 5 on CodePen.

+

参阅 CodePen 上的 React 哲学:第五步

-So far, we've built an app that renders correctly as a function of props and state flowing down the hierarchy. Now it's time to support data flowing the other way: the form components deep in the hierarchy need to update the state in `FilterableProductTable`. +到目前为止,我们已经借助自上而下传递的 props 和 state 渲染了一个应用。现在,我们将尝试让数据反向传递:处于较低层级的表单组件更新较高层级的 `FilterableProductTable` 中的 state。 -React makes this data flow explicit to make it easy to understand how your program works, but it does require a little more typing than traditional two-way data binding. +React 通过一种比传统的双向绑定略微繁琐的方法来实现反向数据传递。尽管如此,但这种需要显式声明的方法更利于人们理解程序的运作方式。 -If you try to type or check the box in the current version of the example, you'll see that React ignores your input. This is intentional, as we've set the `value` prop of the `input` to always be equal to the `state` passed in from `FilterableProductTable`. +如果你在这时尝试在搜索框输入或勾选复选框,React 不会产生任何响应。这是正常的,因为我们之前已经将 `input` 的值设置为了从 `FilterableProductTable` 的 `state` 传递而来的固定值。 -Let's think about what we want to happen. We want to make sure that whenever the user changes the form, we update the state to reflect the user input. Since components should only update their own state, `FilterableProductTable` will pass callbacks to `SearchBar` that will fire whenever the state should be updated. We can use the `onChange` event on the inputs to be notified of it. The callbacks passed by `FilterableProductTable` will call `setState()`, and the app will be updated. +让我们重新梳理一下需要实现的功能:每当用户改变表单的值,我们需要改变 state 来反映用户的当前输入。由于 state 只能由拥有它们的组件进行更改,`FilterableProductTable` 必须将一个能够触发 state 改变的回调函数(callback)传递给 `SearchBar`。我们可以使用输入框的 `onChange` 事件来监视用户输入的变化,并通知 `FilterableProductTable` 传递给 `SearchBar` 的回调函数。然后该回调函数将调用 `setState()`,从而更新应用。 -Though this sounds complex, it's really just a few lines of code. And it's really explicit how your data is flowing throughout the app. +尽管描述起来有点复杂,但实际上只是几行代码而已。你可以清楚地看到你的应用中数据是如何流动的。 -## And That's It +## 这就是全部了 -Hopefully, this gives you an idea of how to think about building components and applications with React. While it may be a little more typing than you're used to, remember that code is read far more than it's written, and it's extremely easy to read this modular, explicit code. As you start to build large libraries of components, you'll appreciate this explicitness and modularity, and with code reuse, your lines of code will start to shrink. :) +希望这篇文档能够帮助你建立起构建 React 组件和应用的一般概念。尽管你可能需要编写更多的代码,但是别忘了:比起写,代码更多地是给人看的。我们一起构建的这个模块化示例应用的代码就很适合阅读。当你开始构建更大的组件库时,你会意识到这种代码模块化和清晰度的重要性。并且随着代码重用程度的加深,你的代码行数也会显著地减少。:)