反应问题 - 子状态改变父母的状态

时间:2017-10-11 10:58:02

标签: javascript reactjs ecmascript-6 material-ui

问题:

如果每个组件都有自己的状态,那么这个事情是怎么发生的, 孩子可能会改变父母的状态吗?

以下是我的应用的完整代码:

import React, { Component } from 'react';
import { render } from 'react-dom';
import AutoComplete from 'material-ui/AutoComplete';
import Chip from 'material-ui/Chip';
import Hello from './Hello';
import './style.css';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';

class App extends Component {
  constructor() {
    super();
    const vehicles = [{value : 1 , label : 'Vehicle 1'},{value : 2 , label : 'Vehicle 2'},{value : 3 , label : 'Vehicle 3'},{value : 4 , label : 'Vehicle 4'},{value : 5 , label : 'Vehicle 5'},{value : 6 , label : 'Vehicle 6'},{value : 7 , label : 'Vehicle 7'},{value : 8 , label : 'Vehicle 8'}];
    this.state = {
      vehicles,
      name: 'React',
      name1: 'React1',
      name2: 'React2'
    };
  }

  render() {
    const dataSourceConfig = {
            text: 'label',
            value: 'value',
        };
    return (
      <div>
      <MuiThemeProvider>
            <div>
              <AutoCompleteHlpr 
              dataSource={this.state.vehicles} 
              dataSourceConfig={dataSourceConfig}
              floatingLabelText='Select Vehicles'
              selectedOption={this.handleSelectedVehicle}/>

              <AutoCompleteHlpr 
              dataSource={this.state.vehicles} 
              dataSourceConfig={dataSourceConfig}
              floatingLabelText='Select Vehicles'
              selectedOption={this.handleSelectedVehicle}/>

              <AutoCompleteHlpr 
              dataSource={this.state.vehicles} 
              dataSourceConfig={dataSourceConfig}
              floatingLabelText='Select Vehicles'
              selectedOption={this.handleSelectedVehicle}/>
            </div>
      </MuiThemeProvider>
        <Hello name={this.state.name} />
        <p>
          Start editing to see some magic happen :)
        </p>
      </div>
    );
  }
}




export class AutoCompleteHlpr extends React.Component {

    constructor(props) {
        super(props);
        this.state = {dataSource : this.props.dataSource , searchText : ''};

        this.styles = {
            chip: {
                margin: 4,
            },
            wrapper: {
                display: 'flex',
                flexWrap: 'wrap',
            },
        };

        this.handleNewRequest = this.handleNewRequest.bind(this);
        this.getDataSource = this.getDataSource.bind(this);
        console.log(this.state);
    }

    handleNewRequest(searchText , index){
        this.state.dataSource[index]['selected'] = true;
        this.setState({dataSource : this.state.dataSource , searchText : '' });
        this.props.selectedOption(this.state.dataSource[index] , this.state.dataSource);
    }

    renderChip(data , index) {
        if(data.selected) {
            var value = this.props.dataSourceConfig.text ? this.props.dataSourceConfig.text : 'text';
            var key = this.props.dataSourceConfig.value ? this.props.dataSourceConfig.value : 'value';
            return (
                <Chip
                    key={data[key]}
                    style={this.styles.chip}
                    onRequestDelete={() => this.handleRequestDelete(index)} >
                    {data[value]}

                </Chip>
            );
        }
    }

    handleRequestDelete(index) {
        this.state.dataSource[index]['selected'] = false;
        this.setState({dataSource : this.state.dataSource , searchText : '' });
        this.props.selectedOption(this.state.dataSource[index] , this.state.dataSource);
    }

    componentWillReceiveProps(nextProps) {
        if(this.props.dataSource !== nextProps.dataSource) {
            this.setState({ dataSource : nextProps.dataSource });
        }
    }

    getDataSource() {
        return this.state.dataSource.map(data => {
            if(!data.selected) {
                return data
            }
        })
    }

    render() {
        return(
            <div>
                <AutoComplete
                    floatingLabelText={this.props.floatingLabelText}
                    filter={AutoComplete.caseInsensitiveFilter}
                    onNewRequest={this.handleNewRequest}
                    searchText={this.state.searchText}
                    dataSource={this.getDataSource()}
                    dataSourceConfig={this.props.dataSourceConfig}
                    openOnFocus={true}
                />
                <div style={this.styles.wrapper}>
                    {this.state.dataSource.map(this.renderChip, this)}
                </div>
            </div>
        )
    }
}

AutoCompleteHlpr.defaultProps = {
    floatingLabelText : 'Type Something',
    dataSource : [],
    dataSourceConfig : {},
    selectedOption: () => { }
};

render(<App />, document.getElementById('root'));

以下是stackblitz(在线代码工作)的链接:

https://stackblitz.com/edit/react-svqwcg

如何生成问题:

  1. 选择&#34;车辆1&#34;来自第一个文本框
  2. 选择&#34;车辆2&#34;来自第二个文本框
  3. 选择&#34;车辆3&#34;来自第三个文本框
  4. 你会明白的。

2 个答案:

答案 0 :(得分:1)

您的问题是在子组件中您正在改变父状态。当您在selected数组中更改项目的vehicles属性时,它会发生在该行上:

this.state.dataSource[index]['selected'] = true;

Mutation传播到所有AutoCompleteHlpr组件,因为您将相同的数组传递给所有组件。您造成的情况实际上是所有AutoCompleteHlpr组件都有一个全局状态。

要修复你需要传递vehicles数组的克隆,那么在克隆对象上更改道具不会影响原始对象。下面你有一些简单的克隆实现,但你可以使用另一个来自lodash等:

const clone = (arg) => JSON.parse(JSON.stringify(arg));

在您的代码中,您可以像这样使用它:

<AutoCompleteHlpr 
          dataSource={clone(this.state.vehicles)} 
          dataSourceConfig={dataSourceConfig}
          floatingLabelText='Select Vehicles'
          selectedOption={this.handleSelectedVehicle}/>

另一种选择是,不要像{/ 1>那样改变selected

this.state.dataSource[index]['selected'] = true;

你可以这样做:

const newDataSource = this.state.dataSource.reduce((ds, item, idx) => index !== idx
   ? ds.concat(item)
   : ds.concat(Object.assign({}, item, { selected: true })), []);
this.setState({ dataSource: newDataSource, searchText: '' }); 

编辑:查看下面的代码段,了解为什么传递[...this.state.vehicle]无效:

&#13;
&#13;
const vehicles = [{value : 1 , label : 'Vehicle 1'},{value : 2 , label : 'Vehicle 2'},{value : 3 , label : 'Vehicle 3'},{value : 4 , label : 'Vehicle 4'},{value : 5 , label : 'Vehicle 5'},{value : 6 , label : 'Vehicle 6'},{value : 7 , label : 'Vehicle 7'},{value : 8 , label : 'Vehicle 8'}];

const vehicles2 = [...vehicles];
console.log(vehicles === vehicles2);
console.log(vehicles[0] === vehicles2[0]);
console.log(vehicles.every((item, idx) => item === vehicles2[idx]))
&#13;
&#13;
&#13;

正如您所看到的,vehiclevehicle2是不同的数组,但他们的项目是对相同对象的引用!这就是你需要深度克隆的原因。

答案 1 :(得分:0)

您正在传递引用,因此子组件可以访问父组件

中的相同车辆数组
<AutoCompleteHlpr 
          dataSource={this.state.vehicles} 
          dataSourceConfig={dataSourceConfig}
          floatingLabelText='Select Vehicles'
          selectedOption={this.handleSelectedVehicle}
/>

你不应该改变它所以我建议使用Object.assign([],[this.state.vehicles])如果它没有嵌套或使用slice这不具有破坏性,只能传递它的副本像dataSource这样的dataSource = this.state.vehicles.slice()道具,还有JSON.parse(JSON.stringify(someobject))选项,这是切片和Object.assign()都为您提供原始对象的浅层副本的最佳选择。你也可以查看帮助库,如lodash等

注意:所有状态操作都应仅使用文档here中所述的this.setState进行。检查您的请求方法。