当props改变时由map()引起的意外渲染

问题描述 投票:0回答:1

我正在使用React和Redux。我希望Redux存储客户端信息(视口尺寸,浏览器名称和版本,操作系统等)。我已经设置了reducers和actions,并将所有状态映射到<App />组件的道具。在<App />组件内部有一个<Header />组件,它将道具传递给<NavigationBar />组件。 <NavigationBar />然后映射道具以渲染<NavigationItems />,然后生成以下导航栏:

Home  | About us | Apply | News | Login

但是由于<App />组件内部的props映射,每次用户调整视口大小时,Redux存储库中的状态更改,导致<App />中的props更改,当然,React重新呈现整个导航栏会产生以下意外行为:

Home | Home | About us | Apply | News | Login

要么

About us | Home | About us | Apply | News | Login

简而言之,<NavigationBar />返回的项目数量应该超出预期。我已经包含了代码结构以便更好地说明:

Store (contains client info) 
---> <App /> (map states, dispatchers from store to props) 
---> <Header /> (functional component acts as container, pass navigation settings as props down to <NavigationBar />)
---> <NavigationBar /> (receives props from <Header /> and renders <NavigationItems /> component with props from <Header /> using map() function)
---> <NavigationItems /> (renders navigation items based on the props)

我还包含了代码以防万一有人需要它:

rootTypes.js(定义动作类型)

export const tUpdateViewport = 'updateViewport'

rootActions.js(定义调度程序)

import { tUpdateViewport } from './rootTypes';

export const aUpdateViewport = () => ({type: tUpdateViewport })

rootReducer.js(定义app reducer并将其与其他人合并)

import { tUpdateViewport } from './rootTypes';
import { combineReducers } from 'redux';

const appStates = {
    viewportWidth: document.documentElement.clientWidth,
    viewportHeight: document.documentElement.clientHeight,
}; 

const appReducer = (states = appStates, action) => {
    switch(action.type) {
        case tUpdateViewport: 
            return {
                ...states,
                viewportWidth: document.documentElement.clientWidth,
                viewportHeight: document.documentElement.clientHeight,
            };
        default:
            return states;
    }
};

export default combineReducers({
    app: appReducer,
})

App.js(定义<App />组件并呈现<Header />

import React from 'react';
import Header from './components/container/header/Header';
import * as actions from './rootActions';
import { connect } from 'react-redux';

const mapStatesToProps = states => ({
    browserInfo: states.app
});

class App extends React.Component {
    componentDidMount = () => {
        window.addEventListener('resize', this.props.aUpdateViewport, false);
    };
    componentWillUnmount = () => {
        window.removeEventListener('resize', this.props.aUpdateViewport, false);
    };
  render = () => {
    return (
            <Header />
    );
  };
}

export default connect(mapStatesToProps, actions)(App)

Header.js(定义<Header />组件并呈现<NavigationBar />组件)

import React from 'react';
import NavigationBar from '../../presentational/header/NavigationBar';
import { Container, Grid, Row, Col } from '../../../css/Grid.css';
import { Header as Navigator } from '../../../css/Layout.css';
import logo from '../../../media/images/logo.png';

const Header = props => {
    return (
        <Navigator>
            <Container>
                <header>
                    <Grid>
                        <Row>
                            <Col>
                                <a href="#"><img src={logo} alt="CKY-UK Logo" /></a>
                            </Col>
                            <Col stretchWidth>
                                <Grid>
                                    <Row stretchHeight alignEnd="md" alignMiddle="md">
                                        <NavigationBar settings={{
                                            items:
                                            [
                                                {
                                                    title: 'Home',
                                                    link: '/'
                                                },
                                                {
                                                    title: 'About us',
                                                    dropdownHierarchy: 'parents',
                                                    icons: [{
                                                        title: 'chevron-down',
                                                        iconPos: 'afterText'
                                                    }],
                                                    items: [{
                                                        title: 'Leadership Team',
                                                        link: '#leadership-team'
                                                    }, 
                                                    {
                                                        title: 'Star players',
                                                        link: '#star-players'
                                                    }]
                                                },
                                                {
                                                    title: 'Apply',
                                                    link: '#apply'
                                                },
                                                {
                                                    title: 'News',
                                                    link: '#news'
                                                },
                                                { 
                                                    title: 'Login',
                                                    link: '#login',
                                                    icons: [{
                                                        title: 'sign-in-alt',
                                                        iconPos: 'beforeText'
                                                    }],
                                                }
                                            ]
                                            }} />
                                    </Row>
                                </Grid>
                            </Col>
                        </Row>
                    </Grid>
                </header>
            </Container>
        </Navigator>
    );
};

export default Header

NavigationBar.js(定义<NavigationBar />组件并映射道具以渲染<NavigationItems />组件)

import React from 'react';
import NavigationItems from './NavigationItems';
import { uniqID } from '../../../common/Functions';
import { Row } from '../../../css/Grid.css';

const NavigationBar = props => {
    const settings = props.settings;
    return (
        <nav>
            <ul>
                <Row>
                    {settings.items.map((contents, index) => 
                        <NavigationItems settings={{...contents}} isDropdown={contents.items ? true : false} key={uniqID()} />
                    )}
                </Row>
            </ul>
        </nav>
    );
};

export default NavigationBar

NavigationItems.js(定义<NavigationItems />并呈现导航栏)

import React from 'react';
import { uniqID } from '../../../common/Functions';
import { NavLi, NavItem, Dropdown } from '../../../css/Navigation.css';
import { Icon } from '../../../css/Common.css';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

class NavigationItems extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            dropdownOpened: false,
            navLiWidth: null,
            navLiHeight: null
        }
    }
    handleClick = e => {
        e.preventDefault();
        this.setState(prevState => ({
            dropdownOpened: !prevState.dropdownOpened
        }));
    };
    handleClickOutside = e => {
        const target = e.target;
        if (!this.NavLi.contains(target)) {
            this.setState({
                dropdownOpened: false
            })
        }
    }
    componentDidMount = () => {
        const navLiWidth = this.NavLi.clientWidth,
                    navLiHeight = this.NavLi.clientHeight;
        this.setState({
            navLiWidth: navLiWidth,
            navLiHeight: navLiHeight
        });
        document.addEventListener('click', this.handleClickOutside, false);
    }
    componentWillUnmount = () => {
        document.removeEventListener('click', this.handleClickOutside, false);
    }
    render = () => {
        const settings = this.props.settings,
                    isDropdown = this.props.isDropdown
        return (
            <NavLi isDropdown={isDropdown} innerRef={x => this.NavLi = x}>
                <NavItem href={settings.link || '#'} onClick={isDropdown ? this.handleClick : null}>
                {settings.icons && settings.icons.filter(contents => contents.iconPos === 'beforeText').map(contents => 
                    <Icon beforeText key={uniqID()}><FontAwesomeIcon icon={contents.title} fixedWidth /></Icon> 
                )} 
                {settings.title}
                {settings.icons && settings.icons.filter(contents => contents.iconPos === 'afterText').map(contents => 
                    <Icon afterText key={uniqID()}><FontAwesomeIcon icon={contents.title} fixedWidth /></Icon>
                )}
                </NavItem>
                {isDropdown && 
                    <Dropdown hierarchy={settings.dropdownHierarchy} opened={this.state.dropdownOpened} topPos={this.state.navLiHeight} leftPos={this.state.navLiWidth} desPos={10} startPos={50}>
                        {settings.items.map(contents => 
                            <NavigationItems settings={{...contents}} isDropdown={contents.items ? true : false} key={uniqID()} />
                                                                                                                                                                                                                                                                                                                                    )} 
                                                                                                                                                                                                                                                                                                         </Dropdown>
              }
        </NavLi> 
        );
    };
};

export default NavigationItems

我怎样才能解决这个问题?谷歌搜索映射问题,但没有找到所需的结果。任何帮助将不胜感激。

reactjs redux
1个回答
1
投票

我猜uniqID只是一个随机数发生器?

key prop特别存在为非随机的。将它更改为每个循环一致且唯一的值,如title,这可能会解决您的问题。

© www.soinside.com 2019 - 2024. All rights reserved.