Article Directory
-
- 1. Project launch: project initialization and configuration
- 2. React and Hook application: implement the project list
- 3. TS Application: JS God Assist - Strong Type
- 4. JWT, user authentication and asynchronous request
- 5. CSS is actually very simple - add styles with CSS-in-JS
- 6. User experience optimization - loading and error state handling
Source of learning content: React + React Hook + TS Best Practice - MOOC
Compared with the original tutorial, I used the latest version at the beginning of my study (2023.03):
item | Version |
---|---|
react & react-dom | ^18.2.0 |
react-router & react-router-dom | ^6.11.2 |
antd | ^4.24.8 |
@commitlint/cli & @commitlint/config-conventional | ^17.4.4 |
eslint-config-prettier | ^8.6.0 |
husky | ^8.0.3 |
lint-staged | ^13.1.2 |
prettier | 2.8.4 |
json-server | 0.17.2 |
craco-less | ^2.0.0 |
@craco/craco | ^7.1.0 |
qs | ^6.11.0 |
dayjs | ^1.11.7 |
react-helmet | ^6.1.0 |
@types/react-helmet | ^6.1.6 |
react-query | ^6.1.0 |
@welldone-software/why-did-you-render | ^7.0.1 |
@emotion/react & @emotion/styled | ^11.10.6 |
The specific configuration, operation and content will be different, and the "pit" will also be different. . .
1. Project launch: project initialization and configuration
2. React and Hook application: implement the project list
3. TS Application: JS God Assist - Strong Type
4. JWT, user authentication and asynchronous request
5. CSS is actually very simple - add styles with CSS-in-JS
6. User experience optimization - loading and error state handling
1. Add Loading and Error states to the page to increase page friendliness
Modification src\screens\ProjectList\index.tsx
(add loading status and request error prompt) (some unmodified content omitted):
...
import {
Typography } from "antd";
export const ProjectList = () => {
const [users, setUsers] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<null | Error>(null);
...
useEffect(() => {
setIsLoading(true)
// React Hook "useHttp" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.
client("projects", {
data: cleanObject(lastParam) }).then(setList)
.catch(error => {
setList([])
setError(error)
})
.finally(() => setIsLoading(false));
// React Hook useEffect has a missing dependency: 'client'. Either include it or remove the dependency array.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [lastParam]);
...
return (
<Container>
<h1>项目列表</h1>
<SearchPanel users={
users} param={
param} setParam={
setParam} />
{
error ? <Typography.Text type="danger">{
error.message}</Typography.Text> : null}
<List loading={
isLoading} users={
users} dataSource={
list} />
</Container>
);
};
...
Modify src\screens\ProjectList\components\List.tsx
( ListProps
inheritance TableProps
, Table
attribute (transparent)) (some unmodified content is omitted):
import {
Table, TableProps } from "antd";
...
interface ListProps extends TableProps<Project> {
users: User[];
}
// type PropsType = Omit<ListProps, 'users'>
export const List = ({
users, ...props }: ListProps) => {
return (
<Table
pagination={
false}
columns={
...}
{
...props }
></Table>
);
};
In order to facilitate the subsequent reconfiguration of the properties (transparent transmission) outside the component
Table
, let directlyListProps
inheritTableProps
andTableProps
extract them separatelyprops
Configure the minimum request time (as shown in the figure below), and you can clearly see loading
the effect
. Configure the failure ratio of the request to be 100%, and you can see the error message:
2. Use advanced Hook-useAsync to uniformly handle Loading and Error states
New src\utils\use-async.ts
(unified management of asynchronous state and request data ):
import {
useState } from "react";
interface State<D> {
error: Error | null;
data: D | null;
stat: 'ready' | 'loading' | 'error' | 'success'
}
const defaultInitialState: State<null> = {
stat: 'ready',
data: null,
error: null
}
export const useAsync = <D>(initialState?: State<D>) => {
const [state, setState] = useState<State<D>>({
...defaultInitialState,
...initialState
})
const setData = (data: D) => setState({
data,
stat: 'success',
error: null
})
const setError = (error: Error) => setState({
error,
stat: 'error',
data: null
})
// run 来触发异步请求
const run = (promise: Promise<D>) => {
if(!promise || !promise.then) {
throw new Error('请传入 Promise 类型数据')
}
setState({
...state, stat: 'loading'})
return promise.then(data => {
setData(data)
return data
}).catch(error => {
setError(error)
return error
})
}
return {
isReady: state.stat === 'ready',
isLoading: state.stat === 'loading',
isError: state.stat === 'error',
isSuccess: state.stat === 'success',
run,
setData,
setError,
...state
}
}
Modification src\screens\ProjectList\components\List.tsx
(will be Project
exported for subsequent reference) (some unmodified content omitted):
...
export interface Project {
...}
...
Modifications src\screens\ProjectList\index.tsx
(some unmodified content omitted):
- Delete before
loading
anderror
related content; - Delete
client
asynchronous requestthen
and subsequent operations; - Use
useAsync
Unified processing of asynchronous state and request data ;
...
import {
List, Project } from "./components/List";
...
import {
useAsync } from "utils/use-async";
export const ProjectList = () => {
const [users, setUsers] = useState([]);
const [param, setParam] = useState({
name: "",
personId: "",
});
// 对 param 进行防抖处理
const lastParam = useDebounce(param);
const client = useHttp();
const {
run, isLoading, error, data: list } = useAsync<Project[]>();
useEffect(() => {
run(client("projects", {
data: cleanObject(lastParam) }))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [lastParam]);
useMount(() => client("users").then(setUsers));
return (
<Container>
...
<List loading={
isLoading} users={
users} dataSource={
list || []} />
</Container>
);
};
...
New src\utils\project.ts
(asynchronous request for Project data is processed separately):
import {
cleanObject } from "utils";
import {
useHttp } from "./http";
import {
useAsync } from "./use-async";
import {
useEffect } from "react";
import {
Project } from "screens/ProjectList/components/List";
export const useProjects = (param?: Partial<Project>) => {
const client = useHttp();
const {
run, ...result } = useAsync<Project[]>();
useEffect(() => {
run(client("projects", {
data: cleanObject(param || {
}) }))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [param]);
return result
}
New src\utils\use-users.ts
(asynchronous request for User data is processed separately):
import {
cleanObject } from "utils";
import {
useHttp } from "./http";
import {
useAsync } from "./use-async";
import {
useEffect } from "react";
import {
User } from "screens/ProjectList/components/SearchPanel";
export const useUsers = (param?: Partial<User>) => {
const client = useHttp();
const {
run, ...result } = useAsync<User[]>();
useEffect(() => {
run(client("users", {
data: cleanObject(param || {
}) }))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [param]);
return result
}
Revised again src\screens\ProjectList\index.tsx
(some unmodified content omitted):
Project
andUser
data acquisition are separated separately
import {
SearchPanel } from "./components/SearchPanel";
import {
List } from "./components/List";
import {
useState } from "react";
import {
useDebounce } from "utils";
import styled from "@emotion/styled";
import {
Typography } from "antd";
import {
useProjects } from "utils/project";
import {
useUsers } from "utils/use-users";
export const ProjectList = () => {
const [param, setParam] = useState({
name: "",
personId: "",
});
// 对 param 进行防抖处理后接入请求
const {
isLoading, error, data: list } = useProjects(useDebounce(param));
const {
data: users } = useUsers();
return (
<Container>
<h1>项目列表</h1>
<SearchPanel users={
users || []} param={
param} setParam={
setParam} />
{
error ? (
<Typography.Text type="danger">{
error.message}</Typography.Text>
) : null}
<List loading={
isLoading} users={
users || []} dataSource={
list || []} />
</Container>
);
};
...
Test function: everything works fine!
Some reference notes are still in draft stage, so stay tuned. . .