正式准备入坑react
打算从小而美的antd组件开始看 Row组件,很多新鲜的代码
(MyUI water-fail)
window.matchMedia
设计思维猜想,首先从媒体的
组件内部React.useEffect(() => {//subscribe的过程相当于把方法作为一个订阅者,不过是notify时候是执行订阅者本身。const token = ResponsiveObserve.subscribe(screen => {const currentGutter = gutterRef.current || 0;if ((!Array.isArray(currentGutter) && typeof currentGutter === 'object') ||(Array.isArray(currentGutter) &&(typeof currentGutter[0] === 'object' || typeof currentGutter[1] === 'object'))) {setScreens(screen);}});// 如果要在组件卸载前执行操作,返回值返回一个方法。return () => ResponsiveObserve.unsubscribe(token);}, []);
path: components\_util\responsiveObserve.tsexport type Breakpoint = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs';export type BreakpointMap = Record<Breakpoint, string>;export type ScreenMap = Partial<Record<Breakpoint, boolean>>;export type ScreenSizeMap = Partial<Record<Breakpoint, number>>;export const responsiveArray: Breakpoint[] = ['xxl', 'xl', 'lg', 'md', 'sm', 'xs'];export const responsiveMap: BreakpointMap = {xs: '(max-width: 575px)',sm: '(min-width: 576px)',md: '(min-width: 768px)',lg: '(min-width: 992px)',xl: '(min-width: 1200px)',xxl: '(min-width: 1600px)',};type SubscribeFunc = (screens: ScreenMap) => void;const subscribers = new Map<Number, SubscribeFunc>();let subUid = -1;let screens = {};const responsiveObserve = {matchHandlers: {} as {[prop: string]: {mql: MediaQueryList;listener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | null;};},dispatch(pointMap: ScreenMap) {screens = pointMap;subscribers.forEach(func => func(screens));return subscribers.size >= 1;},//入口,有一次注册监听器就可以,因为被监听者负责调用所有订阅者即可(所有subscribers对他可见。subscribe(func: SubscribeFunc): number {if (!subscribers.size) this.register();subUid += 1;subscribers.set(subUid, func);//因为初始化或者每次mql回调都会刷新screens,所以说后面注册的方法想要立刻有结果,就是用上次存储的screensfunc(screens);return subUid;},unsubscribe(token: number) {subscribers.delete(token);if (!subscribers.size) this.unregister();},unregister() {Object.keys(responsiveMap).forEach((screen: Breakpoint) => {const matchMediaQuery = responsiveMap[screen];const handler = this.matchHandlers[matchMediaQuery];handler?.mql.removeListener(handler?.listener);});subscribers.clear();},register() {Object.keys(responsiveMap).forEach((screen: Breakpoint) => {const matchMediaQuery = responsiveMap[screen];const listener = ({ matches }: { matches: boolean }) => {//screens一开始是{},赋值语句为:screens = pointMap;经过一轮register的遍历pointMap = { ...screen, xs: false } 此时screens { xs: true }pointMap = { ...screen, sm: false } 此时screens { xs: true, sm: true}//如果后面有media触发addListener,此时screens其实是满的初始状态,payload为:pointMap = { ...screen, sm: true }this.dispatch({...screens,[screen]: matches,});};const mql = window.matchMedia(matchMediaQuery);mql.addListener(listener);this.matchHandlers[matchMediaQuery] = {mql,listener,};//初始化时,用初始化的结果来初始化screens,但是是这样fn好像是会无意义的执行很多次。//但是setState在一个main script里重复执行,其实只执行最后一次。listener(mql);});},};export default responsiveObserve;
根据Row的gutter,计算现在的gutterconst getGutter = (): [number, number] => {const results: [number, number] = [0, 0];//这里的gutter是props, responsiveArray是['xxl', 'xl', 'lg', 'md', 'sm', 'xs'],这里故意从大到小排列,因为语句都是min-width,如果大的满足则立刻生效,所以下面的循环遇到顺序第一个满足就break了。const normalizedGutter = Array.isArray(gutter) ? gutter : [gutter, 0];normalizedGutter.forEach((g, index) => {//最下一层是对象的情况 [{ xs: 12, lt: 16 }]if (typeof g === 'object') {for (let i = 0; i < responsiveArray.length; i++) {const breakpoint: Breakpoint = responsiveArray[i];if (screens[breakpoint] && g[breakpoint] !== undefined) {results[index] = g[breakpoint] as number;break;}}} else {//如果是gutter = 10, gutter = [10, 20]的形式results[index] = g || 0;}});返回的即是当前要渲染的水平/垂直的gapreturn results;};
