38、React应用开发:从组件构建到Redux数据存储

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 函数发送操作对象 |

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值