管理页面我们通常能看到一连串的卡片,以很明显的方式对用户进行提示,也可以是图表嵌在卡片的形式直观展示。那就会存在一个需求,用户可以自定义自己想要的卡片布局,其实也就是自定义宽高这样。
比如说上图,我想自定义成为下面这样,
这样一个需求我们如何去实现呢?
查阅了一些文章之后,我选择了
React-Grid-Layout
这样一个组件,基本放大缩小以及拖拽这些都能实现,参照官方文档就ok,我这里主要讲一下,使用之后会出现的一些问题。因为是基于
hzero
的项目,所有的路由是像一个tab
页一样并列在顶部,并且路由之间的跳转不会触发组件的unmount
方法,这时候就遇到了一个问题。当我们在其他页面进行浏览器的放大和缩小后,再回到使用React-Grid-Layout
的页面,就会发现,所有的卡片挤在一起,样式错乱了,只在使用React-Grid-Layout
的页面缩放就不会有问题,这样试了几次之后,很奇怪,为什么出现这种情况呢,那就只能去看看组件源码了,看了会,我发现了问题。
// @flow
import React from "react";
import PropTypes from "prop-types";
import ReactDOM from "react-dom";
import type { ComponentType as ReactComponentType } from "react";
type WPProps = {
className?: string,
measureBeforeMount: boolean,
style?: Object
};
type WPState = {
width: number
};
/*
* A simple HOC that provides facility for listening to container resizes.
*/
// typescript 类型检查
export default function WidthProvider<
Props,
ComposedProps: { ...Props, ...WPProps }
>(
ComposedComponent: ReactComponentType<Props>
): ReactComponentType<ComposedProps> {
return class WidthProvider extends React.Component<ComposedProps, WPState> {
static defaultProps = {
measureBeforeMount: false
};
static propTypes = {
// If true, will not render children until mounted. Useful for getting the exact width before
// rendering, to prevent any unsightly resizing.
measureBeforeMount: PropTypes.bool
};
state = {
width: 1280
};
mounted: boolean = false;
componentDidMount() {
this.mounted = true;
window.addEventListener("resize", this.onWindowResize);
// Call to properly set the breakpoint and resize the elements.
// Note that if you're doing a full-width element, this can get a little wonky if a scrollbar
// appears because of the grid. In that case, fire your own resize event, or set `overflow: scroll` on your body.
this.onWindowResize();
}
componentWillUnmount() {
this.mounted = false;
window.removeEventListener("resize", this.onWindowResize);
}
onWindowResize = () => {
if (!this.mounted) return;
// eslint-disable-next-line
const node = ReactDOM.findDOMNode(this); // Flow casts this to Text | Element
if (node instanceof HTMLElement)
this.setState({ width: node.offsetWidth });
};
render() {
const { measureBeforeMount, ...rest } = this.props;
if (measureBeforeMount && !this.mounted) {
return (
<div className={this.props.className} style={this.props.style} />
);
}
return <ComposedComponent {...rest} {...this.state} />;
}
};
}
可以看到这里监听了页面的resize
,然后进行onWindowResize
方法,我们在其他页面进行resize
之后,也会触发这个方法,这也是我前面要强调一下路由切换不会unmount
的原因,这里的resize
没法得到正确的node.offsetWidth
,所以样式错乱了,那怎么办呢?
首先我想着,在这个页面写一个时钟,不停的js
去触发resize
事件,让它在切换页面的时候重新计算,这样肯定是ok的,但是这样肯定会有性能问题,因为不停的延时调用,这样我就得想个办法,不那么频繁的刷新了,怎么做呢?
路由切换,页面也切换了,那组件肯定会触发receiveProps
钩子,那我在这里判断一下当前页面的路由,如果是当前路由,那就刷新一下(dispatchEvent('resize')),这样就做到只是进入这个页面就刷新,比时钟要好一些。但是这样还是很low,因为进入其他页面没有resize
操作,只要返回这个页面就会刷新,接下来我就要优化一下,只有当其他页面resize
了之后,回到当前页才dispatchEvent('resize')
。
// 当切换到其他页面并进行了页面缩放之后重新resize当前页面
resize = debounce(() => {
window.dispatchEvent(new Event('resize'));
this.setState({
loading: false,
ifResize: false,
});
}, 1000);
// eslint-disable-next-line
UNSAFE_componentWillReceiveProps() {
if (window.location.href.indexOf('hipsappsa/pandect') > -1 && this.state.ifResize) {
this.setState({
loading: true,
});
this.resize();
}
}
componentDidMount() {
const callback = this.onreSize;
const resize = debounce(() => {
callback();
}, 1000);
window.addEventListener('resize', resize);
}
onResize() {
const callback = this.onreSize;
debounce(() => {
callback();
}, 1000);
}
/**
* 响应式瀑布
*/
@Bind
onreSize() {
if (window.location.href.indexOf('hipsappsa/pandect') === -1) {
this.setState({
ifResize: true,
});
} else {
this.setState({
ifResize: false,
});
}
}
页面挂载的时候就进行resize
监听,当发生resize
事件之后进行一下路由判断,如果是其他页面,就改变state
里的一个值,返回到当前页之后进行一下state
的值判断,为true
才刷新。
为了页面切换的时候更合理一些(不展示错乱的页面),加个loading
延时一秒过度一下,还要记得unmount
时去除掉resize
监听。
这里的图表用的是bizcharts
,它会监听页面resize
的变化,但是当我们改变宽高的时候,发现没有自适应,一开始我的解决方法是把这个渲染的canvas
设置为width:100%
,这样能使它拉伸开,但是很丑,因为是相当于是放大,这样效果太不好了,所以也要解决这个问题。
React-Grid-Layout
组件有一个props
,onResize
事件返回当前改变宽高这样的事件回调,我们在这个回调里手动触发一下页面resize
事件,以实现bizcharts
的自适应。
// 当手动修改resize之后触发一次页面resize使得bizcharts重新渲染
handResize = debounce(() => {
window.dispatchEvent(new Event('resize'));
}, 100);