React应用开发:从组件构建到Redux数据存储
1. 构建完整的React应用
1.1 产品编辑组件
产品编辑组件 ProductEditor 为用户提供了编辑对象属性的字段。以下是其部分代码:
<div className="form-group">
<label>Category</label>
<input className="form-control" name="category"
value={ this.state.formData.category }
onChange={ this.handleChange } />
</div>
<div className="form-group">
<label>Price</label>
<input className="form-control" name="price"
value={ this.state.formData.price }
onChange={ this.handleChange } />
</div>
<div className="text-center">
<button className="btn btn-primary m-1" onClick={ this.handleClick }>
Save
</button>
<button className="btn btn-secondary"
onClick={ this.props.cancelCallback }>
Cancel
</button>
</div>
该组件从 product 属性接收字段的初始值,并用于填充状态数据。点击“Save”按钮会调用 saveCallback 函数,传递状态数据以便保存;点击“Cancel”按钮会调用 cancelCallback 函数。
1.2 产品显示组件
产品显示组件 ProductDisplay 用于在产品表格和产品编辑器之间切换。以下是其代码:
import React, { Component } from "react";
import { ProductTable } from "./ProductTable"
import { ProductEditor } from "./ProductEditor";
export class ProductDisplay extends Component {
constructor(props) {
super(props);
this.state = {
showEditor: false,
selectedProduct: null
}
}
startEditing = (product) => {
this.setState({ showEditor: true, selectedProduct: product })
}
createProduct = () => {
this.setState({ showEditor: true, selectedProduct: {} })
}
cancelEditing = () => {
this.setState({ showEditor: false, selectedProduct: null })
}
saveProduct = (product) => {
this.props.saveCallback(product);
this.setState({ showEditor: false, selectedProduct: null })
}
render() {
if (this.state.showEditor) {
return <ProductEditor
key={ this.state.selectedProduct.id || -1 }
product={ this.state.selectedProduct }
saveCallback={ this.saveProduct }
cancelCallback={ this.cancelEditing } />
} else {
return <div className="m-2">
<ProductTable products={ this.props.products }
editCallback={ this.startEditing }
deleteCallback={ this.props.deleteCallback } />
<div className="text-center">
<button className="btn btn-primary m-1"
onClick={ this.createProduct }>
Create Product
</button>
</div>
</div>
}
}
}
该组件定义了状态数据,用于确定是显示数据表还是编辑器,以及用户想要修改的产品。它将函数属性传递给 ProductEditor 和 ProductTable 组件,并引入了自己的功能。
1.3 供应商功能组件
1.3.1 供应商表格行组件
供应商表格行组件 SupplierTableRow 用于渲染供应商的表格行。以下是其代码:
import React, { Component } from "react";
export class SupplierTableRow extends Component {
render() {
let s = this.props.supplier;
return <tr>
<td>{ s.id }</td>
<td>{ s.name }</td>
<td>{ s.city}</td>
<td>{ s.products.join(", ") }</td>
<td>
<button className="btn btn-sm btn-warning m-1"
onClick={ () => this.props.editCallback(s) }>
Edit
</button>
<button className="btn btn-sm btn-danger m-1"
onClick={ () => this.props.deleteCallback(s) }>
Delete
</button>
</td>
</tr>
}
}
该组件渲染一个表格行,包含供应商的 id 、 name 、 city 和 products 属性。还有“Edit”和“Delete”按钮,用于调用函数属性。
1.3.2 供应商表格组件
供应商表格组件 SupplierTable 用于呈现供应商表格。以下是其代码:
import React, { Component } from "react";
import { SupplierTableRow } from "./SupplierTableRow";
export class SupplierTable extends Component {
render() {
return <table className="table table-sm table-striped table-bordered">
<thead>
<tr>
<th>ID</th><th>Name</th><th>City</th>
<th>Products</th><th></th>
</tr>
</thead>
<tbody>
{
this.props.suppliers.map(s =>
<SupplierTableRow supplier={ s }
key={ s.id }
editCallback={ this.props.editCallback }
deleteCallback={ this.props.deleteCallback } />)
}
</tbody>
</table>
}
}
该组件渲染一个表格,将 suppliers 属性数组中的每个对象映射到 SupplierTableRow 组件。回调函数的属性从父组件接收并传递下去。
1.3.3 供应商编辑器组件
供应商编辑器组件 SupplierEditor 用于编辑供应商信息。以下是其代码:
import React, { Component } from "react";
export class SupplierEditor extends Component {
constructor(props) {
super(props);
this.state = {
formData: {
id: props.supplier.id || "",
name: props.supplier.name || "",
city: props.supplier.city || "",
products: props.supplier.products || [],
}
}
}
handleChange = (ev) => {
ev.persist();
this.setState(state =>
state.formData[ev.target.name] =
ev.target.name === "products"
? ev.target.value.split(",") : ev.target.value);
}
handleClick = () => {
this.props.saveCallback(
{
...this.state.formData,
products: this.state.formData.products.map(val => Number(val))
});
}
render() {
return <div className="m-2">
<div className="form-group">
<label>ID</label>
<input className="form-control" name="id"
disabled
value={ this.state.formData.id }
onChange={ this.handleChange } />
</div>
<div className="form-group">
<label>Name</label>
<input className="form-control" name="name"
value={ this.state.formData.name }
onChange={ this.handleChange } />
</div>
<div className="form-group">
<label>City</label>
<input className="form-control" name="city"
value={ this.state.formData.city }
onChange={ this.handleChange } />
</div>
<div className="form-group">
<label>Products</label>
<input className="form-control" name="products"
value={ this.state.formData.products }
onChange={ this.handleChange } />
</div>
<div className="text-center">
<button className="btn btn-primary m-1" onClick={ this.handleClick }>
Save
</button>
<button className="btn btn-secondary"
onClick={ this.props.cancelCallback }>
Cancel
</button>
</div>
</div>
}
}
该组件定义了状态数据,用于存储表单数据。处理输入变化和保存操作。
1.3.4 供应商显示组件
供应商显示组件 SupplierDisplay 用于管理供应商数据的显示。以下是其代码:
import React, { Component } from "react";
import { SupplierEditor } from "./SupplierEditor";
import { SupplierTable } from "./SupplierTable";
export class SupplierDisplay extends Component {
constructor(props) {
super(props);
this.state = {
showEditor: false,
selected: null
}
}
startEditing = (supplier) => {
this.setState({ showEditor: true, selected: supplier })
}
createSupplier = () => {
this.setState({ showEditor: true, selected: {} })
}
cancelEditing = () => {
this.setState({ showEditor: false, selected: null })
}
saveSupplier= (supplier) => {
this.props.saveCallback(supplier);
this.setState({ showEditor: false, selected: null })
}
render() {
if (this.state.showEditor) {
return <SupplierEditor
key={ this.state.selected.id || -1 }
supplier={ this.state.selected }
saveCallback={ this.saveSupplier }
cancelCallback={ this.cancelEditing } />
} else {
return <div className="m-2">
<SupplierTable suppliers={ this.props.suppliers }
editCallback={ this.startEditing }
deleteCallback={ this.props.deleteCallback }
/>
<div className="text-center">
<button className="btn btn-primary m-1"
onClick={ this.createSupplier }>
Create Supplier
</button>
</div>
</div>
}
}
}
该组件定义了状态数据,用于确定是显示数据表还是编辑器,以及用户想要修改的供应商。它将函数属性传递给 SupplierEditor 和 SupplierTable 组件,并引入了自己的功能。
1.4 完成应用
1.4.1 选择器组件
选择器组件 Selector 用于让用户在产品和供应商功能之间进行选择。以下是其代码:
import React, { Component } from "react";
export class Selector extends Component {
constructor(props) {
super(props);
this.state = {
selection: React.Children.toArray(props.children)[0].props.name
}
}
setSelection = (ev) => {
ev.persist();
this.setState({ selection: ev.target.name});
}
render() {
return <div className="container-fluid">
<div className="row">
<div className="col-2">
{ React.Children.map(this.props.children, c =>
<button
name={ c.props.name }
onClick={ this.setSelection }
className={`btn btn-block m-2
${this.state.selection === c.props.name
? "btn-primary active": "btn-secondary"}`}>
{ c.props.name }
</button>
)}
</div>
<div className="col">
{
React.Children.toArray(this.props.children)
.filter(c => c.props.name === this.state.selection)
}
</div>
</div>
</div>
}
}
该组件是一个容器,为每个子组件渲染一个按钮,并仅显示用户选择的组件。
1.4.2 产品和供应商组件
产品和供应商组件 ProductsAndSuppliers 用于提供应用程序显示的数据和操作数据的回调函数。以下是其代码:
import React, { Component } from 'react';
import { Selector } from './Selector';
import { ProductDisplay } from './ProductDisplay';
import { SupplierDisplay } from './SupplierDisplay';
export default class ProductsAndSuppliers extends Component {
constructor(props) {
super(props);
this.state = {
products: [
{ id: 1, name: "Kayak",
category: "Watersports", price: 275 },
{ id: 2, name: "Lifejacket",
category: "Watersports", price: 48.95 },
{ id: 3, name: "Soccer Ball", category: "Soccer", price: 19.50 }
],
suppliers: [
{ id: 1, name: "Surf Dudes", city: "San Jose", products: [1, 2] },
{ id: 2, name: "Field Supplies", city: "New York", products: [3] },
]
}
this.idCounter = 100;
}
saveData = (collection, item) => {
if (item.id === "") {
item.id = this.idCounter++;
this.setState(state => state[collection]
= state[collection].concat(item));
} else {
this.setState(state => state[collection]
= state[collection].map(stored =>
stored.id === item.id ? item: stored))
}
}
deleteData = (collection, item) => {
this.setState(state => state[collection]
= state[collection].filter(stored => stored.id !== item.id));
}
render() {
return <div>
<Selector>
<ProductDisplay
name="Products"
products={ this.state.products }
saveCallback={ p => this.saveData("products", p) }
deleteCallback={ p => this.deleteData("products", p) } />
<SupplierDisplay
name="Suppliers"
suppliers={ this.state.suppliers }
saveCallback={ s => this.saveData("suppliers", s) }
deleteCallback={ s => this.deleteData("suppliers", s) } />
</Selector>
</div>
}
}
该组件定义了产品和供应商的状态数据属性,并定义了允许删除或保存每个数据类别的对象的方法。它渲染一个 Selector 组件,并将类别显示组件作为其子组件。
1.4.3 应用组件
最后,将 App 组件的内容替换为 ProductsAndSuppliers 组件,以显示自定义组件。以下是 App.js 文件的代码:
import React, { Component } from "react";
import ProductsAndSuppliers from "./ProductsAndSuppliers";
export default class App extends Component {
render() {
return <ProductsAndSuppliers/>
}
}
保存 App 组件的更改后,浏览器将显示完整的示例应用程序。
1.5 示例应用的局限性
1.5.1 数据静态性
示例应用的最大局限性是它使用静态定义的数据,这些数据硬编码在 App 组件中。每次启动应用程序时都会显示相同的数据,并且在浏览器重新加载或关闭时,更改会丢失。
1.5.2 状态数据提升
状态数据被提升到应用程序的顶层。虽然状态数据可以用于组件之间的协调,但在示例应用中,重要的数据(如产品和供应商数组)最终被推到应用程序的顶层。当组件卸载时,其状态数据会丢失,这意味着示例应用中 Selector 以下的任何组件都不适合存储应用程序的数据。
1.5.3 用户操作限制
应用程序要求用户按照特定的任务序列来访问特定功能。在许多应用程序中,用户希望能够轻松地启动他们需要执行的任务,而示例应用仅在用户点击特定元素时才显示其功能。
以下是示例应用的组件结构流程图:
graph TD;
A[App] --> B[ProductsAndSuppliers];
B --> C[Selector];
C --> D[ProductDisplay];
C --> E[SupplierDisplay];
D --> F[ProductTable];
D --> G[ProductEditor];
E --> H[SupplierTable];
E --> I[SupplierEditor];
H --> J[SupplierTableRow];
以下是示例应用的局限性总结表格:
| 局限性 | 描述 | 解决方案 |
| ---- | ---- | ---- |
| 数据静态性 | 使用硬编码的静态数据,更改易丢失 | 使用 Web 服务或本地存储 |
| 状态数据提升 | 重要数据推到顶层,组件卸载数据丢失 | 使用单独的数据存储 |
| 用户操作限制 | 需按特定序列访问功能 | 支持 URL 路由 |
2. 使用 Redux 数据存储
2.1 Redux 简介
数据存储将应用程序的数据移到 React 组件层次结构之外,使用数据存储意味着数据不必提升到顶级组件,也不必通过属性传递来确保在需要的地方访问该数据。这样可以形成更自然的应用程序结构,让 React 组件专注于为用户渲染内容。
Redux 是 React 项目中最流行的数据存储选择。以下是关于 Redux 的一些关键信息:
| 问题 | 答案 |
| ---- | ---- |
| 是什么 | 数据存储将应用程序的数据移到组件层次结构之外,避免数据提升和属性传递 |
| 为什么有用 | 可以简化项目中的组件,使应用程序更易于开发和测试 |
| 如何使用 | 将数据移动到应用程序的专用部分,组件通过属性连接到数据存储 |
| 有哪些陷阱或限制 | 数据存储可能很复杂,工作方式可能违反直觉,一些数据存储包(如 Redux)对处理数据的方法有特定要求 |
| 有哪些替代方案 | 并非所有应用程序都需要数据存储,对于少量数据,使用组件状态功能或 React 上下文 API 可能就足够了 |
2.2 准备工作
在使用 Redux 之前,需要安装相关的包。在项目文件夹中运行以下命令:
npm install redux@4.0.1
npm install react-redux@6.0.0
以下是这些包的描述:
| 名称 | 描述 |
| ---- | ---- |
| redux | 包含主要的 Redux 数据存储功能 |
| react-redux | 包含将 Redux 与 React 集成的功能 |
安装完包后,运行以下命令启动 React 开发工具:
npm start
2.3 创建数据存储
Redux 对数据和更改施加了特定的流程。在 Redux 中,更改不是直接应用于存储中的数据,而是依赖于接受有效负载并更新存储中数据的函数。以下是创建数据存储的关键步骤总结:
| 问题 | 解决方案 |
| ---- | ---- |
| 创建数据存储 | 定义初始数据、操作类型、创建者和归约器 |
| 将数据存储添加到 React 应用程序 | 使用 React-Redux 包中的 Provider 组件 |
| 在 React 组件中使用数据存储 | 使用 connect 函数将组件的属性映射到数据存储的数据和操作创建者 |
| 调度多个数据存储操作 | 在将数据存储操作创建者映射到组件函数属性时直接使用 dispatch 函数 |
2.4 Redux 术语解释
以下是在使用 Redux 时会遇到的一些术语及其解释:
| 术语 | 解释 |
| ---- | ---- |
| 操作类型 | 定义了可以对数据存储执行的操作的类型,通常是一个字符串常量 |
| 操作创建者 | 是一个函数,用于创建操作对象,操作对象包含操作类型和可选的有效负载 |
| 归约器 | 是一个纯函数,接受当前状态和操作对象作为参数,并返回新的状态 |
| 数据存储 | 是一个对象,包含应用程序的状态和用于更新状态的归约器 |
2.5 Redux 数据存储的使用流程
以下是使用 Redux 数据存储的基本流程:
graph LR;
A[组件触发操作] --> B[操作创建者创建操作对象];
B --> C[操作对象被发送到数据存储];
C --> D[归约器根据操作对象更新状态];
D --> E[数据存储状态更新];
E --> F[组件获取更新后的数据];
2.6 总结
通过使用 Redux 数据存储,可以解决示例应用中存在的一些局限性,如状态数据提升和数据静态性问题。Redux 提供了一种更有效的方式来管理应用程序的数据,使组件更加专注于渲染和交互。在后续的开发中,可以进一步探索 Redux 的高级功能,以满足更复杂的应用程序需求。
以下是 Redux 相关操作的步骤总结表格:
| 操作 | 步骤 |
| ---- | ---- |
| 创建数据存储 | 1. 定义初始数据;2. 定义操作类型;3. 创建操作创建者;4. 编写归约器 |
| 集成到 React 应用 | 1. 使用 Provider 组件包裹应用;2. 使用 connect 函数连接组件和数据存储 |
| 调度操作 | 1. 在组件中调用操作创建者;2. 使用 dispatch 函数发送操作对象 |
超级会员免费看
32

被折叠的 条评论
为什么被折叠?



