目标:从局部状态到全局状态,构建完整的状态管理知识体系

1、React的父子组件如何传递参数?兄弟组件如何传递参数?

1、父组件向子组件传递参数

父组件通过 props 将数据传递给子组件。在父组件中,通过属性形式传递数据,子组件通过 props 接收。

// 父组件
function Parent() {
  const data = "Hello from Parent";
  return <Child message={data} />;
}

// 子组件
function Child(props) {
  return <div>{props.message}</div>;
}

2、子组件向父组件传递参数

父组件通过传递回调函数给子组件,子组件调用该回调函数并传递参数

// 父组件
function Parent() {
  const handleData = (data) => {
    console.log(data); // "Hello from Child"
  };
  return <Child onSendData={handleData} />;
}

// 子组件
function Child({ onSendData }) {
  const sendData = () => {
    onSendData("Hello from Child");
  };
  return <button onClick={sendData}>Send Data</button>;
}

3、React 兄弟组件参数传递

通过共同的父组件中转
共享状态提升至父组件父组件通过 props 传递给子组件子组件通过回调函数更新父组件状态。

// 父组件
function Parent() {
  const [sharedData, setSharedData] = useState("");

  return (
    <>
      <ChildA onDataChange={setSharedData} />
      <ChildB data={sharedData} />
    </>
  );
}

// 子组件 A(发送数据)
function ChildA({ onDataChange }) {
  const handleClick = () => {
    onDataChange("Data from ChildA");
  };
  return <button onClick={handleClick}>Update Data</button>;
}

// 子组件 B(接收数据)
function ChildB({ data }) {
  return <div>{data}</div>;
}

使用 Context API
适用于多层级组件全局状态共享

// 创建 Context
const DataContext = createContext();

// 父组件提供数据
function Parent() {
  const [data, setData] = useState("");

  return (
    <DataContext.Provider value={{ data, setData }}>
      <ChildA />
      <ChildB />
    </DataContext.Provider>
  );
}

// 子组件 A(更新数据)
function ChildA() {
  const { setData } = useContext(DataContext);
  const updateData = () => {
    setData("Data from ChildA");
  };
  return <button onClick={updateData}>Update Data</button>;
}

// 子组件 B(读取数据)
function ChildB() {
  const { data } = useContext(DataContext);
  return <div>{data}</div>;
}

使用状态管理库(如 Redux、Zustand)
适用于复杂应用状态管理兄弟组件通过全局状态共享数据

// 示例:Zustand
import { create } from "zustand";

const useStore = create((set) => ({
  data: "",
  setData: (newData) => set({ data: newData }),
}));

// 子组件 A
function ChildA() {
  const setData = useStore((state) => state.setData);
  return <button onClick={() => setData("New Data")}>Update</button>;
}

// 子组件 B
function ChildB() {
  const data = useStore((state) => state.data);
  return <div>{data}</div>;
}

2、使用 useState 的函数式更新方式能带来哪些好处?

1、避免状态依赖过时

函数式更新通过传递前一个状态值(prevState)作为参数,确保更新基于最新的状态。在异步操作连续更新场景中,直接依赖当前状态可能导致计算基于过时的值,而函数式更新能解决这一问题。

2、批量更新优化性能

React 会对多个 setState 调用进行批处理减少重新渲染次数函数式更新能确保在批处理过程中,每次更新都基于前一次的结果,避免因多次直接更新导致的意外覆盖

3、简化复杂状态逻辑

当新状态依赖于前一个状态(如计数器、数组操作等),函数式更新无需手动合并状态。例如修改数组时,直接基于前一个数组展开并添加新元素,代码更简洁且不易出错。

4、示例代码对比

直接更新可能引发问题:

const [count, setCount] = useState(0);
// 连续两次直接更新,结果可能不符合预期
setCount(count + 1); 
setCount(count + 1); // 仍基于初始的 count

函数式更新确保正确性:

setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1); // 每次基于最新值

5、适用于函数依赖

若更新逻辑涉及复杂计算函数调用函数式更新能隔离依赖避免在每次渲染时重新创建函数提升性能与可维护性

3、“状态提升”这种模式有哪些优缺点?它的适用边界在哪里?

1、优点

  • 简化组件通信:将共享状态提升到共同的父组件,避免跨层级传递props的复杂性。
  • 数据一致性单一数据源保证子组件状态同步减少数据不一致风险
  • 便于调试:状态集中管理,更容易追踪状态变化定位问题

2、缺点

  • 组件耦合性增加父组件需管理更多状态,可能变得臃肿,子组件依赖父组件props
  • 性能问题状态变更可能触发不必要的子组件重渲染,需配合优化手段(如React.memo)。
  • 传递链条过长深层嵌套组件需逐层传递props代码可维护性降低

3、适用边界

  • 共享状态场景多个兄弟组件嵌套组件访问同一数据时(如表单控件、主题切换)。
  • 中等复杂度应用:状态规模可控,尚未需要引入状态管理库(如Redux)。
  • 无全局状态需求:若仅局部组件树共享状态状态提升比全局状态管理更轻量

4、替代方案

  • Context API:适合跨多层级传递状态,避免prop drilling。
  • 状态管理库:如ReduxMobX,适用于全局复杂状态逻辑

4、在一个 React 项目中,你会如何进行状态管理方案的选型?

1、React 状态管理方案选型方法

1、评估项目规模和复杂度
小型项目(组件少、交互简单):可直接使用 React 内置的 useState 或 useReducer

中型项目:可考虑 Context API 配合局部状态管理。

大型应用(多团队协作、高频数据更新):需要引入专业状态库如 ReduxMobXZustand

2、分析数据流需求
单向数据流场景(如表单、配置管理)适合 Redux 的纯函数模式。

响应式更新的场景(如实时仪表盘)可选用 MobX

轻量级方案ZustandJotai 的原子化模型更高效。

3、团队熟悉度与学习成本
Redux 需要理解 middlewarereducer 等概念,适合有 FP 经验的团队。

MobX面向对象风格对 OOP 背景开发者更友好。

RecoilJotaiHooks 风格React 原生思维契合,上手更快。

4、性能与调试需求
Redux 提供时间旅行调试严格的可预测性适合对状态追溯要求高的场景

Zustand不可变更新中间件支持平衡了性能与功能。

MobX细粒度更新在大型列表渲染中表现优异。

5、服务端状态处理
若项目涉及大量服务端数据(如 REST/GraphQL 接口),可组合使用 TanStack Query Apollo Client 管理异步状态,配合 Zustand/Redux 管理客户端状态。

6、未来扩展性
考虑状态库的生态兼容性:Redux 有丰富的插件(如 Redux Toolkit、Redux Persist),适合长期维护项目。新兴方案如 Zustand 的插件体系也能满足渐进式扩展需求。

2、典型方案技术对比

| 方案          | 包大小   | 学习曲线 | 适用场景                | 特点                      |
|---------------|---------|----------|-------------------------|---------------------------|
| useState      | 内置    | 低       | 局部组件状态            | 简单直接                  |
| Context API   | 内置    | 中       | 中低频跨组件状态        | 需配合useReducer使用      |
| Redux         | ~20KB   | 高       | 复杂全局状态            | 可预测性强,调试工具完善  |
| MobX          | ~15KB   | 中       | 响应式UI                | 自动依赖追踪              |
| Zustand       | ~3KB    | 低       | 轻量全局状态            | 基于Hook,中间件支持      |
| Jotai         | ~5KB    | 中       | 原子化状态              | 类似Recoil但更轻量       |
 

3、实施建议

1、渐进式采用策略
React 内置方案开始,随着需求增长逐步引入专业库。例如:

  1. 初期用 useState + Context
  2. 中期过渡到 Zustand
  3. 复杂场景再迁移至 Redux Toolkit

2、性能优化注意点
避免 Context 的频繁更新导致无关组件重渲染。Redux 应使用 createSelector 记忆化计算。MobX 需注意 observable 数据的粒度控制。

3、TypeScript 集成
所有现代状态库均支持 TS 类型推断Redux Toolkit 的 createSlice 可自动生成类型,Zustand 的 create 方法支持完整类型推导。

5、如何利用 useContext 和 useReducer 来实现一个轻量级的全局状态管理器?

1、使用 useContext 和 useReducer 实现全局状态管理

通过组合 useContext 和 useReducer,可以构建一个轻量级的全局状态管理方案,避免引入第三方库的复杂性。

2、创建 Reducer 和初始状态

定义状态结构和处理逻辑的 reducer 函数,以及初始状态:

const initialState = {
  count: 0,
  user: null
};

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'SET_USER':
      return { ...state, user: action.payload };
    default:
      return state;
  }
}

3、创建 Context 和 Provider

构建一个 Context 并导出 Provider 组件,将 useReducer 的结果提供给子组件:

import React, { createContext, useContext, useReducer } from 'react';

const StateContext = createContext();

export const StateProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <StateContext.Provider value={{ state, dispatch }}>
      {children}
    </StateContext.Provider>
  );
};

4、在根组件中包裹 Provider

确保应用的最外层被 StateProvider 包裹,使状态全局可用:

import { StateProvider } from './state';

ReactDOM.render(
  <StateProvider>
    <App />
  </StateProvider>,
  document.getElementById('root')
);

5、创建自定义 Hook 访问状态

封装一个自定义 Hook 简化状态dispatch 的访问:

export const useStateValue = () => useContext(StateContext);

6、在组件中使用状态

通过自定义 Hook 获取状态和派发 action

import { useStateValue } from './state';

function Counter() {
  const { state, dispatch } = useStateValue();
  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>
        Increment
      </button>
    </div>
  );
}

7、处理异步操作

对于异步操作,可以在组件中处理封装额外的函数

function fetchUser(dispatch) {
  fetch('/api/user')
    .then(res => res.json())
    .then(user => dispatch({ type: 'SET_USER', payload: user }));
}

8、性能优化

使用 memouseMemo 避免不必要的渲染:

const UserProfile = React.memo(() => {
  const { state } = useStateValue();
  return <div>{state.user?.name}</div>;
});

9、类型安全(TypeScript)

添加类型定义增强类型安全:

interface State {
  count: number;
  user: User | null;
}

type Action = 
  | { type: 'INCREMENT' }
  | { type: 'SET_USER'; payload: User };
这种模式提供了 Redux 的核心功能(单一数据源纯函数更新),同时保持了 React Hooks简洁性适用于中小型应用的状态管理需求

6、如何有效优化因 useContext 引起的性能问题?

1、避免不必要的渲染

使用 React.memo 或 useMemo 包裹子组件,防止因父组件重渲染导致子组件不必要的更新。React.memo 对组件进行浅比较useMemo 缓存计算结果。

const MemoizedChild = React.memo(ChildComponent);

2、拆分 Context

将单一大型 Context 拆分为多个小型 Context,按功能数据维度分离。避免无关数据变更触发全体订阅组件的重渲染。

const UserContext = createContext();
const ThemeContext = createContext();

3、使用选择器模式

通过自定义 HookHOC 实现选择器功能,只订阅 Context 中需要的部分数据。类似 Redux 的 useSelector 原理。

function useUserSelector(selector) {
  const user = useContext(UserContext);
  return selector(user);
}

4、优化 Provider 位置

Provider 放置在组件树中尽可能低的位置,减少其影响范围。避免在根组件放置全局 Provider

function App() {
  return (
    <UserProvider>
      <Header />
      <MainContent /> // 只有这部分需要User数据
    </UserProvider>
  );
}

5、合并状态更新

使用 useReducer 替代多个 useState,减少分散的状态更新。批量处理状态变更可降低渲染次数。

const [state, dispatch] = useReducer(reducer, initialState);

6、惰性初始化 Context

对于计算成本高的初始值,使用函数惰性初始化。避免每次渲染都重新计算初始值。

const MyContext = createContext(() => computeExpensiveValue());

7、使用 Context 穿透库

考虑使用专业库如 use-context-selector,它提供更精细的订阅控制。这类库实现了类似 React-Redux 的选择器功能。

import { useContextSelector } from 'use-context-selector';
const value = useContextSelector(MyContext, v => v.selected);

7、和 useState 相比,useReducer 的优势体现在哪里?我们应该如何在这两者之间做选择?

1、useReducer 的优势

1、处理复杂状态逻辑
useReducer 更适合管理包含多个子状态状态间存在复杂依赖关系的场景。例如表单验证、状态机等场景,reducer 函数可以集中处理所有状态更新逻辑,避免分散在多个 useState 中。

2、可预测的状态更新
通过 dispatch 派发 action 的方式更新状态,所有状态变更都通过 reducer 函数处理,更容易跟踪状态变化路径,尤其适合需要时间旅行调试(如 Redux DevTools)的场景。

3、性能优化
当状态更新依赖前一个状态时,useReducer 能避免 useState 的闭包陷阱。因为 reducer 总能接收到最新状态,而 useState 的更新函数可能依赖过期的闭包值。

4、更适合批量更新
在同一个事件处理函数中多次 dispatchReact自动批量处理更新,而多个 useStatesetState 在某些场景下可能导致不必要的重复渲染。

2、选择依据

1、状态复杂度
简单局部状态(如输入框值、开关状态)用 useState涉及多个关联状态或复杂业务逻辑(如购物车、游戏状态)用 useReducer

2、状态更新逻辑
状态更新逻辑简单(如布尔取反、数值增减),useState 更直观;

更新逻辑涉及条件分支或需要复用(如撤销/重做),useReducer 更清晰。

3、代码可维护性
useReducer 将业务逻辑与组件解耦,便于独立测试和复用 reducer 函数。大型组件或需要共享状态逻辑时优势明显。

4、性能敏感场景
当状态更新导致深层组件树重渲染时,useReducer 结合 context 通常比多个 useState 更高效,可通过选择性传递 dispatch 而非整个状态。

3、示例场景对比

// useState 实现计数器
const [count, setCount] = useState(0);
const increment = () => setCount(prev => prev + 1);

// useReducer 实现相同功能
const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT': return state + 1;
    default: return state;
  }
};
const [count, dispatch] = useReducer(reducer, 0);
const increment = () => dispatch({ type: 'INCREMENT' });

当需求扩展为支持加减、重置等操作时,useReducer 的扩展性优势会立即显现,而 useState 可能需引入多个独立状态或复杂函数。

8、RTK Query 是如何帮助我们简化数据获取和缓存逻辑的?

1、RTK Query 的核心优势

RTK QueryRedux Toolkit 提供的强大数据获取缓存工具,专为简化现代前端应用的数据管理而设计。通过抽象化常见的数据处理模式,开发者能够减少样板代码并提升效率。

2、自动化数据获取与缓存

RTK Query 自动处理服务器数据获取缓存更新。开发者只需定义 API 端点,工具会自动生成相应的 hooksselectors,无需手动编写数据获取逻辑。缓存机制会根据请求的 endpoint 和参数自动管理数据,避免重复请求。

3、标准化状态管理

与传统的 Redux 相比,RTK Query 内置了标准化缓存功能响应数据会自动根据配置的 tagTypes 和 providesTags 进行归一化处理减少重复存储。当数据更新时,相关缓存会自动失效并重新获取保持状态一致性

4、简化乐观更新

通过 onQueryStarted 和 patch 方法,可以实现乐观更新。在发起修改请求时立即更新本地缓存,待服务器响应后自动同步最终结果。这种模式显著提升了用户体验,无需等待网络往返即可看到界面变化。

5、内置请求生命周期管理

每个查询或变更请求都有明确的生命周期状态(如 pending/fulfilled/rejected),可通过生成的 hooks 直接获取。开发者无需手动跟踪加载状态或错误处理大幅减少状态管理代码

6、代码生成示例

定义 API 切片后,RTK Query 会自动生成 React hooks

// 定义 API
const api = createApi({
  endpoints: (build) => ({
    getPosts: build.query({ query: () => 'posts' }),
    addPost: build.mutation({
      query: (body) => ({ url: 'posts', method: 'POST', body })
    })
  })
});

// 自动生成 hooks
const { useGetPostsQuery, useAddPostMutation } = api;

7、与 Redux 无缝集成

作为 Redux Toolkit 的一部分,RTK Query 可与其他 Redux 逻辑协同工作。缓存状态存储在 Redux store 中,支持时间旅行调试,并能与现有 reducermiddleware 结合使用。

8、类型安全支持

配合 TypeScript 时,RTK Query 能自动推断 endpoints 的输入输出类型。生成的 hooks 具备完整的类型提示,确保数据流类型安全减少运行时错误

9、网络请求优化

内置的批处理(batching)重复请求去重(deduplication)机制,避免短时间内重复发送相同请求。支持条件性数据获取仅在满足特定条件时才发起网络请求

10、扩展性与中间件支持

可通过 baseQuery 自定义底层请求逻辑,例如添加认证头、处理错误响应等。支持通过 middleware 拦截请求和响应,实现统一的错误处理或日志记录。

9、你是否了解像 Zustand、Jotai 这类新兴的状态管理库?它们各自有什么特点?

1、Zustand 的特点

  • 轻量级与简洁性Zustand API 设计极简,核心代码仅约 1KB,无需复杂的 Provider 嵌套,直接通过 create 创建 store 并在组件中使用。
  • 基于 Hook 的访问:状态通过 useStore Hook 获取,支持选择器函数优化渲染性能,避免不必要的更新
  • 不可变更新:内置 Immer 支持,允许直接修改 draft 状态自动生成不可变更新
  • 中间件扩展:可通过中间件实现持久化日志Redux 兼容等功能,例如 persist 中间件用于本地存储。

2、Jotai 的特点

  • 原子化模型:灵感来自 Recoil,将状态拆分为原子(atom)通过组合原子构建复杂状态逻辑适合细粒度更新
  • 自动依赖追踪:基于 React Context Suspense原子间依赖关系自动管理无需手动订阅
  • 零样板代码:无需定义 reduceraction直接通过 useAtom Hook 读写原子状态
  • 异步支持:原生支持异步原子,简化数据获取逻辑,可与 Suspense 或错误边界结合使用。

3、对比与适用场景

  • Zustand:适合需要全局共享状态的场景(如主题、用户信息),追求极简 API中间件灵活性
  • Jotai:适合组件间细粒度状态共享(如表单字段、UI 状态),依赖原子化模型和 React 原生集成。

两者均避免 Redux 的模板代码,但 Zustand 更接近传统 store 模式,而 Jotai 更倾向于响应式编程。

10、为什么说 Immer.js 在现代 Redux 生态中扮演着如此重要的角色?

1、Immer.js 的核心价值

Immer.js 通过不可变数据的直观可变操作简化了状态管理。它允许开发者以类似直接修改对象的方式编写代码,但底层自动生成不可变数据,避免了手动展开嵌套结构或深拷贝的繁琐操作。这种特性与 Redux 强调的不可变性原则天然契合。

2、与 Redux Toolkit 的深度集成

Redux Toolkit(RTK)Immer.js 作为默认内置库,用于 createSlice 和 createReducer。开发者只需在 reducer 中编写“可变”逻辑,Immer 会将其转换为不可变更新。这种集成大幅降低了 Redux 的样板代码量提升了开发效率

3、性能优化机制

Immer.js 采用结构共享(Structural Sharing)策略,仅克隆被修改的数据分支,未变化的部分保持引用不变。相比手动深拷贝或 ... 展开运算符,这种机制在大型状态树更新时能显著减少内存占用计算开销

4、错误率降低

手动维护不可变数据容易因嵌套层级遗漏或浅拷贝导致错误。Immer.js自动化处理消除了这类风险,尤其适用于复杂状态结构(如嵌套对象数组),减少了因不可变性引发的 bug

5、开发者体验提升

无需学习特定 API(如 Immutable.js 的 setIn),开发者可直接使用熟悉的 JavaScript 语法操作数据。这种低学习曲线使得团队能快速适应 Redux 的最佳实践,尤其有利于新手过渡

6、生态兼容性

Immer.js 生成的普通 JavaScript 对象Redux 中间件(如 redux-thunkredux-saga)及其他工具链无缝兼容无需额外适配。这种兼容性使其成为 Redux 生态中的“隐形桥梁”。

7、调试支持

Immer.js 提供清晰的错误堆栈修补记录(Patches),便于追踪状态变更来源。结合 Redux DevTools,可以更直观地调试状态变化历史增强可维护性

11、像 SWR 或 React Query 这样的库,它们主要解决了什么问题?

1、数据获取与状态管理问题

传统的React应用中,数据获取通常通过useEffectuseState手动实现,需要处理加载状态错误状态缓存逻辑。这种模式容易导致冗余代码,且难以维护复杂的异步逻辑

2、自动缓存与重复请求去重

当多个组件需要相同数据时,这些库会自动共享缓存避免重复请求。例如,即使多个组件同时调用useSWR('/api/data')实际只会发送一个网络请求,后续调用直接返回缓存结果。

3、后台数据更新与乐观UI

支持自动或手动触发后台数据刷新,同时提供乐观更新功能。可以在请求发送前立即更新UI,增强用户体验,请求失败时自动回滚。

const { data, mutate } = useSWR('/api/todos')
mutate(newData, false) // 立即更新UI,不触发验证
mutate(newData, true)  // 立即更新并重新验证

4、错误重试机制

内置智能重试策略,当请求失败时会自动按指数退避算法进行重试,开发者无需手动实现复杂的错误恢复逻辑。

5、分页与无限加载支持

提供开箱即用的分页查询无限滚动解决方案,简化了复杂列表场景的实现:

const { data, size, setSize } = useSWR('/api/items?page=' + size, fetcher)
// 加载更多只需调用 setSize(size + 1)

6、依赖请求处理

支持声明式地表达请求依赖关系,只有当某些条件满足时才发起请求:

const { data: user } = useSWR('/api/user')
const { data: projects } = useSWR(() => `/api/projects/${user.id}`)

7、离线变更与同步

React Query提供离线变更队列功能,当应用恢复在线状态时自动同步未完成的变更,适合需要离线能力的应用场景。

8、性能优化特性

内置请求去抖节流窗口聚焦时自动刷新网络恢复时重新验证等优化手段,显著提升应用性能表现。

12、在 React 应用中,有哪些方法可以实现状态的持久化存储?

1、使用浏览器本地存储

localStorage 和 sessionStorage 是浏览器提供的存储方案,适合存储简单的键值对数据localStorage 数据永久保存,sessionStorage 仅在会话期间有效。

// 存储数据
localStorage.setItem('key', JSON.stringify(state));

// 读取数据
const savedState = JSON.parse(localStorage.getItem('key'));

2、使用 IndexedDB

IndexedDB 适合存储大量结构化数据,支持索引查询事务操作适合复杂应用场景

const request = indexedDB.open('myDatabase', 1);

request.onsuccess = (event) => {
  const db = event.target.result;
  const transaction = db.transaction('store', 'readwrite');
  const store = transaction.objectStore('store');
  store.put(state, 'key');
};

3、使用 Cookies

Cookies 适合存储小量数据可设置过期时间。每次请求会自动携带,适合服务端读取场景。

document.cookie = `state=${JSON.stringify(state)}; expires=${new Date(Date.now() + 86400e3).toUTCString()}`;

4、使用状态管理库的持久化插件

ReduxZustand 等状态管理库提供持久化插件,如 redux-persist 和 zustand/middleware

// Redux Persist 示例
import { persistStore } from 'redux-persist';
const persistor = persistStore(store);

5、使用服务端存储

通过 API 将状态保存到后端数据库适合多设备同步或敏感数据存储

fetch('/api/save-state', {
  method: 'POST',
  body: JSON.stringify(state)
});

6、使用文件系统

通过 File APIElectron 的 fs 模块将状态保存为本地文件适合桌面应用

// Electron 示例
const fs = require('fs');
fs.writeFileSync('state.json', JSON.stringify(state));

7、使用云存储服务

FirebaseAWS S3 等云服务提供数据存储功能,适合需要跨设备同步的场景

// Firebase 示例
import { set } from 'firebase/database';
set(ref(db, 'state'), state);
每种方法都有适用场景,选择时需考虑数据大小安全性同步需求等因素。

13、你是如何理解 Signals 的?它与 React 现有的状态管理方式有何不同?

1、Signals 的概念理解

Signals 是一种细粒度的响应式编程模型,核心思想是通过自动追踪依赖关系,仅在数据实际变化时触发相关组件的更新。其工作原理类似于电子电路中的信号传播,当源头数据(signal)变化时,依赖该数据的计算(effect)会自动执行。

Signals 通常具备以下特性:

  • 自动依赖追踪:无需手动声明依赖,运行时动态收集。
  • 精准更新:仅更新依赖变化的组件,避免冗余渲染。
  • 轻量级:独立于组件层级,可直接在普通函数中使用。

2、与 React 状态管理的差异

1. 更新粒度
  • React 状态:基于组件层级的粗粒度更新,状态变更会触发组件及其子树的重新渲染,即使子组件未依赖该状态。
  • Signals:细粒度更新,仅触发依赖该信号的代码块(组件或函数),无关部分不受影响。
2. 依赖管理
  • React:依赖关系通过 Hook 规则(如 useEffect 的依赖数组)显式声明,容易遗漏或冗余。
  • Signals:依赖自动收集,无需手动维护,减少人为错误。
3. 性能优化
  • React:需配合 React.memouseMemo 等手动优化,避免不必要的渲染。
  • Signals:默认按需更新,减少开发者优化负担。
4. 使用场景
  • React 状态:适合组件内部状态或全局状态管理(如 Context + Redux)。
  • Signals:适合跨组件的细粒度状态同步,或与非 React 代码(如 Web Workers)交互。

3、代码示例对比

1、React 状态管理
function Counter() {
  const [count, setCount] = useState(0);
  // 组件整体重新渲染
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
2、Signals 实现(以 Preact Signals 为例)
import { signal, effect } from "@preact/signals";

const count = signal(0);
// 仅按钮文本部分更新
effect(() => {
  button.textContent = count.value;
});

button.onclick = () => count.value++;

4、适用性分析

  • React 状态:适合逻辑紧密耦合于组件的场景,符合 React 设计哲学。
  • Signals:适合高频更新或复杂依赖关系的场景,如动画、表单联动等。

两者并非互斥,现代框架如 SolidJS 已内置 SignalsReact 生态也可通过库(如 @preact/signals-react)混合使用。

14、Redux 的中间件(Middleware)机制是如何工作的?

1、Redux 中间件的作用

中间件是 Redux 提供的一种扩展机制,用于在 action 被分发(dispatch)到 reducer 之前或之后插入自定义逻辑。常见的用途包括日志记录异步操作处理(如 Redux-Thunk 或 Redux-Saga)、错误捕获等。

2、中间件的核心原理

Redux 中间件基于函数式编程中的“高阶函数”和“洋葱模型”设计。其核心是通过层层包装 dispatch 方法,形成一个调用链。每个中间件可以:

  • 拦截 action
  • 修改或替换 action
  • 调用下一个中间件或最终的 dispatch
  • action 处理前后执行额外逻辑

3、中间件的实现结构

一个典型的中间件结构如下:

const middleware = store => next => action => {
  // 前置逻辑(如打印日志)
  console.log('Dispatching:', action);
  
  // 调用下一个中间件或原始的 dispatch
  const result = next(action);
  
  // 后置逻辑(如处理异步结果)
  return result;
};
  • storeReduxstore 对象,包含 getState 和原始 dispatch 方法。
  • next:下一个中间件的 dispatch 方法,或者是原始的 dispatch
  • action:当前被分发的 action 对象。

4、中间件的注册流程

Redux 通过 applyMiddleware 函数将多个中间件组合起来:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logger from './loggerMiddleware';

const store = createStore(
  rootReducer,
  applyMiddleware(thunk, logger)
);
  • applyMiddleware 会从右到左依次包装每个中间件。
  • 最终生成的 dispatch 方法是所有中间件层层嵌套后的结果。

异步中间件的示例(Redux-Thunk)

Redux-Thunk 允许 action 创建函数返回一个函数(而不仅是普通对象):

const fetchData = () => {
  return (dispatch, getState) => {
    dispatch({ type: 'FETCH_START' });
    fetch('/api/data')
      .then(res => res.json())
      .then(data => dispatch({ type: 'FETCH_SUCCESS', payload: data }))
      .catch(error => dispatch({ type: 'FETCH_ERROR', error }));
  };
};
  • 当分发 fetchData() 时,Redux-Thunk 会检查 action 是否是函数,如果是则执行该函数并注入 dispatch 和 getState

5、中间件的执行顺序

假设中间件链为 [A, B, C],实际的调用顺序为:

A 前置逻辑 -> B 前置逻辑 -> C 前置逻辑 -> 原始 dispatch  
-> C 后置逻辑 -> B 后置逻辑 -> A 后置逻辑  

这种“洋葱模型”确保每个中间件可以完整控制 action 的处理流程。

6、自定义中间件的场景

  • 日志记录:在 next(action) 前后打印 action状态变化
  • 异步处理:拦截特定 action 并发起异步请求。
  • 错误处理:捕获 action 处理过程中的异常。
  • 数据转换:修改 actionpayload类型

通过理解中间件的机制,可以灵活扩展 Redux 的功能适应复杂应用场景的需求

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐