Reactjs 道具更改时由map()引起的意外渲染

Reactjs 道具更改时由map()引起的意外渲染,reactjs,redux,Reactjs,Redux,我正在与React和Redux合作。我希望Redux存储客户端信息视口尺寸、浏览器名称和版本、操作系统等。我已经设置了还原器和操作,并将所有状态映射到组件的道具。在组件内部,有一个组件将道具传递给组件。然后将道具映射到渲染,然后生成以下导航栏: Home | About us | Apply | News | Login 但是,由于组件内部的道具映射,每当用户调整视口的大小、Redux存储区内部的状态更改、导致内部道具更改,当然,React重新渲染整个导航栏时,都会产生以下意外行为: Hom

我正在与React和Redux合作。我希望Redux存储客户端信息视口尺寸、浏览器名称和版本、操作系统等。我已经设置了还原器和操作,并将所有状态映射到组件的道具。在组件内部,有一个组件将道具传递给组件。然后将道具映射到渲染,然后生成以下导航栏:

Home  | About us | Apply | News | Login
但是,由于组件内部的道具映射,每当用户调整视口的大小、Redux存储区内部的状态更改、导致内部道具更改,当然,React重新渲染整个导航栏时,都会产生以下意外行为:

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

简言之,返回的项目比它应该返回的多。 为了更好地说明,我加入了代码结构:

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)
rootActions.js定义了调度器

import { tUpdateViewport } from './rootTypes';

export const aUpdateViewport = () => ({type: tUpdateViewport })
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定义组件并呈现

Header.js定义组件并呈现组件

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
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
NavigationBar.js定义组件并将道具映射到渲染组件

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
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定义并呈现导航栏

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
我怎样才能解决这个问题?谷歌搜索了地图问题,但没有找到想要的结果。任何帮助都将不胜感激。

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


特定的存在是非随机的。将其更改为每个循环都一致且唯一的值,如标题,这可能会解决您的问题。

我相信这行可以写成
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