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 时,会先考虑把界面拆成一个个碎片,称之为 components,思考不同 components 之间的联系和交互,然后再将它们连接起来,让数据流经过它们。)
Start with the mockup
Imagine that you already have a JSON API and a mockup from a designer.
The JSON API returns some data that looks like this :
1 2 3 4 5 6 7 8 |
[ { category: "Fruits", price: "$1", stocked: true, name: "Apple" }, { category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit" }, { category: "Fruits", price: "$2", stocked: false, name: "Passionfruit" }, { category: "Vegetables", price: "$2", stocked: true, name: "Spinach" }, { category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin" }, { category: "Vegetables", price: "$1", stocked: true, name: "Peas" } ] |
假设上面的 json 是需要展示在页面上的数据,展示的效果图如下:
Step 1: Break the UI into a component hierarchy (按层级拆分界面)
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(单一职责原理), 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.
There are five components on this screen:
FilterableProductTable
(grey) contains the entire app.SearchBar
(blue) receives the user input.ProductTable
(lavender) displays and filters the list according to the user input.ProductCategoryRow
(green) displays a heading for each category.ProductRow
(yellow) displays a row for each product.
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.
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:
FilterableProductTable
SearchBar
ProductTable
ProductCategoryRow
ProductRow
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.(最直接的办法是根据你的数据模型,构建一个不带任何交互的 UI 渲染代码版本…经常是先构建一个静态版本比较简单,然后再一个个添加交互。) 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.
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. 不要在设计静态页面时使用 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(在简单的例子中,自上而下构建通常更简单;而在大型项目中,自下而上构建更简单。).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
function ProductCategoryRow({ category }) { return ( <tr> <th colSpan="2"> {category} </th> </tr> ); } function ProductRow({ product }) { const name = product.stocked ? product.name : <span style={{ color: 'red' }}> {product.name} </span>; return ( <tr> <td>{name}</td> <td>{product.price}</td> </tr> ); } function ProductTable({ products }) { const rows = []; let lastCategory = null; products.forEach((product) => { if (product.category !== lastCategory) { rows.push( <ProductCategoryRow category={product.category} key={product.category} /> ); } rows.push( <ProductRow product={product} key={product.name} /> ); lastCategory = product.category; }); return ( <table> <thead> <tr> <th>Name</th> <th>Price</th> </tr> </thead> <tbody>{rows}</tbody> </table> ); } function SearchBar() { return ( <form> <input type="text" placeholder="Search..." /> <label> <input type="checkbox" /> {' '} Only show products in stock </label> </form> ); } function FilterableProductTable({ products }) { return ( <div> <SearchBar /> <ProductTable products={products} /> </div> ); } const PRODUCTS = [ {category: "Fruits", price: "$1", stocked: true, name: "Apple"}, {category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit"}, {category: "Fruits", price: "$2", stocked: false, name: "Passionfruit"}, {category: "Vegetables", price: "$2", stocked: true, name: "Spinach"}, {category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin"}, {category: "Vegetables", price: "$1", stocked: true, name: "Peas"} ]; export default function App() { return <FilterableProductTable products={PRODUCTS} />; } |
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.(这被称之为 单向数据流,因为数据从树的顶层组件传递到下面的组件。)
Pitfall
At this point, you should not be using any state values. That’s for the next step!
Step 3: Find the minimal but complete representation of UI state (找出 UI 最小力度且完整的 state 表示)
To make the UI interactive, you need to let users change your underlying data model. You will use state for this.
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). 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.
Now think of all of the pieces of data in this example application:
- The original list of products
- The search text the user has entered
- The value of the checkbox
- The filtered list of products
Which of these are state? Identify the ones that are not:
- 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!
What’s left is probably state.
Let’s go through them one by one again:
- The original list of products is passed in as props, so it’s not state.
- The search text seems to be state since it changes over time and can’t be computed from anything.
- The value of the checkbox seems to be state since it changes over time and can’t be computed from anything.
- 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!
Props vs State
There are two types of “model” data in React: props and state. The two are very different:
- Props are like arguments you pass to a function. They let a parent component pass data to a child component and customize its appearance. For example, a
Form
can pass acolor
prop to aButton
.- State is like a component’s memory. It lets a component keep track of some information and change it in response to interactions. For example, a
Button
might keep track ofisHovered
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!