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({
|
const userSlice = createSlice({
|
||||||
name: 'user',
|
name: 'user',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {},
|
reducers: {
|
||||||
|
setUserInfo(state, action: PayloadAction<UserLoginInfo>) {
|
||||||
|
state.info = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const userActions = userSlice.actions;
|
||||||
export const userReducer = userSlice.reducer;
|
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