react使用provider实现一个简单的事件/信号系统
react中,两个组件之间交互可以通过传递状态的方式,但是如果两个组件没有显式的依赖关系,但是又需要交互,这时候可以通过provider 来传递状态。provider提供了一种全局状态机制,可以利用这种全局的机制做一个小事件/信号系统。
设计思路
事件/信号系统必须包含2个最核心的功能:
触发事件/信号
监听事件
除了触发事件之外呢,还可以增加一个辅助功能—重置信号,重置信号是有必要的,例如在某种场景下,会重复触发同一种信号,但是只需要处理一次,可以通过触重置信号来再次接收信号并处理。
通过一个初始化为0的state,让他的值更新为1 或者 当前时间戳,可以实现到底信号能触发多少次。通过useEffect来感知这个信号的变动,如果重复设置为1,useEffect是不会多次触发回调的。如果state设置成当前时间戳,则useEffect可以持续感知state的变化。
实现
provider.jsx
'use client'
import {createContext, useState} from 'react';
export const GlobalContext = createContext({});
const createSignal = (name) => {
return {
name,
state: 0,
data: null
}
}
export function Providers({children}) {
const [signals, setSignals] = useState({
unauthorized: createSignal('unauthorized'), // 预定义信号,一些特殊场景需要在渲染完成之前就处理该信号
});
const listen = (signalName) => {
console.log('监听信号: ', signalName)
if (signals[signalName]) {
return signals
}
const signal = createSignal(signalName)
setSignals({
...signals,
[signalName]: signal
})
return signals
}
const reset = (signal) => {
const __signal = signals[signal.name]
console.log('重置信号: ', __signal)
setSignals({
...signals,
[name]: {...__signal, state: 0, data: null},
})
}
const emit = (signalName, data) => {
const signal = signals[signalName]
if (!signal) {
console.warn('信号不存在: ', signalName)
return
}
signal.state = 1
signal.data = data
console.log('触发信号: ', signal)
setSignals({
...signals,
[signalName]: {...signal},
})
}
return (
<GlobalContext.Provider value={{listen, emit, reset}}>
{children}
</GlobalContext.Provider>
)
}
上面的代码实现了一个事件机制,通过这个provider,业务组件可以使用 listen函数来监听某一种信号或者事件,并通过useEffect回调来感知信号的变动,使用reset函数可以重置某一个信号,使用emit函数可以触发某一个信号,如果信号不存在,则会新建信号。
例子🌰
需求:每次请求我都会监听请求是否401,如果401则为token失效,如果token失效,则要跳转到登录页
通过一个包装过的fetch来处理401请求,遇到401之后emit一个unauthorized信号
function __fetch__(ctx, url, options) {
return fetch(url, options).then(response => {
console.log('response: ', response)
if (response.status !== 200) {
console.error('请求失败:', response.statusText)
return {code: -1, message: '请求失败'}
}
return response.json()
}).then(data => {
return data;
}).catch(e => {
// 401 会执行到这里
console.error('请求异常:', e)
ctx.emit('unauthorized', e)
return {code: -1, message: '请求异常'}
})
}
用一个Auth组件持续监听token是否失效信号,遇到unauthorized信号则跳转到login页
'use client'
import {useContext, useEffect} from "react";
import {GlobalContext} from "@/app/providers";
import {useRouter} from "next/navigation";
function Auth() {
const ctx = useContext(GlobalContext)
const router = useRouter()
const signals = ctx.listen('unauthorized')
console.log('signals: ', signals)
useEffect(() => {
const signal = signals['unauthorized']
console.log('收到信号: ', signal)
if (signal && signal.state) {
console.log('收到信号[unauthorized]')
ctx.reset(signal)
localStorage.removeItem('token')
router.push('/login')
} else {
console.log('忽略信号')
}
}, [signals['unauthorized']])
return (<>
</>);
}