бесконечный рендеринг в 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
}
...
}