确定React密钥属性的正确方法是什么?

时间:2016-04-23 22:39:31

标签: reactjs react-native

我正在尝试学习React-Native。我正在看an example, Responsive image grid by Joshua Sierles.(谢谢Joshua!)在这个示例中,Joshua使用React以可控的方式在移动显示屏上小心地放置图像元素。注意:他只使用三张图像,并在文档中重复多次。不幸的是,如上所述,该示例会生成警告:

  

警告:数组或迭代器中的每个子节点都应该有一个唯一的"键"   支柱。检查YourProjectNameHere的呈现方法。看到   fb.me/react-warning-keys了解更多信息。 (从缩短的形式推断的链接......)

我完全理解React must have a unique key property.生成的行和每一行中的每个元素我不清楚的是如何做到这一点。这是我的黑客/解决方法。 key={Math.random()}

这个黑客工作正常,但它似乎是......错了。这里的问题是,识别单个图像ID的正确方法是什么,以及识别单个行ID?

'use strict';

 var React = require('react-native');
 var {
   AppRegistry,
   StyleSheet,
   Text,
   View,
   Image,
   Dimensions,
   ScrollView
 } = React;

 var _ = require('lodash');
 var {width, height} = Dimensions.get('window');

 var IMAGE_URLS = _.flatten(_.times(9, () => {return ['http://rnplay.org/IMG_0599.jpg', 'http://rnplay.org/IMG_0602.jpg', 'http://rnplay.org/IMG_0620.jpg']}));  // 9 x 3 = 27 images
 var IMAGES_PER_ROW = 4;

 var AwesomeProject1 = React.createClass({

   getInitialState() {
     return {
       currentScreenWidth: width,
       currentScreenHeight: height
     }
   },

   handleRotation(event) {
     var layout = event.nativeEvent.layout
     this.setState({currentScreenWidth: layout.width, currentScreenHeight: layout.height })
   },

   calculatedSize() {
     var size = this.state.currentScreenWidth / IMAGES_PER_ROW
     return {width: size, height: size}
   },


   // note:  I added key={Math.random()} in two places below.
   // Its a BS fix, but it seems to work to avoid the warning message.
   renderRow(images) {
     return images.map((uri) => {
       return (
         <Image style={[styles.image, this.calculatedSize()]} key={Math.random()} source={{uri: uri}} />  //key={Math.random()}
       )
     })
   },

   renderImagesInGroupsOf(count) {
     return _.chunk(IMAGE_URLS, IMAGES_PER_ROW).map((imagesForRow) => {
       return (
         <View style={styles.row} key={Math.random()}>
           {this.renderRow(imagesForRow)}
         </View>
       )
     })
   },

   render: function() {
     return (
       <ScrollView onLayout={this.handleRotation} contentContainerStyle={styles.scrollView}>
         {this.renderImagesInGroupsOf(IMAGES_PER_ROW)}
       </ScrollView>
     );
   }
 });

 var styles = StyleSheet.create({

   row: {
     flexDirection: 'row',
     alignItems: 'center',
     justifyContent: 'flex-start'
   },

   image: {
   }
 });

 AppRegistry.registerComponent('AwesomeProject1', () => AwesomeProject1);

我已经尝试了key=uri.id , imagesForRow.id, images.id, etc的每一个组合,我能想到的。无效与随机数功能一样好。其他想法?什么是正确的方法?

根据Chris Geirman的回复,下面给出了答案:我想展示我的最终代码。

renderRow(images) {
     return images.map((uri, idx) => {
       return (
         <Image style={[styles.image, this.calculatedSize()]} key={uri.concat(idx)} source={{uri: uri}} />  //key={Math.random()}
       )
     })
   },

   renderImagesInGroupsOf(count) {
     return _.chunk(IMAGE_URLS, IMAGES_PER_ROW).map((imagesForRow, idx2) => {
       return (
         <View style={styles.row} key={imagesForRow.concat(idx2)}>
           {this.renderRow(imagesForRow)}
         </View>
       )
     })
   },

2 个答案:

答案 0 :(得分:5)

我认为只有当你要移动组件(例如更改顺序)或前置元素时,关键才真正相关。如果这对您来说不是问题,而您只想使警告静音,则可以使用数组索引(第二个arg到free_result())。

React对帐算法的简短官方说明在React Docs - Listing Mutations

扩展答案

我将对此进行扩展,因为我相信你所接受的另一个答案是误导性的:不是一个合理的解决方案,因为它呈现出来。

我的理解是,React将一个元素子元素解释为一个特殊情况,其中元素的长度或相对位置可能会在需要保留组件实例时发生变化。在React docs中,他们将其描述为:

  

当孩子们被洗牌时(如在搜索结果中)或者新组件被添加到列表的前面(如在流中)。

但是,通常使用数组只是为了方便脚本生成子项,否则可以按字面/静态表示。这就是我认为你在这里的情况。如果不是这种情况,您将需要并且可能已经具有合法的唯一ID。例如,如果我正确理解您的用例,您可以执行类似的操作,在这种情况下,React不会发出任何关键警告:

map()

显然,这将是一种可怕的方式,因此使用数组。但是React并没有将这种数组的使用(与其等效的脚本)区别开来(其中元素的长度和顺序是动态的)并且抱怨关键。你可以忽略警告,但只需指定元素的索引值就可以轻松实现静音。

Simplified example。这些变化产生相同的输出,但不同之处在于字面上声明或编写脚本并发出关键警告。

var image_urls = [
  'http://rnplay.org/IMG_0599.jpg',
  'http://rnplay.org/IMG_0602.jpg',
  'http://rnplay.org/IMG_0620.jpg'
];

{/* #1 */}
<View>
  <Image style={[styles.image, this.calculatedSize()]} source={{uri: image_urls[0]}} />
  <Image style={[styles.image, this.calculatedSize()]} source={{uri: image_urls[1]}} />
  <Image style={[styles.image, this.calculatedSize()]} source={{uri: image_urls[2]}} />
  <Image style={[styles.image, this.calculatedSize()]} source={{uri: image_urls[0]}} />
</View>

{/* ... */

{/* #n */}
<View>
  <Image style={[styles.image, this.calculatedSize()]} source={{uri: image_urls[0]}} />
  <Image style={[styles.image, this.calculatedSize()]} source={{uri: image_urls[1]}} />
  <Image style={[styles.image, this.calculatedSize()]} source={{uri: image_urls[2]}} />
  <Image style={[styles.image, this.calculatedSize()]} source={{uri: image_urls[0]}} />
</View>

答案 1 :(得分:0)

&#34; key&#34;反应的反应是如何确定分配给该节点的对象是否已更改。这是一种优化策略。如果为特定节点分配了相同的密钥,则该渲染为最后一次渲染的渲染,那么它就不会重新渲染。因此,您希望每个键对于该节点都是唯一的,但也是可预测/一致的,因此可以猜测,这样每次渲染该特定节点时,都会为其分配完全相同的唯一键。

Math.random()是密钥的不良选择,因为它无法通过可预测/一致的标准。出于类似的原因,使用数组索引本身也是一个糟糕的选择。

相反,我建议使用数组值+索引

renderRow(images) {
     return images.map((uri, idx, arr) => {
       return (
         <Image style={[styles.image, this.calculatedSize()]} key={uri.concat(idx)} source={{uri: uri}} />  
       )
     })
   },