普通视图

发现新文章,点击刷新页面。
昨天以前首页

Redux 中间件作用(redux-thunk/redux-saga)

作者 光影少年
2026年5月28日 16:59

Redux 中间件(Middleware)本质上是:
dispatch(action) 到达 reducer 之前,对 action 做增强处理的一层机制。

它主要解决:

  • 异步请求
  • 日志打印
  • 权限校验
  • 接口调用
  • 延迟 dispatch
  • 副作用管理

一、Redux 默认的问题

Redux 原生规定:

store.dispatch({
  type: 'ADD'
})

dispatch 只能发送:

  • 普通对象 action

而且:

  • reducer 必须是纯函数
  • reducer 不能写异步

所以:

setTimeout()
axios()
fetch()

这些都不能直接写进 reducer。

这时候就需要:

中间件 Middleware


二、中间件执行流程

Redux 数据流:

dispatch(action)
   ↓
middleware
   ↓
reducer
   ↓
store 更新
   ↓
view 更新

多个中间件:

dispatch
  ↓
thunk
  ↓
logger
  ↓
saga
  ↓
reducer

三、redux-thunk

1. thunk 是什么

Redux Thunk

Thunk 是 Redux 最常用的异步中间件。

它允许:

dispatch(function)

而不是只能:

dispatch(object)

四、redux-thunk 核心思想

普通 Redux:

dispatch({
  type: 'GET_USER'
})

Thunk:

dispatch(async function(dispatch){
   const res = await axios.get('/user')

   dispatch({
      type:'SET_USER',
      payload: res.data
   })
})

也就是:

dispatch 一个函数

函数内部:

  • 可以写异步
  • 可以再次 dispatch
  • 可以拿到 store

五、thunk 工作原理

内部核心思想:

const thunk = store => next => action => {

   if(typeof action === 'function'){
      return action(store.dispatch, store.getState)
   }

   return next(action)
}

意思:

  • 如果 dispatch 的是函数

    • 就执行它
  • 如果是普通对象

    • 继续传给 reducer

六、thunk 使用流程

1. 安装

npm install redux-thunk

2. 注册 middleware

import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'

const store = createStore(
  reducer,
  applyMiddleware(thunk)
)

3. 编写异步 action

export const getUser = () => {

  return async (dispatch) => {

    const res = await axios.get('/api/user')

    dispatch({
      type:'SET_USER',
      payload:res.data
    })
  }
}

4. 页面调用

dispatch(getUser())

七、redux-thunk 优缺点

优点

简单易学

适合:

  • 小项目
  • 中型项目
  • 简单异步

缺点

大型项目容易:

  • 回调地狱
  • action 逻辑混乱
  • 难维护
  • 副作用分散

例如:

dispatch(async ()=>{
   await api1()
   await api2()
   await api3()
})

会越来越复杂。


八、redux-saga

Redux-Saga

Saga 是:

更强大的异步流程管理方案

核心思想:

把异步逻辑单独管理

类似:

  • 后台任务
  • 事件监听
  • 协程
  • generator

九、saga 最大特点

它使用:

Generator

例如:

function* getUserSaga() {

   const res = yield call(api.getUser)

   yield put({
      type:'SET_USER',
      payload:res
   })
}

十、saga 工作流程

dispatch(action)
    ↓
saga监听
    ↓
执行异步任务
    ↓
put(action)
    ↓
reducer

十一、核心 API

takeEvery

监听每次 action

yield takeEvery('GET_USER', getUserSaga)

takeLatest

只保留最后一次请求

适合搜索:

yield takeLatest('SEARCH', searchSaga)

put

等于 dispatch

yield put({
  type:'SET_USER'
})

call

调用异步函数

yield call(api.getUser)

select

获取 store 数据

const state = yield select()

十二、saga 使用流程

1. 安装

npm install redux-saga

2. 创建 sagaMiddleware

import createSagaMiddleware from 'redux-saga'

const sagaMiddleware = createSagaMiddleware()

3. 注册

const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)

4. 启动 saga

sagaMiddleware.run(rootSaga)

5. 编写 saga

function* getUserSaga(){

   const res = yield call(api.getUser)

   yield put({
      type:'SET_USER',
      payload:res
   })
}

function* rootSaga(){
   yield takeEvery('GET_USER', getUserSaga)
}

十三、thunk vs saga

对比 thunk saga
学习成本
异步方式 函数 Generator
复杂流程 一般
维护性
适合项目 小中型 大型
取消请求 不方便 容易
并发控制
副作用管理 分散 集中

十四、实际项目怎么选

小项目

直接:

  • Redux Toolkit
  • thunk

现在主流:

Redux Toolkit

因为 RTK 默认内置 thunk。


大型项目

复杂场景:

  • websocket
  • mqtt
  • 长连接
  • 多请求编排
  • 权限流
  • 工作流

适合:

  • saga

十五、你现在前端开发里更应该学什么

结合你现在 React + 平台开发经验:

建议优先级:

Redux Toolkit
    ↓
RTK Query
    ↓
redux-thunk
    ↓
redux-saga

因为现在很多公司:

  • 已经不用传统 redux
  • 更偏 RTK

十六、现代 Redux 已经变成这样

以前:

redux
redux-thunk
action
reducer
constants
types

现在:

Redux Toolkit
createSlice
createAsyncThunk
RTK Query

代码量减少很多。


十七、现代写法(推荐)

import { createAsyncThunk } from '@reduxjs/toolkit'

export const getUser = createAsyncThunk(
  'user/getUser',
  async ()=>{

     const res = await axios.get('/user')

     return res.data
  }
)

react 单向数据流理解

作者 光影少年
2026年5月8日 15:26

在 React 里,“单向数据流(One-Way Data Flow) ” 是最核心的思想之一。

简单理解:

数据只能从父组件流向子组件,不能反过来直接修改。


一、先用一句话理解

React 中:

父组件 state -> 子组件 props -> 页面UI

数据像水流一样:

Parent
  ↓ props
Child
  ↓ props
GrandChild

只能从上往下。


二、为什么叫“单向”?

因为:

  • 父组件可以把数据传给子组件
  • 子组件不能直接修改父组件的数据

例如:

function Parent() {
  const [count, setCount] = React.useState(0);

  return <Child count={count} />;
}

function Child(props) {
  return <h1>{props.count}</h1>;
}

这里:

  • count 在 Parent 中
  • Parent 通过 props 传给 Child
  • Child 只能“使用”
  • 不能:
props.count = 100; // ❌ 错误

因为 props 是只读的。


三、React 为什么这样设计?

因为:

1. 数据变化更容易追踪

你知道:

谁的数据变了
↓
谁重新渲染

不会乱。


2. 组件更稳定

如果子组件可以随便改父组件数据:

A 改一下
B 改一下
C 再改一下

整个应用会非常难维护。


3. 更符合函数式思想

React 组件本质像:

UI = f(state)

即:

输入数据
↓
输出页面

四、单向数据流完整示例


父组件

import React, { useState } from "react";

function App() {
  const [msg, setMsg] = useState("你好");

  return (
    <div>
      <Child message={msg} />
    </div>
  );
}

function Child(props) {
  return <h1>{props.message}</h1>;
}

export default App;

流程:

App state
  ↓
props.message
  ↓
Child UI

五、子组件想修改怎么办?

虽然不能直接改:

props.message = "新值"; // ❌

但可以:

父组件把“修改方法”传下去。


正确方式

function App() {
  const [count, setCount] = useState(0);

  return (
    <Child
      count={count}
      setCount={setCount}
    />
  );
}

function Child({ count, setCount }) {
  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

这里本质还是:

父组件管理数据
子组件通知父组件修改

所以:

数据仍然是单向流动的。


六、非常重要的理解

很多初学者误以为:

子组件调用了 setCount
=
子组件修改了父组件

其实不是。

真正发生的是:

子组件触发事件
↓
调用父组件传来的函数
↓
父组件自己修改 state
↓
重新向下传递 props

所以数据方向始终没变:

->

七、React 单向数据流 vs Vue 双向绑定

Vue 常见:

v-model

看起来:

数据 ↔ UI

而 React 更强调:

state -> UI

React 更偏:

  • 可预测
  • 易维护
  • 数据透明

大型项目优势非常明显。


八、面试标准回答(很重要)

可以这样回答:


React 的单向数据流指的是:

数据只能从父组件通过 props 向子组件传递,子组件不能直接修改父组件的数据。

React 中:

  • state 通常由父组件管理
  • 子组件通过 props 接收数据
  • 如果子组件需要修改数据,需要调用父组件传递的回调函数

这样可以让:

  • 数据变化可追踪
  • 应用状态更稳定
  • 组件职责更清晰
  • 更容易维护大型项目

九、你必须真正理解的本质

React 核心哲学:

状态驱动视图

即:

state 改变
↓
组件重新渲染
↓
UI 更新

而不是:

直接操作DOM

所以:

React 更关注:

数据怎么流动

而不是:

页面怎么改

十、进阶理解(真正高手)

React 单向数据流最终会形成:

State
 ↓
View
 ↓
Action
 ↓
State

这其实就是:

  • Redux
  • Flux
  • Zustand
  • Mobx(部分)
  • Vuex

等状态管理的核心思想。

也叫:

Flux 架构思想

数据单向循环

十一、最经典一句话

记住:

React 中,谁拥有 state,谁才有资格修改 state。

react函数组件、类组件、纯组件、受控/非受控组件

作者 光影少年
2026年5月5日 09:26

一、函数组件 vs 类组件

1️⃣ 函数组件(Function Component)

本质:就是一个函数,接收 props,返回 JSX

function MyComponent(props) {
  return <div>{props.title}</div>;
}

👉 现在主流写法(配合 Hooks)

特点:

  • 更简洁
  • 使用 useStateuseEffect 等 Hooks 管理状态和副作用
  • 没有 this
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

2️⃣ 类组件(Class Component)

本质:继承 React.Component 的类

class MyComponent extends React.Component {
  render() {
    return <div>{this.props.title}</div>;
  }
}

带状态写法:

class Counter extends React.Component {
  state = { count: 0 };

  render() {
    return (
      <button onClick={() => this.setState({ count: this.state.count + 1 })}>
        {this.state.count}
      </button>
    );
  }
}

特点:

  • 有生命周期(componentDidMount 等)
  • 使用 this
  • 写法相对冗长

✅ 总结

对比 函数组件 类组件
写法 简洁 冗长
状态 Hooks this.state
生命周期 useEffect 生命周期方法
推荐 ✅ 主流 ❌ 已逐渐淘汰

👉 现在基本都用:函数组件 + Hooks


二、普通组件 vs 纯组件(PureComponent)

1️⃣ 普通组件

默认:父组件更新 → 子组件也会重新 render


2️⃣ 纯组件(PureComponent)

类组件写法:

class MyComponent extends React.PureComponent {
  render() {
    return <div>{this.props.name}</div>;
  }
}

👉 自动做 浅比较(shallow compare)

只有 props/state 变化才重新渲染


👉 函数组件对应写法:React.memo

const MyComponent = React.memo(function ({ name }) {
  return <div>{name}</div>;
});

⚠️ 注意点

浅比较意味着:

const obj = { a: 1 };

// ❌ 每次都是新对象,会触发更新
<MyComponent data={{ a: 1 }} />

// ✅ 推荐
const data = useMemo(() => ({ a: 1 }), []);

✅ 总结

类型 作用
PureComponent 类组件性能优化
React.memo 函数组件性能优化

三、受控组件 vs 非受控组件

👉 这个是表单相关最重要的概念


1️⃣ 受控组件(Controlled Component)

👉 数据由 React 控制(state)

function Input() {
  const [value, setValue] = useState("");

  return (
    <input
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
  );
}

特点:

  • 数据来源:React state
  • 输入变化 → 更新 state → UI 更新
  • 单向数据流

👉 推荐使用 ✅


2️⃣ 非受控组件(Uncontrolled Component)

👉 数据由 DOM 自己管理

import { useRef } from "react";

function Input() {
  const inputRef = useRef();

  const handleClick = () => {
    console.log(inputRef.current.value);
  };

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>获取值</button>
    </>
  );
}

特点:

  • 使用 ref 获取值
  • 类似原生 JS 操作 DOM

✅ 总结

对比 受控组件 非受控组件
数据来源 React state DOM
可控性
推荐 ⚠️ 少用

四、一句话理解(面试版)

👉 你可以这样说:

  • 函数组件 vs 类组件:函数组件更简洁,配合 Hooks 替代类组件,是当前主流
  • 纯组件:通过浅比较 props/state 来减少不必要渲染(React.memo / PureComponent)
  • 受控组件:表单数据由 React state 管理
  • 非受控组件:表单数据由 DOM 管理,通过 ref 获取

五、给你一个实际开发建议(结合你现在做的项目)

你现在做 React + Ant Design:

👉 推荐组合:

  • 全部用 函数组件
  • 状态管理:Hooks(useState / useReducer)
  • 性能优化:React.memo + useMemo
  • 表单:优先用 受控组件(或 AntD Form 内部已封装)

reeact虚拟DOM、Diff算法原理、key的作用与为什么不能用index

作者 光影少年
2026年5月4日 10:23

一、虚拟 DOM(Virtual DOM)

1. 是什么

虚拟 DOM 本质就是一个 JS 对象,用来描述真实 DOM 结构。

例如 JSX:

<div className="box">
  <span>Hello</span>
</div>

会被转换成类似:

{
  type: 'div',
  props: {
    className: 'box',
    children: [
      {
        type: 'span',
        props: {
          children: 'Hello'
        }
      }
    ]
  }
}

2. 为什么要有虚拟 DOM?

核心目的:减少真实 DOM 操作

因为:

  • 真实 DOM 操作成本高(重排 / 重绘)
  • JS 计算相对便宜

👉 所以 React 做了一层“中间层”:

状态变化 → 生成新的虚拟DOM → Diff → 最小化更新真实DOM

二、Diff 算法原理

React 的 Diff 不是传统树算法(O(n³)),而是做了优化 → O(n)

核心基于 3 个假设:


1️⃣ 同层比较(不跨层)

👉 React 只比较同一层节点,不会跨层移动

例如:

A
 ├─ B
 └─ C

如果变成:

A
 └─ B
     └─ C

React 会认为:

  • C 被删除
  • 新建一个 C

❗不会复用

👉 这是用空间换时间


2️⃣ 类型不同直接替换

<div />
→
<span />

👉 直接销毁旧节点,创建新节点


3️⃣ 列表使用 key 优化

👉 这是重点(和你下面的问题强相关)


三、key 的作用

本质作用:

👉 标识节点的唯一身份

让 React 在 Diff 时可以:

✔ 复用节点
✔ 只更新变化的部分
✔ 避免错误复用


举例

旧列表:

[{id:1}, {id:2}, {id:3}]

新列表:

[{id:3}, {id:1}, {id:2}]

❌ 没有 key(或用 index)

React 会按位置比较:

旧: 1 2 3
新: 3 1 2

👉 结果:

  • 全部节点都被认为变了
  • 全部重新渲染

✅ 使用 key

key: 1 2 3key: 3 1 2

React 会:

👉 发现只是“顺序变了”
👉 复用节点,只移动 DOM


四、为什么不能用 index 作为 key?

很多人背这个结论,但不理解原因。

核心问题:index 不是稳定标识


场景 1:插入元素

旧:

[A, B, C]
key: 0 1 2

新(头部插入 D):

[D, A, B, C]
key: 0 1 2 3

React 看到的是:

0 → 新 0A → D ❌)
旧 1 → 新 1BA ❌)
旧 2 → 新 2 (C → B ❌)

👉 全错位


后果:

  • 组件状态错乱(最严重问题
  • 输入框内容串位
  • 动画异常

场景 2:删除元素

[A, B, C]
→
[A, C]

index 变化:

B 被删 → C 的 index 从 21

👉 React 误以为:

  • B → C(复用错误)

场景 3:表单输入(经典面试题)

<input value="A" />
<input value="B" />

删除第一个后:

👉 B 会变成 A(错位)


五、什么时候可以用 index?

不是绝对不能用,而是有条件

👉 满足以下条件可以用:

  • 列表不会发生顺序变化
  • 没有插入 / 删除
  • 只是静态展示

例如:

[1,2,3].map((item, index) => <li key={index}>{item}</li>)

✔ 安全


六、总结(面试版)

你可以这样说:

React 通过虚拟 DOM 来减少真实 DOM 操作,在状态更新时生成新的虚拟 DOM,然后通过 Diff 算法进行对比。
Diff 采用同层比较策略,并通过 key 来标识节点,提高复用效率。
key 的作用是帮助 React 识别节点是否可复用,如果使用 index 作为 key,在列表发生插入、删除、排序时会导致节点错位,可能引发状态错乱,因此不推荐使用。


七、给你一个更进阶的理解(加分项)

👉 React Diff 本质:

不是找“最优解”,而是找“足够快的近似解

👉 核心 trade-off:

精确性 ↓
性能 ↑
❌
❌