前言
2019 年写的分析,慎重观看。不过其中的思路还是值得学习的。
useBattery
- 作用:获取电池信息
- 原理 navigator.getBattery, 并监听chargingchange, levelchange,chargingtimechange,dischargingtimechange事件。
useGeolocation
- 获取地址位置
- Geolocation, navigator.geolocation.getCurrentPosition获取地址位置, navigator.geolocation.watchPosition监听变化。
useLocation
- 作用:获取浏览器location信息
- 原理: 监听window的popstate,pushstate,replacestate事件。
useEffect(() => {
const onPopstate = () => setState(buildState('popstate'));
const onPushstate = () => setState(buildState('pushstate'));
const onReplacestate = () => setState(buildState('replacestate'));
on(window, 'popstate', onPopstate);
on(window, 'pushstate', onPushstate);
on(window, 'replacestate', onReplacestate);
return () => {
off(window, 'popstate', onPopstate);
off(window, 'pushstate', onPushstate);
off(window, 'replacestate', onReplacestate);
};
}, []);
useMedia
使用window.matchMedia获得MediaQueryList 对象,然后添加监听事件。
import { useEffect, useState } from 'react';
import { isClient } from './util';
const useMedia = (query: string, defaultState: boolean = false) => {
const [state, setState] = useState(isClient ? () => window.matchMedia(query).matches : defaultState);
useEffect(() => {
let mounted = true;
const mql = window.matchMedia(query);
const onChange = () => {
if (!mounted) {
return;
}
setState(!!mql.matches);
};
mql.addListener(onChange);
setState(mql.matches);
return () => {
mounted = false;
mql.removeListener(onChange);
};
}, [query]);
return state;
};
export default useMedia;
useNetwork
- 监听window的online和offline事件。
- 如果navigator支持connection,那么再监听change事件。
on(window, 'online', onOnline);
on(window, 'offline', onOffline);
if (connection) {
on(connection, 'change', onConnectionChange);
localSetState({
...state,
online: navigator.onLine,
since: undefined,
...getConnectionState(),
});
}
return () => {
off(window, 'online', onOnline);
off(window, 'offline', onOffline);
if (connection) {
off(connection, 'change', onConnectionChange);
}
};
usePageLeave
document监听mouseout事件,判断event.relatedTarget || event.toElement 是不是documentElement。
DOM通过event对象的relatedTarget属性提供了相关元素的信息。这个属性只对于mouseover和mouseout事件才包含值;对于其他事件,这个属性的值是null。 IE不支持realtedTarget属性,但提供了保存着同样信息的不同属性。在mouseover事件触发时,IE的fromElement属性中保存了相关元素;在mouseout事件触发时,IE的toElement属性中保存着相关元素。
const usePageLeave = (onPageLeave, args = []) => {
useEffect(() => {
if (!onPageLeave) {
return;
}
const handler = event => {
event = event ? event : (window.event as any);
const from = event.relatedTarget || event.toElement;
if (!from || (from as any).nodeName === 'HTML') {
onPageLeave();
}
};
document.addEventListener('mouseout', handler);
return () => {
document.removeEventListener('mouseout', handler);
};
}, args);
};
useIdle
- 监听 window的 [‘mousemove’, ‘mousedown’, ‘resize’, ‘keydown’, ‘touchstart’, ‘wheel’] 这些事件。 如果发生即为活跃。
- 监听document的onVisibility事件,如果是当前窗体激活,设置为活跃。
- 启动计时器,如果活跃之后一段时间没有相关操作,即为idle。
const defaultEvents = ['mousemove', 'mousedown', 'resize', 'keydown', 'touchstart', 'wheel'];
const oneMinute = 60e3;
const useIdle = (ms: number = oneMinute, initialState: boolean = false, events: string[] = defaultEvents): boolean => {
const [state, setState] = useState<boolean>(initialState);
useEffect(() => {
let mounted = true;
let timeout: any;
let localState: boolean = state;
const set = (newState: boolean) => {
if (mounted) {
localState = newState;
setState(newState);
}
};
const onEvent = throttle(50, () => {
if (localState) {
set(false);
}
clearTimeout(timeout);
timeout = setTimeout(() => set(true), ms);
});
const onVisibility = () => {
if (!document.hidden) {
onEvent();
}
};
for (let i = 0; i < events.length; i++) {
on(window, events[i], onEvent);
}
on(document, 'visibilitychange', onVisibility);
timeout = setTimeout(() => set(true), ms);
return () => {
mounted = false;
for (let i = 0; i < events.length; i++) {
off(window, events[i], onEvent);
}
off(document, 'visibilitychange', onVisibility);
};
}, [ms, events]);
return state;
};
useScrolling
计时器来实现,滚动事件后150ms设置为不滚动,如期间再滚,清除计时器,重新开启计时器。
const handleScrollEnd = () => {
setScrolling(false);
};
const handleScroll = () => {
setScrolling(true);
clearTimeout(scrollingTimeout);
scrollingTimeout = setTimeout(() => handleScrollEnd(), 150);
};
ref.current.addEventListener('scroll', handleScroll, false);
return () => {
if (ref.current) {
ref.current.removeEventListener('scroll', handleScroll, false);
}
};
}
useSize
采取的内嵌iframe的方式,更多监听方式参考:
https://xiangwenhu.github.io/TakeItEasy/resize/
const ref = useRef<HTMLIFrameElement | null>(null);
let window: Window | null = null;
const setSize = () => {
const iframe = ref.current;
const size = iframe
? {
width: iframe.offsetWidth,
height: iframe.offsetHeight,
}
: { width, height };
setState(size);
};
const onWindow = (windowToListenOn: Window) => {
windowToListenOn.addEventListener('resize', setSize);
DRAF(setSize);
};
useStartTyping
document注册keydown事件,检查事件触发时activeElement是不是input和textarea以及是不是有contenteditable属性,以及检查keyCode的值是不是在有效范围,排除ctl alt等等。
const isFocusedElementEditable = () => {
const { activeElement, body } = document;
if (!activeElement) {
return false;
}
// If not element has focus, we assume it is not editable, too.
if (activeElement === body) {
return false;
}
// Assume <input> and <textarea> elements are editable.
switch (activeElement.tagName) {
case 'INPUT':
case 'TEXTAREA':
return true;
}
// Check if any other focused element id editable.
return activeElement.hasAttribute('contenteditable');
};
const isTypedCharGood = ({ keyCode, metaKey, ctrlKey, altKey }: KeyboardEvent) => {
if (metaKey || ctrlKey || altKey) {
return false;
}
// 0...9
if (keyCode >= 48 && keyCode <= 57) {
return true;
}
// a...z
if (keyCode >= 65 && keyCode <= 90) {
return true;
}
// All other keys.
return false;
};
const useStartTyping = (onStartTyping: (event: KeyboardEvent) => void) => {
useLayoutEffect(() => {
const keydown = event => {
!isFocusedElementEditable() && isTypedCharGood(event) && onStartTyping(event);
};
document.addEventListener('keydown', keydown);
return () => {
document.removeEventListener('keydown', keydown);
};
}, []);
};
useMeasure
- ResizeObserver 采用了 resize-observer-polyfill, 高阶组件react-virtualized-auto-sizer 也能做到类似功能监听功能
- 利用函数ref属性
- useState使用函数参数初始化ResizeObserver实例
const useMeasure = <T>(): [(instance: T) => void, ContentRect] => {
const [rect, set] = useState({
width: 0,
height: 0,
top: 0,
left: 0,
bottom: 0,
right: 0,
});
const [observer] = useState(
() =>
new ResizeObserver(entries => {
const entry = entries[0];
if (entry) {
set(entry.contentRect);
}
})
);
const ref = useCallback(
node => {
observer.disconnect();
if (node) {
observer.observe(node);
}
},
[observer]
);
return [ref, rect];
};
export default useMeasure;
useClickAway
HTML Node的contains方法, 判断节点是否为该节点的后代节点。
const useClickAway = (
ref: RefObject<HTMLElement | null>,
onClickAway: (event: KeyboardEvent) => void,
events: string[] = defaultEvents
) => {
const savedCallback = useRef(onClickAway);
useEffect(() => {
savedCallback.current = onClickAway;
}, [onClickAway]);
useEffect(() => {
const handler = event => {
const { current: el } = ref;
el && !el.contains(event.target) && savedCallback.current(event);
};
for (const eventName of events) {
on(document, eventName, handler);
}
return () => {
for (const eventName of events) {
off(document, eventName, handler);
}
};
}, [events, ref]);
};
useCss
借用了 CSS-in-JS library nano-css。
nano-css本身使用的是 CSSStyleSheet.insertRule
const useCss = (css: object): string => {
const className = useMemo(() => 'react-use-css-' + (counter++).toString(36), []);
const sheet = useMemo(() => new nano.VSheet(), []);
useLayoutEffect(() => {
const tree = {};
cssToTree(tree, css, '.' + className, '');
sheet.diff(tree);
return () => {
sheet.diff({});
};
});
return className;
};
useDrop
监听document的dragover,dragenter,dragleave,dragexit,drop,paste等事件。
useDropArea
传入事件监听函数,返回bound各种方法以及是否hover属性。
boud方法需要挂载到节点,那么突破口就是bound。
通过bound注入事件,然后在事件里面拦截,再调用传入的监听函数。
看看调用
import {useDropArea} from 'react-use';
const Demo = () => {
const [bond, state] = useDropArea({
onFiles: files => console.log('files', files),
onUri: uri => console.log('uri', uri),
onText: text => console.log('text', text),
});
return (
<div {...bond}>
Drop something here.
</div>
);
};
useFullscreen
引入了库 screenfull 。
基本就是request全屏然后,监听change事件。
当然对video进行了额外的处理。
const onWebkitEndFullscreen = () => {
video!.current!.removeEventListener('webkitendfullscreen', onWebkitEndFullscreen);
onClose();
};
const onChange = () => {
if (screenfull) {
const isScreenfullFullscreen = screenfull.isFullscreen;
setIsFullscreen(isScreenfullFullscreen);
if (!isScreenfullFullscreen) {
onClose();
}
}
};
if (screenfull && screenfull.enabled) {
try {
screenfull.request(ref.current);
setIsFullscreen(true);
} catch (error) {
onClose(error);
setIsFullscreen(false);
}
screenfull.on('change', onChange);
} else if (video && video.current && video.current.webkitEnterFullscreen) {
video.current.webkitEnterFullscreen();
video.current.addEventListener('webkitendfullscreen', onWebkitEndFullscreen);
setIsFullscreen(true);
} else {
onClose();
setIsFullscreen(false);
}
useSpeech
这个功能我以前不知道的说。
浏览器支持率居然将近 90%, Can i use |speechSynthesis
其实就用利用了window.speechSynthesis。
原生代码也就是
var utterance = new SpeechSynthesisUtterance('我们都是中国人');
window.speechSynthesis.speak(utterance)
useWait
额,直接用的react-wait