一、react 基础API
1. useEffect
- 浏览器重新绘制之后触发
- 参数(setup,dependencies?)
异步执行- 挂载组件时执行setup,依赖更新时先执行cleanup,再执行setup
2. useLayoutEffect
- 浏览器重新绘制之前触发
- 参数(setup,dependencies?)
同步执行,会阻塞DOM渲染- 挂载组件时执行setup,依赖更新时先执行cleanup,再执行setup
3. useReducer
使用场景:
- 组件数据状态逻辑复杂时
- 数据状态值包含多个,多属性嵌套、多属性关联时
- 数据状态的前后依赖性强时
- 数据状态的操作分散在多个处理函数时
优点:
- 可读性强,结构清晰
- 纯函数,状态独立,状态集中管理/操作
- dispatch 稳定性强,性能好,无需额外的缓存处理
- 同步输入输出,无newState的副作用
示例:
const [state, dispatch] = useReducer(reducerFn, initiaValue, inituaValueFn?)
reducerFn:处理函数,参数:state, action,返回值:新的state
initiaValue:state默认值
inituaValueFn:默认值函数,用于初始化state,优先执行,非必填
const reducerfn = (state, action) => {
if (action==='inc') {
return state++
}
if (action==='dec') {
return state--
}
}
const [state, dispatch] = useReducer(reducerfn, -1, ()=> {
// 高于默认值的优先级
return 0
})
<Button onClick={() => dispatch('inc')}>增加</Button>
<Button onClick={() => dispatch('dec')}>减少</Button>
4. useSyncExternalStore
useSyncExternalStore 用于从外部存储(例如状态管理库、浏览器 API等)获取状态并在组件中同步显示。跟踪外部状态。
useSyncExternalStore 使用场景:
- 订阅外部 store 例如(redux,mobx,Zustand,jotai)
- 订阅浏览器AP| 例如(online,storage,location, history hash)等
抽离逻辑,编写自定义hooks
const res = useSyncExternalStore(subscribe, getSnapshot, getSeeverSnapShot?)
sybscribe:订阅数据源的变化,接受一个回调函数在数据源更新时调用此函数
getSnapShot:获取当前数据源的快照(当前信息)
getSeeverSnapShot:服务端使用
// hooks/useStorage.tsx
import { useSyncExternalStore } from 'react'
export const useStorage = (key: string, initiaValue: number) => {
const subscribe = (callback: () => void) => {
window.addEventListener('storage', callback)
return () => {
window.removeEventListener('storage', callback)
}
}
const getSnapShot = () => {
return localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key)!) : initiaValue
}
const data = useSyncExternalStore(subscribe, getSnapShot)
const setData = (value: any) => {
localStorage.setItem(key, JSON.stringify(value))
// 手动触发eventStorage事件,通知订阅
window.dispatchEvent(new Event('storage'))
}
return [data, setData]
}
// views/page.tsx
import { useStorage } from "./hooks/useStorage";
function App() {
const [token, setToken] = useStorage('token', 0)
return (
<>
<h1>hooks useStorage</h1>
<div>
<p> token is {token} </p>
<button onClick={() => setToken(token + 1)}> token ++ </button>
<button onClick={() => setToken(token - 1)}> token -- </button>
</div>
</>
)
}
export default App
5. useTransition
useTransition 用于优化用户界面响应性的 Hook,它允许你在不阻塞用户交互的前提下,优雅地处理状态更新带来的加载状态(如“挂起”或“过渡”) 。
useTransition 作用:
- 状态更新通常是同步的,调用
setState会立即重新渲染组件树,可能会导致页面卡顿,特别是在处理复杂计算或大量数据更新时。useTransition将某些状态更新标记为“非紧急”(过渡态),从而优先处理更紧急的 UI 更新(比如输入框的响应) 。
useTransition 使用场景:
- 数据加载时的“pending”状态展示
- 页面切换时的平滑过渡
- 大型表单提交等场景
6. useDeferredValue
useDeferredValue 用于优化用户界面响应性的 Hook,它允许你在不阻塞渲染的前提下,延迟更新某些非紧急的状态值,从而保持 UI 的流畅性。
useDeferredValue 作用:
- 延迟使用新的值进行渲染,优先显示旧值,直到浏览器空闲时再更新为新值。
useDeferredValue 使用场景:
-
搜索输入框的自动补全
-
列表滚动时的平滑更新
-
大量数据展示时的渐进式渲染
-
useDeferred 对比 useTransition
| 特性 | useDeferredValue | useTransition |
|---|---|---|
| 控制对象 | 值(value) | 状态更新(state update) |
| 是否需要包装副作用 | 否 | 是 |
是否有 isPending 状态 | 否 | 是 |
| 适用场景 | 延迟更新某个值(如列表、文本) | 控制状态更新优先级,显示 loading 状态 |
8. useImperativeHandle & forwardRef
用于自定义暴露子组件方法或属性给父组件调用的 Hook,它通常与 forwardRef 一起使用。
import { useImperativeHandle, forwardRef, useRef } from "react";
// 子组件,必须配合 forwardRef
const Child_ = forwardRef((props, ref)=>{
const Child_reff = useRef(null)
const focusInput = () => {
Child_reff.current!.focus()
}
const selectInput = () => {
Child_reff.current!.select()
}
const BlurInput = () => {
Child_reff.current!.blur()
}
// 暴露方法给父组件
useImperativeHandle(
ref,
() => ({
focus: focusInput,
select: selectInput,
blur: BlurInput,
}),
[]
)
return <input ref={Child_reff} type="text" />
})
// 父组件
function Parent_() {
const inputRef = useRef(null);
const handleFocus = () => {
inputRef.current!.focus(); // 调用子组件方法
};
const handleSelect = () => {
inputRef.current!.select(); // 调用子组件方法
};
const handleBlur = () => {
inputRef.current!.blur(); // 调用子组件方法
};
return (
<>
<div>
<h1>hooks useImperativeHandle forwardRef</h1>
<p> useImperativeHandle </p>
<br />
<Child_ ref={inputRef} />
<button onClick={handleFocus}>输入框聚焦</button>
<button onClick={handleSelect}>选中输入框文本</button>
<button onClick={handleBlur}>输入框失焦</button>
</div>
</>
)
}
export default Parent_
9. useRef
-
使用场景:
- 访问 DOM 元素,获取对真实 DOM 的引用,进行操作(如聚焦、动画等)
- 定时器
-
特点:
- 值改变不会触发重新渲染 re-rander
- 保持引用不变,保存状态或变量,这些值不会因组件更新而重置
- 避免闭包中捕获旧的 props/state 值的问题
10. useContext
useContext 是一个 Hook(钩子)函数,它允许你在 函数组件 中访问和使用 React 的 Context(上下文)
-
优点:
- 解决数据多层组件嵌套的繁琐问题
- 全局状态的管理更直观便捷
-
缺点:
- 即便引用的数据未变化,消费的子组件也会引起重复渲染
解决方式:
- 子组件使用memo包装
- 把context要传递值用useMemo包装,传递的函数用useCallback包装
- 不适用于高频的数据状态更新(也是性能问题)
- 状态跟踪不方便不直观,不利于复用(尽可能拆分context范围)
- 即便引用的数据未变化,消费的子组件也会引起重复渲染
-
示例
useContext + useReducer//../TestContextProvider.tsx import { FC, ReactNode, createContext, useReducer } from 'react' const initState = { name: 'rubin', age: 28 } const reducer = (state: any, action: any) => { switch (action.type) { case 'setName': return { ...state, name: action?.payload } case 'setAge': return { ...state, age: action?.payload } case 'clear': return { ...state, name: '', age: '' } case 'init': return initState default: return state } } export const TestContext = createContext<{ state: any; actions: any }>({ state: {}, actions: {} }) const TestContextProvider: FC<{ children: ReactNode }> = ({ children }) => { const [state, dispatch] = useReducer(reducer, initState) return ( <TestContext.Provider value={{ state, actions: { setName: (payload: string) => dispatch({ type: 'setName', payload: payload }), setAge: (payload: number) => dispatch({ type: 'setAge', payload: payload }), clear: () => dispatch({ type: 'clear' }), init: () => dispatch({ type: 'init' }) } }} > <>{children}</> </TestContext.Provider> ) } export default TestContextProvider//../入口组件.txs return ( <ContextUser.Provider value={{ data: contextData }}> <子组件1 /> <子组件2 > <子组件21 /> <子组件22 /> <子组件23 /> </子组件2 > <子组件3 /> </ContextUser.Provider> )//../需要使用的组件.txs import { useContext } from 'react' import { TestContext } from '../TestContextProvider' const Context = () => { const { state, actions } = useContext(TestContext) return ( <div className="tc"> <h1>useContext + useReducer</h1> <br /> <br /> <h3>TestContext : {JSON.stringify(state)}</h3> <br /> <br /> <div> <button onClick={() => actions.setName('BIG RUBIN')}>setName</button> -- <button onClick={() => actions.setAge(228)}>setAge</button> -- <button onClick={() => actions.clear()}>clear</button> -- <button onClick={() => actions.init()}>init</button> </div> </div> ) } export default Context
11. useState
- 惰性初始化,提供一个初始化函数,而不是直接提供初始值。
// 初始化数据 const initData = () => { let d = [] for(i=0, i<100000000, i++) { d.push(i) } return d } 合理写法,惰性初始化,只有首次初始化会渲染,避免不要的计算 const [data, setData] = useState(() => initData()) 错误写法,当有别的数据更新时,会有不必要的重新计算 const [data, setData] = useState(initData()) - 更新函数
- 异步更新,这是传递一个更新函数,而不是一个状态,更新函数每次执行都会拿到最新的值
多次调用 `setState(count + 1)`,它们都基于**同一个旧的 `count` 值**。 即使被批处理,最终状态也只会是 `count + 1`。 const [age, setAge] = useState(20) const updateAge1 = () => { setAge((age) => age + 1) // 20 + 1 setAge((age) => age + 1) // 21 + 1 setAge((age) => age + 1) // 22 + 1 } - 同步更新,react 会合并更新任务,多个同样的状态只执行一次。
`setState(prevCount => prevCount + 1)`,React 会将这些 `updater` 函数收集起来。 在批处理时,它会按顺序应用这些函数。 const [age, setAge] = useState(20) const updateAge1 = () => { setAge(age + 1) // 20 + 1 setAge(age + 1) // 20 + 1 setAge(age + 1) // 20 + 1 }
- 异步更新,这是传递一个更新函数,而不是一个状态,更新函数每次执行都会拿到最新的值
12. useCallBack
- 使用场景:
- 子组件传递的函数用useCallback包装
- 复杂计算的函数
- 特点:
- 缓存函数引用,避免重复创建函数
- 传递函数给子组件,避免子组件不必要的渲染
- 避免闭包问题,useContext传递的函数(useCallBack包裹),值(useMemo)
13. HOC
HOC(higher-order-compontent),传入一个组件返回一个新组件,新组件会为原组件添加一些属性。
- 适用场景:
- 在不修改原组件的情况下增强其功能时。
- 例如,权限控制、日志记录等。
- HOC 属性代理
- 不改变state props
- 传入不同数据做不同的逻辑展示,不破坏原组件逻辑
- HOC 反向继承
- 拦截生命周期
- 劫持原组件数据,进行修改state, props
- 修改rander渲染
- 在原组件前后添加额外的内容
三、react 概念理解
1. 函数组件和类组件的本质区别
- 类组件
- 面向对象编程
- 依赖this和生命周期
- 函数组件
- 函数式编程
- 摆脱this
- 依赖hooks
- 代码简洁
- 函数组件中的闭包陷阱
- 正确设置依赖项数组
- 使用函数式更新 useState
- 使用 useRef
2. Props数据为何不可改变
- 单向数据流,易于维护和数据状态追踪,避免数据管理混乱
- 父组件改变时Props会返回新的数据源(内存引用地址不同),只需对比内存索引是否一致(性能提升)
3. react错误边界(Error Boundary),仅类组件可用
- 功能:捕获组件内的JS错误。
- 目的:
- 避免错误导致页面白屏或者渲染崩溃
- 展示友好的错误提示,引导/提示用户操作
- 隔离错误,防止错误蔓延
- 可在此处上报错误日志
- 实现:错误边界必须是类组件(class component),并且定义以下其中1-2个生命周期方法:
static getDerivedStateFromError()更新state展示降级UIcomponentDidCatch()记录错误
示例:
`src/components/ErrorBoundary.js`
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
// 捕捉错误的核心
componentDidCatch(error, errorInfo) {
console.error("Error caught by ErrorBoundary:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>很抱歉,发生了一些错误。</h1>;
}
return this.props.children;
}
}
export default ErrorBoundary;
`src/views/app.js`
import React from 'react';
import ErrorBoundary from './components/ErrorBoundary';
function App() {
return (
<ErrorBoundary>
<BrokenComponent />
</ErrorBoundary>
);
}
export default App;
4. react-router 路由模式
HashRouter:使用 URL 的 hash 部分(即#后面的内容)来模拟一个完整的 URL,当 URL 改变时,页面不会重新加载。- 优点:无需服务端资源配置;支持HTML5之前的不支持History API兼容问题。
- 缺点:带“#”不美观,不符合RESTful原则
BrowserRouter:利用浏览器的 History API(pushState,replaceState等)来进行URL管理,允许创建真实的URL路径而不需要重新加载页面。- 优点:不带“#”,清晰美观。
- 缺点:需要服务器端的支持,以确保当用户直接访问或刷新页面时能够正确地提供相应的HTML文档
MemoryRouter:不在地址栏中显示任何路径信息,所有的路由变化都保存在内存中。- 优点:适用于某些非浏览器环境下的测试或渲染场景,如单元测试或者在Node.js环境中运行的应用程序
- 缺点:用户无法通过地址栏直接访问某个状态。
5. BrowserRouter 实现原理
BrowserRouter 依赖于 HTML5 提供的 History API 事件来动态地更新浏览器地址栏中的 URL 而不触发页面刷新。
- 实现流程:
- 依赖 HTML5 提供的 History API 监听 url 变化,如:
history.pushStatehistory.replaceStatewindow.onpopstate。 - 创建一个上下文,其中包含当前的路由信息(如路径名、搜索参数等),并监听 URL 变化。
- 拿到当前的路由信息后,匹配路由并加载渲染对应的组件。
BrowserRouter维护了一个内部的历史堆栈,用于追踪用户的浏览历史。每当一个新的路由被激活时,相关信息会被推入这个堆栈中。允许用户使用浏览器的“前进”和“后退”按钮在不同的视图之间导航,同时保持应用状态的一致性。
- 依赖 HTML5 提供的 History API 监听 url 变化,如:
6. react中的状态管理
-
useContext + reducer
- 适用于:中小型项目,状态数据有限的项目(主题切换,token身份等)
- 优点:原生React语法,学习成本低
- 缺点:性能浪费,消费的子组件不管依赖值是否变化都会重复渲染
-
redux / redux toolkit
- 适用于:大型项目,需要有严格的数据流管理
- 优点:强大的调试工具,成熟的技术生态,丰富的中间件,RTK提供简化的代码模板
- 缺点:虽然有RTK,代码仍有些繁重
-
Zustand
- 适用于:中、大型项目,快速,小巧
- 优点:
- 轻量级(包体积小)
- 灵活性极简API,无代码模板,不需要actions reducers dispatchers
- 高性能,高频优化(短暂更新),细分订阅状态切片,优化渲染
可以脱离react 上下文使用,可在js文件中使用
- 保持数据不变性,使用
import { immer } from 'zustand/middleware/immer' - 数据持久化,使用
import { persist } from 'zustand/middleware'
// zustandStore.js import { create } form 'zustand' import { persist } from 'zustand/middleware' import { immer } from 'zustand/middleware/immer' // 111111111111111 export const useUserStore = create<State & Action>()( // 保持数据不变性 immer immer( // 数据持久化 persist persist( (set, get) => ({ ...initialState, settoken: (token: string) => { set((state) => ({ ...state, token })) }, incnum: () => { set((state) => ({ ...state, num: get().num + 1 })) }, decnum: () => { set((state) => ({ ...state, num: get().num - 1 })) }, syncData: async () => { set((state) => ({ ...state, isPending: true })) const d = await new Promise((resolve) => { setTimeout(() => { resolve({ title: Math.fround(Math.random() * 100).toFixed(2) }) }, 2000) }) set((state) => ({ ...state, isPending: false, data: [...state.data, d] })) } }), { name: 'zustand-storage' // persist 持久化,存储名称 } ) ) ) // 222222222222222222222222 const useStore = create((set) => { count:0, increment: () => set({count: state.count+1}), msg:'nihao', setMsg: (newMsg) => set({msg: newMsg}) }) export default useStore // 使用 impprt useStore form './zustandStore' const App = ()=> { const count = useStore((count)=> state.count) const msg = useStore((msg)=> state.msg) const increment = useStore((increment)=> state.increment) const setMag = useStore((setMag)=> state.setMag) return ( <div> <div>count:{count}</div> <div>msg:{msg}</div> <button onclick={increment}>increment</button> <button onclick={()=> setMag('22222222')}>setMag</button> </div> ) } -
Jotai: 原子化
- 优点:
- 更轻量、更灵活,并且与 React 的函数组件和 Hook 高度契合
- 基于原子(Atoms),状态被定义为独立的“原子”,多个组件可以独立读写。
- 与 React Hook 集成,使用
useAtomHook 来读取和更新状态。 - 可以组合多个原子来构建更复杂的状态逻辑
- 体积小(<1KB),无依赖
- 支持异步设置原子初始状态
- 数据持久化,使用
import { atomWithStorage } from 'jotai/utils'
// storejotai.js export const Acount = atom<number>(0) const Abasecount = atom<string>('10086:::') export const Acombinecount = atom<string>((get) => get(Abasecount) + get(Acount)) export const APending = atom<boolean>(false) export const Acostom = atom<string>( (get) => 'Acostom:::' + (get(Acount) + 1000), (get, set) => { set(Acount, get(Acount) + 2) } ) export const Aobj = atom<State | null>(null, (get, set, action, data) => { switch (action) { case 'name': set(Aobj, { ...get(Aobj), name: data }) break case 'age': set(Aobj, { ...get(Aobj), age: data }) break } }) export default counterAtom // 使用 import { useAtom } from 'jotai'; impprt counterAtom form './storejotai' const App = ()=> { const [count, setCount] = useAtom(counterAtom) return ( <div> <p>Count: {count}</p> <button onClick={() => setCount((prev) => prev + 1)}>Increment</button> <button onClick={() => setCount((prev) => prev - 1)}>Decrement</button> </div> ) } - 优点:
7. Immer.js 在数据管理中的意义
核心特点:
- immer 是一个不可变数据的
可变更新库。 - 自动生成不可变的更新结果,使用“可变”的方式来更新状态,内部生成一个不可变的新对象。
使用场景:
- 在不可变更新的场景中,处理嵌套对象或复杂状态结构。
- Immer 可与
redux结合使用,zustand/jotai也有类似immer的实现逻辑。
性能:
- Immer 使用
Proxy或ES5的克隆方式,对性能有一定影响。
8. React 性能观察工具
- React DevTools Profiler
- 作用:
- 可视化组件渲染耗时
- 识别不必要的组件重渲染
- 分析commit阶段性能。
- 功能:
- 火焰图显示组件层级及各组件渲染时间
- 按渲染耗时组件排序
- 查看组件再多次提交中的渲染情况
- 作用:
- chrome DevTools Performance
- 作用:
- 分析整体应用性能:js执行,渲染,网络等。
- 识别JS长任务。
- 分析布局抖动和高频绘制风暴
- 功能:
- Timings 标记组件生命周期和更新。
- JS调用栈、事件处理、渲染过程。
- 作用:
9. React 性能优化
-
常见性能瓶颈
- 不必要的组件重复渲染 re-rander
- 庞大的组件树嵌套
- 大量的数据处理和渲染
- 首屏加载时间过长
-
优化方式
useMemo缓存值,根据依赖项更新useCallback缓存函数,根据依赖项更新memo缓存组件,组件浅比较更新渲染lazy&Suspense组件懒加载,动态导入组件会抛出Promise查找最近的<Suspense>边界,该边界处显示其fallback属性定义的内容虚拟化列表,仅渲染可视区域内的DOM,减少性能开销