正式准备入坑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.ts
export 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,所以说后面注册的方法想要立刻有结果,就是用上次存储的screens
func(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,计算现在的gutter
const 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;
}
});
返回的即是当前要渲染的水平/垂直的gap
return results;
};