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

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

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

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

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

1 需求澄清

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

2 根据UI界面划分组件

React应用是使用一个一个的组件拼装而成的,因此第一步要做的就是根据UI设计稿将页面拆分成一个个的Component。如下图所示,最外层的Component就是最终被调用的组件(FilterableProductTable),然后以该组件为根节点拆分成若干个子组件。
React从入门到入土系列3-使用React构建你的应用

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(<ProductCategoryRowcategory={product.category}key={product.category} />);}rows.push(<ProductRowproduct={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: stringprice: stringstocked: booleanname: 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: stringinStockOnly: boolean
}) {const rows: any = []let lastCategory: string | null = nullproducts.forEach((product) => {// 如果只显示有库存的数据,并且当前商品无库存,直接不显示if (inStockOnly && !product.stocked) {return}// 如果过滤文本不在当前商品名称中存在,不显示该商品if (filterText &&product.name.toLocaleLowerCase().indexOf(filterText.toLocaleLowerCase()) === -1) {return}if (product.category !== lastCategory) {rows.push(<ProductCategoryRowcategory={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: stringinStockOnly: boolean
}) {return (<form><input type="text" value={filterText} placeholder="Search..." /><label><input type="checkbox" checked={inStockOnly} /> Only show products instock</label></form>)
}function FilterableProductTable({ products }: { products: Array<any> }) {const [filterText, setFilterText] = useState('')const [inStockOnly, setInStockOnly] = useState(false)return (<div><SearchBar filterText={filterText} inStockOnly={inStockOnly} /><ProductTablefilterText={filterText}inStockOnly={inStockOnly}products={products}/></div>)
}export default FilterableProductTable

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


function SearchBar({filterText,inStockOnly,onFilterTextChange,onInStockOnlyChange,
}: {filterText: stringinStockOnly: booleanonFilterTextChange: FunctiononInStockOnlyChange: Function
}) {return (<form><inputtype="text"value={filterText}placeholder="Search..."onChange={(e) => onFilterTextChange(e.target.value)}/><label><inputtype="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><SearchBarfilterText={filterText}inStockOnly={inStockOnly}onFilterTextChange={setFilterText}onInStockOnlyChange={setInStockOnly}/><ProductTablefilterText={filterText}inStockOnly={inStockOnly}products={products}/></div>)
}

最终的呈现效果如下,能够只显示有库存的商品:
React从入门到入土系列3-使用React构建你的应用
能够根据输入文本对结果进行过滤:
React从入门到入土系列3-使用React构建你的应用
本文完,希望能够对你有所帮助~