学习内容来源:React + React Hook + TS 最佳实践-慕课网
相对原教程,我在学习开始时(2023.03)采用的是当前最新版本:
项 | 版本 |
---|---|
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 |
具体配置、操作和内容会有差异,“坑”也会有所不同。。。
- 一、项目起航:项目初始化与配置
- 二、React 与 Hook 应用:实现项目列表
- 三、 TS 应用:JS神助攻 - 强类型
- 四、 JWT、用户认证与异步请求(上)
- 四、 JWT、用户认证与异步请求(下)
- 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(上)
- 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(下)
- 六、用户体验优化 - 加载中和错误状态处理(上)
- 六、用户体验优化 - 加载中和错误状态处理(中)
修改 src\components\lib.tsx
(新增全屏 Loading 组件 和 全屏 Error 展示组件):
import { Spin, Typography } from "antd";
import { DevTools } from "jira-dev-tool";
...
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>
<DevTools/>
<Typography.Text type="danger">{error?.message}</Typography.Text>
</FullPage>
- 为了展示报错信息的同时,DevTools 依旧展示,需要引入
修改 src\context\auth-context.tsx
(使用 useAsync
改造,并新增全屏 Loading
组件 和 全屏 Error
展示组件)(部分未修改内容省略):
...
import { useAsync } from "utils/use-async";
import { FullPageErrorFallback, FullPageLoading } from "components/lib";
...
export const AuthProvider = ({ children }: { children: ReactNode }) => {
// 这里要考虑到初始值的类型与后续值类型,取并组成一个泛型
// const [user, setUser] = useState(null);
const { data: user, error, isLoading, isReady, isSuccess, isError, run, setData: setUser } = useAsync<User | null>()
...
useMount(() => run(initUser()));
if (isReady || isLoading) {
return <FullPageLoading/>
}
if (isError) {
return <FullPageErrorFallback error={error}/>
}
return (...);
};
...
查看效果:完美!
修改 src\unauthenticated-app\index.tsx
(新增一个“抛出异常”按钮):
...
export const UnauthenticatedApp = () => {
...
return (
<Container>
<Header />
<Background />
<Button onClick={() => {
throw new Error('点击抛出一个异常')
}}>抛出异常</Button>
<ShadowCard>...</ShadowCard>
</Container>
);
};
...
修改 src\authenticated-app.tsx
(新增一个变量展示它不存在的一个属性):
...
export const AuthenticatedApp = () => {
...
const value: any = undefined;
...
return (
<Container>
{ value.notExist }
...
</Container>
);
};
...
编译代码并全局安装推荐的 serve
库,然后启动并访问:
npm run build
yarn global add serve
serve -s build
点击“抛出异常”按钮:
登录后:
这两种异常对比可看出:在渲染阶段出现未被捕获的异常,整个组件树都会被卸载(错误的展示内容比空白内容更可怕)
- 错误边界 – React
接下来写一个错误边界捕获组件 —— 新建:src\components\error-boundary.tsx
:
import React, { ReactNode } from "react";
type FallbackRender = (props: { error: Error | null }) => React.ReactElement
// children: ReactNode
export class ErrorBoundary extends React.Component<React.PropsWithChildren<{fallbackRender: FallbackRender}>, { error: Error | null }> {
state = { error: null }
// 当子组件抛出异常,这里会接受到并更改 state
static getDerivedStateFromError(error: Error) {
return { error }
}
render() {
const { error } = this.state
const { fallbackRender, children } = this.props
return error ? fallbackRender({ error }) : children
}
}
- 如果一个
class
组件中定义了static getDerivedStateFromError()
或componentDidCatch()
这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界React.PropsWithChildren
是React
中的一个Utility Types
(工具类型) 类型处理器,将传入属性以类似Object.assign
的方式合并:
type PropsWithChildren
= P & { children?: ReactNode | undefined };
修改:src\App.tsx
(使用错误边界组件 ErrorBoundary
包裹,并将异常展示在 FullPageErrorFallback
中):
...
import { ErrorBoundary } from "components/error-boundary";
import { FullPageErrorFallback } from "components/lib";
function App() {
...
return (
<div className="App">
<ErrorBoundary fallbackRender={FullPageErrorFallback}>
{user ? <AuthenticatedApp /> : <UnauthenticatedApp />}
</ErrorBoundary>
</div>
);
}
...
重新编译代码并重启serve,然后访问:
npm run build
serve -s build
手动抛出错误还是原样,渲染异常导致的边界错误被截获并展示!
Cannot read property 'notExist' of undefined
测试过程中可能会需要清除 localStorage:
测试结束后清除以下两个文件中的测试内容(“抛出异常”按钮 和 “value”):
src\unauthenticated-app\index.tsx
src\authenticated-app.tsx
部分引用笔记还在草稿阶段,敬请期待。。。