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 (<> </>);}