一个可展开和收缩的 react 组件,组件内部有一个按钮切换收缩和展开,并在组件内部支持下拉收缩,上拉展开,并发在变化时抛出 onChange 事件
如何将该组件改写成 React16.4 推荐的格式
import React from 'react';
import styles from './style.scss';
class ExpandedView extends React.Component {
constructor(props) {
super(props);
this.state = {
expanded: false,
};
// 触摸检测
this.containerNode = null;
this.startX = 0;
this.startY = 0;
this.endX = 0;
this.endY = 0;
this.clickArrow = this.clickArrow.bind(this);
this.onContainerTouchStart = this.onContainerTouchStart.bind(this);
this.onContainerTouchEnd = this.onContainerTouchEnd.bind(this);
}
componentWillReceiveProps(nextProps) {
const { expanded: newExpanded, onChange } = nextProps;
if (this.state.expanded !== newExpanded) {
onChange && onChange(newExpanded);
this.setState({ expanded: newExpanded });
}
}
async componentDidMount() {
let { expanded, onChange } = this.props;
onChange && onChange(expanded);
this.setState({ expanded });
if (this.containerNode) {
this.containerNode.addEventListener('touchstart', this.onContainerTouchStart);
this.containerNode.addEventListener('touchend', this.onContainerTouchEnd);
}
}
componentWillUnmount() {
if (this.containerNode) {
this.containerNode.removeEventListener('touchstart', this.onContainerTouchStart);
this.containerNode.removeEventListener('touchend', this.onContainerTouchEnd);
}
}
onContainerTouchStart(e) {
this.startX = e.touches[0].pageX;
this.startY = e.touches[0].pageY;
}
onContainerTouchEnd(e) {
let { onChange } = this.props;
this.endX = e.changedTouches[0].pageX;
this.endY = e.changedTouches[0].pageY;
const direction = getDirection(this.startX, this.startY, this.endX, this.endY, 30);
// 向上滑打开
if (direction === 'up') {
this.setState({ expanded: true });
onChange && onChange(true);
// 向下滑关闭
} else if (direction === 'down') {
this.setState({ expanded: false });
onChange && onChange(false);
}
}
// 点击箭头
clickArrow() {
let { onChange } = this.props;
this.setState((prevState) => {
let expanded = !prevState.expanded;
onChange && onChange(expanded);
return { expanded };
});
}
render() {
const { expanded } = this.state;
return (
<div
ref={node => this.containerNode = node}
className={cn(styles.container, { [styles.collapsed]: !expanded })}
>
<div className={styles['arrow-wrapper']} onClick={() => this.clickArrow()} />
<div className={cn(styles['collapsed-panel'], { [styles.hide]: expanded })}>
<div>collapsed</div>
</div>
<div className={cn(styles['expanded-panel'], { [styles.hide]: !expanded })}>
<div>expanded</div>
</div>
</div>
);
}
}
export default ExpandedView;
我改写成
static getDerivedStateFromProps(nextProps, prevState) {
const { expanded: currentExpanded, onChange } = nextProps;
if (currentExpanded !== prevState.expanded) {
onChange && onChange(currentExpanded);
return {
expanded: currentExpanded,
};
}
return null;
}
后发现,clickArrow 的 setState 也会调用到 getDerivedStateFromProps ,导致无法切换收缩与展开,我应该如何解决该问题?
像类似这种组件控制自身状态的行为,是应该在组件内部执行,还是仅仅抛出事件,让外部组件通过传递 props 决定 expanded 的值