为什么 React 不允许直接修改 State?
React 希望通过 setState 统一管理组件状态的更新。
- 直接修改 React 无法感知,不会触发重新渲染,视图不更新;
- 违背不可变数据设计,引用地址不变,浅比较判定无更新;
- 会让 useEffect、生命周期、批量更新逻辑错乱;
- 破坏调试工具和状态可回溯能力,容易产生异步时序 Bug。
本质原因 : React 做更新对比时,用的是 浅比较(Object.is) 引用地址没变 → 判定状态无变化 → 不重渲染。
React 为什么要坚持「不可变数据」?
- 依赖引用做浅比较,判断是否需要重新渲染;
- 保证状态可追踪、可快照、可时间旅行调试;
- 避免意外副作用、防止旧状态污染新逻辑;
- 适配 React 批量更新、Hooks 依赖、Redux 状态管理;
- 让逻辑可预测、易于排查 Bug。
【追问】日常怎么写不可变?
- 对象:用 扩展运算符 {...obj}
- 数组:用 map/filter/reduce/ 扩展 [...arr],不用 push/pop/splice
- 复杂嵌套:用 Immer 简化写法,不用手动层层解构
React 是什么?
React 是声明式、组件化、基于虚拟 DOM 的前端 UI 库,用于构建单页应用,遵循单向数据流。
虚拟 DOM 是什么?
用 JS 对象 描述真实 DOM 结构;
数据变化生成新虚拟 DOM,通过 Diff 算法 对比差异,只更新变化部分,提升性能。
为什么不用直接操作真实 DOM?
真实 DOM 操作开销大、重绘重排昂贵;虚拟 DOM 批量对比、最小化补丁,性能更好。
Diff 算法原理
- 同层比较,不跨层级比对
- 标签不同直接销毁重建
- 标签相同对比属性
- 列表渲染必须加 key 提升 Diff 效率
key 的作用是什么?
帮助 Diff 算法精准识别节点 ,减少不必要销毁重建;
不要用 index 做 key,会导致列表错乱、输入框状态丢失。
函数组件 vs 类组件区别
- 函数组件:无生命周期、无this,用 Hooks 管理状态
- 类组件:有生命周期、this、state 原生支持
现在项目全用函数组件+Hooks
State 和 Props 区别
- Props:父传子、只读、不可修改
- State:组件自身私有、可修改
单向数据流:父改 props 传给子
生命周期常用有哪些?
类组件:
- componentDidMount:挂载后发请求
- componentDidUpdate:更新后执行逻辑
- componentWillUnmount:组件销毁清除定时器、监听
useEffect 作用
函数组件替代生命周期;
可以模拟挂载、更新、销毁,处理副作用:请求、定时器、事件监听。
useEffect 依赖数组作用
- 空数组:只执行一次(挂载)
- 不传依赖:每次渲染都执行
- 传依赖:依赖变化才执行
useState 原理
在函数组件保存状态快照 ,调用更新函数触发重渲染;更新是异步批量更新。
useRef 作用
- 获取 DOM 元素
- 保存不变的值、定时器、不触发渲染的变量
useMemo 和 useCallback 作用
- useMemo:缓存计算结果,避免重复计算
- useCallback:缓存函数引用 ,防止子组件不必要重渲染
都是性能优化
组件通信方式
- 父子:props / 回调函数
- 跨层:useContext 全局上下文
- 任意组件:Redux / Zustand / 事件总线
Context 是什么
跨层级组件透传数据,不用一层一层 props 往下传;适合主题、用户信息、全局配置。
受控组件 vs 非受控组件
- 受控组件:value 绑定 state,表单值由 React 控制
- 非受控组件:用 useRef 获取 DOM 值,不受 state 控制
React 事件机制
React 事件是合成事件 ,不是原生 DOM 事件;
做了事件委托,统一兼容浏览器,性能更好。
什么是高阶组件 HOC
函数接收组件,返回新组件;用来复用逻辑、权限控制、日志埋点,现在多用 Hooks 替代。