бесконечный рендеринг в React

у меня возникли проблемы с выяснением, почему мое приложение делает бесконечный рендеринг.

внутри, мой компонент с состоянием, я вызываю действие redux в методе componentDidMount (вызов 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 я буквально ничего не делаю, кроме этого (обратите внимание на совпадение внутри плоского списка)

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

теперь, когда я консольный журнал в моем рендере совпадения, я вижу бесконечный журнал внутри rende вот!--16-->

[вопрос:] может ли кто-нибудь помочь мне понять, почему это может произойти?

вы можете нажмите здесь, чтобы увидеть мои действия и щелкните здесь, чтобы увидеть мой редуктор.

[Update:] мой репозиторий здесь если вы хотите клонировать и увидеть его сами.

[обновления: 2]: я нажал вышеуказанный общий код на github, и он все равно будет войдите в бесконечную консоль.log операторы (если вы можете клонировать,выполнить и вернуться к этой фиксации ).

[Update: 3]: я больше не использую <ScrollView /> на <FlatList /> также, когда я имею в виду бесконечный рендеринг, я имею в виду, что он бесконечен (& Unecessarily), передавая те же реквизиты дочернему компоненту (<Coincard />), если я использую PureComponent, он не будет бесконечно входить в render () { а в componentWillRecieveProps, если я делаю console.log(nextProps), Я вижу, что один и тот же журнал проходит снова и снова

3 ответов


Как упоминает 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);

подведем итоги:

  • Components: самый глупый элемент, который обычно должен иметь только метод рендеринга и какой-либо другой обработчик методов для пузырьков пользовательских событий. Этот метод заботится только о том, чтобы показать свои свойства пользователю. Не используйте состояние, если у вас нет визуальной информации, которая принадлежит только этому компоненту (например, видимость всплывающего окна). Все, что показано или обновлено, поступает сверху: компонент более высокого уровня или контейнер. Он не решает обновлять свои собственные значения. В лучшем случае он обрабатывает пользовательское событие в подкомпоненте, а затем всплывает другое событие выше и... ну, может быть, в какой-то момент некоторые новые свойства будут возвращены своим контейнером!
  • контейнер: очень глупая логика, которая заключается в обертывании компонента верхнего уровня в redux для подключения событий к действиям и подключения некоторой части магазина к свойствам
  • Redux thunk (или redux наблюдаемый): это тот, который обрабатывает всю логику пользовательского приложения. Этот парень единственный, кто знает, что и когда запускать. Если часть вашего переднего конца должна содержать сложность, это она!
  • редукторы: определите, как организовать данные в хранилище, чтобы они были максимально удобными для использования.
  • хранилище: в идеале один контейнер верхнего уровня, единственный, который содержит данные, которые должны быть показаны пользователю. Никто не должен.

Если вы будете следовать этим принципы, вы никогда не должны сталкиваться с такой проблемой, как "почему, черт возьми, это называется дважды? и... кто его сделал? и почему именно сейчас?"

что-то еще: если вы используете redux, используйте структуру неизменяемости. В противном случае вы можете столкнуться с проблемами, поскольку редукторы должны быть чистыми функциями. Для этого вы можете использовать популярный неизменными.js, но совсем не удобно. И покойный оузидер, который на самом деле убийца: Иммер (сделанный автором или mobx).


есть некоторые моменты, чтобы отметить в коде.

  • на CoinCard компонент должен быть PureComponent, который не будет оказывать, если props мелкие-равны.
  • вы не должны делать свой Flatlist внутри ScrollView компонент, который сделает компонент рендеринг всех компонентов внутри него сразу, что может вызвать больше циклов между Flatlist и ScrollView.
  • вы также можете определенный height к вынесено компонент чтобы уменьшить количество раз, компонент отображается для других реквизитов.
  • еще одно замечание,отображаются только реквизиты в компоненте в нижней части прокрутки на основе инструкции log, упомянутой ниже.

    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>
      )  
     }
    }
    

обновление

это дополнительное ведение журнала связано с реквизитом из Flatlist к вашему компоненту без PureComponent мелкий сравнение.

отметим, что componentWillReceiveProps() является устаревшим, и вы должны избегать их в своем коде. React.PureComponent работает под капотом и использует shouldComponentUpdate использовать неглубокое сравнение между current и updated бутафория. Поэтому журнал console.log(this.props.item.long) в своем PureComponent' render будет регистрировать уникальный список, который можно проверить.


Кажется, Джейкобу в приведенном выше комментарии удалось сделать компонент рендеринга только дважды.

Это определенно вызовет двойной начальный рендер (и вызовет бесконечный рендер, если он не был 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
    }
    ...
}

ссылка на проблему в вашем репозитории