mirror of https://github.com/msgbyte/tailchat
refactor(web): 主页面登录状态的管理
parent
0cdd7a10bb
commit
66e158d86d
@ -1,11 +1,21 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import type { UserLoginInfo } from '../../model/user';
|
||||
|
||||
const initialState = {};
|
||||
interface UserState {
|
||||
info: UserLoginInfo | null;
|
||||
}
|
||||
|
||||
const initialState: UserState = { info: null };
|
||||
|
||||
const userSlice = createSlice({
|
||||
name: 'user',
|
||||
initialState,
|
||||
reducers: {},
|
||||
reducers: {
|
||||
setUserInfo(state, action: PayloadAction<UserLoginInfo>) {
|
||||
state.info = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const userActions = userSlice.actions;
|
||||
export const userReducer = userSlice.reducer;
|
||||
|
@ -1,28 +0,0 @@
|
||||
import { createStore } from 'pawchat-shared';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useEnsureSocket } from '../hooks/useEnsureSocket';
|
||||
import { LoadingSpinner } from './LoadingSpinner';
|
||||
import { Provider as ReduxProvider } from 'react-redux';
|
||||
import { useSetupRedux } from '../hooks/useSetupRedux';
|
||||
|
||||
/**
|
||||
* 主页面核心数据Provider
|
||||
* 在主页存在
|
||||
*/
|
||||
export const MainProvider: React.FC = React.memo((props) => {
|
||||
const store = useMemo(() => createStore(), []);
|
||||
const { socket, loading } = useEnsureSocket();
|
||||
|
||||
useSetupRedux(socket, store);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="w-screen h-screen flex items-center justify-center bg-gray-700 text-white text-xl">
|
||||
<LoadingSpinner tip="正在连接到聊天服务器..." />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <ReduxProvider store={store}>{props.children}</ReduxProvider>;
|
||||
});
|
||||
MainProvider.displayName = 'MainProvider';
|
@ -0,0 +1,11 @@
|
||||
import type { AppState } from 'pawchat-shared/redux/slices';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
export function useAppSelector<T>(
|
||||
selector: (state: AppState) => T,
|
||||
equalityFn?: (left: T, right: T) => boolean
|
||||
) {
|
||||
return useSelector<AppState, T>(selector, equalityFn);
|
||||
}
|
||||
|
||||
export const useAppDispatch = useDispatch;
|
@ -1,25 +0,0 @@
|
||||
import { createSocket, useAsync } from 'pawchat-shared';
|
||||
import { getUserJWT } from '../utils/jwt-helper';
|
||||
|
||||
/**
|
||||
* 创建全局Socket
|
||||
*/
|
||||
export function useEnsureSocket() {
|
||||
const {
|
||||
value: socket,
|
||||
loading,
|
||||
error,
|
||||
} = useAsync(async () => {
|
||||
const token = await getUserJWT();
|
||||
if (typeof token !== 'string') {
|
||||
throw new Error('Token不合法');
|
||||
}
|
||||
|
||||
const socket = await createSocket(token);
|
||||
console.log('当前socket连接成功');
|
||||
|
||||
return socket;
|
||||
}, []);
|
||||
|
||||
return { loading, error, socket };
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import { AppSocket, AppStore, setupRedux } from 'pawchat-shared';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* 初始化 全局上下文 只执行一次
|
||||
*/
|
||||
export function useSetupRedux(socket: AppSocket, store: AppStore) {
|
||||
useEffect(() => {
|
||||
if (socket !== undefined && store !== undefined) {
|
||||
setupRedux(socket, store);
|
||||
}
|
||||
}, [socket, store]);
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import { useAppSelector } from '../../hooks/useAppSelector';
|
||||
|
||||
/**
|
||||
* 导航栏组件
|
||||
*/
|
||||
const Navbar: React.FC = React.memo(() => {
|
||||
const userInfo = useAppSelector((state) => state.user.info);
|
||||
|
||||
return <div></div>;
|
||||
});
|
||||
Navbar.displayName = 'Navbar';
|
@ -0,0 +1,81 @@
|
||||
import {
|
||||
createSocket,
|
||||
createStore,
|
||||
setupRedux,
|
||||
useAsync,
|
||||
userActions,
|
||||
} from 'pawchat-shared';
|
||||
import React, { useMemo } from 'react';
|
||||
import { LoadingSpinner } from '../../components/LoadingSpinner';
|
||||
import { Provider as ReduxProvider } from 'react-redux';
|
||||
import { getGlobalUserLoginInfo } from '../../utils/user-helper';
|
||||
import _isNil from 'lodash/isNil';
|
||||
import { loginWithToken } from 'pawchat-shared/model/user';
|
||||
import { getUserJWT } from '../../utils/jwt-helper';
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
/**
|
||||
* 应用状态管理hooks
|
||||
*/
|
||||
function useAppState() {
|
||||
const history = useHistory();
|
||||
|
||||
const { value, loading } = useAsync(async () => {
|
||||
let userLoginInfo = getGlobalUserLoginInfo();
|
||||
if (_isNil(userLoginInfo)) {
|
||||
// 如果没有全局缓存的数据, 则尝试自动登录
|
||||
try {
|
||||
const token = await getUserJWT();
|
||||
if (typeof token !== 'string') {
|
||||
throw new Error('Token 不合法');
|
||||
}
|
||||
userLoginInfo = await loginWithToken(token);
|
||||
} catch (e) {
|
||||
// 当前 Token 不存在或已过期
|
||||
history.replace('/entry/login');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 到这里 userLoginInfo 必定存在
|
||||
// 创建Redux store
|
||||
const store = createStore();
|
||||
store.dispatch(userActions.setUserInfo(userLoginInfo));
|
||||
|
||||
// 创建 websocket 连接
|
||||
const socket = await createSocket(userLoginInfo.token);
|
||||
|
||||
// 初始化Redux
|
||||
setupRedux(socket, store);
|
||||
|
||||
return { store, socket };
|
||||
}, [history]);
|
||||
|
||||
const store = value?.store;
|
||||
const socket = value?.socket;
|
||||
|
||||
return { loading, store, socket };
|
||||
}
|
||||
|
||||
/**
|
||||
* 主页面核心数据Provider
|
||||
* 在主页存在
|
||||
*/
|
||||
export const MainProvider: React.FC = React.memo((props) => {
|
||||
const { loading, store } = useAppState();
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="w-screen h-screen flex items-center justify-center bg-gray-700 text-white text-xl">
|
||||
<LoadingSpinner tip="正在连接到聊天服务器..." />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (_isNil(store)) {
|
||||
return <div>出现异常, Store 创建失败</div>;
|
||||
}
|
||||
|
||||
return <ReduxProvider store={store}>{props.children}</ReduxProvider>;
|
||||
});
|
||||
MainProvider.displayName = 'MainProvider';
|
@ -0,0 +1,11 @@
|
||||
import { UserLoginInfo } from 'pawchat-shared/model/user';
|
||||
|
||||
let _userLoginInfo: UserLoginInfo;
|
||||
|
||||
export function setGlobalUserLoginInfo(loginInfo: UserLoginInfo) {
|
||||
_userLoginInfo = loginInfo;
|
||||
}
|
||||
|
||||
export function getGlobalUserLoginInfo() {
|
||||
return _userLoginInfo;
|
||||
}
|
Loading…
Reference in New Issue