useGunContext(https://github.com/SuaYoo/gundb-react-express-auth-example/blob/main/server/server.js)代码创建了一个上下文(Context)来为整个应用提供 GUN 实例及其用户认证逻辑,在多个组件之间共享 GUN 数据库连接和用户状态数据()。useGunContext()钩子提供以下内容:
{
getGun: Function, // 返回 `gun` 实例
getUser: Function, // 返回 `gun.user()` 实例
getCertificate: Function, // 返回当前用户的证书
setCertificate: Function, // 设置当前用户的证书
onAuth: Function, // 接收回调函数,在 `gun.user().auth()` 时触发
}
概述
-
GUN 实例管理:
- 通过
useRef保持 GUN 实例、用户对象和认证 token 的持久性。 - 确保整个应用只使用一个 GUN 实例,避免多次初始化。
- 通过
-
用户认证:
- 在
useEffect中监听 GUN 的auth事件,当用户认证成功时,通过 API 获取accessToken和certificate。 - 支持自动记住用户登录状态(使用 sessionStorage)。
- 在
-
跨组件共享:
- 通过
GunContext.Provider将 GUN 实例、用户对象和相关方法传递给整个应用,使各个组件可以轻松访问。
- 通过
代码解释
1. 引入依赖和createContext函数
import React, { createContext, useContext, useRef, useEffect } from 'react';
import Gun from 'gun/gun';
import 'gun/sea';
const GunContext = createContext({
getGun: () => {},
getUser: () => {},
getCertificate: () => {},
setCertificate: () => {},
onAuth: () => () => {},
});
createContext:createContext函数用于创建 Context 对象。Context 允许你在组件树中共享数据,而无需显式地通过每一级组件的 props 进行传递。- 使用
Context.Provider提供数据<MyContext.Provider value={value}> …… </MyContext.Provider> - 使用
useContext消费数据:用于在子组件中访问GunContext提供的值。
2. GunContextProvider组件
- 虽然在技术上你可以使用普通对象来存储这些引用,如:
var gunRef = {}; gunRef.current = ...。如果你在组件内部直接使用 var gunRef = {},每次组件重新渲染时 gunRef 都会重新初始化。 - 但 useRef 提供了更好的组件内数据持久化方式,同时确保与 React 的生命周期管理相匹配。
export const GunContextProvider = ({ children }) => {
// 使用 useRef 保存 GUN 实例、用户对象、认证 token 和回调函数
const gunRef = useRef();
const userRef = useRef();
const certificateRef = useRef();
const accessTokenRef = useRef();
const onAuthCbRef = useRef();
// 初始化 GUN 和用户认证(依赖数组为[],仅在组件挂载时运行)
useEffect(() => {
// 调用 Gun() 创建一个实例时,Gun.js 会触发一个 opt 事件。这个事件用于配置 Gun 的上下文(ctx)
// GUN 的中间件,用于在每个请求的消息头中添加 accessToken
Gun.on('opt', (ctx) => {
if (ctx.once) return;
// 监听所有 出站消息(即发送到 Gun 网络的请求)
ctx.on('out', function (msg) {
const to = this.to;// this.to 是 Gun 内部的下一个中间件,即消息的下一个处理程序。
// 添加 accessToken 到请求头
msg.headers = {
accessToken: accessTokenRef.current,// accessTokenRef.current 是通过 useRef 保存的 访问令牌,用于身份验证
};
to.next(msg); // 继续传递到下一个中间件
// 如果 accessToken 无效,则处理错误(未实现)
if (msg.err === 'Invalid access token') {
// 这里可以处理 token 失效的情况,比如刷新 token 或重定向登录页面
}
});
});
// 初始化 GUN 实例,连接本地服务器
const gun = Gun(['http://localhost:8765/gun']);
// 创建用户实例,并使用 sessionStorage 记住用户登录信息
const user = gun
.user()
.recall({ sessionStorage: true });
// 当用户认证成功时的回调
gun.on('auth', (...args) => {
// 如果没有 accessToken,则通过 API 从server获取新的 token
if (!accessTokenRef.current) {
user.get('alias').once((username) => {
fetch('http://localhost:8765/api/tokens', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, pub: user.is.pub }),
})
.then((resp) => resp.json())
.then(({ accessToken }) => {
accessTokenRef.current = accessToken; // 保存 token
});
});
}
// 如果没有 certificate,则通过 API 获取新的 certificate
if (!certificateRef.current) {
user.get('alias').once((username) => {
fetch('http://localhost:8765/api/certificates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, pub: user.is.pub }),
})
.then((resp) => resp.json())
.then(({ certificate }) => {
certificateRef.current = certificate; // 保存 certificate
});
});
}
// 如果有认证回调函数,则调用
if (onAuthCbRef.current) {
onAuthCbRef.current(...args);
}
});
// 将 GUN 和用户实例保存到 ref 中,供其他组件使用
gunRef.current = gun;
userRef.current = user;
}, []);
// 将 GUN 和用户相关方法通过 Context 提供给子组件
return (
<GunContext.Provider
value={{
getGun: () => gunRef.current,
getUser: () => userRef.current,
getCertificate: () => certificateRef.current,
setCertificate: (v) => {
certificateRef.current = v;
},
onAuth: (cb) => {
onAuthCbRef.current = cb;
},
}}
>
{children}
</GunContext.Provider>
);
};
- 注: value中使用函数
() => gunRef.current来获取 gunRef.current 确保始终获取最新的值。
3. useGunContext Hook
export default function useGunContext() {
return useContext(GunContext);
}
useGunContext:这是一个自定义 Hook,便于在组件中使用GunContext提供的值和方法。例如:const { getGun, getUser, onAuth } = useGunContext();
使用示例
// index.js
import { GunContextProvider } from './useGunContext';
import App from './App';
// 在较高层级使用 Provider
ReactDOM.render(
<GunContextProvider> // GunContextProvider 包裹了 App,使得 App 及其子组件能够访问 Context 中的 Gun 实例和用户方法
<App />
</GunContextProvider>,
document.getElementById('root') // 将整个应用挂载到 HTML 页面中 id 为 'root' 的元素上
);
// App.js
import useGunContext from './useGunContext';
const App = () => {
const { getGun, getUser, onAuth } = useGunContext();
// 当用户认证成功时执行回调
onAuth(() => {
console.log('authenticated!');
});
const handleClick = () => {
getGun().get('ours').put('this');
getUser().get('mine').put('that');
};
return <button onClick={handleClick}>do something</button>;
};

被折叠的 条评论
为什么被折叠?



