Browse Source

优化 useAsync 和依赖页,设置错误边界

redux
YuJian920 3 years ago
parent
commit
3b3323b8e6
  1. 23
      src/components/errorBoundary.tsx
  2. 22
      src/components/lib.tsx
  3. 79
      src/context/auth-context.tsx
  4. 14
      src/hook/useAsync.ts
  5. 10
      src/pages/App.tsx
  6. 8
      src/pages/Auth/Login/index.tsx
  7. 27
      src/pages/Auth/Register/index.tsx
  8. 10
      src/pages/Auth/index.tsx
  9. 4
      src/utils/token.ts

23
src/components/errorBoundary.tsx

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
import React from "react";
type FallbackRender = (props: { error: Error | null }) => React.ReactElement;
class ErrorBoundary extends React.Component<
React.PropsWithChildren<{ fallbackRender: FallbackRender }>
> {
state = { error: null };
static getDerivedStateFromError(error: Error) {
return { error };
}
render() {
const { error } = this.state;
const { fallbackRender, children } = this.props;
if (error) return fallbackRender({ error });
else return children;
}
}
export default ErrorBoundary;

22
src/components/lib.tsx

@ -1,4 +1,7 @@ @@ -1,4 +1,7 @@
import React from "react";
import styled from "@emotion/styled";
import { Spin, Typography } from "antd";
export const Row = styled.div<{
gap?: number | boolean;
between?: boolean;
@ -20,3 +23,22 @@ export const Row = styled.div<{ @@ -20,3 +23,22 @@ export const Row = styled.div<{
: undefined};
}
`;
export const FullPage = styled.div`
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
`;
export const FullPageLoading = () => (
<FullPage>
<Spin size="large" />
</FullPage>
);
export const FullPageErrorFallback = ({ error }: { error: Error | null }) => (
<FullPage>
<Typography.Text type="danger">{error?.message}</Typography.Text>
</FullPage>
);

79
src/context/auth-context.tsx

@ -1,7 +1,9 @@ @@ -1,7 +1,9 @@
import React, { useState, useContext, ReactNode } from "react"
import useMount from "../hook/useMount"
import React, { useState, useContext, ReactNode } from "react";
import { FullPageLoading } from "../components/lib";
import useAsync from "../hook/useAsync";
import useMount from "../hook/useMount";
import { request } from "../utils/request";
import * as auth from "../utils/token"
import * as auth from "../utils/token";
interface User {
id: string;
@ -9,52 +11,69 @@ interface User { @@ -9,52 +11,69 @@ interface User {
email: string;
title: string;
organization: string;
token: string
token: string;
}
interface AuthForm {
username: string,
password: string
username: string;
password: string;
}
interface AuthContext {
user: User | null
login: (form: AuthForm) => Promise<void>
register: (form: AuthForm) => Promise<void>
logout: () => Promise<void>
user: User | null;
login: (form: AuthForm) => Promise<void>;
register: (form: AuthForm) => Promise<void>;
logout: () => Promise<void>;
}
const AuthContext = React.createContext<AuthContext | undefined>(undefined)
AuthContext.displayName = 'AuthContext'
const AuthContext = React.createContext<AuthContext | undefined>(undefined);
AuthContext.displayName = "AuthContext";
const bootstrapUser = async () => {
let user = null
const token = auth.getToken()
let user = null;
const token = auth.getToken();
if (token) {
const data = await request('/me', { token })
user = data.user
const data = await request("/me", { token });
user = data.user;
}
return Promise.resolve(user)
}
return Promise.resolve(user);
};
export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [user, setUser] = useState<User | null>(null)
const {
data: user,
isLoading,
isIdle,
run,
setData: setUser,
} = useAsync<User | null>();
const login = (form: AuthForm) => auth.login(form).then(setUser)
const register = (form: AuthForm) => auth.register(form).then(setUser)
const logout = () => auth.logout().then(() => setUser(null))
const login = (form: AuthForm) => auth.login(form).then(setUser);
const register = (form: AuthForm) => auth.register(form).then(setUser);
const logout = () => auth.logout().then(() => setUser(null));
useMount(() => {bootstrapUser().then(setUser)})
useMount(() => {
run(bootstrapUser());
});
return <AuthContext.Provider children={children} value={{ user, login, register, logout }} />
}
if (isIdle || isLoading) {
return <FullPageLoading />;
}
return (
<AuthContext.Provider
children={children}
value={{ user, login, register, logout }}
/>
);
};
/**
* Auth Context hook
* @returns
* @returns
*/
export const useAuth = () => {
const context = useContext(AuthContext)
if (!context) throw new Error("useAuth必须在AuthProvider中使用")
return context
}
const context = useContext(AuthContext);
if (!context) throw new Error("useAuth必须在AuthProvider中使用");
return context;
};

14
src/hook/useAsync.ts

@ -12,15 +12,24 @@ const defaultInitialState: State<null> = { @@ -12,15 +12,24 @@ const defaultInitialState: State<null> = {
error: null,
};
const useAsync = <D>(initialState?: State<D>) => {
const defaultConfig = {
throwOnError: false,
};
const useAsync = <D>(
initialState?: State<D>,
initialConfig?: typeof defaultConfig
) => {
const config = { ...defaultConfig, initialConfig };
const [state, setState] = useState<State<D>>({
...defaultInitialState,
...initialState,
});
const setData = (data: D) => setState({ data, stat: "success", error: null });
const setError = (error: Error) =>
const setError = (error: Error) => {
setState({ data: null, stat: "error", error });
}
const run = (promise: Promise<D>) => {
if (!promise || !promise.then) throw new Error("请传入 Promise 数据类型");
@ -32,6 +41,7 @@ const useAsync = <D>(initialState?: State<D>) => { @@ -32,6 +41,7 @@ const useAsync = <D>(initialState?: State<D>) => {
})
.catch((error) => {
setError(error);
if (config.throwOnError) return Promise.reject(error);
return error;
});
};

10
src/pages/App.tsx

@ -3,10 +3,18 @@ import Home from "./Home"; @@ -3,10 +3,18 @@ import Home from "./Home";
import AuthPage from "./Auth";
import { useAuth } from "../context/auth-context";
import "../style/global.css";
import ErrorBoundary from "../components/errorBoundary";
import { FullPageErrorFallback } from "../components/lib";
const App = () => {
const { user } = useAuth();
return <div className="App">{user ? <Home /> : <AuthPage />}</div>;
return (
<div className="App">
<ErrorBoundary fallbackRender={FullPageErrorFallback}>
{user ? <Home /> : <AuthPage />}
</ErrorBoundary>
</div>
);
};
export default App;

8
src/pages/Auth/Login/index.tsx

@ -2,14 +2,16 @@ import React from "react"; @@ -2,14 +2,16 @@ import React from "react";
import { Form, Input } from "antd";
import { useAuth } from "../../../context/auth-context";
import { LongButton } from "../style";
import useAsync from "../../../hook/useAsync";
const { Item } = Form;
const Login = () => {
const Login = ({ onError }: { onError: (error: Error) => void }) => {
const { login, user } = useAuth()
const { run, isLoading } = useAsync(undefined, { throwOnError: true });
const handleSubmit = (values: { username: string, password: string }) => {
login(values)
run(login(values).catch(onError))
}
return (
@ -21,7 +23,7 @@ const Login = () => { @@ -21,7 +23,7 @@ const Login = () => {
<Input placeholder="密码" type="text" id="password" />
</Item>
<Item>
<LongButton htmlType="submit" type="primary"></LongButton>
<LongButton loading={isLoading} htmlType="submit" type="primary"></LongButton>
</Item>
</Form>
);

27
src/pages/Auth/Register/index.tsx

@ -2,14 +2,27 @@ import React from "react"; @@ -2,14 +2,27 @@ import React from "react";
import { Form, Input } from "antd";
import { useAuth } from "../../../context/auth-context";
import { LongButton } from "../style";
import useAsync from "../../../hook/useAsync";
const { Item } = Form;
const Register = () => {
const Register = ({ onError }: { onError: (error: Error) => void }) => {
const { register, user } = useAuth();
const { run, isLoading } = useAsync(undefined, { throwOnError: true });
const handleSubmit = (values: { username: string; password: string }) => {
register(values);
const handleSubmit = ({
cpassword,
...values
}: {
username: string;
password: string;
cpassword: string;
}) => {
if (cpassword !== values.password) {
onError(new Error("请确认两次输入的密码一致"));
return;
}
run(register(values).catch(onError));
};
return (
@ -23,8 +36,14 @@ const Register = () => { @@ -23,8 +36,14 @@ const Register = () => {
<Item name="password" rules={[{ required: true, message: "请输入密码" }]}>
<Input placeholder="密码" type="text" id="password" />
</Item>
<Item
name="cpassword"
rules={[{ required: true, message: "请确认密码" }]}
>
<Input placeholder="确认密码" type="text" id="cpassword" />
</Item>
<Item>
<LongButton htmlType="submit" type="primary">
<LongButton loading={isLoading} htmlType="submit" type="primary">
</LongButton>
</Item>

10
src/pages/Auth/index.tsx

@ -1,21 +1,25 @@ @@ -1,21 +1,25 @@
import React, { useState } from "react";
import { Divider, Button } from "antd";
import { Divider, Button, Typography } from "antd";
import Login from "./Login";
import Register from "./Register";
import { Header, Container, ShadowCard, Background, Title } from "./style";
const { Text } = Typography;
const AuthPage = () => {
const [isRegister, setIsRegister] = useState(false);
const [error, setError] = useState<Error | null>(null);
return (
<Container>
<Header />
<Background />
<ShadowCard>
<Title>{isRegister ? "请注册" : "请登录"}</Title>
{isRegister ? <Register /> : <Login />}
{error ? <Text type="danger">{error.message}</Text> : null}
{isRegister ? <Register onError={setError} /> : <Login onError={setError} />}
<Divider />
<Button type="link" onClick={() => setIsRegister(!isRegister)}>
Button{isRegister ? "已经有帐号了?直接登录" : "没有账号?注册新账号"}
{isRegister ? "已经有帐号了?直接登录" : "没有账号?注册新账号"}
</Button>
</ShadowCard>
</Container>

4
src/utils/token.ts

@ -24,7 +24,7 @@ export const login = async (data: { username: string, password: string }) => { @@ -24,7 +24,7 @@ export const login = async (data: { username: string, password: string }) => {
body: JSON.stringify(data)
})
if (result.ok) return handleUserResponse(await result.json())
else return Promise.reject(data)
else return Promise.reject(await result.json())
}
export const register = async (data: { username: string; password: string }) => {
@ -35,7 +35,7 @@ export const register = async (data: { username: string; password: string }) => @@ -35,7 +35,7 @@ export const register = async (data: { username: string; password: string }) =>
})
if (result.ok) return handleUserResponse(await result.json())
else return Promise.reject(data)
else return Promise.reject(await result.json())
}
export const logout = async () => window.localStorage.removeItem(localStorageKey)
Loading…
Cancel
Save