React
构建react项目
使用vite快速构建react项目
npm vite initHook
数据驱动
useState
useState允许你向组件添加一个状态变量
useReducer
useImmer
useSyncExternalStore
useTransition
useDeferredValue
副作用
useEffect
useLayoutEffect
useInsertionEffect
状态传递
useRef
useImperativeHandle
useContext
状态派生
useMemo
useCallback
工具Hooks
useDebugValue
useId
useId可以生成传递给无障碍属性的唯一ID
import {useId} from 'react';
import {Button, Input} from 'antd';
const useIdHook = () => {
const id = useId();
console.log('useIdHook---', id);
return <>
<p>{id}</p>
<label htmlFor={id}>useIdHook</label>
<Input id={id} type="text" aria-describedby={id}/>
<p id={id}>
请输入有效的电子邮件地址,例如:xxxx@example.com
</p>
<Button type="primary" onClick={() => window.onShowMessage()}>全局消息</Button>
</>;
};
export default useIdHook;Component
Props
createPortal
使用createPortal创建一个 portal,将子节点渲染到指定的 DOM 节点中
import React from 'react';
import {createPortal} from 'react-dom';
const Portal: React.FC = () => {
return createPortal(<div>Modify</div>, document.body);
};
export default Portal;Suspense
Suspense 允许在子组件完成加载前展示后备方案,如下即表示异步加载<AsyncComponent/>组件,组件加载完成之前显示loading,组件加载完成之后显示组件内容
import React, {ComponentType, lazy, Suspense} from 'react';
const AsyncComponent = lazy<ComponentType>(() => import('./AsyncComponent'));
const SuspenseComponent: React.FC = () => {
return <>
<Suspense fallback={<div>loading...</div>}>
<AsyncComponent/>
</Suspense>
</>;
};
export default SuspenseComponent;HOC
Css
Tailwind Css
Router
路由
在路由中通过children属性嵌套路由
export const layoutRouters= [
{
path: '/layout',
Component: Layout,
children: [
{
path: 'home',
Component: Home
},
{
path: 'about',
Component: About
}
]
}
];import {Outlet} from 'react-router';
function Content() {
return <Outlet/>;
}传参
Query
直接在url中添加参数,可以通过useSearchParams、useLocation获取参数
<NavLink to="/param?name=modify&age=18&address=北京">Go to Param</NavLink>;const param: React.FC = () => {
const [searchParams, setSearchParams] = useSearchParams();
const {search} = useLocation();
console.log('useLocation', decodeURIComponent(search));
const name = searchParams.get('name');
const age = searchParams.get('age');
const address = searchParams.get('address');
const handleClick = () => {
setSearchParams(prev => {
prev.set('name', '王老五');
prev.set('age', '26');
prev.set('address', '广州');
return prev;
});
};
return <>
<p>Name: {name}</p>
<p>Age: {age}</p>
<p>Address: {address}</p>
<Button type="primary" onClick={handleClick}>Update</Button>
</>;
};Params
通过/user/:id传递参数,通过useParams获取参数
State
通过state可以传递复杂参数,通过useLocation获取参数
<NavLink state={['1', '2', '3']}>Go to Param</NavLink>const param: React.FC = () => {
const {state } = useLocation();
console.log('state', state);
}| 方式 | 示例 | 适用场景 | 特点 | 限制 |
|---|---|---|---|---|
| Params | /user/:id | 传递必要的路径参数(如ID) | 符合 RESTful 规范,刷新不丢失 | 只能传字符串,参数显示在 URL 中 |
| Query | /user?name=xiaoman | 传递可选的查询参数 | 灵活多变,支持多参数 | URL 可能较长,参数公开可见 |
| State | (无 URL 体现) | 传递复杂数据结构 | 支持任意类型数据,参数不显示在 URL | 刷新可能丢失,不利于分享 |
选择建议:必要参数用 Params,筛选条件用 Query,临时数据用 State
懒加载
通过lazy方法实现懒加载
import {lazy} from 'react';
const About = lazy(() => import('./About'));
const hookRouters = [
{
path: '/',
Component: About
}]const hookRouters = [{
path: 'about/:id',
lazy: async () => {
await sleep(2000);
const About = await import('../pages/layout/About.tsx');
return {
Component: About.default
};
}
}]const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));使用useNavigation优化,当路由切换时候加载组件是耗时很长,useNavigation可以在组件加载完成后再渲染组件,避免页面卡顿
import React from 'react';
import {Outlet, useNavigation} from 'react-router';
import {Alert, Spin} from 'antd';
const content: React.FC = () => {
const navigation = useNavigation();
console.log(navigation.state);
const isLoading = navigation.state === 'loading';
return <>
{isLoading ? <Spin size="large" tip="loading...">
<Alert description="loading..." message="加载中" type="info"/>
</Spin> : <Outlet/>
}
</>;
};
export default content;路由操作
路由中提供了loader和action方法,可以在路由加载和路由操作时执行方法
- 其中loader方法会在路由加载时执行,只有GET请求才会触发loader,可以直接通过
useLoaderData获取数据 - action方法会在路由操作时执行,如点击提交按钮,会触发action方法,通过
useSubmit进行提交,提交的默认格式是表单- encType:指定提交的数据格式
- "application/json"
- "text/plain"
- "application/x-www-form-urlencoded"
- encType:指定提交的数据格式
const data = [
{name: '张三', age: 18, address: '广州'}
];
const routers = [
{
path: 'about/:id',
lazy: async () => {
await sleep(2000);
const About = await import('../pages/layout/About.tsx');
return {
Component: About.default
};
},
loader: async () => {
return {
data,
message: 'success'
};
},
action: async ({request}) => {
await sleep(2000);
const formData = await request.formData();
data.push({
address: formData.get('name'),
name: formData.get('name'),
age: formData.get('age')
});
return {
data,
success: true
};
}
}
]const {data, message} = useLoaderData();import React from 'react';
import {useNavigation, useSubmit} from 'react-router';
import {Button, Form, Input} from 'antd';
const About: React.FC = () => {
const submit = useSubmit();
const navigation = useNavigation();
const handleSubmit = (values: any) => {
submit(values, {method: 'post'});
};
return <>
<Form onFinish={handleSubmit}>
<Form.Item name="name" label="姓名">
<Input/>
</Form.Item>
<Form.Item name="age" label="年龄">
<Input/>
</Form.Item>
<Button type="primary" htmlType="submit" disabled={navigation.state === 'submitting'}>提交</Button>
</Form>
</>;
};
export default About;导航
Link和NavLink组件的参数
- to:要导航到的路径
- replace:是否替换当前路径
- state:要传递给目标页面的状态
- relative:相对于当前路径的导航方式
- reloadDocument:是否重新加载页面
- preventScrollReset:是否阻止滚动位置重置
- viewTransition:是否启用视图过渡
边界处理
404 Not Found
添加一个通配符用来处理不存在的路由展示404页面
const routers = [{
path: '*',
lazy: async () => {
const notFound = await import('../pages/notFound.tsx');
return {
Component: notFound.default
};
}
}];ErrorBoundary
用于捕获路由loader或action的错误,并进行处理,通过ErrorBoundary指向Error组件,Error组件中可以通过useRouteError获取错误信息
const routers = [{
path: 'home',
Component: Home,
loader: async () => {
throw {
code: 404,
message: 'Loader Error By ErrorBoundary'
};
},
ErrorBoundary: Error
}];import {useRouteError} from 'react-router';
const Error = () => {
const error = useRouteError();
console.log(error);
return <>
<h1>ERROR PAGE</h1>
<p>{error.message}</p>
<p>{error.code}</p>
</>;
};
export default Error;Zustand
安装 & 使用
npm install zustand- 通过create方法创建store,这个函数会返回一个对象,对象中包含store的所有属性和方法
- get方法获取store的所有属性,再.一下就可以获取到属性的值
- set方法设置store的属性,通过参数state来进行设置值
- 简单数据类型,在set方法中不用...state进行赋值展开
import {create} from 'zustand';
interface CountStoreInterface {
count: number;
other: string,
increment: () => void;
decrement: () => void;
getCount: () => number;
}
const useCountStore = create<CountStoreInterface>((set, get) => ({
count: 0,
other: 'other',
increment: () => set((state) => ({count: state.count + 1})),
decrement: () => set((state) => ({count: state.count - 1})),
getCount: () => get().count
}));
export default useCountStore;import useCountStore from '../../store/countStore';
import {Button, Flex} from 'antd';
import React from 'react';
const countOne: React.FC = () => {
console.log('countOne Render');
const {count, other, increment, decrement, getCount} = useCountStore();
return <>
<h1>CountA: {count}</h1>
<h1>Other: {other}</h1>
<Flex gap="small" wrap="wrap">
<Button type="primary" onClick={() => increment()}>+</Button>
<Button type="primary" onClick={() => decrement()}>-</Button>
<Button type="primary" onClick={() => alert(getCount())}>Get Count</Button>
</Flex>
</>;
};
export default countOne;状态处理
- 数据类型为多层对象嵌套的时候(useUserStore),再进行set的时候,需要展开所有的层,否则会覆盖掉其他层的属性
- 这样每次展开的时候使用感觉就很麻烦,引入immer库
- 使用immer库,可以自动展开多层对象,并且不会覆盖其他层的属性,适合数据结构为多层对象嵌套
import {create} from 'zustand';
interface User {
gourd: {
oneChild: string,
twoChild: string,
threeChild: string,
fourChild: string,
fiveChild: string,
sixChild: string,
sevenChild: string,
},
updateGourd: () => void
}
// 创建 store
const useUserStore = create<User>(((set) => ({
gourd: {
oneChild: 'oneChild',
twoChild: 'twoChild',
threeChild: 'threeChild',
fourChild: 'fourChild',
fiveChild: 'fiveChild',
sixChild: 'sixChild',
sevenChild: 'sevenChild'
},
// 更新方法
updateGourd: () => set((state) => ({
gourd: {
...state.gourd,
oneChild: 'oneChild-update'
}
}))
})));
export default useUserStore;import {create} from 'zustand';
import {immer} from 'zustand/middleware/immer';
interface UserInterface {
gourd: {
oneChild: string,
twoChild: string,
threeChild: string,
fourChild: string,
fiveChild: string,
sixChild: string,
sevenChild: string,
},
updateGourd: () => void
}
// 注意:使用 immer 中间件时的特殊结构
const useUserImmerStore = create<UserInterface>()(immer((set) => ({
gourd: {
oneChild: 'oneChild',
twoChild: 'twoChild',
threeChild: 'threeChild',
fourChild: 'fourChild',
fiveChild: 'fiveChild',
sixChild: 'sixChild',
sevenChild: 'sevenChild'
},
updateGourd: () => set((state) => {
// 使用 immer 直接修改状态
state.gourd.oneChild = 'oneChild-update';
state.gourd.twoChild = 'twoChild-update';
state.gourd.threeChild = 'threeChild-update';
})
})));
export default useUserImmerStore;状态简化
- 还是一样的,先定义一个store
- 在组件A当中使用这个store,并且添加按钮去触发updateAge事件
- 通过解构取值,当在组件A触发事件后,该组件并未使用age属性也会被重新渲染
- 那么可以通过store当中的state进行取值,但是这样的话,会导致组件定义了一堆变量,看起来不美观
- 使用useShallow可以避免组件重新渲染
import {create} from 'zustand';
import {immer} from 'zustand/middleware/immer';
export interface InfoInterface {
name: string,
age: number,
address: {
county: string,
province: string,
city: string
},
updateAge: () => void
}
const useInfoStore = create<InfoInterface>()(immer((set) => ({
name: 'modify',
age: 18,
address: {
county: 'China',
province: 'GuangDong',
city: 'GuangZhou'
},
updateAge: () => set((state) => {
state.age += 1;
})
})));
export default useInfoStore;import useInfoStore from '../../store/infoStore';
import React from 'react';
const countTwo: React.FC = () => {
console.log('countTwo Render');
const {name, address} = useInfoStore();
return <>
<p>Name: {name}</p>
<p>Address: {address.county} {address.province} {address.city}</p>
</>;
};
export default countTwo;const name = useInfoStore((state) => state.name);
const address = useInfoStore((state) => state.address);const {name, address} = useInfoStore(useShallow((state: InfoInterface) => ({
name: state.name,
address: state.address
})));中间件
自定义中间件
中间件是一个函数,返回一个函数,这个函数接收三个参数,set,get,api
- set:用于更新状态的函数
- get:用于获取当前状态的函数
- api:包含 store 的完整 API(如 setState, getState, subscribe, destroy 等方法)
const logger = (config) => (set, get, api) => config((...args) => {
console.log(api);
console.log('before', get());
set(...args);
console.log('after', get());
}, get, api);devtools
一个用于调试的工具,它可以帮助我们更好地管理状态
persist
一个用于持久化状态的工具,它可以帮助我们更好地管理状态,默认是存储在 localStorage 中,可以指定存储方式
使用persist中间件之后会自动增加一个clearStorage方法,用于清空缓存
import {createJSONStorage, devtools, persist} from 'zustand/middleware';
const useInfoStore = create<InfoInterface>()(
immer(
persist(
devtools(
logger(
(set) => ({
name: 'modify',
age: 18,
address: {
county: 'China',
province: 'GuangDong',
city: 'GuangZhou'
},
updateAge: () => set((state) => {
state.age += 1;
})
})
),
{
enabled: true, // 是否开启devtools
name: '用户信息' // 仓库名称
}
),
{
name: 'info',
storage: createJSONStorage(() => localStorage), // 存储方式 可选 localStorage sessionStorage IndexedDB 默认localStorage
// 部分状态持久化
partialize: (state) => ({
name: state.name,
age: state.age
})
}
)
)
);订阅
添加subscribeWithSelector中间件
