最近摸鱼时间自己手动实现了几个系统级交互的hooks,由简单到复杂,依次分享给大家!
这个hook主要借助了navigator
全局属性和offline/online
事件监听
import { useEffect, useState } from "react"
export const useNetwork = () => {
const [state, setState] = useState<boolean>(navigator.onLine)
useEffect(() => {
window.addEventListener('offline', () => setState(false))
window.addEventListener('online', () => setState(true))
return () => {
window.removeEventListener('offline', () => setState(false))
window.removeEventListener('online', () => setState(true))
}
}, [])
return state
}
const onlineState = useNetwork()
return onlineState ? <App /> : <OfflineTip />
类似的方法还可以探索很多有意思的事件属性,例如复制
时加版权标识
复制
加版权标识import { useEffect } from "react"
export const useCopy = () => {
useEffect(() => {
const onCopy = () => navigator.clipboard.readText()
.then((text) => {
navigator.clipboard.writeText(text + ': @copyright萌萌哒草头将军')
})
addEventListener('copy', () => onCopy())
return () => removeEventListener('copy', () => onCopy())
}, [])
}
useCopy() // 复制:abc
// 粘贴:abc :@copyright萌萌哒草头将军
import { useEffect, useState } from 'react';
export const useResize = () => {
const [width, setWidth] = useState<number>(() =>
window.document.body.offsetWidth);
useEffect(() => {
window.addEventListener('resize', (e) =>
setWidth((e?.target as any).innerWidth),
);
return () => window.removeEventListener('resize', (e) =>
setWidth((e?.target as any).innerWidth),
);
}, []);
return width;
};
const width = useResize()
return width > 1200 ? <PcApp /> : width > 720 ? <PadApp /> : <PhoneApp />
为了防止因为频繁触发监听事件导致宽度也频繁变化,这里可以使用上期文章提到的useDeferredValue
优化
const [width, setWidth] = useState<number>(() =>
window.document.body.offsetWidth);
// 延时更新⏰
const newWidth = useDeferredValue(value);
useEffect(() => {
window.addEventListener('resize', (e) =>
setWidth((e?.target as any).innerWidth),
);
return () => window.removeEventListener('resize', (e) =>
setWidth((e?.target as any).innerWidth),
);
}, []);
return newWidth;
或者添加防抖函数,不管触发多少次,在一定时间内,只更新最后一次
function debounce(fn, delay) {
let timer = null;
return function (...arg) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arg);
}, delay);
}
}
然后
const [width, setWidth] = useState<number>(() =>
window.document.body.offsetWidth);
useEffect(() => {
const fn = debounce((e) =>
setWidth((e?.target as any).innerWidth),
), 2000)
window.addEventListener('resize', (e) => fn(e);
return () => window.removeEventListener('resize', (e) => fn(e)
}, []);
return width;
只要思想不滑坡,方法总比困难多!
import { useEffect, useState } from "react";
export const useTheme = () => {
const themeMedia = window.matchMedia("(prefers-color-scheme: light)");
const [value, setValue] = useState<string | null>(themeMedia.matches ? 'light' : 'dark');
useEffect(() => {
themeMedia.addEventListener('change', e =>
setValue(e.matches ? 'light' : 'dark')
);
return () => themeMedia.removeEventListener("change", e =>
setValue(e.matches ? 'light' : 'dark')
);
}, []);
return value;
};
const theme = useTheme()
return (
<ConfigProvider
theme={{
algorithm: theme === 'light'
? theme.defaultAlgorithm
: theme.darkAlgorithm,
}}
>
<App />
</ConfigProvider>
)
import { useEffect, useState } from "react";
export const useStorage = () => {
const [value, setValue] = useState<string | null>();
useEffect(() => {
window.addEventListener("storage", (ev) => setValue(ev.newValue));
return () =>
window.removeEventListener("storage", (ev) => setValue(ev.newValue));
}, []);
return value;
};
有了前面几个hook定义经验,这个不是手到擒来啊。结果一使用根本不起作用,百度原因才发现storage
仅仅对同源下的不同页面起作用,作为单页面应用SPA
,还得再想办法。
思前想后,在不大动大改的前提下,我重写了window.localStorage
下的方法,命名保持一直,这样所有之前使用过的页面只需要引入我定义好的localStorage
,同时去掉window.
export const localStorage = {
getItem: (key: string) => window.localStorage.getItem(key),
setItem: (key: string, value: any) => window.localStorage.setItem(key, value),
clear: () => return window.localStorage.clear(),
removeItem: (key: string) => window.localStorage.removeItem(key),
key: window.localStorage.key,
length: window.localStorage.length,
}
接下来让每次的修改、删除、清空都可以被监听到。这里我借助的是前面文章提到的中介者模式
,负责监听storage
的变化。所以正确的定义方法如下
import { useState } from "react"
// 中介者
const mediator = (function () {
let topics: {[key: string]: {callback: (value: any) => void,
uuid: number}[]} = {},
uuid = 0;
function subscribe (topic: string, callback: (value: any) => void) {
uuid ++
topics[topic] = topics[topic]
? [...topics[topic], { callback, uuid }]
: [{ callback, uuid }]
}
function publish (topic: string, value: any) {
if (topics[topic]) {
topics[topic].map(item => item.callback(value))
}
}
return {
install: function (obj: any) {
obj.uuid = uuid
obj.publish = publish
obj.subscribe = subscribe
return obj
}
}
})()
// 创建中介者函数
const createMediator = (obj: object) => mediator.install(obj)
// 记录所有监听的key
const keys: string[] = []
// 重新 window.localStorage
export const localStorage = {
getItem: (key: string) => {
return window.localStorage.getItem(key)
},
setItem: (key: string, value: any) => {
// 防止重复发布
if (!keys.includes(key)) keys.push(key)
const sub = createMediator({})
// 被修改就发布事件
sub.publish(key, value)
return window.localStorage.setItem(key, value)
},
clear: () => {
const sub = createMediator({})
// 被删除就每个key发布事件
keys.map(key => sub.publish(key, undefined))
// 发布后清空记录key的数组
keys.length = 0
return window.localStorage.clear()
},
removeItem: (key: string) => {
keys.splice(keys.indexOf(key), 1)
const sub = createMediator({})
// 被移除就发布 undefined
sub.publish(key, undefined)
return () => window.localStorage.removeItem(key)
},
key: window.localStorage.key,
length: window.localStorage.length,
}
// 监听key最新变化
export const useStorage = (key: string) => {
// 默认初始值
const [value, setValue] = useState<null | string>(window.localStorage.getItem(key))
const sub = createMediator({})
// 为指定的key订阅变更事件
sub.subscribe(key, (value: any) => setValue(value))
return value
}
import { localStorage, useStorage } from './useStorage.ts'
export const App = () => {
const random = useStorage('random')
useEffect(() => console.log(random), [random])
return (
<div
onClick={() =>
localStorage.setItem('random', Math.random().toString())
}
>random: {random} </div>
)
}
在确定可以监听到的时候,我的心情
好了,今天的分享到这了,如果发现错误,可以联系我,多谢指正~
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。