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.

选择字体进行切换