React从入门到入土系列3-使用React构建你的应用

这是我自己系统整理的React系列博客,主要参考2023年3月开放的最新版本react官网内容,欢迎你阅读本系列内容,希望能有所收货。
本文是该系列的第3篇文章,阅读完本文后你将收获:

  • 如何使用React逐步构建你的应用
  • 了解props和state的概念

1 需求澄清

假如你现在是一个React项目的开发负责人,设计师已经根据需求设计出了一个UI界面,你需要按照需求开发如下界面:
在这里插入图片描述
这是一个菜单应用,能够展示商品列表及其价格,如果某商品没有库存了将以红色显示;另外,用户还可以根据上方的搜索框对结果进行过滤,或者勾选单选框以显示还有库存的商品。
本文将以这样一个需求为例,梳理React应用的开发步骤,帮助你快速上手。

2 根据UI界面划分组件

React应用是使用一个一个的组件拼装而成的,因此第一步要做的就是根据UI设计稿将页面拆分成一个个的Component。如下图所示,最外层的Component就是最终被调用的组件(FilterableProductTable),然后以该组件为根节点拆分成若干个子组件。
在这里插入图片描述

3 根据设计稿编写一个静态版本的代码

接下来就根据前一个阶段的分析结果,创建多个组件并将结果展示出来。注意,在这个阶段你可以先不考虑组件的交互性,只需要按照设计稿编写静态代码,能够演示的效果即可。因此你需要编写:

  • FilterableProductTable.tsx
  • SearchBar.tsx
  • ProductTable.tsx
  • ProductCategoryRow.tsx
  • ProductRow.tsx
    以上五个组件的静态代码,组件中呈现的数据可以不考虑接入真实的接口,可以直接使用Mock或者静态数据。由于组件之间存在嵌套关系,因此不可避免地,你需要使用props将数据从父组件传递给子组件,如:在ProductTable组件中肯定已经存储了商品列表products,然后该组件又将每个商品的数据传递给其子组件ProductRow或者ProductCategoryRow,这种时候就需要使用props了。
    编写好的静态呈现代码可能如下所示:
/**
 * 这是React.dev官网Quick Start中的Demo应用
 * 对应章节:https://react.dev/learn/thinking-in-react
 * 
 * @author Howard Wonnaut
 * @date 2023-4-9
 */

import "./FilterableProductTable.css";

export type Product = {
    
    
    category: string;
    price: string;
    stocked: boolean;
    name: string;
}

function ProductCategoryRow({
    
     category }: {
    
    category: string}) {
    
    
  return (
    <tr>
      <th colSpan={
    
    2}>
        {
    
    category}
      </th>
    </tr>
  );
}

function ProductRow({
    
     product }: {
    
    product: 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 }: {
    
    products: Array<Product>}) {
    
    
  const rows: any = [];
  let lastCategory: string | null = 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 }: {
    
    products: Array<any>}) {
    
    
  return (
    <div>
      <SearchBar />
      <ProductTable products={
    
    products} />
    </div>
  );
}

export default FilterableProductTable;

4 分析组件间的逻辑关系,给组件设置state状态

在前面的小节里,我们已经使用props将products自顶向下进行传递,并且将页面渲染了出来。接下来,我们需要考虑如何让这个页面可以交互起来,即根据输入框的值对结果进行过滤、根据单选框的状态对没有库存的结果进行显示/隐藏切换,为了达成这个目的,我们就需要使用state来存储对应的状态了。

我们可以根据如下三个问题来确定是否需要使用state来存储数据:

  1. 随着时间的推移,数据是否保持不变?如果是,那么不需要state
  2. 数据是否是父组件通过props传递过来的?如果是,那么不需要state
  3. 是否能够根据已经存在的state或者props计算出该数据?如果是,那么不需要state

根据上面的原则,我们梳理一下这个示例应用程序中的数据有哪些,以及是否需要使用state:

  1. 最外层的产品列表products:直接通过props传入即可,不需要state
  2. 用户在搜索框输入的字符:会随着用户的输入而改变,且不能被计算出来,因此需要state
  3. 单选框的状态:会随着用户的操作而改变,且不能被计算出来,因此需要state
  4. 产品列表的筛选结果:可以根据products,输入框的字符和单选框状态计算得到,因此不需要state

基于上述分析,我们清楚了:输入框和单选框需要使用state保存其状态,其余的数据则不需要。

Props和State的对比
到这里,我们已经对该示例程序中的props和state进行了梳理,现将这二者的区别总结如下:

  • Props类似你像函数传入的参数,将父组件的数据传递给子组件;
  • State类似于一个组件的记忆,其允许组件保存某些信息,并且在用户进行交互操作之后更新存储的数据。例如:一个Button组件能够通过state存储其hover状态isHovered

接下来,我们需要再分析一下,应该将state放在哪个组件里面。由于搜索框和单选框都在SearchBar组件中,那么能不能直接把state放在该组件中呢?答案是不能,因为对于搜索框和单选框进行的交互操作都会影响ProductTable组件的呈现结果,如果将state保存在SearchBar中,ProductTable组件将无法及时感知到用户的操作行为,从而导致数据更新异常。因此,需要将state存放在SearchBar和ProductTable组件的公共父组件FilterableProductTable中,然后使用props将filterText和inStockOnly传递给子组件,此时,对应的代码为:

/**
 * 这是React.dev官网Quick Start中的Demo应用
 * 对应章节:https://react.dev/learn/thinking-in-react
 *
 * @author Howard Wonnaut
 * @date 2023-4-9
 */

import {
    
     useState } from 'react'
import './FilterableProductTable.css'

export type Product = {
    
    
  category: string
  price: string
  stocked: boolean
  name: string
}

function ProductCategoryRow({
    
     category }: {
    
     category: string }) {
    
    
  return (
    <tr>
      <th colSpan={
    
    2}>{
    
    category}</th>
    </tr>
  )
}

function ProductRow({
    
     product }: {
    
     product: 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,
}: {
    
    
  products: Array<Product>
  filterText: string
  inStockOnly: boolean
}) {
    
    
  const rows: any = []
  let lastCategory: string | null = null

  products.forEach((product) => {
    
    
    // 如果只显示有库存的数据,并且当前商品无库存,直接不显示
    if (inStockOnly && !product.stocked) {
    
    
      return
    }

    // 如果过滤文本不在当前商品名称中存在,不显示该商品
    if (
      filterText &&
      product.name
        .toLocaleLowerCase()
        .indexOf(filterText.toLocaleLowerCase()) === -1
    ) {
    
    
      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,
}: {
    
    
  filterText: string
  inStockOnly: boolean
}) {
    
    
  return (
    <form>
      <input type="text" value={
    
    filterText} placeholder="Search..." />
      <label>
        <input type="checkbox" checked={
    
    inStockOnly} /> Only show products in
        stock
      </label>
    </form>
  )
}

function FilterableProductTable({
    
     products }: {
    
     products: Array<any> }) {
    
    
  const [filterText, setFilterText] = useState('')
  const [inStockOnly, setInStockOnly] = useState(false)

  return (
    <div>
      <SearchBar filterText={
    
    filterText} inStockOnly={
    
    inStockOnly} />
      <ProductTable
        filterText={
    
    filterText}
        inStockOnly={
    
    inStockOnly}
        products={
    
    products}
      />
    </div>
  )
}

export default FilterableProductTable

此时,只完成了数据自顶向下的传递,但是用户在SearchBar中的操作还没有传递到FilterableProductTable组件中,因此需要完善数据向上传递的链路,修改了FilterableProductTable和SearchBar组件的代码逻辑:


function SearchBar({
    
    
  filterText,
  inStockOnly,
  onFilterTextChange,
  onInStockOnlyChange,
}: {
    
    
  filterText: string
  inStockOnly: boolean
  onFilterTextChange: Function
  onInStockOnlyChange: Function
}) {
    
    
  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>
  )
}

function FilterableProductTable({
    
     products }: {
    
     products: Array<any> }) {
    
    
  const [filterText, setFilterText] = useState('')
  const [inStockOnly, setInStockOnly] = useState(false)

  return (
    <div>
      <SearchBar
        filterText={
    
    filterText}
        inStockOnly={
    
    inStockOnly}
        onFilterTextChange={
    
    setFilterText}
        onInStockOnlyChange={
    
    setInStockOnly}
      />
      <ProductTable
        filterText={
    
    filterText}
        inStockOnly={
    
    inStockOnly}
        products={
    
    products}
      />
    </div>
  )
}

最终的呈现效果如下,能够只显示有库存的商品:
在这里插入图片描述
能够根据输入文本对结果进行过滤:
在这里插入图片描述
本文完,希望能够对你有所帮助~

猜你喜欢

转载自blog.csdn.net/qq_26822029/article/details/130047761