Skip to content

🔥函数式与 React:为什么 Hooks 天然适合 FP?

——useState 是 State Monad?useReducer 是纯函数驱动?

“React 不是函数式框架,
但它把函数式编程的精髓,
编译成了 DOM。”

一、Hooks 的设计哲学:函数即组件

组件 = 纯函数(理想)

js
const Profile = ({ user }) => {
  return <div>{user.name}</div>;
};
  • 输入:props
  • 输出:UI(虚拟 DOM)
  • 无副作用 → 可预测、可测试

这就是函数式的核心思想:纯函数映射。

二、useState:状态的“封装”与“转换”

传统类组件的问题

js
class Counter extends Component {
  state = { count: 0 };
  
  increment = () => {
    this.setState({ count: this.state.count + 1 }); // 命令式修改
  }
}
  • 状态分散
  • 方法依赖 this
  • 难以复用逻辑

useState 的函数式抽象

js
const [count, setCount] = useState(0);

const increment = () => setCount(count + 1);

关键洞察:

  • setCount 不是直接修改
  • 它是调度一个状态转换
  • 下次渲染时,count 是新值

这正是 State Monad 的行为!

三、useState ≈ State Monad?

回顾:Monad 的核心

  1. 封装值(Promise, Maybe
  2. chain / flatMap 实现序列化操作

State Monad 模拟

js
// State Monad: S -> (A, S)
const State = run => ({
  run,
  map: f => State(s => {
    const [a, s1] = run(s);
    return [f(a), s1];
  }),
  chain: f => State(s => {
    const [a, s1] = run(s);
    return f(a).run(s1);
  })
});

useState 的类比

js
// 初始状态
const [state, setState] = useState(initial);

// setState 类似于 State Monad 的“状态转换”
setState(newState); 
// 相当于 调度一个 (S -> S) 函数

虽然 useState 不暴露 chain,但它的组合方式符合 Monad 思想:

js
const [a, setA] = useState(1);
const [b, setB] = useState(2);

// 多个状态独立,通过 props 向下传递 —— 类似 Monad 的组合

四、useReducer:纯函数驱动的状态管理

useReducer API

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

reducer 必须是纯函数!

js
const reducer = (state, action) => {
  switch (action.type) {
    case 'INC':
      return { ...state, count: state.count + 1 };
    case 'RESET':
      return { ...state, count: 0 };
    default:
      return state;
  }
};
  • 输入:state + action
  • 输出:newState
  • 无副作用、可重放、可时间旅行

这正是函数式中“状态机”的典范!

五、Hooks 就是高阶函数的组合

自定义 Hook:逻辑复用的函数式方式

js
const useLocalStorage = (key, initialValue) => {
  const [value, setValue] = useState(() => {
    const saved = localStorage.getItem(key);
    return saved ? JSON.parse(saved) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
};

特性:

  • 纯函数结构(输入 props,返回状态)
  • 闭包封装私有状态
  • 副作用隔离useEffect
  • 可组合(像普通函数一样调用)
js
const [theme, setTheme] = useLocalStorage('theme', 'dark');

这不就是高阶函数 + 闭包 + 副作用管理吗?

六、React Fiber 架构:函数式的底层支持

React 16+ 的 Fiber Reconciler

  • 将 UI 分解为“工作单元”(Fiber nodes)
  • 可中断、可暂停、可优先级调度

与函数式思想一致:

  • 不可变性:每次更新生成新 Fiber 树
  • 惰性求值:按需计算 diff
  • 组合优先:组件树即函数调用栈

七、函数式优势在 React 中的体现

优势在 React Hooks 中的表现
可测试性组件是纯函数,易单元测试
可复用性自定义 Hook 跨组件共享逻辑
可预测性useReducer + 纯 reducer 易调试
可组合性useXuseYuseZ 链式构建复杂逻辑

八、但也有限制:不是完全的 FP

1. 副作用必须用 useEffect 显式声明

js
useEffect(() => {
  // 副作用(订阅、DOM 操作)
}, []);

这是对纯函数的妥协。

2. 性能优化仍需 useMemo / useCallback

js
const expensive = useMemo(() => calc(data), [data]);

避免不必要的重新计算 —— 应对“中间数组”问题。

结语:Hooks 是函数式思想的胜利

它证明了:

  • UI 可以是纯函数的输出
  • 状态可以是转换的序列
  • 逻辑可以像函数一样组合与复用

useState 虽不完全是 State Monad,
但它让状态变更变得可声明、可组合

useReducer 虽运行在命令式环境,
但其 reducer 是纯粹的数学函数。

React 并没有强迫你写 FP,
但它为你提供了通往函数式的大门

当你写出:

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

你已经在用纯函数驱动应用状态了。

这才是真正的“Learn Once, Write Anywhere”。