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