【问题标题】:How to avoid 'children with same key' when replacing state in ReactJs component在 ReactJs 组件中替换状态时如何避免“具有相同键的孩子”
【发布时间】:2014-11-23 04:35:39
【问题描述】:

我创建了一个 React 组件,它在给定 id 数组的情况下呈现一组子元素。 ids 数组保持在父组件的状态,然后我根据 ids 运行一些 ajax 调用来获取要渲染的数据。获取的数据存储在状态中的单独数据数组中。渲染的组件使用 id 作为键

id 可以根据组件外部的操作而改变,所以我在组件上使用 setState 来替换数组。 更新后的 id-state 可能包含一些与原始数组中相同的 id。 同时我清空了“数据数组”,以便再次渲染所有内容。

当我这样做时,有时会收到关键警告:

警告:flattenChildren(...): 遇到两个相同的孩子 钥匙。子键必须是唯一的;当两个孩子共用一把钥匙时,只有 将使用第一个孩子。

新数组不包含任何重复项。那么为什么会发生这种情况,我该怎么做才能避免这种情况呢?

编辑:应要求添加了一些代码。注意:我使用的是Infinite Scroll module。会不会是这个原因?

初始状态:

getInitialState: function() {
  return {
    hasMore: true,
    num: 0,
    movieIds: this.props.movieIds,
    movies: []
  };
},

渲染函数:

render: function() {
  var InfiniteScroll = React.addons.InfiniteScroll;

  return (
    <InfiniteScroll
        pageStart={0}
        loadMore={this.loadMore}
        threshold='20'
        hasMore={this.state.hasMore}>
        <ul className="movieList">
          {this.state.movies}
        </ul>
    </InfiniteScroll>       
);
}

简化加载更多:

comp = this;
$.ajax( {
  url: url,
  contentType: "json",
  success: function (data) {
    var m = createMovieLi(data);
    var updatedMovies = comp.state.movies;
    updatedMovies[num] = m;
    comp.setState({movies: updatedMovies});
  }
});

最后在组件外部更新时:

movieBox.setState({
  hasMore: true,
  num: 0,
  movieIds: filteredIds,
  movies: []
});

【问题讨论】:

  • 给出一个显示问题的最小代码示例。这可能是一个简单的修复,但完全取决于您的代码。

标签: reactjs


【解决方案1】:

我发现了我的错误,它与 React 本身无关。这是missing javascript closure inside a loop的经典案例。

由于存在重复的可能性,我将每个 ajax 响应存储在 window.localStorage 中的 movieId 上。或者我是这么想的。

在 React Infinite Scroll 中,列表中的每个项目都是通过调用 loadMore 函数按顺序绘制的。在这个函数中,我进行了 ajax 调用,并将结果存储在浏览器缓存中。代码看起来像这样:

  var cachedValue = window.localStorage.getItem(String(movieId));
  var cachedData = cachedValue ? JSON.parse(cachedValue) : cachedValue;

  if (cachedData != null) {
    comp.drawNextMovie(cachedData);
  } else { 
    $.ajax( {
      type: "GET",
      url: this.state.movieUrl + movieId,
      contentType: "json",
      success: function (movieData) {
        window.localStorage.setItem(String(movieId), JSON.stringify(movieData));
        comp.drawNextMovie(movieData);
      }
    });  
  }    

你能找出错误吗?当 ajax 调用返回时,movieId 不再是原来的样子。所以我最终用错误的 id 存储数据,并得到一些奇怪的 React 警告作为回报。因为这是在 InfiniteScroll 模块调用的 loadMore 函数中完成的,所以我不知道这个函数的范围不正确。

我通过添加 Immediately-invoked function expression 来修复它。

【讨论】:

  • IIFE 需要围绕您刚刚描述的整个区块。
【解决方案2】:

我不会使用来自后端的 ID 作为 React 中的关键属性。如果你这样做了,你就依赖于一些离你的组件有点远的逻辑来确保你的键是唯一的。如果它们的键不是唯一的,你可以破坏 react 的渲染,所以这很重要。

在我看来,这就是为什么你应该坚持在 for 循环或类似的循环中使用索引来设置关键属性。这样你就知道它们永远不会是非唯一的,而且这是做到这一点的最简单方法。

如果不确切知道您的 ID 是如何工作的,就不可能说出是什么导致了这里的非唯一冲突。然而,由于key 只是为了让 React 能够正确识别元​​素,仅此而已,所以除了简单的计数或索引之外,它没有任何意义。

var children = [];
for (var i=0; i<yourArray.length; i++) {
    children.push(
        <div key={i}>{yourArray[i].someProp}</div>
    );
}

【讨论】:

  • 例外情况是项目是否可以重新排序,或者可以在除结尾之外的任何位置插入/删除。
  • 感谢您的意见。但它并没有解决我的问题。
  • 如果您使用React.Children.map 遍历对象列表,您将如何获得要使用的索引号?你可以使用map 函数中的任何东西吗?还是您自己创建一个?
  • @BenGrunfeld 索引将作为第二个参数传递给您提供给map 的函数。示例:jsfiddle.net/69z2wepo/4120
  • @mheiber 你甚至没有在你的例子中使用 key 属性?
猜你喜欢
  • 2020-09-16
  • 1970-01-01
  • 2017-06-03
  • 2015-01-22
  • 2020-05-17
  • 1970-01-01
  • 1970-01-01
  • 2016-11-21
  • 1970-01-01
相关资源
最近更新 更多