【问题标题】:Rendering a list of items in React with shared state在 React 中呈现具有共享状态的项目列表
【发布时间】:2017-08-16 01:02:18
【问题描述】:

原始问题

我正在尝试使用 React 呈现项目列表。关键是item共享一个共同的状态,可以被每个item控制。

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

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

如何让 React 处理这种情况?

注意:点击代码sn-p中的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>

【问题讨论】:

  • 请更好地描述原始问题。你需要实施什么?看来您尝试这样做的方式是完全错误的。
  • @AlexanderElgin 我已经改写了我的问题以使其更清楚。这是一个很好的练习,因为它帮助我解决了我自己的问题,因为我不得不重新思考我的代码。无论如何,谢谢!

标签: javascript reactjs


【解决方案1】:

在将 toggleIsEditing 传递给子组件时,您没有绑定父范围

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

【讨论】:

  • 谢谢@ZhangBruce。接得好。这绝对是一个问题,但它并没有让我一路找到解决方案。为了清楚起见,我重新表述了我的问题。在此过程中,我能够解决我自己的问题,我将在这里简要发布。
【解决方案2】:

当我重新表述我的问题时,通过重新思考我的实施,我找到了解决方案。我的原始实现遇到了一些问题:

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

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

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'));
<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>

【讨论】:

  • 这种设计的问题是,如果在Item组件已经渲染之后,其他的子组件编辑item数组,Item组件将无从知晓。因为它有自己的本地状态(这主要是邪恶的)。
  • @ZhangBruce 你会如何处理这种情况?你会做出哪些改变?
  • 我认为您的初始方法是正确的,在父状态中管理您的所有状态,只需确保所有方法传入道具以更改父状态需要与父范围绑定。或者在 redux 之类的库中管理它们,但我认为这对于小型项目来说太过分了。
  • @ZhangBruce 知道了。感谢您的反馈。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-20
相关资源
最近更新 更多