47、React开发中的高级URL路由与RESTful Web服务使用

React开发中的高级URL路由与RESTful Web服务使用

高级URL路由

在React开发中,URL路由是一个重要的部分,它可以帮助我们根据不同的URL显示不同的内容。下面将详细介绍如何在React中实现高级URL路由。

1. 动态生成路由

在之前的实现中,App组件将Selector作为容器,并为其提供子组件进行显示。示例代码如下:

import React, { Component } from "react";
import { Provider } from "react-redux";
import dataStore from "./store";
import { Selector } from "./Selector";
import { ProductDisplay } from "./ProductDisplay";
import { SupplierDisplay } from "./SupplierDisplay";

export default class App extends Component {
    render() {
        return  <Provider store={ dataStore }>
                    <Selector>
                        <ProductDisplay name="Products" />
                        <SupplierDisplay name="Suppliers" />
                    </Selector>
                </Provider>
    }
}

而现在,我们对Selector组件进行了修改,使其能够根据子组件的props动态生成路由。具体代码如下:

import React, { Component } from "react";
import { BrowserRouter as Router, Route, Switch, Redirect, Prompt }
    from "react-router-dom";
import { ToggleLink } from "./routing/ToggleLink";
import { CustomPrompt } from "./routing/CustomPrompt";

export class Selector extends Component {
    constructor(props) {
        super(props);
        this.state = {
            showPrompt: false,
            message: "",
            callback: () => {}
        }
    }
    customGetUserConfirmation = (message, navCallback) => {
        this.setState({
            showPrompt: true, message: message,
            callback: (allow) => { navCallback(allow);
                this.setState({ showPrompt: false}) }
        });
    }
    render() {
        const routes = React.Children.map(this.props.children, child => ({
            component: child,
            name: child.props.name,
            url: `/${child.props.name.toLowerCase()}`
        }));
        return <Router getUserConfirmation={ this.customGetUserConfirmation }>
            <div className="container-fluid">
                <div className="row">
                    <div className="col-2">
                        { routes.map(r => <ToggleLink key={ r.url } to={ r.url }>
                                            { r.name }
                                        </ToggleLink>)}
                    </div>
                    <div className="col">
                        <CustomPrompt show={ this.state.showPrompt }
                            message={ this.state.message }
                            callback={ this.state.callback } />
                        <Prompt message={ loc =>
                            `Do you want to navigate to ${loc.pathname}?`} />
                        <Switch>
                            { routes.map( r => <Route key={ r.url } path={ r.url }
                                    render={ () => r.component } />)}
                            <Redirect to={ routes[0].url } />
                        </Switch>
                    </div>
                </div>
            </div>
        </Router>
    }
}

这个过程可以用以下流程图表示:

graph TD
    A[App组件] --> B[Selector组件]
    B --> C[处理子组件props]
    C --> D[生成路由映射]
    D --> E[渲染ToggleLink和Route组件]
2. 连接数据存储组件的路由使用

为了完成示例应用中路由的采用,我们将协调组件的剩余状态数据从数据存储中移除,并使用一组URL来管理。以下是示例应用所需的URL及其描述:
| 名称 | 描述 |
| — | — |
| /products/table | 显示产品表格 |
| /products/create | 显示创建新产品的编辑器 |
| /products/edit/4 | 显示编辑现有产品的编辑器,最后一个URL段标识要更改的产品 |
| /suppliers/table | 显示供应商表格 |
| /suppliers/create | 显示创建新供应商的编辑器 |
| /suppliers/edit/4 | 显示编辑现有供应商的编辑器,最后一个URL段标识要更改的供应商 |

这些URL可以通过一个带有URL参数的路径来处理:

/:datatype/:mode?/:id?
3. 替换显示组件

ProductDisplay和SupplierDisplay组件之前负责决定为特定数据类型显示表格还是编辑器。随着示例应用功能的增加,这些组件之间的差异已经减少,并且引入URL路由意味着单个组件可以轻松处理两种数据类型的内容选择。我们创建了一个名为RoutedDisplay.js的文件,并定义了如下组件:

import React, { Component } from "react";
import { ProductTable } from "../ProductTable"
import { ProductEditor } from "../ProductEditor";
import { EditorConnector } from "../store/EditorConnector";
import { PRODUCTS } from "../store/dataTypes";
import { TableConnector } from "../store/TableConnector";
import { Link } from "react-router-dom";
import { SupplierEditor } from "../SupplierEditor";
import { SupplierTable } from "../SupplierTable";

export const RoutedDisplay = (dataType) => {
    const ConnectedEditor = EditorConnector(dataType, dataType === PRODUCTS
        ? ProductEditor: SupplierEditor);
    const ConnectedTable = TableConnector(dataType, dataType === PRODUCTS
        ? ProductTable : SupplierTable);
    return class extends Component {
        render() {
            const modeParam = this.props.match.params.mode;
            if (modeParam === "edit" || modeParam === "create") {
                return <ConnectedEditor key={ this.props.match.params.id || -1 } />
            } else {
                return <div className="m-2">
                    <ConnectedTable />
                    <div className="text-center">
                        <Link to={`/${dataType}/create`}
                                className="btn btn-primary m-1">
                            Create
                        </Link>
                    </div>
                </div>
            }
        }
    }
}
4. 更新连接的编辑器组件

EditorConnector组件负责创建连接到Redux数据存储的ProductEditor或SupplierEditor。我们使用withRouter函数创建了一个既提供路由数据又保持与数据存储连接的组件。代码如下:

import { connect } from "react-redux";
import { PRODUCTS, SUPPLIERS  } from "./dataTypes";
import { saveAndEndEditing } from "./multiActionCreators";
import { withRouter } from "react-router-dom";

export const EditorConnector = (dataType, presentationComponent) => {
    const mapStateToProps = (storeData, ownProps) => {
        const mode = ownProps.match.params.mode;
        const id = Number(ownProps.match.params.id);
        return {
            editing: mode === "edit" || mode === "create",
            product: (storeData.modelData[PRODUCTS].find(p => p.id === id)) || {},
            supplier:(storeData.modelData[SUPPLIERS].find(s => s.id === id)) || {}
        }
    }
    const mapDispatchToProps = {
        saveCallback: (data) => saveAndEndEditing(data, dataType)
    }
    const mergeProps = (dataProps, functionProps, ownProps) => {
        let routedDispatchers = {
            cancelCallback: () => ownProps.history.push(`/${dataType}`),
            saveCallback: (data) => {
                functionProps.saveCallback(data);
                ownProps.history.push(`/${dataType}`);
            }
        }
        return Object.assign({}, dataProps, routedDispatchers, ownProps);
    }
    return withRouter(connect(mapStateToProps,
        mapDispatchToProps, mergeProps)(presentationComponent));
}

需要注意的是,我们使用Number来解析id URL参数,因为它是以字符串形式呈现的,而我们需要id值为数字才能定位对象。同时,withRouter和connect函数一起使用时,可能会导致组件不总是更新,为避免这个问题,需要简化props结构以便更容易检测到更改。

5. 更新连接的表格组件

同样,我们对连接显示对象的表格和数据存储的组件也进行了处理。代码如下:

import { connect } from "react-redux";
import { deleteProduct, deleteSupplier } from "./modelActionCreators";
import { PRODUCTS, SUPPLIERS } from "./dataTypes";
import { withRouter } from "react-router-dom";

export const TableConnector = (dataType, presentationComponent) => {
    const mapStateToProps = (storeData, ownProps) => {
        if (dataType === PRODUCTS) {
            return { products: storeData.modelData[PRODUCTS] };
        } else {
            return {
                suppliers: storeData.modelData[SUPPLIERS].map(supp => ({
                    ...supp,
                    products: supp.products.map(id =>
                        storeData.modelData[PRODUCTS]
                            .find(p => p.id === Number(id)) || id)
                            .map(val => val.name || val)
                }))
            }
        }
    }
    const mapDispatchToProps = (dispatch, ownProps) => {
        if (dataType === PRODUCTS) {
            return {
                deleteCallback: (...args) => dispatch(deleteProduct(...args))
            }
        } else {
            return {
                deleteCallback: (...args) => dispatch(deleteSupplier(...args))
            }
        }
    }
    const mergeProps = (dataProps, functionProps, ownProps) => {
        let routedDispatchers = {
            editCallback: (target) => {
                ownProps.history.push(`/${dataType}/edit/${target.id}`);
            },
            deleteCallback: functionProps.deleteCallback
        }
        return Object.assign({}, dataProps, routedDispatchers, ownProps);
    }
    return withRouter(connect(mapStateToProps,
        mapDispatchToProps, mergeProps)(presentationComponent));
}
6. 完成路由配置

最后,我们更新了路由配置以支持之前定义的URL。更新后的Selector组件如下:

import React, { Component } from "react";
import { BrowserRouter as Router, Route, Switch, Redirect }
    from "react-router-dom";
import { ToggleLink } from "./routing/ToggleLink";
import { RoutedDisplay } from "./routing/RoutedDisplay";

export class Selector extends Component {
    render() {
        const routes = React.Children.map(this.props.children, child => ({
            component: child,
            name: child.props.name,
            url: `/${child.props.name.toLowerCase()}`,
            datatype: child.props.datatype
        }));
        return <Router getUserConfirmation={ this.customGetUserConfirmation }>
            <div className="container-fluid">
                <div className="row">
                    <div className="col-2">
                        { routes.map(r => <ToggleLink key={ r.url } to={ r.url }>
                                            { r.name }
                                        </ToggleLink>)}
                    </div>
                    <div className="col">
                        <Switch>
                            { routes.map(r =>
                               <Route key={ r.url }
                                   path={ `/:datatype(${r.datatype})/:mode?/:id?`}
                                   component={ RoutedDisplay(r.datatype)} />
                            )}
                            <Redirect to={ routes[0].url } />
                        </Switch>
                    </div>
                </div>
            </div>
        </Router>
    }
}

同时,App组件也进行了相应的修改:

import React, { Component } from "react";
import { Provider } from "react-redux";
import dataStore from "./store";
import { Selector } from "./Selector";
import { PRODUCTS, SUPPLIERS } from "./store/dataTypes";

export default class App extends Component {
    render() {
        return  <Provider store={ dataStore }>
                    <Selector>
                        <data name="Products" datatype={ PRODUCTS } />
                        <data name="Suppliers" datatype ={ SUPPLIERS } />
                    </Selector>
                </Provider>
    }
}

通过这些步骤,数据存储不再用于组件之间的协调,现在完全通过URL来处理。

RESTful Web服务的使用

在实际应用中,我们可能需要一个永久的数据存储。接下来将介绍如何创建一个Web服务并使用它来管理应用程序的数据。

1. 准备工作

在开始之前,我们需要对项目进行一些准备工作,包括安装额外的包和创建应用程序将依赖的Web服务。

首先,在productapp文件夹中运行以下命令来安装所需的包:

npm install json-server@0.14.0 --save-dev
npm install npm-run-all@4.1.3 --save-dev
npm install axios@0.18.0

这些包的作用如下:
| 名称 | 描述 |
| — | — |
| json-server | 提供一个Web服务,应用程序将查询该服务以获取数据。此命令使用save-dev安装,因为它是开发所需,不是应用程序的一部分。 |
| npm-run-all | 允许并行运行多个命令,以便同时启动Web服务和开发服务器。此命令使用save-dev安装,因为它是开发所需,不是应用程序的一部分。 |
| axios | 应用程序将使用此包向Web服务发出HTTP请求。 |

2. 准备Web服务

为了让json-server包有数据可处理,我们在productapp文件夹中添加一个名为restData.js的文件,并添加以下代码:

module.exports = function () {
    var data = {
        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 },
            { id: 4, name: "Corner Flags", category: "Soccer", price: 34.95 },
            { id: 5, name: "Stadium", category: "Soccer", price: 79500 },
            { id: 6, name: "Thinking Cap", category: "Chess", price: 16 },
            { id: 7, name: "Unsteady Chair", category: "Chess", price: 29.95 },
            { id: 8, name: "Human Chess Board", category: "Chess", price: 75 },
            { id: 9, name: "Bling Bling King", category: "Chess", price: 1200 }
        ],
        suppliers: [
            { id: 1, name: "Surf Dudes", city: "San Jose", products: [1, 2] },
            { id: 2, name: "Goal Oriented", city: "Seattle", products: [3, 4, 5] },
            { id: 3, name: "Bored Games", city: "New York", products: [6, 7, 8, 9] },
        ]
    }
    return data
}

json-server包可以处理JSON或JavaScript文件。我们选择了JavaScript选项,这样可以通过编程方式生成数据,并且重启进程将返回原始数据,这在示例中很有用,因为它可以轻松返回到已知状态,同时仍允许应用程序访问持久数据。

为了配置json-server包以响应以/api开头的URL请求,我们在productapp文件夹中创建一个名为api.routes.json的文件,并添加相应的内容。

通过以上步骤,我们完成了在React应用中实现高级URL路由和使用RESTful Web服务的相关操作。这些技术可以帮助我们更好地管理应用程序的状态和数据,提高应用程序的可维护性和扩展性。

React开发中的高级URL路由与RESTful Web服务使用

RESTful Web服务的使用(续)
3. 从Web服务获取数据

在组件中直接使用Web服务时,我们需要创建一个数据源,该数据源会发起HTTP请求,并通过回调函数将数据反馈回应用程序。以下是具体步骤:
1. 创建数据源 :使用 axios 包发起HTTP请求。
2. 使用回调函数 :在请求成功后,调用回调函数并使用 setState 方法更新组件状态。

示例代码如下:

import axios from 'axios';

class DataSource {
    constructor() {
        this.apiUrl = '/api';
    }

    getData(callback) {
        axios.get(`${this.apiUrl}/products`)
          .then(response => {
                callback(response.data);
            })
          .catch(error => {
                console.error('Error fetching data:', error);
            });
    }
}

class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            products: []
        };
        this.dataSource = new DataSource();
    }

    componentDidMount() {
        this.dataSource.getData((data) => {
            this.setState({ products: data });
        });
    }

    render() {
        return (
            <div>
                {this.state.products.map(product => (
                    <div key={product.id}>{product.name}</div>
                ))}
            </div>
        );
    }
}

这个过程可以用以下流程图表示:

graph TD
    A[组件挂载] --> B[调用数据源的getData方法]
    B --> C[发起HTTP请求]
    C --> D{请求成功?}
    D -- 是 --> E[调用回调函数更新状态]
    D -- 否 --> F[捕获错误并输出日志]
4. 执行额外的数据操作

除了获取数据,我们还可以对数据执行其他操作,如创建、更新和删除。具体操作步骤如下:
1. 扩展数据源 :添加不同的方法来处理不同的HTTP请求。
2. 响应组件事件 :在组件中触发这些请求。

示例代码如下:

class DataSource {
    constructor() {
        this.apiUrl = '/api';
    }

    createProduct(product, callback) {
        axios.post(`${this.apiUrl}/products`, product)
          .then(response => {
                callback(response.data);
            })
          .catch(error => {
                console.error('Error creating product:', error);
            });
    }

    updateProduct(product, callback) {
        axios.put(`${this.apiUrl}/products/${product.id}`, product)
          .then(response => {
                callback(response.data);
            })
          .catch(error => {
                console.error('Error updating product:', error);
            });
    }

    deleteProduct(id, callback) {
        axios.delete(`${this.apiUrl}/products/${id}`)
          .then(response => {
                callback(response.data);
            })
          .catch(error => {
                console.error('Error deleting product:', error);
            });
    }
}

class ProductComponent extends React.Component {
    constructor(props) {
        super(props);
        this.dataSource = new DataSource();
    }

    handleCreate = () => {
        const newProduct = { name: 'New Product', price: 100 };
        this.dataSource.createProduct(newProduct, (data) => {
            console.log('Product created:', data);
        });
    }

    handleUpdate = (product) => {
        this.dataSource.updateProduct(product, (data) => {
            console.log('Product updated:', data);
        });
    }

    handleDelete = (id) => {
        this.dataSource.deleteProduct(id, (data) => {
            console.log('Product deleted:', data);
        });
    }

    render() {
        return (
            <div>
                <button onClick={this.handleCreate}>Create Product</button>
                {/* 处理更新和删除的按钮 */}
            </div>
        );
    }
}
5. 处理请求错误

在发起HTTP请求时,可能会出现各种错误。为了确保用户能够得到及时的反馈,我们需要捕获这些错误并将其传递给组件,以便向用户显示警告信息。具体步骤如下:
1. 使用 try/catch :在请求代码中使用 try/catch 块捕获错误。
2. 传递错误信息 :将错误信息传递给组件。

示例代码如下:

class DataSource {
    constructor() {
        this.apiUrl = '/api';
    }

    getData(callback) {
        try {
            axios.get(`${this.apiUrl}/products`)
              .then(response => {
                    callback(response.data);
                })
              .catch(error => {
                    throw error;
                });
        } catch (error) {
            callback(null, error);
        }
    }
}

class ErrorHandlingComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            products: [],
            error: null
        };
        this.dataSource = new DataSource();
    }

    componentDidMount() {
        this.dataSource.getData((data, error) => {
            if (error) {
                this.setState({ error: error.message });
            } else {
                this.setState({ products: data });
            }
        });
    }

    render() {
        if (this.state.error) {
            return <div>Error: {this.state.error}</div>;
        }
        return (
            <div>
                {this.state.products.map(product => (
                    <div key={product.id}>{product.name}</div>
                ))}
            </div>
        );
    }
}
6. 使用数据存储与Web服务

我们可以使用中间件来拦截数据存储的操作,并向Web服务发送所需的请求。请求完成后,将操作转发到数据存储以更新状态。以下是具体步骤:
1. 创建中间件 :拦截数据存储的操作。
2. 发送请求 :根据操作类型发送相应的HTTP请求。
3. 更新数据存储 :请求完成后,将操作转发到数据存储。

示例代码如下:

import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import axios from 'axios';

const apiUrl = '/api';

const initialState = {
    products: []
};

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case 'FETCH_PRODUCTS_SUCCESS':
            return { ...state, products: action.payload };
        default:
            return state;
    }
};

const fetchProducts = () => {
    return (dispatch) => {
        axios.get(`${apiUrl}/products`)
          .then(response => {
                dispatch({ type: 'FETCH_PRODUCTS_SUCCESS', payload: response.data });
            })
          .catch(error => {
                console.error('Error fetching products:', error);
            });
    };
};

const store = createStore(reducer, applyMiddleware(thunk));

store.dispatch(fetchProducts());

总结

通过以上内容,我们详细介绍了在React应用中实现高级URL路由和使用RESTful Web服务的方法。高级URL路由可以帮助我们更好地管理应用程序的状态和导航,而RESTful Web服务则为我们提供了一个永久的数据存储解决方案。

在实现高级URL路由时,我们通过动态生成路由、连接数据存储组件、替换显示组件等步骤,使数据存储不再用于组件之间的协调,而是完全通过URL来处理。

在使用RESTful Web服务时,我们从准备工作开始,包括安装必要的包和配置Web服务,然后介绍了如何从Web服务获取数据、执行额外的数据操作、处理请求错误以及如何将Web服务与数据存储结合使用。

这些技术的应用可以提高应用程序的可维护性和扩展性,使我们能够更好地开发和管理React应用。希望这些内容对大家在React开发中有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值