我正在从事React项目,并制作了自己的手风琴组件。在应用程序的一页上,我需要呈现一个手风琴列表,并且单击打开标题后,必须从API中获取每个手风琴的内容。当前,我的手风琴有一个状态叫做open,我允许组件的用户传递两个回调:onClickOpen和onClickClose。这是一个handleClick函数,用于设置状态,然后在setState回调中调用该回调。
我的问题是,似乎从未调用过set state,因为在我控制台日志中this.state.open的值始终为false。我假设回调发生了什么,但我不确定是什么。
手风琴组件(称为Section):
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import shortid from 'shortid';
import {
SectionContainer,
ToggleButton,
HeaderContainer,
Heading,
BodyContainer,
Body,
Icon,
Button,
} from './sectionStyles';
class Section extends Component {
static propTypes = {
titles: PropTypes.array.isRequired,
children: PropTypes.node,
data: PropTypes.object,
noExpand: PropTypes.bool,
showSecondButton: PropTypes.bool,
onSecondButtonClick: PropTypes.func,
color: PropTypes.string,
widths: PropTypes.array,
fontSize: PropTypes.number,
fontWeight: PropTypes.number,
secondIconName: PropTypes.string,
secondIconColor: PropTypes.string,
onClickOpen: PropTypes.func,
onClickClose: PropTypes.func,
};
static defaultProps = {
children: null,
noExpand: false,
showSecondButton: false,
onSecondButtonClick: () => {},
data: {},
onClickOpen: () => {},
onClickClose: () => {},
};
state = {
open: false,
};
handleClick = () => {
if (this.props.noExpand) return;
if (this.state.open) {
this.setState({ open: false }, () => {
if (this.props.onClickClose) this.props.onClickClose();
});
} else {
this.setState({ open: true }, () => {
if (this.props.onClickOpen) this.props.onClickOpen();
});
}
}
renderHeadings() {
return this.props.titles.map((title, i) => {
return (
<Heading width={this.props.widths ? this.props.widths[i] : null} fontSize={this.props.fontSize} fontWeight={this.props.fontWeight} key={shortid.generate()}>
{
this.props.showSecondButton &&
(
<Button onClick={() => this.props.onSecondButtonClick(this.props.data)}>
<Icon className="material-icons md-32" color={this.props.secondIconColor}>{this.props.secondIconName}</Icon>
</Button>
)
}
{title}
</Heading>
);
});
}
render() {
return (
<SectionContainer>
{ !this.props.noExpand && <ToggleButton color={this.props.color} open={this.state.open} />}
<HeaderContainer open={this.state.open} onClick={() => this.handleClick()}>
{this.renderHeadings()}
</HeaderContainer>
<BodyContainer open={this.state.open}>
<Body>
{this.props.children}
</Body>
</BodyContainer>
</SectionContainer>
);
}
}
export default Section;
样式化组件的样式:
import styled from 'styled-components';
import { colors } from '../../../theme/vars';
import { themes as themeTypes } from '../../../types';
export const SectionContainer = styled.div(props => ({
boxSizing: 'border-box',
position: 'relative',
width: '100%',
borderBottom: `1px solid ${colors.SLATE_BLUE_20}`,
borderLeft: props.open === true ? `1px solid ${colors.MOSS_GREEN_FOCUS_13}` : 'none',
borderRight: props.open === true ? `1px solid ${colors.MOSS_GREEN_FOCUS_13}` : 'none',
}));
export const ToggleButton = styled.button`
box-sizing: border-box;
position: absolute;
right: 0;
margin: 0;
padding: 0;
height: 3em;
width: 3em;
outline: 0;
border: 0;
background: none;
text-indent: -9999%;
pointer-events: none;
font-size: 14px;
line-height: 18px;
&:before {
content: '';
display: block;
position: absolute;
height: 12px;
width: 4px;
border-radius: .3em;
background: ${props => props.color || colors.MOSS_GREEN_100};
transform-origin: 50%;
top: 50%;
left: 50%;
transition: all .25s ease-in-out;
transform: ${props => props.open === true ? `translate(0%, -50%) rotate(-45deg)` : `translate(75%, -50%) rotate(45deg)` };
}
&:after {
content: '';
display: block;
position: absolute;
height: 12px;
width: 4px;
border-radius: .3em;
background: ${props => props.color || colors.MOSS_GREEN_100};
transform-origin: 50%;
top: 50%;
left: 50%;
transition: all .25s ease-in-out;
transform: ${props => props.open === true ? `translate(0%, -50%) rotate(45deg)` : `translate(-75%, -50%) rotate(-45deg)`};
}
`;
export const HeaderContainer = styled.div`
box-sizing: border-box;
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: row;
flex-wrap: nowrap;
padding-left: 16px;
height: 40px;
width: 100%;
overflow: hidden;
cursor: pointer;
white-space: nowrap;
text-overflow: ellipsis;
color: ${colors.SLATE_BLUE_100};
background: ${props => props.open === true ? colors.MOSS_GREEN_FOCUS_13 : colors.WHITE};
border-bottom: 1px solid ${colors.SLATE_BLUE_20};
`;
export const Heading = styled.div(props => ({
boxSizing: 'border-box',
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
verticalAlign: 'center',
width: props.width || '25%',
fontSize: `${props.fontSize || 14}px`,
fontWeight: props.fontWeight || 400,
lineHeight: '32px',
color: colors.SLATE_BLUE_100,
}));
export const BodyContainer = styled.div(props => ({
boxSizing: 'border-box',
overflow: 'hidden',
height: props.open === true ? 'auto' : '0px', // how to transition this...
transition: 'all .2s ease-in',
borderLeft: props.open === true ? `1px solid ${colors.MOSS_GREEN_FOCUS_13}` : 'none',
borderRight: props.open === true ? `1px solid ${colors.MOSS_GREEN_FOCUS_13}` : 'none',
borderBottom: props.open === true ? `2px solid ${colors.MOSS_GREEN_100}` : 'none',
background: colors.WHITE,
}));
export const Body = styled.div(props => ({
boxSizing: 'border-box',
padding: '1em',
color: '#333',
lineHeight: '1.3',
}));
export const Icon = styled.i`
color: ${props => props.color};
font-size: '32px';
margin-top: auto;
`;
export const Button = styled.button`
display: flex;
flex-direction: row;
align-self: center;
margin-right: 10px;
background: none;
border: none;
`;
我像这样使用Section组件:
class MEPlotAccordion extends Component {
static propTypes = {
row: PropTypes.object.isRequired,
clearSelectedNode: PropTypes.func.isRequired,
fetchSelectedNode: PropTypes.func.isRequired,
selectedNode: PropTypes.object,
isFetchingSelectedNode: PropTypes.bool.isRequired,
};
static defaultProps = {
selectedNode: {},
};
onClickOpen = (nodeId) => {
// the callback that is called from the Section handleClick
this.props.fetchSelectedNode({ type: nodeTypes.MODEL_ELEMENT, id: nodeId });
};
onClickClose = () => {
this.props.clearSelectedNode();
};
renderMetaDataPage() {
if (this.props.isFetchingSelectedNode) {
return 'Loading...';
}
if (this.props.selectedNode) {
// this component requires data from API to render
return (
<ModelElementMetaDataPage
modelElement={this.props.selectedNode}
/>
);
}
return null;
}
renderSeries() {
return this.props.row.series.map((series) => {
return (
<Section
key={series.id}
titles={[`${series.name} - ${series.tagName}`]}
onClickOpen={() => this.onClickOpen(series.id)}
onClickClose={() => this.onClickClose()}
fontSize={18}
fontWeight={500}
>
{this.renderMetaDataPage()}
</Section>
);
});
}
render() {
return (
<AccordionContainer>
{this.renderSeries()}
</AccordionContainer>
);
}
}
const mapStateToProps = state => ({
state,
selectedNode: selectors.selectedNode(state),
isFetchingSelectedNode: selectors.isFetchingSelectedNode(state),
});
const mapDispatchToProps = dispatch => ({
clearSelectedNode: () => dispatch(actions.clearSelectedNode()),
fetchSelectedNode: (nodeType, id) => dispatch(actions.fetchSelectedNode(nodeType, id)),
});
export default connect(mapStateToProps, mapDispatchToProps)(MEPlotAccordion);
答案 0 :(得分:1)
因此,在与问题作者讨论之后,问题在于父组件之一正在每个渲染器上卸载。卸载的原因是,为组件赋予了shortid.generate()
在每个渲染器上生成的不同密钥。