React 的核心特性
- 组件化:将 UI 拆分为独立可复用的组件
- 虚拟 DOM:提高渲染性能
- 单向数据流:数据流动更可预测
- JSX:声明式 UI 编写方式
React 组件的生命周期
- 挂载阶段:constructor → getDerivedStateFromProps → render → componentDidMount
- 更新阶段:getDerivedStateFromProps → shouldComponentUpdate → render → getSnapshotBeforeUpdate → componentDidUpdate
- 卸载阶段:componentWillUnmount
在函数组件中使用 useEffect 替代 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途
控制 useEffect 的执行时机:
- 空数组[]:只在组件挂载和卸载时执行
- 有依赖项:依赖项变化时执行
- 无依赖项:每次渲染后都执行
为什么不能在循环、条件或嵌套函数中调用 Hook
React 依赖 Hook 的调用顺序来正确关联状态,在条件或循环中使用会破坏调用顺序的一致性
什么是高阶组件
就是一个函数组件接收一个组件作为参数,并且返回值是一个组件。不修改原组件,而是组合生成新组件,是纯函数,没有副作用,通过 props 传递数据,与渲染逻辑解耦。常用的场景是
- 代码复用与逻辑抽象,多个组件需要共享相同逻辑时
- 条件渲染与权限控制,权限控制 HOC
- 数据获取与状态注入,数据获取 HOC
- 性能优化,避免不必要的重新渲染,只在特定 props 变化时渲染
- 表单处理增强,为表单组件添加统一处理逻辑
受控组件与非受控组件
- 受控组件:数据由 React 组件的 state 完全控制 ,输入值的变化必须通过 React 的状态更新来驱动。 符合 React 单向数据流,易于调试和追踪状态变化,支持即时验证、禁用提交按钮等动态交互。
- 非受控组件:数据由 DOM 自身管理 ,而不是 React state,通过
ref直接访问 DOM 元素的值(类似原生 JavaScript) 更接近原生 HTML,代码更简洁。避免频繁更新 state 带来的性能开销(如大表单输入)
React 官方推荐优先使用受控组件,但在特定场景下非受控组件更合适。
SPA 应用都会提供一个 hash 路由好处是什么?
它的核心好处可以概括为:简单、兼容性好,且无需服务器端配合。
- 不会触发页面刷新和服务器请求
- 极高的兼容性
- 无需服务器端配置
React Hook 的闭包陷阱的理解有哪些解决方案?
函数组件的每次渲染都是一个独立的“快照”,它拥有自己独立的 Props、State 和事件处理函数。这些函数(包括 useEffect、useCallback、事件处理函数等)通过闭包“捕获”了定义它们时那个快照中的状态值。
当某个 Effect 或回调函数依赖于某个状态,但又没有在其依赖数组中正确声明时,这个函数内部引用的状态就永远是它被创建时的那个旧值,而不是最新的值。这就形成了“闭包陷阱”或“过时闭包”。
import {useState, useEffect} from 'react';
function Counter() {const [count, setCount] = useState(0);
useEffect(() => {
// 设置一个定时器,意图每秒 count + 1
const timer = setInterval(() => {console.log(`Current count: ${count}`); // 这里永远输出 0
setCount(count + 1); // 这里等价于 setCount(0 + 1)
}, 1000);
return () => clearInterval(timer);
}, []); // 🔴 依赖数组为空,Effect 只在挂载时执行一次
return <div>Count: {count}</div>; // 界面上会一直显示 1
}
// 解决办法:// ✅ 将 count 添加到依赖项
React 中,怎么给 children 添加额外的属性?
将父组件封装,在父组件内部渲染子组件时自定义添加属性
import React from 'react';
function ParentComponent({children}) {
// 使用 React.cloneElement 克隆子元素并添加新的属性
const childWithExtraProp = React.cloneElement(children, {customProp: 'Hello from Parent'});
return <div>{childWithExtraProp}</div>;
}
// 使用 ParentComponent
function App() {
return (<ParentComponent>
<ChildComponent />
</ParentComponent>
);
}
function ChildComponent({customProp}) {return <div>Received: {customProp}</div>; // 输出: Received: Hello from Parent
}
React 中为什么不直接使用 requestldleCallback?
- 浏览器兼容性问题
- 时间控制精度不足
requestIdleCallback的执行时机由浏览器决定,React 需要更精确的控制:- 无法保证在关键更新时及时执行
- 空闲时间可能很短,不适合复杂的渲染任务
- 无法预测下一次空闲时间
- 任务调度的复杂性
- 任务优先级(Immediate, UserBlocking, Normal, Low, Idle)
- 任务中断和恢复
- 任务超时处理
- 多个任务之间的协调
- 更好的控制权和一致性:自定义调度器让 React 能够
- 在所有浏览器中保持一致的行为
- 实现时间切片(Time Slicing)
- 支持并发模式(Concurrent Mode)
- 提供更细粒度的优先级控制
- 性能优化
- 批量更新,减少不必要的渲染
- 在合适的时机执行任务,避免阻塞用户交互
- 支持任务的暂停和恢复
说说 Reactcommit 阶段的执行过程
在 Commit 阶段之前是 Render 阶段 (Reconciliation/ 协调阶段),它通过 beginWork 和 completeWork 构建出了一棵完整的 Fiber 树 和一个 副作用链表(effectList)。Commit 阶段的任务就是将这些 ” 副作用 ” 实际应用到 DOM 上。
- 修改 DOM 前
- 修改 DOM:实际更新 DOM
- 修改 DOM 后(同步):
useLayoutEffect的销毁函数(在 Mutation 阶段,对于删除的组件)和执行函数(在 Layout 阶段)。componentDidMount/componentDidUpdateref更新
- 浏览器绘制
- 浏览器绘制后(异步):
useEffect的销毁函数和执行函数
说说 React render 阶段的执行过程
是 React 渲染流程中 可中断的、异步的 核心阶段。主要任务是:通过 Diff 算法找出需要更新的组件,构建一棵新的 Fiber 树,并收集副作用(effects)。
- 副作用链表(effectList)的构建: 在 completeWork 过程中,React 会构建一个副作用链表
- Concurrent Mode 下的可中断渲染: 在并发模式下,Render 阶段是可中断的, 执行优先级高的
- 纯计算:不执行任何实际的 DOM 操作
- 深度优先遍历:采用 ” 递 ” 和 ” 归 ” 的遍历方式
- 副作用收集:构建 effectList 供 Commit 阶段使用
- 双缓存机制:同时维护 current 和 workInProgress 两棵树
React 中,怎么实现父组件调用子组件中的方法?
- 使用 ref + useImperativeHandle(推荐)
import React, {forwardRef, useImperativeHandle} from 'react';
const ChildComponent = forwardRef((props, ref) => {const [count, setCount] = React.useState(0);
// 暴露给父组件的方法
useImperativeHandle(ref, () => ({getCount: () => {return count;}
}));
return <div>子组件计数: {count}</div>;
});
export default ChildComponent;
import React, {useRef} from 'react';
import ChildComponent from './ChildComponent';
const ParentComponent = () => {const childRef = useRef();
const handleGetCount = () => {const count = childRef.current?.getCount();
console.log('当前计数:', count);
};
return (<div>
<ChildComponent ref={childRef} />
<button onClick={handleGetCount}>获取计数 </button>
</div>
);
};
export default ParentComponent;
- 使用 ref + 类组件
import React from 'react';
class ChildComponent extends React.Component {constructor(props) {super(props);
this.state = {count: 0};
}
getCount = () => {return this.state.count;};
render() {return <div>计数: {this.state.count}</div>;
}
}
export default ChildComponent;
import React, {useRef} from 'react';
import ChildComponent from './ChildComponent';
const ParentComponent = () => {const childRef = useRef();
const handleIncrement = () => {childRef.current?.increment();
};
return (<div>
<ChildComponent ref={childRef} />
<button onClick={handleIncrement}>调用子组件方法 </button>
</div>
);
};
export default ParentComponent;
[更多学习参考](https://blog.huixiangwuyou.com/react/#/react/)