使用共享状态

时间:2017-08-16 01:02:19

标签: javascript reactjs

原始问题

<击> 我正在尝试使用React渲染项目列表。关键是项目共享一个共同的状态,可以由每个项目控制。

为简单起见,假设我们有一个字符串数组。我们有一个映射在数组上的List组件,并生成Item个组件。每个Item都有一个按钮,当点击它时,它会改变列表中所有项目的状态(我已经包含了一个代码片段来传达我正在尝试做的事情)。

我将state存储在List组件中,并通过Item将其值传递给每个props子组件。我遇到的问题是按钮单击(在Item内)并没有改变UI状态。我认为这个问题与items在单击按钮时没有改变的事实有关(正确地说是这样),因此React不会重新呈现列表(我本来期望某种UI更新,因为当isEditing状态发生变化时,传递到Item的道具List会发生变化。

如何让React处理这种情况?

注意:单击代码段中的Edit按钮时似乎存在脚本错误,但是当我在本地运行它时,我没有遇到它。相反,不会抛出任何错误,但UI中的任何内容都不会更新。当我调试它时,我可以看到List中的状态变化没有传播给它的子节点

已编辑的问题

鉴于最初的问题不够明确,我在下面重述它。

目标

我想在React中呈现项目列表。每个项目都应显示一个单词和Edit按钮。用户应该只能一次编辑一个项目。

验收标准

  • 加载后,用户会看到一个单词列表,每个单词旁边都有一个Edit按钮。
  • 点击项目1的Edit时,只有第1项可编辑,Edit按钮变为Save按钮。列表中的其他项目不应再显示相应的Edit按钮。
  • 单击项目0的Save后,将显示该项目的新值。所有编辑按钮(对于其余项目)应该再次可见。

问题

在我原来的实现中,我在父组件(List)中存储了一个 edit 状态,但是这个状态没有正确传播到它的Item个孩子

注意:我的原始实现缺乏状态管理逻辑,我后来发现它是主要的罪魁祸首(请参阅下面的回复)。它还有一个 bind 错误,如下面@Zhang所述。我将它留在这里供将来参考,虽然它不是一个很好的例子。

这是我最初的实现:

const items = ['foo', 'bar'];
 
class List extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isEditing: false
    };
  }
  toggleIsEditing() {
    this.setState((prevState) => {
      return {
        isEditing: !prevState.isEditing
      }
    });
  }
  render() {
    return (
      <ul>
        {items.map((val) => (
          <Item value={val}
                toggleIsEditing={this.toggleIsEditing}
                isEditing={this.state.isEditing}/>
        ))}
      </ul>
    );
  }
}
 
class Item extends React.Component {
  render() {
    return (
      <li>
        <div>
          <span>{this.props.value}</span>
          { !this.props.isEditing &&
            (<button onClick={this.props.toggleIsEditing}>
              Edit
            </button>)
          }
          { this.props.isEditing &&
            (<div>
              <span>...Editing</span>
              <button onClick={this.props.toggleIsEditing}>
                Stop
              </button>
            </div>)
          }
        </div>
      </li>
    );
  }
}

ReactDOM.render(<List />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<body>
  <div id="app" />
</body>

2 个答案:

答案 0 :(得分:0)

在将toggleIsEditing传递给子组件

时,您没有绑定父作用域

          <Item value={val}
                toggleIsEditing={this.toggleIsEditing.bind(this)}
                isEditing={this.state.isEditing}/>

答案 1 :(得分:0)

当我通过重新思考我的实施来改写我的问题时,我想出了解决方案。我的原始实现有一些问题:

  • this类中非生命周期方法中的List未绑定到类范围(如@ZhangBruce在其答案中所述)。
  • List中的状态管理逻辑缺乏能够处理用例的其他属性。

此外,我认为将state添加到Item组件本身对于正确传播更新非常重要。具体来说,添加state.val是关键(根据我的理解)。可能还有其他方式(可能更简单),在这种情况下我很想知道,但与此同时我的解决方案:

&#13;
&#13;
const items = ['foo', 'bar'];
 
class List extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      editingFieldIndex: -1
    };
  }
  setEdit = (index = -1) => {
    this.setState({
      editingFieldIndex: index
    });
  }
  render() {
    return (
      <ul>
        {items.map((val, index) => (
          <Item val={val}
                index={index}
                setEdit={this.setEdit} 
                editingFieldIndex={this.state.editingFieldIndex} />
        ))}
      </ul>
    );
  }
}
 
class Item extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      val: props.val
    };
  }
  save = (evt) => {
    this.setState({
      val: evt.target.value
    });
  }
  render() {
    const { index, setEdit, editingFieldIndex } = this.props;
    const { val } = this.state;
    
    const shouldShowEditableValue = editingFieldIndex === index;
    const shouldShowSaveAction = editingFieldIndex === index;
    const shouldHideActions = 
      editingFieldIndex !== -1 && editingFieldIndex !== index;
    
    const editableValue = (
      <input value={val} onChange={(evt) => this.save(evt)}/>
    )
    const readOnlyValue = (
      <span>{val}</span>
    )
    const editAction = (
      <button onClick={() => setEdit(index)}>
        Edit
      </button>
    )
    const saveAction = (
      <button onClick={() => setEdit()}>
        Save
      </button>
    )
    
    return (
      <li>
        <div>
        { console.log(`index=${index}`) }
        { console.log(`editingFieldIndex=${editingFieldIndex}`) }
        { console.log(`shouldHideActions=${shouldHideActions}`) }
        { 
          shouldShowEditableValue
            ? editableValue
            : readOnlyValue
        }
        {
          !shouldHideActions
            ? shouldShowSaveAction
              ? saveAction
              : editAction
            : ""
        }
        </div>
      </li>
    );
  }
}

ReactDOM.render(<List />, document.getElementById('app'));
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<body>
  <div id="app" />
</body>
&#13;
&#13;
&#13;