组件更新 redux store 中的 React Native setState

React Native setState in component updating redux store

我在多个 Web 应用程序中使用了 redux 和 react。目前我正在使用 Redux 开发我的第一个 React Native 应用程序。

我遇到了一个很奇怪的问题。

我创建了一个商店并将其传递给 Provider 组件,该组件将 App 组件呈现为子组件。 (react-redux的基本用法)

在应用程序中我有一个连接的组件。它在调用 navigator.push(route) 时将从商店接收到的数据传递给 Navigator Route。此路线上的组件不是连接组件。它接收道具并 将道具存储在其状态中。 道具不仅仅是文字,而是 objects/arrays。 根据用户交互,此组件通过 setState 更新其状态。 此操作直接更新商店

难道我不应该在通过 matchStateToProps 从商店接收的组件状态上设置 props 吗?虽然是这样,但 setState 发生在不同的组件中。商店不应该简单地自我更新。

我很困惑。请帮忙

(如果问题不清楚或令人困惑,我将从我的代码中添加相关代码片段)

编辑 1:

Here is a fiddle which conveys my problem

    const intialState = {
    0: {
    orgId: 0,
    peopleInfo: {
      0 : {
        pID: 0,
        name: 'parent',
        children: [
          {
            userID: 1,
            name: 'abc',
          },
          {
            userID: 2,
            name: 'xyz',
          },
        ]
      }
    }
  }
}


function reducer (currentState, action) {
    currentState = intialState;
  console.log(currentState[0].peopleInfo[0]); // priniting the store every time an action is dispatched
  // NO CHANGES TO THE STORE WHEN ACTION IS DISPATCHED
    return currentState;
}


// Create Store
var store = Redux.createStore(reducer);

// action creator which will simply trigger the reducer
function save(){
    return {
    type: 'SAVE'
  }
}

// Presentational Components (No state, only props and render)
var OrgsContainer = React.createClass({
    render() {
    return (
        <div>
        <div>
          <div>1. Open console first</div>
          <div>2. Change parent name - no change in the name property for the record on the store </div>
          <div>3. Change any child - it changes the property on the store even if there is no implementation in the reducer</div>
        <br />
        </div>
        <PeopleContainer people ={this.props.peopleInfo} saveAction = {this.props.save} />
        </div>
    )
  }
})

var PeopleContainer = React.createClass({
  componentWillMount(){
  console.log(this.props)
    this.setState({
      currentChildren: this.props.people[0].children,
      parentName: this.props.people[0].name
    })
  },
  onChildChangeName(event,index){
  console.log(event.target.value,index);

    var newChildrenArray = this.state.currentChildren;
    newChildrenArray[index].name = event.target.value
    this.setState({
      currentChildren: newChildrenArray
    })
    this.props.saveAction();
  },
  onParentChangeName(event){

    this.setState({
        parentName: event.target.value,
    })
    this.props.saveAction()
  },
  render(){
    return (
      <div>
        Parent Name : <input value={this.state.parentName} onChange={(event) => this.onParentChangeName(event)} />
         <div><br /></div> 
        {this.state.currentChildren.map((child, index) => {
         return(<div key={index}>
         Name : <input value={child.name} onChange={(event) => this.onChildChangeName(event,index)} /> <div><br /></div> 
         </div>)
        })}
      </div>
    )
  }

})

// Map state and dispatch to props
function mapStateToProps (state) {
    return {
    peopleInfo: state[0].peopleInfo,
    };
}

function mapDispatchToProps (dispatch) {
    return Redux.bindActionCreators({
    save: save,
  }, dispatch);
 }

 // Container components (Pass props into presentational component)
 var OrgsContainer = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(OrgsContainer);


 // Top-Level Component
 var App = React.createClass({
    render: function () {
    return (
        <div>
        <h3>App</h3>
        <OrgsContainer />
      </div>
     );
  }
 });



// Render to DOM
var Provider = ReactRedux.Provider; // Injects store into context of all descendents
ReactDOM.render(
    <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('container')
);

所以它与 React Native 无关。

reducer 上的结构模仿了我在我的应用程序中拥有的数据模型。

正如在 fiddle 中所看到的,很明显我们不能将 props 传递到 state 并在那里进行更改。很明显,由于对象引用而形成的存储有link。更新此类引用最终会更新商店。

虽然最好不要在状态上设置道具,但我的场景有点需要它。现在我已经使用 Object.assign() 来创建一个新对象并在状态上使用这个对象,这对我有用。

我可能在 redux 文档中遗漏了一些关于此的信息。如果有人碰巧找到了什么,我会很高兴知道。

但我仍然觉得这很奇怪。

最终,您的问题的原因是,在您的 onChildChangeName 方法中:

var newChildrenArray = this.state.currentChildren;
newChildrenArray[index].name = event.target.value

您正在改变您的商店也引用的相同底层对象实例。

您可以通过将此行添加到 onChildChangeName:

来确认这一点
console.log(intialState[0].peopleInfo[0].children === newChildrenArray); // true

解决方案一: 最简单的解决方案是,您可以在首次设置状态时对数据创建深拷贝,例如:

this.setState({
  currentChildren: _.cloneDeep(this.props.people[0].children),
  parentName: this.props.people[0].name
})

由于这会在数组上创建一个深层副本,数组和数组中的项目都不会引用与 initialState(您的商店)相同的数据,因此您现在可以安全地更改 array/object(s) 不用担心副作用。


方案二: 另一种选择是首先创建数组的浅表副本,然后确保在需要修改数组中的项目时创建新的对象实例。例如,当第一次调用 setState 时,通过执行 slice() 创建一个新数组,以确保对组件数组的更改不会发生在 initialState 引用的实例中,例如:

this.setState({
  currentChildren: this.props.people[0].children.slice(),
  parentName: this.props.people[0].name
})

然后在 onChildChangeName 中,您总是创建一个新实例而不是改变现有实例,例如:

var newChildrenArray = this.state.currentChildren;
newChildrenArray[index] = {...newChildrenArray[index], name: event.target.value}

虽然 React/Redux 在构建组件时传递各种数据实例,但它们不会执行任何类型的 clone/copy 操作来确保您使用的是新引用,因此您将必须自己做才能避免这些问题。