React:更新状态对象数组的正确方法

React: Proper way to update an array of objects in state

我是一名专业的 Web 开发人员,自学 React。我创建这个 table 作为一个更大的表格的一部分。 table 在表单组件内部调用

<ProductList
    products={this.state.products}
    onChange={products => this.sendUpdate('products', products)}
/>

this.sendUpdate:

sendUpdate(field, value) {
    this.setState({[field]: value});
    socket.emit('updateItem', this.state.id, {[field]: value});
}

这部分对我所有的表单更新都非常有用。但现在我想弄清楚如何处理 table 内的更新。每个产品都是这样调用的 table 的一行:

<tbody>
    {this.props.products.map((product, i) =>
        <Product key={i} data={product} products={this}/>
    )}
</tbody>

当我输入其中一个输入时更新状态的正确方法是什么?

<FormControl
    value={this.props.data.species}
    onClick={e => this.updateProduct('species', e.target.value)}
/>

ProductList 的完整代码

import React from "react";
import {Button, Table, FormControl} from "react-bootstrap";

class Product extends React.Component {

    updateField(...props){
        this.props.products.updateProduct(this.data, ...props)
    }

    render() {
        return (
            <tr>
                <td>
                    <FormControl
                        value={this.props.data.species}
                        onClick={e => this.updateProduct('species', e.target.value)}
                    />
                </td>
                <td><FormControl/></td>
                <td><FormControl/></td>
                <td><FormControl/></td>
                <td><FormControl/></td>
                <td><FormControl/></td>
                <td><FormControl type="number"/></td>
                <td><Button bsStyle="danger" onClick={() => this.props.products.deleteProduct(this.props.data)}>X</Button></td>
            </tr>
        );
    }
}

export default class ProductList extends React.Component {
    constructor(...props) {
        super(...props);
    }

    addProduct() {
        let products = this.props.products.concat([{timestamp: Date.now()}]);
        this.props.onChange(products);
    }

    updateProduct(product, field, newValue) {
        this.props.products;
        // ???
    }

    deleteProduct(product) {
        let products = this.props.products.filter(p => {
            return p !== product
        });
        this.props.onChange(products);
    }

    render() {
        return (
            <Table responsive>
                <thead>
                <tr>
                    <th>Species</th>
                    <th>Dried</th>
                    <th>Cut</th>
                    <th>Dimensions Green</th>
                    <th>Dimensions Dry</th>
                    <th>Color</th>
                    <th>Quantity</th>
                    <th className="text-right">
                        <Button bsStyle="success" bsSize="xsmall" onClick={() => this.addProduct()}>Add</Button>
                    </th>
                </tr>
                </thead>
                <tbody>
                {this.props.products.map(product => <Product key={product.timestamp} data={product} products={this}/>)}
                </tbody>
            </Table>
        );
    }
}

这是我根据接受的答案得出的结论:

import React from "react";
import {Button, Table, FormControl} from "react-bootstrap";


export default class ProductList extends React.Component {
    constructor(...props) {
        super(...props);
    }

    addProduct() {
        let products = this.props.products.concat([{}]);
        this.props.onChange(products);
    }

    updateProduct(product, field, newValue) {
        const products = this.props.products.map(p => {
            return p === product ? {...p, [field]: newValue} : p;
        });
        this.props.onChange(products);
    }

    deleteProduct(product) {
        let products = this.props.products.filter(p => {
            return p !== product
        });
        this.props.onChange(products);
    }

    render() {
        return (
            <Table responsive striped>
                <thead>
                <tr>
                    <th>Species</th>
                    <th>Dried</th>
                    <th>Cut</th>
                    <th>Dimensions Green</th>
                    <th>Dimensions Dry</th>
                    <th>Color</th>
                    <th>Quantity</th>
                    <th className="text-right">
                        <Button bsStyle="success" bsSize="xsmall" onClick={() => this.addProduct()}>Add</Button>
                    </th>
                </tr>
                </thead>
                <tbody>
                {this.props.products.map((product, i) => this.renderRow(i, product, this))}
                </tbody>
            </Table>
        );
    }

    renderRow(i, product) {
        return (
            <tr key={i}>
                <td>
                    <FormControl
                        value={product.species || ''}
                        onChange={e => this.updateProduct(product, 'species', e.target.value)}
                    />
                </td>
                <td>
                    <FormControl
                        value={product.dried || ''}
                        onChange={e => this.updateProduct(product, 'dried', e.target.value)}
                    />
                </td>
                <td>
                    <FormControl
                        value={product.cut || ''}
                        onChange={e => this.updateProduct(product, 'cut', e.target.value)}
                    />
                </td>
                <td>
                    <FormControl
                        value={product.dimensionsGreen || ''}
                        onChange={e => this.updateProduct(product, 'dimensionsGreen', e.target.value)}
                    />
                </td>
                <td>
                    <FormControl
                        value={product.dimensionsDry || ''}
                        onChange={e => this.updateProduct(product, 'dimensionsDry', e.target.value)}
                    />
                </td>
                <td>
                    <FormControl
                        value={product.color || ''}
                        onChange={e => this.updateProduct(product, 'color', e.target.value)}
                    />
                </td>
                <td>
                    <FormControl
                        type="number"
                        value={product.quantity || 0}
                        onChange={e => this.updateProduct(product, 'quantity', e.target.value)}
                    />
                </td>
                <td><Button bsStyle="danger" onClick={() => this.deleteProduct(product)}>X</Button></td>
            </tr>
        );
    }
}

在您的 ProductsListrender() 中,将数组映射更改为如下内容:

{this.props.products.map((product, index) => <Product key={product.timestamp} data={product} index={index} products={this}/>)}

然后在您的 Product 中将 updateField() 更改为:

updateField(...props){
    this.props.products.updateProduct(this.props.index, ...props)
}

最后,将 ProductsListupdateProduct() 更改为:

updateProduct(index, field, newValue) {
    const products = this.props.products.map((product, productIndex)) => {
        if (index === productIndex) {
            return {
                ...product,
                [field]: newValue
            };
        }
        return product;
    })
    this.props.onChange(products);
}

此外,Product 渲染中有一个轻微的拼写错误。 FormControlonClick 应显示为 onClick={e => this.updateField('species', e.target.value)}