【问题标题】:infinite Render in ReactReact 中的无限渲染
【发布时间】:2018-09-07 00:05:05
【问题描述】:

我无法弄清楚为什么我的应用程序进行无休止的渲染。

在里面,我的有状态组件,我在componentDidMount方法中调用了一个redux动作(调用componentWillMount也做无限渲染)

class cryptoTicker extends PureComponent {
  componentDidMount() {
    this.props.fetchCoin()
    // This fetches some 1600 crypto coins data,Redux action link for the same in end
  }

  render() {
    return (
      <ScrollView>
        <Header />
        <View>
          <FlatList
            data={this.state.searchCoin ? this.displaySearchCrypto : this.props.cryptoLoaded}
            style={{ flex: 1 }}
            extraData={[this.displaySearchCrypto, this.props.cryptoLoaded]}
            keyExtractor={item => item.short}
            initialNumToRender={50}
            windowSize={21}
            removeClippedSubviews={true}
            renderItem={({ item, index }) => (
              <CoinCard
                key={item["short"]}
              />
            )} 
          />
        </View>
      </ScrollView>
    )
  }
}

在 CoinCard 中,除此之外我什么都不做(注意平面列表中的 CoinCard)

class CoinCard extends Component {
  render () { 
    console.log("Inside rende here")
    return (
        <View> <Text> Text </Text>  </View>
    )  
  }
}

现在,当我控制台登录我的 coincard 渲染时,我可以看到 Inside rende here 的无限日志

[问题:]谁能帮我弄清楚为什么会发生这种情况?

您可以click here to see my actionsclick here to see my reducer

[更新:]我的repository is here,如果您想克隆并自己查看。

[更新:2]:我已经在github上推送了上面的共享代码,它仍然会记录无尽的console.log语句(如果你可以克隆,run and move back to this commit)。

[Update:3]: 我不再在&lt;FlatList /&gt; 中使用&lt;ScrollView /&gt;,当我的意思是无限渲染时,我的意思是它是无限的(并且不必要地)将相同的道具传递给子组件(&lt;Coincard /&gt;),如果我使用 PureComponent,它不会在render () { 中无休止地登录,而是在componentWillRecieveProps 中,如果我使用console.log(nextProps),我可以看到相同的日志一遍又一遍地传递

【问题讨论】:

  • 没有足够的信心发布完整的答案,但我认为您的问题可能是here。当您更新该组件的道具时,我认为componentDidUpdate 会触发,这将再次更新道具等。这也会导致FlatListdata 道具发生变化,这也会每次都渲染它,导致无限渲染。
  • 请在问题中包含相关代码。
  • @izb 非常感谢您的回答!我删除了 componentDidUpdate ,实际上我从代码中删除了所有内容并将其归结为我在上面共享的内容,但我仍然可以看到那些无穷无尽的日志:\
  • 您的项目可能不止一个问题。具体来说,您永远不应该分配给道具github.com/irohitb/Crypto/blob/…
  • 动作和减速器很好。如果我剥离 all 视图/列表/等,只渲染组件,并删除所有状态更新函数,所有内容只会渲染两次。这似乎是 React Native 特有的,我不是这方面的专家。我已在您的问题中添加了 react-native 标签,因此希望这有助于找到合适的人。祝你好运,这似乎是一个非常棘手的问题。我怀疑您会想要使用虚拟化列表,重新渲染是由布局问题引起的。

标签: javascript reactjs react-native


【解决方案1】:

您的代码中有一些需要注意的地方。

  • CoinCard 组件必须是 PureComponent,如果 props 浅相等,则不会重新渲染。
  • 您不应该在ScrollView 组件内渲染您的Flatlist,这会使该组件一次渲染其中的所有组件,这可能会导致FlatlistScrollView 之间出现更多循环。
  • 您还可以为渲染的组件指定height,以减少为其他道具渲染组件的次数。
  • 另外需要注意的是,只有组件中的 props 才会在滚动底部呈现,基于下面提到的日志语句。

    import {Dimensions} from 'react-native'
    
    const {width, height} = Dimensions.get('window)
    
    class CoinCard extends React.PureComponent {
    render () { 
      console.log(this.props.item.long)  //... Check the prop changes here, pass the item prop in parent Flatlist. This logs component prop changes which will show that same items are not being re-rendered but new items are being called.
      return (
        <View style={{height / 10, width}}> //... Render 10 items on the screen
          <Text>
            Text
          </Text>
        </View>
      )  
     }
    }
    

更新

这个额外的日志记录是由于 props 从 Flatlist 到您的组件而没有 PureComponent 浅层比较

请注意,componentWillReceiveProps() 已被弃用,您应该在代码中避免使用它们。 React.PureComponent 在幕后工作并使用 shouldComponentUpdatecurrentupdated 道具之间进行浅比较。因此,在 PureComponent' 中记录 console.log(this.props.item.long) render 将记录可以检查的唯一列表。

【讨论】:

    【解决方案2】:

    就像 izb 提到的,pb 的根本原因是在纯组件上完成的业务调用,而它刚刚加载。这是因为您的组件做出了业务决策(“我决定何时必须在我自己身上展示某些东西”)。这在 React 中不是一个好习惯,在使用 redux 时更是如此。组件必须尽可能愚蠢,甚至不决定做什么和什么时候做。

    正如我在您的项目中看到的,您没有正确处理组件和容器的概念。你的容器中不应该有任何逻辑,因为它应该只是一个愚蠢的纯组件的包装器。像这样:

    import { connect, Dispatch } from "react-redux";
    import { push, RouterAction, RouterState } from "react-router-redux";
    import ApplicationBarComponent from "../components/ApplicationBar";
    
    export function mapStateToProps({ routing }: { routing: RouterState }) {
        return routing;
    }
    
    export function mapDispatchToProps(dispatch: Dispatch<RouterAction>) {
        return {
            navigate: (payload: string) => dispatch(push(payload)),
        };
    }
    const tmp = connect(mapStateToProps, mapDispatchToProps);
    export default tmp(ApplicationBarComponent);
    

    和匹配的组件:

    import AppBar from '@material-ui/core/AppBar';
    import IconButton from '@material-ui/core/IconButton';
    import Menu from '@material-ui/core/Menu';
    import MenuItem from '@material-ui/core/MenuItem';
    import { StyleRules, Theme, withStyles, WithStyles } from '@material-ui/core/styles';
    import Tab from '@material-ui/core/Tab';
    import Tabs from '@material-ui/core/Tabs';
    import Toolbar from '@material-ui/core/Toolbar';
    import Typography from '@material-ui/core/Typography';
    import AccountCircle from '@material-ui/icons/AccountCircle';
    import MenuIcon from '@material-ui/icons/Menu';
    import autobind from "autobind-decorator";
    import * as React from "react";
    import { push, RouterState } from "react-router-redux";
    
    const styles = (theme: Theme): StyleRules => ({
      flex: {
        flex: 1
      },
      menuButton: {
        marginLeft: -12,
        marginRight: 20,
      },
      root: {
        backgroundColor: theme.palette.background.paper,
        flexGrow: 1
      },
    });
    export interface IProps extends RouterState, WithStyles {
      navigate: typeof push;
    }
    
    @autobind
    class ApplicationBar extends React.PureComponent<IProps, { anchorEl: HTMLInputElement | undefined }> {
      constructor(props: any) {
        super(props);
        this.state = { anchorEl: undefined };
      }
      public render() {
    
        const auth = true;
        const { classes } = this.props;
        const menuOpened = !!this.state.anchorEl;
        return (
          <div className={classes.root}>
            <AppBar position="fixed" color="primary">
              <Toolbar>
                <IconButton className={classes.menuButton} color="inherit" aria-label="Menu">
                  <MenuIcon />
                </IconButton>
                <Typography variant="title" color="inherit" className={classes.flex}>
                  Title
                </Typography>
    
                <Tabs value={this.getPathName()} onChange={this.handleNavigate} >
                  {/* <Tabs value="/"> */}
                  <Tab label="Counter 1" value="/counter1" />
                  <Tab label="Counter 2" value="/counter2" />
                  <Tab label="Register" value="/register" />
                  <Tab label="Forecast" value="/forecast" />
                </Tabs>
    
                {auth && (
                  <div>
                    <IconButton
                      aria-owns={menuOpened ? 'menu-appbar' : undefined}
                      aria-haspopup="true"
                      onClick={this.handleMenu}
                      color="inherit"
                    >
                      <AccountCircle />
                    </IconButton>
                    <Menu
                      id="menu-appbar"
                      anchorEl={this.state.anchorEl}
                      anchorOrigin={{
                        horizontal: 'right',
                        vertical: 'top',
                      }}
                      transformOrigin={{
                        horizontal: 'right',
                        vertical: 'top',
                      }}
                      open={menuOpened}
                      onClose={this.handleClose}
                    >
                      <MenuItem onClick={this.handleClose}>Profile</MenuItem>
                      <MenuItem onClick={this.handleClose}>My account</MenuItem>
                    </Menu>
                  </div>
                )}
              </Toolbar>
            </AppBar>
          </div >
        );
      }
      private getPathName(): string {
        if (!this.props.location) {
          return "/counter1";
        }
        return (this.props.location as { pathname: string }).pathname;
      }
      private handleNavigate(event: React.ChangeEvent<{}>, value: any) {
        this.props.navigate(value as string);
      }
    
      private handleMenu(event: React.MouseEvent<HTMLInputElement>) {
        this.setState({ anchorEl: event.currentTarget });
      }
    
      private handleClose() {
        this.setState({ anchorEl: undefined });
      }
    }
    export default withStyles(styles)(ApplicationBar);
    

    然后你会告诉我:“但是我应该从哪里发起呼叫来填满我的列表呢?” 好吧,我在这里看到您使用 redux-thunk(我更喜欢 redux observable... 学习起来更复杂,但 waaaaaaaaaaaaaaaaaay 更强大),那么这应该是启动这个调度的 thunk!

    总结一下:

    • 组件:最愚蠢的元素,通常应该只有渲染方法,以及其他一些用于冒泡用户事件的方法处理程序。此方法只负责向用户显示其属性。不要使用状态,除非您有仅属于该组件的可视信息(例如弹出窗口的可见性)。任何显示或更新的内容都来自上面:更高级别的组件或容器。它不决定更新自己的值。充其量,它处理子组件上的用户事件,然后在上面冒泡另一个事件,然后……也许在某个时候,它的容器会返回一些新属性!
    • 容器:非常愚蠢的逻辑,包括将顶级组件包装到 redux 中,以便将事件插入到操作中,并将存储的某些部分插入到属性中
    • Redux thunk(或 redux observable):它处理整个用户应用程序逻辑。这个家伙是唯一一个知道触发什么以及何时触发的人。如果前端的一部分必须包含复杂性,那就是这个!
    • Reducers:定义如何组织存储中的数据以使其尽可能易于使用。
    • 商店:理想情况下,每个顶级容器一个,唯一一个包含必须向用户显示的数据。没有其他人应该这样做。

    如果您遵循这些原则,您将永远不会遇到诸如“这到底是为什么这被称为两次?以及......是谁制造的?为什么在这个时候?”这样的问题。

    其他:如果你使用 redux,请使用不变性框架。否则你可能会遇到问题,因为 reducer 必须是纯函数。为此,您可以使用流行的 immutable.js,但一点也不方便。以及实际上是杀手的已故 ousider:immer(由作者或 mobx 制作)。

    【讨论】:

      【解决方案3】:

      上面评论中的 Jacob 似乎设法使组件只渲染了两次。

      这肯定会导致双重初始渲染(如果不是 PureComponent 会导致无限渲染):

      componentDidUpdate() {
          var updateCoinData;
      
          if (!updateCoinData) { // <- this is always true
              updateCoinData = [...this.props.cryptoLoaded];
              this.setState({updateCoinData: true}); // <- this will trigger a re render since `this.state.updateCoinData` is not initially true
          }
          ...
      }
      

      Link to the issue in your repository

      【讨论】:

      • 在我的最新提交中,我将其更改为if (this.state.updateCoinData || this.updateCoinData.length &lt; 1 ) { this.updateCoinData = [...this.props.cryptoLoaded]; this.setState({updateCoinData: true}) },即使没有它,它仍然会导致无限渲染
      猜你喜欢
      • 2020-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-26
      • 1970-01-01
      • 1970-01-01
      • 2021-07-01
      相关资源
      最近更新 更多