当道具改变

时间:2017-05-31 07:37:16

标签: react-native react-native-listview

我遇到了新的FlatList组件的问题。具体来说,它不会重新渲染它的行,即使该行依赖于变化的道具。

FlatList文档说:

  

这是一个PureComponent,这意味着它不会重新渲染   道具仍然浅薄相等。确保renderItem的所有内容   函数依赖于作为更新后的===传递,   否则您的UI可能无法更新更新。这包括数据   道具和父母组成部分。

问题

然而,当我更改selectedCategory项目的ID时 - 应该指示行是否被“选中”的道具 - 我相信道具应该重新渲染。我错了吗?

我检查了列表和行组件的'componentWillReceiveProps'方法,列表接收更新就好了,但是从不调用行的生命周期方法。

如果我在列表组件中包含一个随机的,无用的布尔状态值,并在道具更新时来回切换它,它可以工作 - 但我不知道为什么?

state = { updated: false };

componentWillReceiveProps(nextProps) {
  this.setState(oldstate => ({
    updated: !oldstate.updated,
  }));
}

<FlatList
  data={this.props.items.allAnimalCategories.edges}
  renderItem={this._renderRow}
  horizontal={true}
  keyExtractor={(item, index) => item.node.id}
  randomUpdateProp={this.state.updated}
/>

代码

我的代码的结构是这样的:我有一个包含所有逻辑和状态的容器组件,它包含一个FlatList组件(表示,没有状态),它还包含一个自定义的表示行。

Container
  Custom list component that includes the FlatList component
  (presentational, stateless) and the renderRow method
    Custom row (presentational, stateless)

容器包含以下组件:

 <CustomList
   items={this.props.viewer}
   onCategoryChosen={this._onCategoryChosen}
   selectedCategory={this.state.report.selectedCategory}
 />

CustomList:

class CustomList extends Component {
  _renderRow = ({ item }) => {
    return (
      <CustomListRow
        item={item.node}
        selectedCategory={this.props.selectedCategory}
        onPressItem={this.props.onCategoryChosen}
      />
    );
  };

  render() {
    return (
      <View style={_styles.container}>
        <FlatList
          data={this.props.items.categories.edges}
          renderItem={this._renderRow}
          horizontal={true}
          keyExtractor={(item, index) => item.node.id}
          randomUpdateProp={this.state.updated}
        />
      </View>
    );
  }

}

(数据来自Relay)

最后一行:

render() {
    const idsMatch = this.props.selectedCategory.id == this.props.item.id;
    return (
      <TouchableHighlight onPress={this._onItemPressed}>
        <View style={_styles.root}>
          <View style={[
              _styles.container,
              { backgroundColor: this._getBackgroundColor() },
            ]}>
            {idsMatch &&
              <Image
                style={_styles.icon}
                source={require('./../../res/img/asd.png')}
              />}
            {!idsMatch &&
              <Image
                style={_styles.icon}
                source={require('./../../res/img/dsa.png')}
              />}
            <Text style={_styles.text}>
              {capitalizeFirstLetter(this.props.item.name)}
            </Text>
          </View>
          <View style={_styles.bottomView}>
            <View style={_styles.greyLine} />
          </View>
        </View>
      </TouchableHighlight>
    );
  }

这一行不是那么有趣,但我把它包括在内,表明它完全是无国籍的,并且依赖于它的父母道具。

状态更新如下:

_onCategoryChosen = category => {
    var oldReportCopy = this.state.report;
    oldReportCopy.selectedCategory = category;
    this.setState(Object.assign({}, this.state, { report: oldReportCopy }));
  };
国家看起来像这样:

state = {
    ...
    report: defaultStateReport,
  };

const defaultStateReport = {
  selectedCategory: {
    id: 'some-long-od',
    name: '',
  },
  ...
};

8 个答案:

答案 0 :(得分:53)

这里的问题在于

  1. 您正在改变现有的状态切片,而不是创建变异副本
  2. _onCategoryChosen = category => {
        var oldReportCopy = this.state.report; // This does not create a copy!
        oldReportCopy.selectedCategory = category;
        this.setState(Object.assign({}, this.state, { report: oldReportCopy }));
    };
    

    这应该是

    _onCategoryChosen = category => {
        var oldReportCopy = Object.assign({}, this.state.report);
        oldReportCopy.selectedCategory = category;
        // setState handles partial updates just fine, no need to create a copy
        this.setState({ report: oldReportCopy });
    };
    
    1. FlatList的道具保持不变,你的_renderRow函数可能依赖于确实发生变化的selectedCategory道具(如果不是第一个错误),但FlatList组件不知道关于那个。要解决此问题,请使用extraData prop。

      <FlatList
        data={this.props.items.categories.edges}
        renderItem={this._renderRow}
        horizontal={true}
        keyExtractor={(item, index) => item.node.id}
        extraData={this.props.selectedCategory}
      />
      

答案 1 :(得分:21)

您可以通过以下方式解决此问题:将道具传递给平面列表组件中的 extraData

  <FlatList
    data={this.props.data}
    extraData={this.props}
    keyExtractor={this._keyExtractor}
    renderItem={this._renderItem}
  />

答案 2 :(得分:3)

就我而言,使用keyExtractor

时我犯了一个简单的错误

我改变了

keyExtractor={(item, index) => index.toString()} 

收件人

keyExtractor={(item, index) => item.key} 

在过滤列表后,我看到了一些奇怪的效果,该列表显示了被滤除的组件的道具代替新组件的道具,因此我的直觉是因为我使用的是数组的索引而不是唯一键,所以我只是将旧道具传递给新组件,即使基础组件实际上发生了变化。

答案 3 :(得分:2)

我同意尼梅利安。另外,如果您的状态是一个数组,则可以通过执行以下操作从该状态创建一个数组对象:

 var oldReportCopy = Object.assign([], this.state.report);

然后使用.push()方法向其添加新对象,如下所示:

oldReportCopy.push(selectedCategory);

然后您可以将此新的数组对象设置回状态:

this.setState({ report: oldReportCopy });

答案 4 :(得分:0)

也许其他任何人都不会这样,但是我意识到,当FlatList呈现的项目数组为空时,我只是遇到麻烦。就我而言,我只需要根本不渲染FlatList,而是在其位置渲染一个不同的View,当然可以解决“不重新渲染”的问题。

答案 5 :(得分:0)

查看第4行

_onCategoryChosen = category => {
    var oldReportCopy = this.state.report;
    oldReportCopy.selectedCategory = category;
    this.setState({ report: [...oldReportCopy] }); // Notice this line
  };

答案 6 :(得分:0)

这对我不起作用

       setTabData(tabD);

这对我有用

      setTabData([...tabD]);

答案 7 :(得分:0)

在 react hooks 中,您可以执行以下操作:

const onPressLeaderSelect = (item, index) => {
    let oldMemberCopy = Object.assign([], teamMemberArr);  //'teamMemberArr' is local state
    let objIndex = oldMemberCopy.findIndex((obj => obj.teamLeader == 1));
    oldMemberCopy[objIndex].teamLeader = 0
    oldMemberCopy[index].teamLeader = 1
    console.log('onPressLeaderSelect', oldMemberCopy)
    setteamMemberArr(oldMemberCopy)
}