Skip to content

React

React 中文文档

小满zs的React教程文档

React Project In Github

SWC site

React-Router v7.9.6

Ant Design 5.0

构建react项目

使用vite快速构建react项目

bash
npm vite init

Hook

数据驱动

useState

useState允许你向组件添加一个状态变量

useReducer

useImmer

useSyncExternalStore

useTransition

useDeferredValue

副作用

useEffect

useLayoutEffect

useInsertionEffect

状态传递

useRef

useImperativeHandle

useContext

状态派生

useMemo

useCallback

工具Hooks

useDebugValue

useId

useId可以生成传递给无障碍属性的唯一ID

tsx
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 节点中

tsx
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,组件加载完成之后显示组件内容

tsx
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

Tailwind Css安装

Router

路由

在路由中通过children属性嵌套路由

ts
export const layoutRouters= [
  {
    path: '/layout',
    Component: Layout,
    children: [
      {
        path: 'home',
        Component: Home
      },
      {
        path: 'about',
        Component: About
      }
    ]
  }
];
tsx
import {Outlet} from 'react-router';

function Content() {
  return <Outlet/>;
}

传参

Query

直接在url中添加参数,可以通过useSearchParams、useLocation获取参数

tsx
<NavLink to="/param?name=modify&age=18&address=北京">Go to Param</NavLink>;
tsx
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获取参数

tsx
<NavLink state={['1', '2', '3']}>Go to Param</NavLink>
tsx
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方法实现懒加载

ts
import {lazy} from 'react';
const About = lazy(() => import('./About'));
const hookRouters = [
  {
    path: '/',
    Component: About
  }]
ts
const hookRouters = [{
    path: 'about/:id',
    lazy: async () => {
        await sleep(2000);
        const About = await import('../pages/layout/About.tsx');
        return {
            Component: About.default
        };
    }
}]
ts
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

使用useNavigation优化,当路由切换时候加载组件是耗时很长,useNavigation可以在组件加载完成后再渲染组件,避免页面卡顿

tsx
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"
ts
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
      };
    }
  }
]
tsx
const {data, message} = useLoaderData();
tsx
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页面

ts
const routers = [{
  path: '*',
  lazy: async () => {
    const notFound = await import('../pages/notFound.tsx');
    return {
      Component: notFound.default
    };
  }
}];

ErrorBoundary

用于捕获路由loader或action的错误,并进行处理,通过ErrorBoundary指向Error组件,Error组件中可以通过useRouteError获取错误信息

ts
const routers = [{
  path: 'home',
  Component: Home,
  loader: async () => {
    throw {
      code: 404,
      message: 'Loader Error By ErrorBoundary'
    };
  },
  ErrorBoundary: Error
}];
tsx
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

安装 & 使用

bash
npm install zustand
  • 通过create方法创建store,这个函数会返回一个对象,对象中包含store的所有属性和方法
  • get方法获取store的所有属性,再.一下就可以获取到属性的值
  • set方法设置store的属性,通过参数state来进行设置值
  • 简单数据类型,在set方法中不用...state进行赋值展开
ts
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;
tsx
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库,可以自动展开多层对象,并且不会覆盖其他层的属性,适合数据结构为多层对象嵌套
ts
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;
ts
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可以避免组件重新渲染
ts
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;
tsx
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;
tsx
const name = useInfoStore((state) => state.name);
const address = useInfoStore((state) => state.address);
tsx
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 等方法)
ts
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方法,用于清空缓存

ts
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中间件

By Modify.