React编程思想

631 阅读4分钟

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」。

这是React清风拂面系列的第二篇,第一篇在这里

开始

本文主要通过一个例子来说明在使用React的时候,如何能用React编程思想指导我们更快更高效的实现功能。

先看下面的例子

filterTable

一个常见的搜索列表,带有过滤功能。 其数据结构类似

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" }
]
  • category: 种类
  • price: 价格
  • stocked:是否有库存
  • name: 名称

如果我们要用React编程思想实现上面的例子,需要经历5个步骤。

步骤一:将UI分割成组件层级

我们拿到UI的第一步就是要将其分割成多个组件。

以上面的例子为例,我们可以将它分割成以下几个组件。

draft

  1. FilterableProductTable(灰色)容器组件,包含整个app。
  2. SearchBar(蓝色)搜索条组件,接受用户输入,并有一个checkbox做筛选库存情况。
  3. ProductTable(紫色)下面列表的容器,用于展示列表。
  4. ProductCategoryRow(绿色) 显示每个类目。
  5. ProductRow(黄色) 具体的每个产品卡片。

组件层级的拆分应该体现UI的结构,因此上面几个组件的层级关系如下:

  • FilterableProductTable

    • SearchBar

    • ProductTable

      • ProductCategoryRow
      • ProductRow

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

构建组件层级可以用自顶向下自底向上两种方式。

前者适合比较简单的场景,后者适合大型项目。

对于我们这个项目,适合自顶向下。组件层级按照我们上面拆分结构。

  1. 先设计 FilterableProductTable 组件
export default function App() {
  return <FilterableProductTable products={PRODUCTS} />;
}
  1. 添加 搜索容器组件和列表容器组件
function FilterableProductTable({ products }) {
      return (
        <div>
          <SearchBar />
          <ProductTable products={products} />
        </div>
      );
}
  1. 展示列表
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>
  );
}

完整代码看这里

React 使用单向数据流来传递数据,在例子中所有的数据是由最顶层的FilterableProductTable一直往下传到ProductRow组件里面。

步骤三 找到最小集state

最小集state指的是尽可能让变量减少到最低。

在第一章指出几个原则:

  1. 只读的属性,不能定义在state里面。
  2. props传过来的属性,不能定义在state里面
  3. 可以根据其他state或者props计算的属性,不能定义在state里。

所以看下我们定义的几个组件。

  1. FilterableProductTable。容器型组件,传props给下面,不需要state。
  2. SearchBar有个input来记录用户输入,需要一个state,还有一个checkbox,来过滤库存,也需要一个state。
  3. ProductTable 需要展示初始化列表,另外如果checkbox被选中,需要展示过滤列表。初始化列表数据由props传过来,过滤列表可以由初始化列表和过滤条件计算得来,所以不需要state。
  4. 剩下2个组件,都是纯展示,不需要state。

所以,我们只需要输入框和checkbox两个state变量。

步骤四 在合适的地方定义state

解决了需要哪些state变量之后,接下来就是要解决在哪里定义这些变量。因为这两个变量直接是SearchBar的内容,我们的第一个反应就是直接在SearchBar定义即可,但是下面的ProductTable 也用到了这两个变量,所以如果在 SearchBar定义,如何与ProductTable 组件通信也是个问题。因此,需要考虑有没有其他可能。

React编程思想里面对于哪里定义state有自己的原则:

  1. 找出所有需要用到state的组件
  2. 找到它们的最近公共父组件
  3. 判断哪里定义state。
    • 直接定义在最近公共父组件上
    • 一直往上找,直到根组件
    • 如果都不合适,在父组件的上面新建一个组件用来存在state

基于上面的原则,可以看到,如果我们有两个以上的组件用到了某个state,应该把state的定义位置上升到最近公共父组件,或者一直递归到根组件,或者新建一个祖先组件。

在我们这个例子里面,因为SearchBar和 ProductTable都用到了两个变量,他们的最近公共父组件是FilterableProductTable。所以定义在FilterableProductTable组件里面合适,完整代码见这里

function FilterableProductTable({ products }) {
  const [filterText, setFilterText] = useState('');
  const [inStockOnly, setInStockOnly] = useState(false);
  
  return (
      <div>
          <SearchBar 
            filterText={filterText} 
            inStockOnly={inStockOnly} />
          <ProductTable 
            products={products}
            filterText={filterText}
            inStockOnly={inStockOnly} />
      </div>
  )
}


步骤五 反转数据流

React是单向数据流,上面我们定义的例子,保证了 FilterableProductTable 将 filterText 和 inStockOnly 传给了 两个子组件。但是 SearchBar 里面用户输入的值和是否存库 checkbox 的状态如何反馈回去。

针对这种情况,React 里面一般用回调函数来解决,父组件传给子组件一个props 函数,如果子组件里面需要改变父组件的state,回调一下这个props。

function FilterableProductTable({ products }) {
  const [filterText, setFilterText] = useState('');
  const [inStockOnly, setInStockOnly] = useState(false);

  return (
    <div>
      <SearchBar 
        filterText={filterText} 
        inStockOnly={inStockOnly}
+        onFilterTextChange={setFilterText}
         />

然后input监听值的变动,调用 props 函数

<input 
  type="text" 
  value={filterText} 
  placeholder="Search..." 
+ onChange={(e) => onFilterTextChange(e.target.value)} />

完整代码这里

总结

上面五个步骤揭露了如何用React思想编程,在我们日常开发中可以参考借鉴。