跳到主要内容

React 哲学

React 可以改变你对可见设计和应用构建的思考。

当你使用 React 构建用户界面时。

  1. 首先,把界面分解成一个个组件。
  2. 然后,在把一个个组件链接在一起。
  3. 使数据流经它们。

步骤一:将 UI 拆解为组件层级结构

  • 程序设计 ——使用同样的技术决定你是否应该创建一个新的函数或者对象。
    1. 单一功能原理,一个组件理想情况下应仅做一件事情。
    1. 随着功能的持续增长,它应该被分解为更小的子组件。
  • CSS——思考你将把类选择器用于何处。(然而,组件并没有那么细的粒度。)
  • 设计——思考你将如何组织布局的层级。

步骤二:使用 React 构建一个静态版本

  1. 构建一个静态版本比较简单,
  2. 然后再一个个添加交互。
  3. 这一步,不需要使用任何 state,这是下一步的内容!

构建一个静态版本需要写大量的代码,并不需要什么思考; 但添加交互需要大量的思考,却不需要大量的代码。

  • 构建应用程序的静态版本来渲染你的数据模型,
  • 将构建 组件 并复用其它的组件,然后使用 props 进行传递数据。
  • Props 是从父组件向子组件传递数据的一种方式。
  • 如果你对 state 章节很熟悉,不要在静态版本中使用 state 进行构建
  • state 只是为交互提供的保留功能,即数据会随着时间变化。
  • 因为这是一个静态应用程序,所以并不需要。

  • 你既可以通过从层次结构更高层组件开始“自上而下”构建,
  • 也可以通过从更低层级组件“自下而上”进行构建。

  • 在构建你的组件之后,即拥有一个渲染数据模型的可复用组件库。
  • 因为这是一个静态应用程序,组件仅返回 JSX。
  • 最顶层组件(FilterableProductTable)将接收你的数据模型作为其 prop。
  • 这被称之为 单向数据流,因为数据从树的顶层组件传递到下面的组件。

步骤三:找出 UI 精简且完整的 state 表示

  • 为了使 UI 可交互,你需要用户更改潜在的数据结构。
  • 你将可以使用 state 进行实现。

  • 考虑将 state 作为应用程序需要记住改变数据的最小集合。
  • 组织 state 最重要的一条原则是保持它 DRY(不要自我重复)
  • 计算出你应用程序需要的绝对精简 state 表示,按需计算其它一切。
  • 举个例子,如果你正在构建一个购物列表,你可将他们在 state 中存储为数组。
  • 如果你同时想展示列表中物品数量,不需要将其另存为一个新的 state。
  • 取而代之,可以通过读取你数组的长度来实现。

其中哪些是 state 呢?标记出那些不是的:

  • 随着时间推移 保持不变?如此,便不是 state。
  • 通过 props 从父组件传递?如此,便不是 state。
  • 是否可以基于已存在于组件中的 state 或者 props 进行计算?如此,它肯定不是 state!
  • 剩下的可能是 state。

让我们再次一条条验证它们:

  • 原始列表中的产品 被作为 props 传递,所以不是 state。

  • 搜索文本 似乎应该是 state,因为它会随着时间的推移而变化,并且无法从任何东西中计算出来。

  • 复选框的值 似乎是 state,因为它会随着时间的推移而变化,并且无法从任何东西中计算出来。

  • 过滤后列表中的产品 不是 state,因为可以通过被原始列表中的产品,根据搜索框文本和复选框的值进行计算。

    这就意味着只有 搜索文本复选框的值 是 state!非常好!

    步骤四:验证 state 应该被放置在哪里

    在验证你应用程序中的最小 state 数据之后,你需要验证哪个组件是通过改变 state 实现可响应的,或者 拥有 这个 state。记住:React 使用单向数据流,通过组件层级结构从父组件传递数据至子组件。要搞清楚哪个组件拥有哪个 state。如果你是第一次阅读此章节,可能会很有挑战,但可以通过下面的步骤搞定它!

现在为这个 state 贯彻我们的策略:

  1. 验证使用 state 的组件

    1. ProductTable 需要基于 state (搜索文本和复选框值) 过滤产品列表。
    2. SearchBar 需要展示 state (搜索文本和复选框值)。
  2. 寻找它们的父组件:它们的第一个共同父组件为 FilterableProductTable。

  3. 决定 state 放置的地方:我们将放置过滤文本和勾选 state 的值于 FilterableProductTable。

步骤五:添加反向数据流

  • 目前你的应用程序可以带着 props 和 state 随着层级结构进行正确渲染。

  • 但是根据用户的输入改变 state,需要通过其它的方式支持数据流:

  • 深层结构的表单组件需要在 FilterableProductTable 中更新 state。

  • React 使数据流显式展示,是与双向数据绑定相比,需要更多的输入。

  • 如果你尝试在上述的例子中输入或者勾选复选框,发现 React 忽视了你的输入。这点是有意为之的。

  • 通过 <input value={filterText} />,已经设置了 input 的 value 属性,使之恒等于从 FilterableProductTable 传递的 filterText state。

  • 只要 filterText state 不设置,(输入框的)输入就不会改变。

import { useState } from 'react'

function FilterableProductTable({ products }) {
// 3. 通过props接收传过来的产品数据

// 把 setFilterText,setInStockOnly 看成回调函数即可。
const [filterText, setFilterText] = useState('') // 改变搜索的值
const [inStockOnly, setInStockOnly] = useState(false) // 改变单选框的值

// 3.1 将产品数据传递给组件 ProductTable
return (
<div>
<SearchBar
filterText={filterText}
inStockOnly={inStockOnly}
onFilterTextChange={setFilterText}
onInStockOnlyChange={setInStockOnly}
/>
<ProductTable
products={products}
filterText={filterText}
inStockOnly={inStockOnly}
/>
</div>
)
}

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, filterText, inStockOnly }) {
const rows = []
let lastCategory = null
// 4. 接收products 并 循环渲染展示产品数据
products.forEach((product) => {
if (product.name.toLowerCase().indexOf(filterText.toLowerCase()) === -1) {
return
}
if (inStockOnly && !product.stocked) {
return
}
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({
filterText,
inStockOnly,
onFilterTextChange,
onInStockOnlyChange,
}) {
// 搜索
return (
<form>
<input
type="text"
value={filterText}
placeholder="Search..."
onChange={(e) => onFilterTextChange(e.target.value)}
/>
<label>
<input
type="checkbox"
checked={inStockOnly}
onChange={(e) => onInStockOnlyChange(e.target.checked)}
/>{' '}
Only show products in stock
</label>
</form>
)
}

// 1. 一个对象数组 产品数据PRODUCTS
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' },
]

// 2. 把产品数据PRODUCTS传递给 FilterableProductTable 组件
export default function App() {
return <FilterableProductTable products={PRODUCTS} />
}