三合一网站建站做暧昧视频网站
36.Fiber的更新机制
React Fiber 更新机制详解
React Fiber 是 React 16 引入的核心架构重构,旨在解决可中断渲染和优先级调度问题,提升复杂应用的流畅性。其核心思想是将渲染过程拆分为可控制的工作单元,实现更细粒度的任务管理。以下是其核心机制:
一、Fiber 架构的设计目标
- 可中断与恢复:允许渲染过程被高优先级任务(如用户输入)打断,后续恢复。
 - 增量渲染:将渲染任务拆分为多个小任务(时间分片),避免阻塞主线程。
 - 优先级调度:根据任务类型(如动画、数据加载)分配不同优先级。
 - 并发模式支持:为 Suspense、Transition 等特性提供底层支持。
 
二、Fiber 节点:工作单元的基础
每个 Fiber 节点对应一个组件或 DOM 节点,构成链表树结构,包含以下关键信息:
-  
组件类型:函数/类组件、HTML 标签等。
 -  
状态与 Props:
state、props、context。 -  
副作用标记:增/删/更新 DOM、调用生命周期等(通过
flags字段标识)。 -  
链表指针:
child:指向第一个子节点。sibling:指向下一个兄弟节点。return:指向父节点。
 -  
优先级:
lane模型标记任务优先级(如 SyncLane、InputContinuousLane)。 
三、更新流程:从触发到提交
1. 触发更新
- 来源:
setState、useState、父组件重渲染、Context 变更等。 - 创建更新对象:包含新状态、优先级等信息,添加到 Fiber 的更新队列。
 
2. 调度阶段(Scheduler)
- 任务分片:将整个渲染流程拆分为多个 Fiber 节点的处理单元。
 - 优先级排序:使用 
lane模型分配优先级,高优先级任务可抢占低优先级。 - 时间切片:通过 
requestIdleCallback或MessageChannel在浏览器空闲时段执行任务。 
3. 协调阶段(Reconciler)
- 构建 WorkInProgress 树:在内存中生成新 Fiber 树(双缓存机制)。
 - Diff 算法:对比新旧 Fiber 节点,标记变更(如 
Placement、Update、Deletion)。 - 生命周期触发:执行函数组件的渲染、类组件的 
render方法。 
4. 提交阶段(Commit)
-  
同步执行:不可中断,一次性将变更应用到 DOM。
 -  
副作用处理:
- DOM 操作:增删改节点。
 - 生命周期:类组件的 
componentDidMount/Update。 - Hooks:
useLayoutEffect回调。 
 -  
切换当前树:将
WorkInProgress树标记为current树。 
四、优先级调度与中断机制
- Lane 模型:用二进制位表示优先级(如 
0b0001和0b0010可合并为0b0011)。 - 高优先级抢占:用户交互触发的更新(如按钮点击)可中断正在进行的低优先级渲染(如大数据列表渲染)。
 - 饥饿问题处理:长时间未执行的低优先级任务会被提升优先级。
 
示例场景:
 用户输入搜索关键词时,输入框的即时响应(高优先级)会中断后台数据渲染(低优先级)。
五、双缓存技术
- Current 树:当前屏幕上显示的 Fiber 树。
 - WorkInProgress 树:正在构建的新树,完成后替换 Current 树。
 - 优势:减少渲染过程中的页面闪烁,确保原子性更新。
 
六、并发模式下的更新
-  
过渡更新(Transition) :通过
startTransition标记非紧急更新(如页面切换),可被用户交互打断。const [isPending, startTransition] = useTransition(); startTransition(() => {setPage(newPage); // 低优先级更新 }); -  
Suspense:配合懒加载组件,在数据加载时显示 fallback UI。
 
七、性能优化启示
- 减少渲染粒度:使用 
React.memo、useMemo避免无效渲染。 - 合理分配优先级:紧急操作使用高优先级,长任务用 
startTransition包裹。 - 优化 Fiber 树深度:扁平化组件结构,减少协调时间。
 
总结
React Fiber 通过可中断的异步渲染和优先级调度,彻底改变了 React 的渲染机制。其核心价值在于:
- 更流畅的交互:高优先级任务快速响应,避免界面卡顿。
 - 更高效的渲染:增量更新减少主线程阻塞。
 - 面向未来的扩展:为并发特性(如 Suspense、Server Components)奠定基础。
 
37.React18有哪些更新
React 18 主要更新详解
React 18 引入了多项重要改进和新特性,旨在提升性能、开发体验及扩展能力。以下是其核心更新内容:
1. 并发渲染(Concurrent Rendering)
-  
核心机制:通过可中断的渲染过程,实现任务优先级调度与时间分片。
- 并发模式(Concurrent Mode) :现称为“并发特性”,无需全局开启,按需使用。
 - API支持:
startTransition、useDeferredValue等。 
 -  
优势:
- 高优先级任务(如用户输入)可中断低优先级渲染,提升交互流畅度。
 - 支持复杂场景下的无缝过渡(如页面切换、数据加载)。
 
 
示例:
import { startTransition } from 'react';// 标记非紧急更新
startTransition(() => {setSearchQuery(input); // 延迟渲染搜索结果,保持输入响应
});
 
2. 自动批处理(Automatic Batching)
-  
改进点:在更多场景下合并状态更新,减少渲染次数。
- React 17及之前:仅在事件处理函数中批处理。
 - React 18:扩展至Promise、setTimeout等异步操作。
 
 -  
效果:降低不必要的重渲染,优化性能。
 
示例:
// React 18:两次setState合并为一次渲染
setTimeout(() => {setCount(1);setFlag(true);
}, 1000);
 
3. 新的根API(createRoot)
-  
替换旧API:使用
createRoot替代ReactDOM.render,启用并发特性。 -  
用法:
import { createRoot } from 'react-dom/client';const root = createRoot(document.getElementById('root')); root.render(<App />); 
4. Suspense 增强
-  
服务端渲染(SSR)支持:
- 流式HTML传输:逐步发送HTML,加速首屏加载。
 - 选择性Hydration:优先为交互部分注水,提升可交互时间(TTI)。
 
 -  
客户端扩展:支持在更多场景包裹异步组件或数据加载。
 
示例:
<Suspense fallback={<Loading />}><AsyncComponent />
</Suspense>
 
5. 新Hooks API
-  
useId:生成唯一ID,解决SSR与客户端ID不一致问题。
const id = useId(); // 生成如 ":r1:" -  
useSyncExternalStore:简化外部状态库(如Redux)集成。
const state = useSyncExternalStore(store.subscribe, store.getState); -  
useInsertionEffect:适用于CSS-in-JS库动态插入样式。
useInsertionEffect(() => {const style = document.createElement('style');style.innerHTML = `.css { color: red }`;document.head.appendChild(style); }); 
6. 过渡API(Transitions)
-  
区分紧急/非紧急更新:通过
startTransition延迟非关键渲染。 -  
UI反馈:
useTransition提供isPending状态,显示加载指示。const [isPending, startTransition] = useTransition();startTransition(() => {setTab(newTab); // 非紧急导航 });return isPending ? <Spinner /> : <Content />; 
7. 严格模式增强
-  
开发环境行为:
- 双调用Effects:模拟组件卸载/挂载,暴露副作用问题。
 - 组件重复挂载:检查是否正确处理清理逻辑(如定时器、订阅)。
 
 
8. 服务端组件(实验性)
-  
核心能力:
- 服务端渲染组件:在服务端执行,减少客户端代码体积。
 - 无缝数据获取:直接访问后端API,传递序列化数据至客户端。
 
 -  
使用场景:静态内容、SEO优化、性能敏感页面。
 
示例:
// ServerComponent.server.js
export default function ServerComponent() {const data = fetchData(); // 服务端执行return <div>{data}</div>;
}
 
9. 其他改进
- 性能优化:减少内存占用,提升大型应用渲染效率。
 - TypeScript支持:更严格的类型推断,减少显式类型声明。
 - 开发者工具:增强并发模式调试支持,可视化渲染优先级。
 
升级指南
-  
兼容性:React 18 保持向后兼容,逐步采用新特性。
 -  
迁移步骤:
- 使用 
createRoot替换ReactDOM.render。 - 按需引入并发API(如 
startTransition)。 - 测试严格模式下的副作用处理。
 
 - 使用 
 
总结
React 18 通过并发渲染、自动批处理、Suspense增强等特性,显著提升了应用性能与用户体验。开发者可通过渐进式升级,利用新API优化交互流畅度与渲染效率,同时为未来特性(如服务端组件)奠定基础。
38.Rect19有哪些新特性
具体详见官网:
 中文:React 19 新特性
 英文:React 19 新特性
核心新特性
1. Actions
解决问题:简化数据变更和状态更新流程
- 以前需要手动处理待定状态、错误、乐观更新和顺序请求
 - 需要维护多个状态变量(isPending, error 等)
 
新特性:
function UpdateName() {const [state, submitAction, isPending] = useActionState(async (prevState, formData) => {const error = await updateName(formData.get("name"));if (error) return error;redirect("/path");return null;},null);return (<form action={submitAction}><input name="name" /><button disabled={isPending}>Update</button>{state?.error && <p>{state.error}</p>}</form>);
}
 
主要改进:
- 自动处理待定状态
 - 内置错误处理
 - 支持乐观更新
 - 简化表单处理
 
2. useFormStatus
解决问题:简化表单组件状态访问
- 避免通过 props 传递表单状态
 - 提供统一的表单状态访问方式
 
function SubmitButton() {const { pending, data, method } = useFormStatus();return (<button disabled={pending}>{pending ? 'Submitting...' : 'Submit'}</button>);
}
 
3. useOptimistic
解决问题:提供更好的用户体验
- 立即显示操作结果
 - 处理异步操作的状态更新
 
function LikeButton({ id }) {const [likes, setLikes] = useState(0);const [optimisticLikes, addOptimisticLike] = useOptimistic(likes,(state, increment) => state + increment);async function handleLike() {addOptimisticLike(1); // 立即更新 UIawait updateLikes(id); // 后台进行实际更新}
}
 
4. use() Hook
解决问题:统一资源使用方式
- 简化 Promise 和 Context 的使用
 - 支持条件性使用
 - 提供更好的类型推断
 
function Comments({ commentsPromise }) {const comments = use(commentsPromise); // 自动处理 Suspensereturn comments.map(comment => <p>{comment}</p>);
}
 
架构改进
1. Document 流式渲染
解决问题:改善首次加载体验
- 支持 HTML 流式传输
 - 优化资源加载顺序
 
function AsyncPage() {return (<Document><Suspense fallback={<Loading />}><AsyncContent /></Suspense></Document>);
}
 
2. 资源处理优化
样式表支持
解决问题:简化样式管理
- 自动处理样式表加载顺序
 - 支持组件级样式声明
 
function Component() {return (<><link rel="stylesheet" href="styles.css" precedence="default" /><div className="styled-content">...</div></>);
}
 
异步脚本支持
解决问题:优化脚本加载
- 自动处理脚本去重
 - 优化加载优先级
 
function MyComponent() {return (<div><script async={true} src="widget.js" /><div>Widget Content</div></div>);
}
 
开发体验改进
1. 错误处理增强
解决问题:提供更清晰的错误信息
- 消除重复错误日志
 - 提供更详细的错误上下文
 
createRoot(container, {onCaughtError: (error) => {// 错误边界捕获的错误},onUncaughtError: (error) => {// 未被捕获的错误},onRecoverableError: (error) => {// 可恢复的错误}
});
 
2. 自定义元素支持
解决问题:改善与 Web Components 的集成
- 完整支持自定义元素
 - 正确处理属性和属性传递
 
最佳实践建议
-  
渐进式采用
- 优先使用新的表单处理方式
 - 在关键交互中使用乐观更新
 - 利用新的资源加载优化
 
 -  
性能优化
- 使用流式渲染改善加载体验
 - 合理使用资源预加载
 - 优化并发更新
 
 -  
错误处理
- 使用新的错误边界
 - 实现适当的降级策略
 - 监控错误模式
 
 
服务器组件
1. 服务器组件基础
解决问题:优化应用性能和开发体验
- 减少客户端 bundle 大小
 - 直接访问后端资源
 - 改善数据获取模式
 
// 服务器组件
async function Notes() {// 直接访问数据库,无需 API 层const notes = await db.notes.getAll();return (<div>{notes.map(note => (<Expandable key={note.id}><p>{note.content}</p></Expandable>))}</div>);
}
 
2. 服务器组件与客户端组件集成
解决问题:平滑处理服务器和客户端组件交互
- 支持渐进式增强
 - 保持交互性
 - 优化数据流
 
// 服务器组件
import Expandable from './Expandable';  // 客户端组件async function NotesContainer() {const notes = await db.notes.getAll();return (<div>{/* 服务器组件可以渲染客户端组件 */}<Expandable><NotesList notes={notes} /></Expandable></div>);
}// 客户端组件
'use client'
function Expandable({ children }) {const [expanded, setExpanded] = useState(false);return (<div><button onClick={() => setExpanded(!expanded)}>{expanded ? 'Collapse' : 'Expand'}</button>{expanded && children}</div>);
}
 
3. 异步组件
解决问题:简化异步数据处理
- 支持 async/await 语法
 - 自动处理 Suspense 集成
 - 优化加载状态
 
// 服务器组件中的异步数据获取
async function Page({ id }) {const note = await db.notes.get(id);// 开始获取评论但不等待const commentsPromise = db.comments.get(id);return (<div><h1>{note.title}</h1><Suspense fallback={<Loading />}><Comments commentsPromise={commentsPromise} /></Suspense></div>);
}
 
Refs 作为 Props
1. 将 ref 作为 prop
从 React 19 开始,你现在可以在函数组件中将 ref 作为 prop 进行访问:
function MyInput({placeholder, ref}) {return <input placeholder={placeholder} ref={ref} />
}//...
<MyInput ref={ref} />
 
新的函数组件将不再需要 forwardRef,我们将发布一个 codemod 来自动更新你的组件以使用新的 ref prop。在未来的版本中,我们将弃用并移除 forwardRef。
2. Ref 稳定性改进
解决问题:优化 ref 更新和同步
- 更可预测的 ref 更新时机
 - 更好的并发模式支持
 - 改进的性能特性
 
function AutoFocusInput() {const inputRef = useRef<HTMLInputElement>(null);// ref 回调模式的改进const setRef = useCallback((element: HTMLInputElement | null) => {if (element) {element.focus();}}, []);return <input ref={setRef} />;
}
 
服务器组件最佳实践
-  
数据获取策略
- 在服务器组件中直接访问数据源
 - 使用流式传输处理大量数据
 - 实现适当的缓存策略
 
 -  
组件分割
- 将有状态逻辑的组件标记为客户端组件
 - 保持服务器组件纯粹
 - 优化组件边界
 
 -  
性能优化
- 使用服务器组件减少客户端 bundle
 - 实现渐进式加载
 - 优化数据预取
 
 
// 示例:优化的服务器组件结构
async function BlogPost({ id }) {const post = await db.posts.get(id);const authorPromise = db.authors.get(post.authorId);const commentsPromise = db.comments.get(id);return (<article><h1>{post.title}</h1><Suspense fallback={<AuthorSkeleton />}><Author authorPromise={authorPromise} /></Suspense><Content>{post.content}</Content><Suspense fallback={<CommentsSkeleton />}><Comments commentsPromise={commentsPromise} /></Suspense></article>);
}
 
注意事项
-  
服务器组件限制
- 不能使用浏览器 API
 - 不能使用状态和生命周期
 - 需要正确处理数据获取错误
 
 -  
Ref 使用考虑
- 注意 ref 的生命周期
 - 合理处理 ref 清理
 - 避免过度依赖 ref
 
 -  
性能注意事项
- 平衡服务器和客户端渲染
 - 合理使用 Suspense 边界
 - 优化数据预加载策略
详细请看链接 
 
39.React为什么不直接使用this.state改变数据
在 React 中,不能直接通过 this.state 来改变数据,而必须使用 this.setState() 来更新状态。这背后有几个关键的原因,主要是为了保证 组件的可预测性、性能优化 和 避免直接修改状态 所带来的潜在问题。接下来,我将详细解释这些原因。
1. 不可变数据和状态管理原则
React 提倡 不可变数据(immutable data)原则,即状态对象一旦创建,它的值就不应该直接修改。直接修改 this.state 会导致组件行为变得难以预测,难以追踪和调试。通过 this.setState(),React 可以保证每次状态更新时,状态对象都是全新的对象,而不是直接修改原有对象。
为什么要避免直接修改 this.state?
- 直接修改状态会破坏数据的不可变性,使得 React 无法检测到变化。
 - 状态不再是新的引用,这使得 React 无法有效地进行比较,进而影响渲染效率。
 
举个例子,如果直接修改 this.state:
this.state.someValue = newValue;  // 不推荐
 
这样 React 就不会知道状态发生了变化,因此不会触发重新渲染,也就无法同步 UI 和状态。
而通过 this.setState():
this.setState({ someValue: newValue });  // 推荐
 
this.setState() 会创建一个新的状态对象,确保 React 能检测到状态变化,并触发 UI 更新。
2. 异步更新与批量更新
this.setState() 的更新是异步的,而直接修改 this.state 是同步的。React 内部有一种机制,用来批量更新状态,以减少不必要的重新渲染。这种机制不仅提高了性能,还避免了多次渲染的重复计算。
例如,假设你直接修改了 this.state,并且立即访问了 this.state 来获取新值。由于 React 的 setState() 是异步的,直接修改 this.state 可能会导致你获取到的状态值不是更新后的值。
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // 可能不会立即反映出最新的状态
 
React 会将多个 setState() 调用合并到一个批量更新中,以减少不必要的渲染和性能开销。通过使用 this.setState(),React 可以处理这些合并和异步更新的操作。
3. 性能优化
this.setState() 触发的更新过程与直接修改 this.state 的过程有所不同。当调用 setState() 时,React 会合并当前的状态和新的状态,只有发生了变化的部分会被更新。这对于性能优化至关重要。
如果你直接修改 this.state,React 就无法知道哪些部分发生了变化,也就无法进行智能的 diff 和批量更新。例如:
this.state.count = 10;  // 直接修改
this.setState({ count: 10 });  // 通过 setState 更新
 
在 setState() 中,React 会比较前后的状态,判断是否需要重新渲染组件,而直接修改 this.state 则无法触发这种比较。
4. 组件的生命周期和渲染
this.setState() 触发状态更新时,React 会在合适的生命周期方法中触发组件的重新渲染。例如,在状态更新时,shouldComponentUpdate、componentDidUpdate 等生命周期方法会被调用,以便开发者可以在状态变化时执行一些操作。如果直接修改 this.state,React 不会知道组件状态发生变化,进而不会触发这些生命周期方法。
这会导致一些问题,比如无法对比新旧状态、无法做条件渲染等操作。
5. React 状态更新的队列机制
React 通过维护一个更新队列来优化状态的更新和渲染。当你调用 this.setState() 时,React 会把状态更新请求放入一个队列,批量处理这些更新。在队列中的多个状态更新可以合并,这避免了不必要的重新渲染,提高了性能。
直接修改 this.state 不会加入更新队列,React 不会触发它的重新渲染机制,因此无法享受 React 的批量更新和性能优化。
6. 追踪组件的变化
React 使用 this.setState() 来管理组件状态,并且通过对比旧的虚拟 DOM 和新的虚拟 DOM,来决定哪些部分需要重新渲染。这个过程需要 React 在底层追踪和比较状态,而直接修改 this.state 会使得这个追踪变得困难,影响性能优化。
通过 this.setState(),React 能够确保组件状态的变化得到跟踪,并且在状态变化时触发重新渲染,确保 UI 始终与状态一致。
7. React 16+ 的 Fiber 架构
在 React 16 及更高版本中,React 引入了 Fiber 架构,这使得 React 的渲染变得更加灵活和高效。通过 this.setState(),React 能够控制每一次渲染的优先级,并且通过增量更新来保持 UI 响应性。如果你直接修改 this.state,这种增量更新机制就无法发挥作用。
总结:
- 数据不可变性:
this.setState()遵循不可变数据的原则,避免直接修改原有状态。 - 异步和批量更新:
this.setState()支持异步更新,React 可以批量处理多个状态更新,提升性能。 - 生命周期管理:通过 
setState,React 能够触发生命周期钩子,确保组件正确更新。 - 性能优化:
this.setState()可以通过智能的差异化更新减少不必要的 DOM 操作,而直接修改this.state会跳过这一过程。 
因此,直接修改 this.state 会破坏 React 的更新机制,导致状态和 UI 不一致,影响性能和可维护性。React 的设计理念和架构要求我们通过 this.setState() 来更新状态,从而确保更高效和可预测的渲染过程。
40.原来的react虚拟dom树遍历是深度优先遍历还是层序遍历?存储是链表存储还是栈存储,fiber机制下的是如何存储
React 虚拟DOM遍历与存储机制详解
一、传统虚拟DOM(React 15及之前)
-  
遍历方式:深度优先遍历(DFS)
-  
递归处理:从根组件开始,递归处理每个组件及其子组件,直到叶子节点,再回溯处理兄弟节点。
 -  
顺序示例:
A → A.child B → B.child C → C.child D → 回溯到 B → B.sibling E → E.child F 
 -  
 -  
存储结构:隐式调用栈
-  
依赖调用栈:递归调用栈隐式管理遍历过程,无显式数据结构存储节点关系。
 -  
缺点:
- 不可中断:递归一旦开始必须执行完毕,导致主线程阻塞。
 - 性能瓶颈:深层嵌套组件树易引发栈溢出或卡顿。
 
 
 -  
 
二、Fiber架构(React 16+)
-  
遍历方式:可中断的迭代式深度优先遍历
-  
顺序不变:仍按深度优先顺序处理节点(与之前一致)。
 -  
实现变化:从递归改为循环+链表指针手动遍历,支持暂停与恢复。
 -  
流程示例:
let fiber = rootFiber; while (fiber) {process(fiber); // 处理当前节点if (fiber.child) {fiber = fiber.child; // 优先处理子节点continue;}while (fiber) {completeWork(fiber); // 完成当前节点if (fiber.sibling) {fiber = fiber.sibling; // 转向兄弟节点break;}fiber = fiber.return; // 回溯父节点} } 
 -  
 -  
存储结构:显式链表树
-  
Fiber节点结构:
interface Fiber {tag: ComponentType; // 组件类型child: Fiber | null; // 第一个子节点sibling: Fiber | null; // 下一个兄弟节点return: Fiber | null; // 父节点alternate: Fiber | null; // 指向另一棵树(双缓存)flags: number; // 副作用标记(增/删/更新)lanes: Lanes; // 优先级// ...其他字段(stateNode、props等) } -  
双缓存机制:
- Current树:当前渲染的树(对应屏幕显示内容)。
 - WorkInProgress树:正在构建的新树,完成后替换Current树。
 - 优势:避免渲染中间状态导致的UI闪烁。
 
 
 -  
 
三、Fiber架构的核心改进
| 维度 | 传统虚拟DOM | Fiber架构 | 
|---|---|---|
| 遍历控制 | 递归(不可中断) | 迭代(可中断 + 恢复) | 
| 数据结构 | 隐式调用栈 | 显式链表(child/sibling/return) | 
| 任务调度 | 同步执行 | 优先级调度 + 时间分片 | 
| 性能优化 | 易阻塞主线程 | 增量渲染,避免卡顿 | 
| 扩展能力 | 有限 | 支持并发模式(Suspense/Transition) | 
四、Fiber遍历流程示例
假设组件树结构为:
A
├─ B
│  ├─ C
│  └─ D
└─ E└─ F
 
遍历顺序:
- 进入A → 处理A
 - 进入A.child B → 处理B
 - 进入B.child C → 处理C
 - C无子节点 → 完成C,回溯到B
 - 进入B.sibling D → 处理D
 - D无子节点 → 完成D,回溯到B → 完成B,回溯到A
 - 进入A.sibling E → 处理E
 - 进入E.child F → 处理F
 - F无子节点 → 完成F,回溯到E → 完成E,回溯到A → 完成A
 
五、Fiber架构的优势
- 可中断渲染:高优先级任务(如用户输入)可打断低优先级渲染。
 - 增量更新:将渲染任务拆分为多个帧执行,避免主线程阻塞。
 - 精准副作用提交:通过 
flags标记变更,一次性提交DOM操作。 - 并发模式支持:实现服务端渲染流式输出、Suspense等高级特性。
 
总结
- 传统虚拟DOM:深度优先遍历 + 递归调用栈,简单但不可中断。
 - Fiber架构:深度优先遍历 + 显式链表结构,通过迭代实现可中断渲染,结合优先级调度与双缓存机制,为React带来革命性性能提升与扩展能力。
 - 核心价值:将同步渲染转化为异步可调度任务,使复杂应用保持流畅交互。
 
41.React如何创建工程环境(js,ts),eject的作用是什么?
一、创建React工程环境
1. JavaScript项目
使用 Create React App (CRA) 快速搭建React项目:
npx create-react-app my-app
 
- 这会生成一个默认的JavaScript项目,包含基础配置(Webpack、Babel等),无需手动配置。
 
2. TypeScript项目
在创建时通过--template typescript指定TypeScript模板:
npx create-react-app my-app --template typescript
 
-  
或对已有JS项目添加TypeScript支持:
npm install --save typescript @types/react @types/react-dom将文件后缀改为
.tsx或.ts,CRA会自动识别并配置TypeScript。 
二、eject的作用
 
1. 功能
- 暴露隐藏配置:运行
npm run eject会将CRA封装的配置(如Webpack、Babel、ESLint)完全解压到项目目录,允许直接修改。 - 不可逆操作:一旦执行,无法回退到CRA的默认封装状态。
 
2. 使用场景
- 需要深度定制构建工具(如修改Webpack配置、添加插件)。
 - CRA默认配置无法满足项目需求(如自定义代码分割规则)。
 
3. 注意事项
- 维护成本:需自行管理所有配置,增加复杂性。
 - 替代方案:优先考虑非侵入式工具(如
react-app-rewired、@craco/craco)覆盖配置,避免eject。 
三、总结
- 创建项目:CRA是官方推荐工具,支持JS/TS开箱即用。
 eject:谨慎使用,仅在必要时暴露配置,否则优先选择灵活配置方案。
42.React常见hooks有哪些
React常见Hooks及用途
React Hooks 是函数组件中管理状态、副作用和其他功能的工具。以下是常用Hooks及其核心作用:
1. useState
 
-  
用途:管理组件内部状态。
 -  
示例:
const [count, setCount] = useState(0); 
2. useEffect
 
-  
用途:处理副作用(如数据请求、订阅、DOM操作)。
 -  
依赖控制:通过第二个参数(依赖数组)控制执行时机。
useEffect(() => {fetchData();return () => { /* 清理逻辑 */ }; }, [dependency]); 
3. useContext
 
-  
用途:跨组件共享数据(如主题、用户信息),避免逐层传递props。
 -  
示例:
const theme = useContext(ThemeContext); 
4. useReducer
 
-  
用途:管理复杂状态逻辑,类似Redux的Reducer模式。
 -  
适用场景:状态更新涉及多步骤或依赖之前状态。
const [state, dispatch] = useReducer(reducer, initialState); 
5. useRef
 
-  
用途:
- 访问DOM节点(如聚焦输入框)。
 - 保存可变值(如定时器ID),不触发重渲染。
 
 -  
示例:
const inputRef = useRef(null); <input ref={inputRef} /> 
6. useMemo
 
-  
用途:缓存计算结果,避免重复计算。
 -  
优化场景:依赖项未变化时复用缓存值。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); 
7. useCallback
 
-  
用途:缓存函数,避免子组件因函数引用变化而重渲染。
 -  
示例:
const handleClick = useCallback(() => { doSomething(a) }, [a]); 
8. useLayoutEffect
 
- 用途:与
useEffect类似,但同步执行(在DOM更新后、浏览器绘制前)。 - 适用场景:需要直接操作DOM或避免视觉抖动。
 
其他Hooks
useImperativeHandle:自定义暴露给父组件的ref实例。useDebugValue:在React开发者工具中显示自定义Hook的标签。
总结
- 核心Hooks:
useState、useEffect、useContext、useReducer、useRef。 - 性能优化:
useMemo、useCallback。 - 高级场景:
useLayoutEffect、useImperativeHandle。 - 自定义Hook:封装可复用的逻辑(如
useFetch)。 
43.React中函数式组件如何模拟生命周期
| 类组件生命周期 | 函数式组件实现方式 | 
|---|---|
componentDidMount | useEffect + 空依赖数组 | 
componentDidUpdate | useEffect + 监听依赖项 | 
componentWillUnmount | useEffect 的清理函数 | 
shouldComponentUpdate | React.memo 或 useMemo | 
getDerivedStateFromProps | useState + useEffect | 
44.React如何通过hooks通过数据接口,访问数据?
React通过Hooks访问数据接口的实现步骤
在React中,使用Hooks访问数据接口通常涉及以下步骤,结合状态管理、副作用处理及异步操作:
1. 使用useState管理数据状态
 
定义状态变量存储数据、加载状态及错误信息:
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
 
2. 使用useEffect触发数据请求
 
在useEffect中发起异步请求,处理数据获取逻辑:
useEffect(() => {const fetchData = async () => {try {const response = await fetch('https://api.example.com/data');if (!response.ok) throw new Error('请求失败');const result = await response.json();setData(result);} catch (err) {setError(err.message);} finally {setIsLoading(false);}};fetchData();
}, []); // 空依赖数组表示仅在组件挂载时执行
 
3. 处理组件卸载时的请求中断(可选)
使用AbortController取消未完成的请求,避免内存泄漏:
useEffect(() => {const abortController = new AbortController();const fetchData = async () => {try {const response = await fetch('https://api.example.com/data', {signal: abortController.signal,});// ...处理数据} catch (err) {if (err.name !== 'AbortError') setError(err.message);}};fetchData();return () => abortController.abort(); // 清理函数中中断请求
}, []);
 
4. 展示数据与状态
根据状态渲染UI:
return (<div>{isLoading ? (<div>加载中...</div>) : error ? (<div>错误:{error}</div>) : (<div>{JSON.stringify(data)}</div>)}</div>
);
 
5. 自定义Hook封装(进阶)
将数据获取逻辑抽象为可复用的自定义Hook:
const useFetch = (url) => {const [data, setData] = useState(null);const [isLoading, setIsLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {const fetchData = async () => {try {const response = await fetch(url);const result = await response.json();setData(result);} catch (err) {setError(err.message);} finally {setIsLoading(false);}};fetchData();}, [url]);return { data, isLoading, error };
};// 在组件中使用
const MyComponent = () => {const { data, isLoading, error } = useFetch('https://api.example.com/data');// ...渲染逻辑
};
 
6. 使用TypeScript定义数据类型(可选)
为接口返回数据添加类型约束:
interface ApiData {id: number;name: string;
}const useFetch = (url: string) => {const [data, setData] = useState<ApiData | null>(null);// ...其他逻辑
};
 
关键点总结
- 状态管理:
useState管理数据、加载状态和错误。 - 副作用控制:
useEffect处理异步请求,依赖项控制触发时机。 - 请求中断:
AbortController避免组件卸载后更新状态导致的内存泄漏。 - 自定义Hook:封装逻辑提升复用性。
 - 类型安全:TypeScript确保数据结构正确性。
 
45.React hook的使用,有哪些注意方式?为什么会有hooks?
React Hooks 使用注意事项
1. 遵守Hooks的调用规则
-  
只在顶层调用:
不可在条件、循环或嵌套函数中使用Hooks,确保每次渲染时Hooks的调用顺序一致。// 错误示例:条件中使用Hook if (condition) {const [state, setState] = useState(); // 会导致后续Hooks顺序错乱 } -  
仅用于React函数组件或自定义Hooks:
不可在普通JavaScript函数中调用Hooks。 
2. 正确处理依赖数组
-  
useEffect、useMemo、useCallback的依赖项:
明确列出所有外部依赖,避免闭包陷阱或过时数据。useEffect(() => {fetchData(id); // 若依赖id,需将其加入依赖数组 }, [id]); -  
空依赖数组的用途:
仅在组件挂载时执行一次(模拟componentDidMount)。 
3. 性能优化
-  
避免不必要的渲染:
使用React.memo、useMemo、useCallback减少子组件重复渲染。const memoizedValue = useMemo(() => computeValue(a, b), [a, b]); const handleClick = useCallback(() => action(a), [a]); -  
避免滥用
useState:
合并相关状态,减少渲染次数。// 合并为对象 const [user, setUser] = useState({ name: 'Alice', age: 20 }); 
4. 清理副作用
-  
useEffect的清理函数:
取消订阅、定时器或网络请求,防止内存泄漏。useEffect(() => {const timer = setInterval(() => {}, 1000);return () => clearInterval(timer); }, []); 
5. 自定义Hooks规范
- 命名以
use开头:
便于React识别并应用Hooks规则,例如useFetch、useLocalStorage。 
为什么需要Hooks?
1. 解决类组件的痛点
- 逻辑复用困难:
类组件中复用状态逻辑需通过高阶组件(HOC)或Render Props,导致“嵌套地狱”。 - 生命周期方法分散逻辑:
相关代码分散在componentDidMount、componentDidUpdate等生命周期中,难以维护。 this指向问题:
类组件中需要绑定this,增加代码复杂度。
2. 函数组件的增强
- 赋予函数组件状态能力:
通过useState、useEffect等Hooks,函数组件可管理状态和副作用,无需转换为类组件。 - 逻辑聚合:
将相关逻辑集中到同一Hook中,提升代码可读性(如将数据请求与状态管理封装为useFetch)。 
3. 更简洁的代码结构
- 减少模板代码:
避免类组件的构造函数、生命周期方法等冗余代码。 - 函数式编程优势:
更易编写纯函数,方便测试和调试。 
4. 社区与未来趋势
- 函数式编程普及:
Hooks推动React向函数式范式发展,与现代JavaScript生态更契合。 - 渐进式迁移:
支持在现有类组件中逐步引入Hooks,降低重构成本。 
总结
| 注意事项 | 设计动机 | 
|---|---|
| 调用顺序一致性 | 解决类组件的逻辑复用与生命周期碎片化 | 
| 依赖数组精确管理 | 简化状态管理与副作用控制 | 
| 性能优化与副作用清理 | 提升代码可维护性与可读性 | 
| 自定义Hooks规范 | 推动函数式组件成为主流开发模式 | 
46.React是如何获取组件对应的DOM元素?
React获取组件对应DOM元素的方法
在React中,通常不推荐直接操作DOM,但在需要访问特定元素(如管理焦点、集成第三方库)时,可通过以下方式获取:
1. 使用ref属性
 
核心方法:通过ref绑定到JSX元素,获取其对应的DOM节点。
类组件
-  
React.createRef()
创建ref对象并附加到元素:class MyComponent extends React.Component {constructor(props) {super(props);this.myRef = React.createRef();}componentDidMount() {// 访问DOM节点console.log(this.myRef.current); // 输出对应的DOM元素}render() {return <div ref={this.myRef}>Hello</div>;} } 
函数组件
-  
useRefHook
创建可持久化的ref对象:import { useRef, useEffect } from 'react';function MyComponent() {const myRef = useRef(null);useEffect(() => {console.log(myRef.current); // 组件挂载后访问}, []);return <div ref={myRef}>Hello</div>; } 
2. 回调Ref(动态绑定)
通过函数接收DOM元素,适用于动态绑定或类组件:
class MyComponent extends React.Component {setRef = (element) => {this.myRef = element; // 直接保存DOM元素};render() {return <div ref={this.setRef}>Hello</div>;}
}
 
3. 转发Ref(访问子组件DOM)
当需要获取子组件的DOM时,使用React.forwardRef:
子组件(支持Ref转发)
const ChildComponent = React.forwardRef((props, ref) => {return <div ref={ref}>{props.children}</div>;
});
 
父组件
function ParentComponent() {const childRef = useRef(null);useEffect(() => {console.log(childRef.current); // 子组件的DOM元素}, []);return <ChildComponent ref={childRef}>Child</ChildComponent>;
}
 
4. 函数组件暴露方法(useImperativeHandle)
 
控制子组件暴露给父组件的实例方法,而非直接暴露DOM:
const ChildComponent = React.forwardRef((props, ref) => {const inputRef = useRef();useImperativeHandle(ref, () => ({focus: () => inputRef.current.focus(),}));return <input ref={inputRef} />;
});// 父组件调用子组件的focus方法
function ParentComponent() {const childRef = useRef();return (<><ChildComponent ref={childRef} /><button onClick={() => childRef.current.focus()}>聚焦输入框</button></>);
}
 
注意事项
- 访问时机:
Ref的current属性在组件挂载后才有效,应在useEffect或componentDidMount中访问。 - 避免滥用:
直接操作DOM可能破坏React的声明式特性,优先通过状态(State/Props)控制UI。 - 清理Ref:
若在Ref中保存了订阅或定时器,需在useEffect的清理函数或componentWillUnmount中释放资源。 
总结
| 场景 | 方法 | 
|---|---|
| 类组件获取DOM | React.createRef() | 
| 函数组件获取DOM | useRef | 
| 动态绑定DOM | 回调Ref(函数形式) | 
| 访问子组件DOM | React.forwardRef | 
| 控制子组件暴露的实例 | useImperativeHandle + forwardRef | 
47.React什么是状态提升,子父组件如何通信?
在React中,状态提升(State Lifting)和子父组件通信是组件间数据交互的核心机制
1. 状态提升(State Lifting)
概念
状态提升是指将多个子组件需要共享的状态(State)移动到它们最近的共同父组件中,通过props向下传递数据,再通过回调函数让子组件通知父组件更新状态。这种方式遵循单向数据流原则,确保状态管理的可预测性和一致性。
适用场景
- 多个组件需要同步同一份数据(如温度转换器、表单联动输入)。
 - 需要集中管理状态以避免冗余或冲突。
 
实现步骤
- 提升状态:将共享状态定义在父组件中。
 - 传递数据:通过
props将状态传递给子组件。 - 传递回调:父组件将更新状态的函数通过
props传给子组件,子组件触发回调以更新父组件状态。 
示例
// 父组件
function TemperatureConverter() {const [celsius, setCelsius] = useState(0);return (<div><CelsiusInput value={celsius} onChange={setCelsius} /><FahrenheitDisplay celsius={celsius} /></div>);
}// 子组件1:输入摄氏度
function CelsiusInput({ value, onChange }) {return (<inputtype="number"value={value}onChange={(e) => onChange(Number(e.target.value))}/>);
}// 子组件2:显示华氏度
function FahrenheitDisplay({ celsius }) {const fahrenheit = celsius * 9 / 5 + 32;return <div>{fahrenheit}°F</div>;
}
 
2. 子父组件通信
核心机制
子组件通过调用父组件传递的回调函数(通过props)与父组件通信。父组件定义状态更新逻辑,子组件触发回调并传递数据。
实现步骤
- 父组件定义回调:在父组件中编写状态更新函数(如
handleChange)。 - 传递回调给子组件:通过
props将回调函数传递给子组件。 - 子组件触发回调:子组件在特定事件(如用户输入)中调用回调,并传递参数。
 
示例
// 父组件
function Parent() {const [data, setData] = useState("");const handleChildData = (childData) => {setData(childData);};return <Child onSendData={handleChildData} />;
}// 子组件
function Child({ onSendData }) {const sendData = () => {onSendData("Data from Child");};return <button onClick={sendData}>Send Data to Parent</button>;
}
 
关键点总结
- 状态提升:共享状态应置于共同父组件,通过
props下发,保持单一数据源。 - 子父通信:子组件通过父组件传递的回调函数通知状态变更。
 - 优势:遵循单向数据流,状态更易追踪,减少冗余和冲突。
 
通过这种方式,React应用能够实现高效、可维护的组件间通信,适用于复杂场景下的状态管理。
48.React 你用的react是什么版本,react版本有什么区别?
React 版本及区别
1. 我使用的 React 版本
 目前使用的是 React 18,但如果项目需要最新特性,我也会关注 React 19 的更新和优化点。
2. 主要 React 版本的区别
| 版本 | 主要区别 | 
|---|---|
| React 16 (2017) | - 引入 Fiber 架构,优化渲染性能 - React.createRef() 方式创建 ref - 新的生命周期方法,如 getDerivedStateFromProps、componentDidCatch (错误边界) | 
| React 17 (2020) | - 主要是渐进式升级,没有新特性 - 事件系统更新,支持事件委托至 root 而非 document | 
| React 18 (2022) | - 并发渲染(Concurrent Rendering) - useTransition、useDeferredValue 以优化渲染体验 - React.startTransition 用于标记非紧急更新 - createRoot() 替代 ReactDOM.render() | 
| React 19 (2024) | - useOptimistic:用于优化状态更新,使 UI 响应更流畅 - useFormStatus & useFormState:改进表单状态管理 - React Compiler(实验性):更智能的代码优化 - React Server Components (RSC) 更稳定,可与 Next.js 结合优化渲染 | 
3. React 版本对项目的影响
- 如果是老项目(React 16/17),升级到 React 18 是较稳妥的选择,因为它带来了显著的性能优化,但不会影响现有代码。
 - 如果是新项目,可以考虑直接使用 React 19,尤其是在 Server Components 方面有更好的支持,并能提升用户体验。
 
这样能展现出对 React 版本的全面了解,并结合实际项目需求进行分析。
49.React中setState到底是同步还是异步?
如果我是前端面试者,我会这样回答:
React 中 setState 是同步还是异步?
 
React 的 setState 既不是完全同步,也不是完全异步,它的行为取决于执行环境:
- 在 React 事件处理(如 
onClick)或生命周期函数中,setState是异步的,React 会批量更新以提高性能。 - 在原生事件(如 
setTimeout、Promise.then)或非 React 控制的回调中,setState是同步的,状态会立即更新。 
示例 1:React 事件中 setState 体现异步性
 
import React, { useState } from "react";const Example = () => {const [count, setCount] = useState(0);const handleClick = () => {console.log("Before setState:", count); // 0setCount(count + 1);console.log("After setState:", count);  // 仍然是 0(未立即更新)};return <button onClick={handleClick}>Count: {count}</button>;
};export default Example;
 
解释:
setState(count + 1)触发更新,但不会立即修改count变量,因为 React 在事件处理中会批量更新以优化渲染。console.log("After setState:", count);仍然是旧值。
示例 2:在 setTimeout 或 Promise 中 setState 是同步的
 
import React, { useState, useEffect } from "react";const Example = () => {const [count, setCount] = useState(0);useEffect(() => {setTimeout(() => {console.log("Before setState:", count); // 0setCount(count + 1);console.log("After setState:", count);  // 仍然是 0(闭包问题)}, 1000);}, []);return <p>Count: {count}</p>;
};export default Example;
 
解释:
- 在 
setTimeout里,setState不会被批量处理,所以它的执行方式与 JavaScript 的普通函数类似。 - 但注意闭包问题,
count在setTimeout内部保持初始值(即 0),导致setCount(count + 1)其实等于setCount(1)。 - 解决方案:使用 
setCount(prev => prev + 1),这样prev总是最新值。 
React 18 及以上:批量更新
React 18 默认开启批处理,即使 setTimeout 或 Promise 中 setState 也会异步执行:
import React, { useState } from "react";const Example = () => {const [count, setCount] = useState(0);const handleClick = () => {Promise.resolve().then(() => {setCount((prev) => prev + 1);setCount((prev) => prev + 1);});};return <button onClick={handleClick}>Count: {count}</button>;
};export default Example;
 
- React 18 之前:两次 
setCount会执行两次更新,最终count只加 1。 - React 18 之后:React 自动批处理,最终 
count会加 2。 
总结
| 场景 | setState 是同步还是异步? | 说明 | 
|---|---|---|
| React 事件处理 | 异步 | React 会批量更新,优化性能 | 
| 生命周期函数 | 异步 | 如 componentDidMount、useEffect | 
setTimeout、Promise | React 17 及以前同步,React 18 及以上异步 | React 18 开启了自动批处理 | 
useTransition、startTransition | 异步 | 标记低优先级更新,提高流畅度 | 
最佳实践
- 要获取最新的 state,使用回调形式
 
setCount(prev => prev + 1);
 
- React 18 需要 
flushSync()强制同步 
import { flushSync } from "react-dom";flushSync(() => {setCount(count + 1);
});
console.log(count); // 立即获取新值
 
总结一句话
setState 在 React 事件和生命周期中是 异步的,在 setTimeout 等原生异步任务中是 同步的(React 18 之后默认也是异步的)。
50.在React如何实现Vue中的expose的能力?
如何在 React 中实现 Vue 3 的 expose 能力?
 
在 Vue 3 中,expose 允许子组件显式暴露特定的属性或方法,避免父组件访问子组件整个实例。例如:
<script setup>
import { ref, defineExpose } from "vue";const count = ref(0);
const increment = () => count.value++;defineExpose({ count, increment }); // 仅暴露 count 和 increment
</script>
 
React 中如何实现类似能力?
React 没有 expose 这个 API,但可以通过 forwardRef + useImperativeHandle 组合实现类似的功能,使父组件只能访问子组件暴露的方法,而非整个子组件实例。
示例:使用 forwardRef + useImperativeHandle
 
import React, { useState, forwardRef, useImperativeHandle } from "react";// 子组件
const Child = forwardRef((props, ref) => {const [count, setCount] = useState(0);const increment = () => setCount((prev) => prev + 1);// 仅暴露 `increment` 方法useImperativeHandle(ref, () => ({increment,}));return <p>Count: {count}</p>;
});// 父组件
const Parent = () => {const childRef = React.useRef(null);return (<div><Child ref={childRef} /><button onClick={() => childRef.current?.increment()}>增加</button></div>);
};export default Parent;
 
对比 Vue expose 和 React useImperativeHandle
 
| 框架 | API | 作用 | 
|---|---|---|
| Vue 3 | defineExpose | 仅暴露指定属性和方法给父组件 | 
| React | useImperativeHandle | 通过 ref 仅暴露特定方法,避免父组件访问不必要的状态 | 
总结
在 React 中,useImperativeHandle + forwardRef 可以实现 Vue expose 的能力,确保父组件只访问必要的方法,而不是整个子组件实例。这在 封装组件库或希望控制组件暴露接口 时非常有用。
51.useLayoutEffect和useEffect 有什么区别呢?
1. useLayoutEffect 基本概念
useLayoutEffect 是 React 的一个 Hook,它的函数签名与 useEffect 完全相同,但它会在所有的 DOM 变更之后同步调用 effect。它可以用来读取 DOM 布局并同步触发重渲染。
2. useLayoutEffect vs useEffect
2.1 执行时机对比
| Hook 名称 | 执行时机 | 执行方式 | 使用场景 | 
|---|---|---|---|
| useEffect | DOM 更新后且浏览器重新绘制屏幕之后异步执行 (组件渲染完成后) | 异步执行,不阻塞浏览器渲染 | 大多数副作用,如数据获取、订阅 | 
| useLayoutEffect | DOM 更新后且浏览器重新绘制屏幕之前同步执行(组件将要渲染时) | 同步执行,会阻塞浏览器渲染 | 需要同步测量 DOM 或更新布局 | 
2.2 执行顺序示例
function ExampleComponent() {const [count, setCount] = useState(0);useEffect(() => {console.log('useEffect 执行'); // 后执行});useLayoutEffect(() => {console.log('useLayoutEffect 执行'); // 先执行});return (<div onClick={() => setCount(c => c + 1)}>点击次数:{count}</div>);
}
 
3.1 何时使用 useLayoutEffect
- 需要同步测量 DOM 元素
 - 需要在视觉更新前进行 DOM 修改
 - 需要避免闪烁或布局抖动
 - 处理依赖于 DOM 布局的动画
 
3.2 何时使用 useEffect
- 数据获取
 - 订阅事件
 - 日志记录
 - 其他不需要同步 DOM 测量或修改的副作用
 
4. 最佳实践
- 优先使用 useEffect
 
// ✅ 大多数情况下使用 useEffect 即可
useEffect(() => {// 异步操作,不影响渲染fetchData();
}, []);
 
- 仅在必要时使用 useLayoutEffect
 
// ✅ 需要同步 DOM 测量和更新时使用 useLayoutEffect
useLayoutEffect(() => {// 同步操作,立即更新 DOMupdateDOMPosition();
}, []);
 
- 注意性能影响
 
// ❌ 避免在 useLayoutEffect 中进行耗时操作
useLayoutEffect(() => {// 不要在这里进行大量计算或 API 调用heavyComputation();
}, []);// ✅ 耗时操作应该放在 useEffect 中
useEffect(() => {heavyComputation();
}, []);
 
6. 注意事项
- useLayoutEffect 在服务器端渲染(SSR)中会收到警告,因为它只能在客户端执行
 - 过度使用 useLayoutEffect 可能会导致性能问题
 - 应该将耗时的操作放在 useEffect 中,只在 useLayoutEffect 中处理视觉相关的同步更新
 
52.React如何实现一个 withRouter?(React Router v6 之后,withRouter 被移除)选背
 
如果我是前端面试者,我会这样回答:
如何在 React 中实现 withRouter?
 
在 React Router v5 及之前,withRouter 是一个高阶组件(HOC),用于向类组件提供 history、location 和 match 这三个路由对象。
但在 React Router v6 之后,withRouter 被移除,官方推荐使用 useNavigate、useLocation 等 Hook 直接在函数组件中使用。
1. 在 React Router v5 中手写 withRouter
 
实现一个 withRouter 高阶组件:
import { withRouter } from "react-router-dom";const MyComponent = ({ history, location, match }) => {return (<div><p>Current Path: {location.pathname}</p><button onClick={() => history.push("/home")}>Go Home</button></div>);
};export default withRouter(MyComponent);
 
手写实现 withRouter:
 其实就是写一个叫withRouter的HOC,传入组件component,将history、location 和 match用属性代理的方式传入然后返回新的component,本质是一个高阶组件的用法
import { useNavigate, useLocation, useParams } from "react-router-dom";const withRouter = (Component) => {return (props) => {const navigate = useNavigate();const location = useLocation();const params = useParams();return <Component {...props} navigate={navigate} location={location} params={params} />;};
};export default withRouter;
 
使用方式
const MyComponent = ({ navigate, location, params }) => {return (<div><p>Current Path: {location.pathname}</p><button onClick={() => navigate("/home")}>Go Home</button></div>);
};export default withRouter(MyComponent);
 
2. 在 React Router v6 中替代 withRouter
 
React Router v6 移除了 withRouter,推荐直接在函数组件中使用 useNavigate 和 useLocation:
import { useNavigate, useLocation, useParams } from "react-router-dom";const MyComponent = () => {const navigate = useNavigate();const location = useLocation();const params = useParams();return (<div><p>Current Path: {location.pathname}</p><button onClick={() => navigate("/home")}>Go Home</button></div>);
};export default MyComponent;
 
3. withRouter 适用场景
 
虽然在 React Router v6 中推荐使用 Hook,但如果项目中仍然使用 类组件,可以用 withRouter 来注入路由信息:
import React from "react";
import { withRouter } from "./withRouter"; // 之前实现的手写 HOCclass ClassComponent extends React.Component {render() {return (<div><p>Current Path: {this.props.location.pathname}</p><button onClick={() => this.props.navigate("/home")}>Go Home</button></div>);}
}export default withRouter(ClassComponent);
 
总结
- React Router v5 及之前:
withRouter是官方提供的 HOC,可用于类组件。 - React Router v6 及以后:推荐使用 
useNavigate、useLocation等 Hook,直接在函数组件中获取路由信息。 - 如果必须支持类组件:可以自己手写 
withRouter,使用useNavigate和useLocation封装 HOC。 
53.react-router-dom v6 提供了哪些新的API?
总结
| API | 作用 | 替代的旧 API | 
|---|---|---|
createBrowserRouter | 新的路由创建方式 | <BrowserRouter> | 
useRoutes | 动态生成路由 | <Routes> | 
useNavigate | 进行页面跳转 | useHistory().push() | 
useSearchParams | 处理 URL 查询参数 | window.location.search | 
useLoaderData | 加载数据 | useEffect | 
Outlet | 处理嵌套路由 | children | 
useParams | 获取 URL 参数 | match.params | 
Navigate | 重定向 | Redirect | 
| ** | 
React Router v6 新 API 介绍
React Router v6 于 2021 年发布,相比 v5 进行了重大改进,包括 API 变化、更简洁的写法以及更强大的功能。
1. createBrowserRouter / createHashRouter
 
用于替代
<BrowserRouter>,支持数据加载和路由控制。
import { createBrowserRouter, RouterProvider } from "react-router-dom";const router = createBrowserRouter([{path: "/",element: <Home />,},{path: "/about",element: <About />,},
]);export default function App() {return <RouterProvider router={router} />;
}
 
✅ 优势:
- 允许在路由定义时加载数据
 - 适用于 React 18 的流式渲染
 
2. useRoutes
 
动态创建路由,替代
<Switch>
import { useRoutes } from "react-router-dom";const routes = [{ path: "/", element: <Home /> },{ path: "/about", element: <About /> },
];const App = () => {const element = useRoutes(routes);return element;
};
 
✅ 优势:
- 更简洁,不需要手写 
<Routes>和<Route> - 灵活可配置,适用于动态路由
 
3. useNavigate
 
替代
useHistory,进行导航跳转
import { useNavigate } from "react-router-dom";const Home = () => {const navigate = useNavigate();return <button onClick={() => navigate("/about")}>Go to About</button>;
};
 
✅ 优势:
- 更清晰,不再需要 
history.push() - 支持向前/向后跳转:
navigate(-1) 
4. useSearchParams
 
用于管理 URL 查询参数
import { useSearchParams } from "react-router-dom";const Users = () => {const [searchParams, setSearchParams] = useSearchParams();const userId = searchParams.get("id");return (<div><p>User ID: {userId}</p><button onClick={() => setSearchParams({ id: "123" })}>Set ID</button></div>);
};
 
✅ 优势:
- 替代 
query-string,更简洁 - 直接修改 URL,无需手动拼接参数
 
5. useLoaderData
 
配合
loader进行数据加载
import { createBrowserRouter, RouterProvider, useLoaderData } from "react-router-dom";const fetchUser = async () => {const res = await fetch("/api/user");return res.json();
};const Profile = () => {const user = useLoaderData();return <p>{user.name}</p>;
};const router = createBrowserRouter([{path: "/profile",element: <Profile />,loader: fetchUser,},
]);const App = () => <RouterProvider router={router} />;
 
✅ 优势:
- SSR 友好,支持数据预加载
 - 简化数据获取,无需 
useEffect 
6. Outlet
 
用于嵌套路由
const Layout = () => (<div><h1>Header</h1><Outlet /> {/* 这里会渲染子路由 */}</div>
);const router = createBrowserRouter([{path: "/",element: <Layout />,children: [{ path: "home", element: <Home /> },{ path: "about", element: <About /> },],},
]);
 
✅ 优势:
- 清晰管理嵌套路由
 - 更符合组件化设计
 
7. useParams
 
获取动态路由参数
import { useParams } from "react-router-dom";const User = () => {const { id } = useParams();return <p>User ID: {id}</p>;
};// 路由配置: path: "/user/:id"
 
✅ 优势:
- 更简单,无需 
match.params - 与 
useNavigate结合使用,体验更佳 
8. Navigate 组件
 
替代
Redirect
import { Navigate } from "react-router-dom";const PrivateRoute = ({ isAuth }) => {return isAuth ? <Dashboard /> : <Navigate to="/login" />;
};
 
✅ 优势:
- 更符合 JSX 语法
 - 清晰的重定向逻辑
 
54.useRoutes是如何使用的?如何使用useRoutes进行动态路由加载
如果我是前端面试者,我会这样回答:
1. useRoutes 介绍
 
useRoutes 是 React Router v6 提供的一个 Hook,用于基于配置动态生成路由,它替代了 v5 版本的 <Routes> + <Route> 组合,让路由更加声明式和清晰。
2. useRoutes 基本用法
 
替代
<Routes>直接定义路由
传统 <Routes> 方式(v5/v6)
 
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./Home";
import About from "./About";const App = () => (<BrowserRouter><Routes><Route path="/" element={<Home />} /><Route path="/about" element={<About />} /></Routes></BrowserRouter>
);
 
使用 useRoutes 方式(v6 推荐)
 
import { BrowserRouter, useRoutes } from "react-router-dom";
import Home from "./Home";
import About from "./About";const routes = [{ path: "/", element: <Home /> },{ path: "/about", element: <About /> },
];const AppRoutes = () => {return useRoutes(routes);
};const App = () => (<BrowserRouter><AppRoutes /></BrowserRouter>
);
 
✅ 优势:
- 让路由更加模块化,避免在 
App.tsx里堆积<Route> - 适用于动态路由加载
 
3. useRoutes 处理嵌套路由
 
如果有子路由,可以使用 children 进行嵌套:
import { BrowserRouter, useRoutes, Outlet } from "react-router-dom";const Layout = () => (<div><h1>Header</h1><Outlet /> {/* 子路由会渲染到这里 */}</div>
);const routes = [{path: "/",element: <Layout />,children: [{ path: "home", element: <Home /> },{ path: "about", element: <About /> },],},
];const AppRoutes = () => useRoutes(routes);const App = () => (<BrowserRouter><AppRoutes /></BrowserRouter>
);
 
✅ 优势:
Outlet允许嵌套渲染子路由,替代this.props.children- 更容易管理多级路由
 
4. useRoutes 进行动态路由加载
 
在实际项目中,通常需要按需加载路由组件,减少首屏加载时间。可以结合 React.lazy + Suspense 实现动态导入:
🚀 使用 React.lazy 进行按需加载
 
import { BrowserRouter, useRoutes } from "react-router-dom";
import { lazy, Suspense } from "react";// 动态导入组件
const Home = lazy(() => import("./Home"));
const About = lazy(() => import("./About"));const routes = [{ path: "/", element: <Home /> },{ path: "/about", element: <About /> },
];const AppRoutes = () => useRoutes(routes);const App = () => (<BrowserRouter><Suspense fallback={<div>Loading...</div>}><AppRoutes /></Suspense></BrowserRouter>
);
 
✅ 优势:
- 仅在访问对应页面时才加载组件,提升首屏加载速度
 - 适用于大型项目,减少初始打包体积
 
5. useRoutes 结合后端动态生成路由
 
在某些场景下,前端的路由数据可能是由后端返回的。例如,后端返回用户可访问的菜单,我们可以根据 API 返回的路由表动态生成路由:
import { BrowserRouter, useRoutes } from "react-router-dom";
import { useEffect, useState } from "react";const fetchRoutesFromAPI = async () => {return [{ path: "/", element: <Home /> },{ path: "/about", element: <About /> },];
};const AppRoutes = () => {const [routes, setRoutes] = useState([]);useEffect(() => {fetchRoutesFromAPI().then(setRoutes);}, []);return useRoutes(routes);
};const App = () => (<BrowserRouter><AppRoutes /></BrowserRouter>
);
 
✅ 优势:
- 适用于权限管理,根据用户权限动态生成可访问路由
 - 后端驱动路由,前端无需硬编码
 
6. useRoutes vs 传统 <Routes>
 
| 特性 | useRoutes | <Routes> | 
|---|---|---|
| 代码简洁度 | ✅ 更清晰,避免 JSX 嵌套 | ❌ 需要多个 <Route> | 
| 动态加载 | ✅ 支持 React.lazy | ❌ 需要手动 Suspense | 
| 动态路由 | ✅ 适用于 API 获取路由 | ❌ 需手动 map 生成 <Route> | 
| 嵌套路由 | ✅ children 方式 | ✅ 但更麻烦 | 
| 适用场景 | ✅ 组件化、大型项目 | ✅ 适合小项目 | 
7. 总结
useRoutes让路由声明更加清晰,避免 JSX 里嵌套<Route>。- 适用于动态路由,可以从后端 API 加载路由表并渲染。
 - 结合 
React.lazy实现懒加载,提高首屏加载速度。 - 推荐在 React Router v6 中使用,特别是当路由结构较复杂时。
 
✨ useRoutes 适用于大型项目,配合动态加载、权限管理等方案,能显著优化路由管理!
55.redux的中间件是如何实现的?
redux中间件是指在redux发出action后执行Reducer改变state之前劫持action做出其他开发者想操作的多余步骤如打印log,接受函数式dispatch,处理异步操作或者promise等然后再分发action到reducer改变 state。
 实现原理是通过 **applyMiddleware**方法将中间件从右左形成中间件函数调用链,一层一层的调用中间件,最好执行真正的dispatch去出发Reducer。同时将store和 dispatch重写放入调用链中增强dispatch功能。
子问题:
applyMiddleware为什么要从右往左进行反向组装中间件链:从右到左依次包装 dispatch
Redux的applyMiddleware从右到左反向组装中间件链,是为了确保中间件的执行顺序与用户传入的顺序一致(从左到右) 。这种设计源于函数式编程的组合逻辑:每个中间件接收的next参数代表链中下一个中间件处理后的dispatch,通过从右到左依次包装,最终形成的调用链会按照中间件的声明顺序(从左到右)依次执行。例如,中间件[A, B, C]的组合顺序是C → B → A,但执行时A最先处理Action,调用next后触发B,再调用next触发C,最后到达原始dispatch,从而保证用户直观的中间件执行顺序。
 具体的实现原理如下
Redux 中间件的作用
在 Redux 中,中间件(Middleware)用于扩展 dispatch 方法的功能,可以在派发(dispatch)Action 和 Reducer 处理之前执行额外的逻辑,比如:
- 处理 异步操作(如 
redux-thunk、redux-saga) - 日志记录(如 
redux-logger) - 拦截/修改 action(如 
redux-promise) - 错误处理
 
Redux 中间件的核心在于增强 dispatch 方法,使其能够处理函数、Promise 等异步操作,而不仅仅是普通的对象。
Redux 中间件的实现原理
Redux 的中间件是基于函数式编程的高阶函数(Higher-Order Function) 。
 一个 Redux 中间件的基本结构如下:
const exampleMiddleware = (store) => (next) => (action) => {console.log("中间件触发:", action);return next(action); // 继续传递 action
};
 
解析:
store—— Redux store 对象,包含getState()和dispatch()方法。next—— 代表下一个中间件,或者最终到达reducer。action—— 被 dispatch 的 action。
✅ 核心逻辑:
- 在 
dispatch(action)之后,中间件先拦截 action,可以对 action 进行修改、日志记录、异步处理等操作。 - 调用 
next(action)将 action 传递给下一个中间件,最终到达 reducer。 - 如果不调用 
next(action),Redux 流程就会被中断(用于拦截某些 action)。 
手写 Redux 中间件机制
1️⃣ Redux applyMiddleware 实现
 
Redux 提供 applyMiddleware 这个方法,它的核心逻辑如下:
const applyMiddleware = (...middlewares) => (createStore) => (reducer) => {const store = createStore(reducer); // 创建 Redux storelet dispatch = store.dispatch; // 原始 dispatch 方法const middlewareAPI = {getState: store.getState,dispatch: (action) => dispatch(action), // 让中间件能够调用 dispatch};// 依次执行每个中间件,得到增强后的 dispatchconst chain = middlewares.map((middleware) => middleware(middlewareAPI));dispatch = chain.reduceRight((next, middleware) => middleware(next), store.dispatch);return { ...store, dispatch }; // 返回增强后的 store
};
 
✅ 核心流程:
- 先创建 Redux 
store,获取原始dispatch方法。 - 给中间件传递 
middlewareAPI(getState、dispatch)。 - 链式调用所有中间件,最终生成增强版 
dispatch,替换 Redux 默认的dispatch。 
2️⃣ 使用 applyMiddleware 来增强 Redux
 
import { createStore, applyMiddleware } from "redux";// 自定义一个日志中间件
const loggerMiddleware = (store) => (next) => (action) => {console.log("当前状态:", store.getState());console.log("派发 action:", action);const result = next(action); // 继续执行 actionconsole.log("更新后状态:", store.getState());return result;
};// Reducer
const reducer = (state = { count: 0 }, action) => {switch (action.type) {case "INCREMENT":return { count: state.count + 1 };default:return state;}
};// 创建 Redux store,使用 applyMiddleware 处理中间件
const store = createStore(reducer, applyMiddleware(loggerMiddleware));// 触发 action
store.dispatch({ type: "INCREMENT" });
 
✅ 运行结果:
当前状态: { count: 0 }
派发 action: { type: "INCREMENT" }
更新后状态: { count: 1 }
 
总结:
applyMiddleware(loggerMiddleware)增强了dispatch,在每次dispatch(action)时打印日志。next(action)负责传递 action,最终到达reducer,否则 Redux 流程会被拦截。
3️⃣ Redux 异步中间件示例
Redux 默认不支持异步 action,我们需要用中间件来处理异步逻辑。
✅ Redux-Thunk(处理 dispatch 函数)
 
redux-thunk 允许 dispatch 支持函数,而不仅仅是对象:
const thunkMiddleware = (store) => (next) => (action) => {if (typeof action === "function") {return action(store.dispatch, store.getState);}return next(action);
};// 使用 thunk 中间件
const store = createStore(reducer, applyMiddleware(thunkMiddleware));// 异步 action
const fetchData = () => {return (dispatch) => {setTimeout(() => {dispatch({ type: "INCREMENT" });}, 1000);};
};// 触发异步 action
store.dispatch(fetchData());
 
✅ thunkMiddleware 逻辑:
- 如果 
action是一个函数,则执行这个函数,并传入dispatch和getState,支持异步操作。 - 如果 
action是普通对象,直接传递给next(action),进入 reducer 处理。 
✅ Redux-Promise(处理 Promise action)
 
redux-promise 让 dispatch 支持 Promise,比如:
const promiseMiddleware = (store) => (next) => (action) => {if (action instanceof Promise) {return action.then(next);}return next(action);
};// 使用 promise 中间件
const store = createStore(reducer, applyMiddleware(promiseMiddleware));// 触发 Promise action
store.dispatch(new Promise((resolve) => {setTimeout(() => resolve({ type: "INCREMENT" }), 1000);})
);
 
✅ 原理:
- 如果 
action是一个Promise,等Promise解析后再执行next(action),使dispatch能够直接处理异步请求。 
4️⃣ 总结
-  
Redux 中间件是
dispatch的高阶增强,可以拦截、修改、异步处理 action。 -  
核心实现是:
(store) => (next) => (action) => {}这种 函数式组合。- 通过 
applyMiddleware增强dispatch,形成中间件链。 
 -  
常见 Redux 中间件:
redux-thunk→ 让dispatch支持function,用于异步操作。redux-promise→ 让dispatch支持Promise,简化异步请求。redux-logger→ 记录dispatch过程中的state变化。
 -  
手写中间件的关键点:
- 拦截 
dispatch(action) - 可以修改/处理 
action - 调用 
next(action)传递给 reducer 
 - 拦截 
 
总结一句话:Redux 中间件是一个 dispatch 的增强函数,它让 Redux 可以处理异步、日志、权限等功能,核心就是一个 store -> next -> action 结构的高阶函数! 🚀
56.React的render props是什么?
如果我是前端面试者,我会这样回答:
什么是 Render Props?
Render Props 是 React 组件的一种模式,它指的是:一个组件接收一个函数作为 props,然后在组件内部调用这个函数并渲染其返回的内容。
通常用于 组件复用,特别是共享组件逻辑(例如:状态管理、动画、数据获取等)。
Render Props 的基本用法
const MouseTracker = (props) => {const [position, setPosition] = React.useState({ x: 0, y: 0 });const handleMouseMove = (event) => {setPosition({ x: event.clientX, y: event.clientY });};return (<div style={{ height: "200px", border: "1px solid black" }} onMouseMove={handleMouseMove}>{props.render(position)}  {/* 调用 render props 函数 */}</div>);
};const App = () => (<MouseTrackerrender={(position) => <h1>鼠标位置:{position.x}, {position.y}</h1>}/>
);
 
✅ 工作原理:
MouseTracker组件封装了鼠标位置的状态,并通过render传递给子组件。App组件通过render传入函数,决定如何渲染position数据。- 这避免了继承,允许灵活的 UI 复用。
 
Render Props vs 其他模式
| 模式 | 适用场景 | 缺点 | 
|---|---|---|
| Render Props | 组件复用、状态共享 | 嵌套较深时可能影响可读性 | 
| HOC(高阶组件) | 逻辑复用,如权限控制 | 可能导致 props 冲突,React DevTools 难以调试 | 
| Hooks | 现代 React 组件逻辑复用 | 仅适用于函数组件 | 
Render Props 的应用场景
- 共享组件状态(如 
MouseTracker示例) - 数据获取(如封装 
fetch逻辑) - 动画、过渡(如 
react-motion) 
Render Props 现状
React 16.8+ 之后,Hooks(如 useState, useEffect, useContext)的出现减少了 Render Props 的使用,因为 Hooks 让组件逻辑复用变得更加简洁。
 但在某些场景下,Render Props 仍然适用,特别是当你想要封装一个逻辑复用但 UI 仍然可自定义的组件时。
总结:Render Props 是 React 组件复用的一种模式,通过传递函数 props 让组件逻辑和 UI 解耦,适用于状态共享、数据获取等场景。 🚀
57.为什么之前react组件要写import react from ‘react’,现在又不用了?
在 React 17 之前,组件需要显式导入 React(如 import React from 'react'),是因为 JSX 语法会被 Babel 等工具转换为 React.createElement 调用,必须确保 React 在作用域内可用。而 React 17 引入了 新的 JSX 转换方式,通过自动从 react/jsx-runtime 注入 jsx 或 jsxs 函数,不再依赖全局的 React 变量,因此无需手动导入。这一变化简化了代码,减少了冗余,同时为未来优化(如更高效的编译输出)奠定了基础。
详细解释
-  
React 17 之前
-  
JSX 转换依赖
React.createElement:
当编写<Button />时,Babel 会将其转换为React.createElement(Button, null),因此必须导入React以访问createElement方法。// 代码 import React from 'react'; function App() { return <Button />; }// 转换后 import React from 'react'; function App() { return React.createElement(Button, null); } 
 -  
 -  
React 17+ 的新 JSX 转换
-  
自动引入运行时函数:
Babel 会将 JSX 转换为_jsx或_jsxs函数,这些函数从react/jsx-runtime自动导入,不再依赖全局React。// 代码(无需导入 React) function App() { return <Button />; }// 转换后 import { jsx as _jsx } from 'react/jsx-runtime'; function App() { return _jsx(Button, {}); } 
 -  
 -  
优势
- 代码更简洁:省略不必要的 
import React。 - 避免错误:消除因忘记导入 
React导致的React is not defined错误。 - 性能优化:新的 JSX 运行时可能生成更高效的代码(如编译时优化)。
 
 - 代码更简洁:省略不必要的 
 -  
注意事项
-  
类组件仍需导入
React:若使用class App extends React.Component,仍需导入React。 -  
直接使用 React API:如
useState、useEffect需从'react'导入,但无需引入整个React对象:import { useState } from 'react'; // ✅ 正确 // import React from 'react'; // ❌ 不再需要 
 -  
 
总结
- 旧方式:JSX → 
React.createElement→ 强制导入React。 - 新方式:JSX → 
_jsx(自动注入)→ 无需导入React。 - 升级条件:使用 React 17+ 和 Babel 7.9.0+(或相应工具链)。
 
59.说说 stack reconciler和fiber reconciler
在 React 中,Stack Reconciler 和 Fiber Reconciler 是两种不同的调和(Reconciliation)算法,主要区别在于性能优化和更新方式。
1. Stack Reconciler(旧版调和器)
特点:
- React 15 及之前使用的是 Stack Reconciler。
 - 采用 递归调用 组件树的方式进行协调(Reconciliation)。
 - 由于 JavaScript 引擎的调用栈大小有限,组件树过深时可能会导致 递归调用栈溢出。
 - 同步更新:一旦开始调和,就必须一次性完成整个更新任务,无法中断。
 
缺点:
- 递归调用导致大任务无法拆分,阻塞主线程,影响用户体验(如页面卡顿)。
 - 无法进行任务优先级调度,所有更新一视同仁。
 
2. Fiber Reconciler(新版调和器)
特点:
- React 16 及之后引入 Fiber Reconciler,完全重写了协调算法。
 - 采用 Fiber 数据结构,将组件树转换为一个可操作的链表结构,使调和过程变成 可中断、可恢复 的。
 - 时间切片(Time Slicing) :更新可以被拆分成多个小任务,并在浏览器的空闲时间继续执行,从而提高页面响应速度。
 - 任务优先级调度:React 可以根据更新的重要性(如用户输入 vs. 动画 vs. 数据加载)分配不同的优先级。
 
Fiber 工作原理:
-  
Render 阶段(可中断)
- 以 深度优先遍历 方式遍历 Fiber 树,创建新的 Fiber 节点,并标记需要更新的部分。
 - 这一步可以被 分片执行,允许浏览器在空闲时继续计算,避免卡顿。
 
 -  
Commit 阶段(不可中断)
-  
经过调度后,将最终的变更提交到 DOM 上,分为:
- beforeMutation(调用 
getSnapshotBeforeUpdate) - Mutation(更新 DOM)
 - Layout(触发 
componentDidMount和componentDidUpdate) 
 - beforeMutation(调用 
 
 -  
 
总结
| 对比项 | Stack Reconciler | Fiber Reconciler | 
|---|---|---|
| 数据结构 | 组件树(递归) | Fiber 链表(双缓冲) | 
| 调和方式 | 递归同步 | 可中断 & 分片调度 | 
| 性能 | 任务无法拆分,容易阻塞 | 任务可拆分,提升页面流畅度 | 
| 任务优先级 | 一视同仁 | 可分配不同优先级 | 
| React 版本 | React 15 及之前 | React 16 及之后 | 
Fiber Reconciler 解决了 Stack Reconciler 中的同步阻塞问题,使 React 可以更高效地渲染 UI,特别是在复杂应用和动画交互中带来了更好的用户体验。
你可以补充一些实际的 Fiber 调度 API(如 requestIdleCallback、scheduler 任务调度)来展示你的深入理解!
60.react中有哪几种数据结构,分别是干什么的?
在 React 源码中,主要涉及以下几种重要的数据结构,每种都有特定的用途:
1. Fiber 树(FiberNode)—— 组件调和
-  
作用:用于描述 React 组件树,并支持可中断的渲染更新。
 -  
数据结构:双向链表(单个 Fiber 节点有
child、sibling、return指针)。 -  
关键字段:
tag:表示当前 Fiber 节点的类型(如函数组件、类组件、DOM 元素等)。stateNode:存放与 Fiber 关联的 DOM 节点或组件实例。child、sibling、return:指向子节点、兄弟节点、父节点,形成 Fiber 树。alternate:指向前一次更新的 Fiber,形成 双缓存机制,用于 Diff 计算。
 
2. Update Queue(更新队列)—— 组件状态管理
-  
作用:存储组件的
state更新任务。 -  
数据结构:链表,存放多个
update对象(如setState触发的更新)。 -  
关键字段:
shared.pending:指向等待处理的更新。baseState:上一次计算的state值。memoizedState:本次计算的state值。effects:存放副作用(useEffect)的更新列表。
 
3. Effect List(副作用链表)—— 处理副作用
-  
作用:存储需要执行的副作用(
useEffect、componentDidMount、componentDidUpdate等)。 -  
数据结构:单向链表(所有需要执行副作用的 Fiber 形成链表)。
 -  
关键字段:
flags:标记当前 Fiber 节点的副作用类型,如Placement(插入)、Update(更新)、Deletion(删除)。nextEffect:指向下一个需要执行副作用的 Fiber 节点。
 
4. Lanes & Scheduler(优先级调度)—— 任务调度
-  
作用:控制 React 的并发更新,确保高优先级任务先执行。
 -  
数据结构:位运算(bitmask),类似二进制位的调度系统。
 -  
关键字段:
lanes:存储当前任务的优先级,React 16+ 通过 bitmask 进行任务分配。currentPriorityLevel:当前正在执行的任务优先级。pendingLanes:所有等待执行的任务。
 
5. Hook 链表(useState、useEffect)—— 函数组件状态管理
-  
作用:管理
useState、useReducer、useEffect等 Hook 状态。 -  
数据结构:单向链表,每个 Hook 形成链表节点。
 -  
关键字段:
memoizedState:存储 Hook 的当前值。next:指向下一个 Hook 节点。queue:存储setState触发的更新队列。
 
总结
| 数据结构 | 作用 | 关键字段 | 
|---|---|---|
| Fiber 树 | 组件调和(可中断更新) | tag、stateNode、child、sibling | 
| Update Queue | 管理 state 更新 | shared.pending、memoizedState | 
| Effect List | 处理副作用(useEffect) | flags、nextEffect | 
| Lanes & Scheduler | 任务优先级调度 | lanes、pendingLanes | 
| Hook 链表 | 函数组件状态管理 | memoizedState、next | 
61.说一下react的更新流程
React 的更新流程
React 的更新流程可以分为 触发更新、调和(Reconciliation)、提交(Commit) 三个阶段。React 采用 Fiber Reconciler 进行调和,使更新可以拆分并中断,提高渲染效率。
1. 触发更新(Trigger Update)
触发方式
更新可以由以下方式触发:
setState(类组件)useState/useReducer(函数组件)forceUpdateContext变化props变更Suspense触发回退 UI事件、定时器等外部触发
存入更新队列
- 每个 
Fiber节点都有一个 更新队列(Update Queue) ,当setState等触发更新时,新的update被加入队列。 React通过Lanes机制计算优先级,决定何时执行更新。
2. 调和(Reconciliation,Render 阶段)
- 工作原理:使用 Fiber 架构 遍历组件树,计算需要更新的部分。
 - 特点:这个阶段是 可中断的(React 18 中的并发模式利用 
requestIdleCallback和Scheduler进行时间切片)。 
过程
-  
生成新的 Fiber 树
- 通过 
workInProgress指向当前正在构建的新 Fiber 树。 - 通过 Diff 算法 对比新旧 Fiber 树,标记需要更新的节点。
 - 生成 
Effect List记录需要执行的副作用(如useEffect)。 
 - 通过 
 -  
任务调度
- React 使用 Scheduler(调度器) 分配更新优先级。
 - 高优先级(如用户输入)可以中断低优先级(如网络请求)。
 
 -  
计算更新
- 组件的 
render方法或函数组件被执行,返回新的VNode结构。 - 更新 
memoizedState,存储新的state计算结果。 
 - 组件的 
 
3. 提交(Commit 阶段,不可中断)
作用:将更新应用到 DOM,并执行副作用。
阶段
-  
Before Mutation(更新前)
- 触发 
getSnapshotBeforeUpdate - 记录旧 DOM 信息
 
 - 触发 
 -  
Mutation(更新 DOM)
- 遍历 
Effect List,执行Placement(插入)、Update(更新)、Deletion(删除) - React 直接操作 DOM 进行渲染
 
 - 遍历 
 -  
Layout(更新后)
- 触发 
componentDidMount、componentDidUpdate - 执行 
useEffect、useLayoutEffect - 组件状态更新完成
 
 - 触发 
 
4. 重点总结
| 阶段 | 作用 | 关键点 | 
|---|---|---|
| 触发更新 | 组件 setState、props 变化 | Update Queue、Lanes 机制 | 
| 调和(Render) | 计算更新部分,构建新 Fiber 树 | Diff 算法、Effect List | 
| 提交(Commit) | 更新 DOM,执行副作用 | Mutation、useEffect、生命周期 | 
优化点
-  
减少不必要的渲染
React.memouseMemo/useCallbackshouldComponentUpdate
 -  
避免 Reconciliation
useRef保存不变数据PureComponent避免无效更新
 -  
提高并发性能
startTransition(低优先级更新)useDeferredValue(减少重新渲染)
 
理解 React 的更新流程 及 优化策略,可以更高效地调试和优化应用,提高面试通过率!
62.什么是闭包陷阱?
闭包陷阱(Closure Trap)
闭包(Closure) 是 JavaScript 重要的特性之一,它允许函数“记住”其定义时的作用域。然而,使用不当时,会导致 性能问题、变量引用错误、意外的内存泄漏 等问题,这些问题被称为 闭包陷阱(Closure Trap) 。
常见闭包陷阱
1. 循环中的闭包(var 作用域问题)
📌 问题:var 没有块级作用域,导致所有回调函数共享同一个变量,最终 i 变成了 3。
for (var i = 0; i < 3; i++) {setTimeout(() => {console.log(i); // 3, 3, 3}, 1000);
}
 
✅ 解决方案:
- 使用 
let使i形成块级作用域 - 使用 立即执行函数表达式(IIFE) 传递 
i 
// 方案1:使用 let
for (let i = 0; i < 3; i++) {setTimeout(() => {console.log(i); // 0, 1, 2}, 1000);
}// 方案2:使用 IIFE
for (var i = 0; i < 3; i++) {(function (i) {setTimeout(() => {console.log(i); // 0, 1, 2}, 1000);})(i);
}
 
2. 闭包导致内存泄漏
📌 问题:闭包中的变量被长时间引用,导致无法被垃圾回收。
function createClosure() {let largeData = new Array(1000000); // 占用大量内存return function () {console.log(largeData.length);};
}const closureFn = createClosure(); // `largeData` 无法被回收
 
✅ 解决方案:
- 显式置 
null,释放引用 - 仅在必要时使用闭包
 
function createClosure() {let largeData = new Array(1000000);return function () {console.log(largeData.length);largeData = null; // 释放内存};
}
 
3. 事件监听中的闭包
📌 问题:事件监听中使用闭包,导致 DOM 变量无法被回收。
function attachEvent() {const element = document.getElementById("btn");element.addEventListener("click", function () {console.log(element.id);});
}
attachEvent(); // `element` 仍然在闭包中,无法回收
 
✅ 解决方案:
- 解绑事件监听
 - 使用 
this代替闭包 
function attachEvent() {const element = document.getElementById("btn");element.addEventListener("click", function () {console.log(this.id); // 这里 `this` 指向 `element`});element = null; // 释放引用
}
 
总结
| 闭包陷阱 | 原因 | 解决方案 | 
|---|---|---|
| 循环中的闭包 | var 作用域导致变量共享 | 使用 let 或 IIFE | 
| 内存泄漏 | 变量被闭包长时间引用 | 置 null 释放引用 | 
| 事件监听泄漏 | DOM 节点被闭包引用 | 解绑事件监听,使用 this | 
63.闭包陷阱的成因与解法?闭包陷阱的成因与解法?
闭包陷阱的成因与解法
闭包(Closure) 是 JavaScript 中的一个强大特性,它允许函数“记住”创建它时的作用域。然而,如果使用不当,闭包可能导致 变量引用错误、内存泄漏、性能问题,这些问题统称为 闭包陷阱(Closure Trap) 。
1. 闭包陷阱的成因
(1) 变量共享(作用域链问题)
📌 成因:
 在 for 循环中使用 var 时,var 变量在 函数作用域 内是共享的,因此闭包函数访问的是最终的 var 变量值,而不是循环时的 i 值。
🚨 问题示例:
for (var i = 0; i < 3; i++) {setTimeout(() => {console.log(i); // 输出 3, 3, 3}, 1000);
}
 
所有 setTimeout 共享同一个 i,在 for 循环结束时 i = 3,所以 console.log(i) 输出 3, 3, 3。
✅ 解法:
- 使用 
let(块级作用域) 
for (let i = 0; i < 3; i++) {setTimeout(() => {console.log(i); // 0, 1, 2}, 1000);
}
 
let 使 i 在每次循环时都有自己的作用域,不会共享。
- 使用 IIFE(立即执行函数表达式)
 
for (var i = 0; i < 3; i++) {(function (i) {setTimeout(() => {console.log(i); // 0, 1, 2}, 1000);})(i);
}
 
IIFE 创建了一个独立作用域,每次循环 i 值都会被“冻结”在该作用域中。
(2) 闭包导致的内存泄漏
📌 成因:
 闭包函数持有对 大对象或 DOM 元素 的引用,导致变量不会被垃圾回收(GC)。
🚨 问题示例:
function createClosure() {let largeData = new Array(1000000); // 大量数据return function () {console.log(largeData.length);};
}
const closureFn = createClosure(); // `largeData` 仍然存活
 
largeData 被 closureFn 持有,无法被垃圾回收,造成 内存泄漏。
✅ 解法:
- 在不需要时手动释放引用
 
function createClosure() {let largeData = new Array(1000000);return function () {console.log(largeData.length);largeData = null; // 释放引用};
}
 
(3) 事件监听中的闭包陷阱
📌 成因:
 闭包函数引用了 DOM 元素,但未及时移除事件监听,导致 DOM 对象无法被回收。
🚨 问题示例:
function attachEvent() {const element = document.getElementById("btn");element.addEventListener("click", function () {console.log(element.id); // `element` 仍然被引用});
}
attachEvent();
 
element 被闭包引用,即使 btn 被移除,内存仍然无法回收。
✅ 解法:
- 解绑事件监听
 
function attachEvent() {const element = document.getElementById("btn");const handler = function () {console.log(element.id);};element.addEventListener("click", handler);element.removeEventListener("click", handler); // 解绑监听
}
 
- 使用 
this代替闭包 
function attachEvent() {const element = document.getElementById("btn");element.addEventListener("click", function () {console.log(this.id); // `this` 指向 `element`});
}
 
这样不会在闭包内存储 element,减少引用链。
(4) 计时器 & 异步回调
📌 成因:
 setTimeout 和 setInterval 可能导致闭包持有变量的引用,导致 变量长时间存活。
🚨 问题示例:
function startTimer() {let data = "重要数据";setTimeout(() => {console.log(data); // `data` 长时间存活}, 5000);
}
startTimer();
 
✅ 解法:
- 手动释放数据
 
function startTimer() {let data = "重要数据";let timer = setTimeout(() => {console.log(data);data = null; // 释放数据}, 5000);
}
startTimer();
 
- 清除计时器
 
let timer = setTimeout(() => console.log("执行任务"), 5000);
clearTimeout(timer); // 及时清理
 
2. 闭包陷阱的总结
| 闭包陷阱 | 成因 | 解决方案 | 
|---|---|---|
| 变量共享 | var 作用域导致变量被共享 | 使用 let 或 IIFE | 
| 内存泄漏 | 变量被闭包长时间引用 | 置 null 释放引用 | 
| 事件监听泄漏 | DOM 元素被闭包引用 | 解绑事件监听,使用 this | 
| 计时器/回调 | 定时器 & 回调函数持有变量 | 手动释放数据,清除计时器 | 
64.在react中,如何实现渲染控制?
React 中的渲染控制(Render Control)
在 React 中,渲染控制指的是 有选择性地渲染组件,以提高性能或优化用户体验。可以使用 条件渲染、列表渲染、优化渲染性能 等多种方式来实现。
1. 条件渲染(Conditional Rendering)
1.1 三元运算符
function Greeting({ isLoggedIn }) {return (<div>{isLoggedIn ? <h1>欢迎回来!</h1> : <h1>请登录</h1>}</div>);
}
 
1.2 && 短路运算
 
function ShowMessage({ hasMessage }) {return <div>{hasMessage && <p>您有新消息</p>}</div>;
}
 
⚠️ 注意:当
hasMessage为false或0时,不会渲染<p>。
1.3 if-else 语句
 
function Greeting({ isLoggedIn }) {if (isLoggedIn) {return <h1>欢迎回来!</h1>;} else {return <h1>请登录</h1>;}
}
 
2. 列表渲染
使用 map() 动态生成 JSX 元素
function UserList({ users }) {return (<ul>{users.map((user) => (<li key={user.id}>{user.name}</li>))}</ul>);
}
 
⚠️ 注意:必须给每个列表项加上唯一的 key,否则 React 可能会导致渲染错误或性能下降。
3. 避免不必要的渲染
3.1 React.memo()(浅比较 props)
 
适用于:纯函数组件,当 props 没有变化时跳过重新渲染。
const MemoizedComponent = React.memo(function MyComponent({ name }) {console.log("渲染了!"); // 仅当 `name` 变化时触发return <p>你好,{name}!</p>;
});
 
3.2 shouldComponentUpdate()(类组件)
 
适用于:类组件,可手动控制组件是否重新渲染。
class MyComponent extends React.PureComponent {shouldComponentUpdate(nextProps) {return nextProps.value !== this.props.value; // 仅当 props 变化时更新}render() {return <div>{this.props.value}</div>;}
}
 
PureComponent继承shouldComponentUpdate(),默认对props进行浅比较。
3.3 useMemo() & useCallback()(函数组件)
 
useMemo():缓存计算结果,避免重复计算useCallback():缓存函数引用,避免组件重新渲染时创建新函数
const memoizedValue = useMemo(() => computeExpensiveValue(data), [data]);
const memoizedCallback = useCallback(() => doSomething(), []);
 
4. 组件卸载(移除不必要的组件)
4.1 通过 state 控制组件的挂载
 
function App() {const [show, setShow] = useState(true);return (<div><button onClick={() => setShow(!show)}>切换组件</button>{show && <MyComponent />}</div>);
}
 
⚠️ 注意:当组件被卸载时,会触发 useEffect 的清理函数(return () => {...})。
5. 使用 Suspense 和 lazy 实现懒加载
 
React 允许对 组件进行懒加载,提升首屏渲染性能。
const LazyComponent = React.lazy(() => import("./LazyComponent"));function App() {return (<Suspense fallback={<div>加载中...</div>}><LazyComponent /></Suspense>);
}
 
Suspense提供fallback组件,在组件加载时展示占位内容。
总结
| 渲染控制方法 | 用途 | 示例 | 
|---|---|---|
| 条件渲染 | 仅在满足条件时渲染 | if-else、&&、三元运算符 | 
| 列表渲染 | 动态渲染数组数据 | map() | 
| 避免重复渲染 | 仅当数据变更时重新渲染 | React.memo()、useMemo()、shouldComponentUpdate() | 
| 控制组件挂载 | 组件按需显示/隐藏 | useState 控制组件是否渲染 | 
| 懒加载 | 按需加载组件,提高性能 | React.lazy() + Suspense | 
65.如何实现一个redux?
实现一个 Redux
Redux 是一个 状态管理库,核心思想是 单一数据源(Store)、纯函数(Reducer)和不可变状态。下面我们手写一个简化版 Redux,并解析其实现原理。
1. Redux 的核心概念
store(仓库) :存储应用的全局状态state(状态) :存储的数据action(动作) :描述对state进行的操作(必须是一个普通对象)reducer(纯函数) :根据action计算新的statedispatch(派发) :触发action,通知reducer更新statesubscribe(订阅) :监听state变化,触发回调
2. 手写 Redux
📌 核心代码
function createStore(reducer) {let state; // 存储状态let listeners = []; // 存储订阅者// 获取当前状态function getState() {return state;}// 触发 action,更新 statefunction dispatch(action) {state = reducer(state, action); // 计算新 statelisteners.forEach(listener => listener()); // 通知所有订阅者}// 订阅 state 变化function subscribe(listener) {listeners.push(listener); // 添加订阅者return () => {listeners = listeners.filter(l => l !== listener); // 取消订阅};}// 初始化 store,触发一次 reducer,获取初始值dispatch({ type: "@@INIT" });return { getState, dispatch, subscribe };
}
 
3. 使用 createStore
 
(1) 定义 reducer
 
function counterReducer(state = { count: 0 }, action) {switch (action.type) {case "INCREMENT":return { count: state.count + 1 };case "DECREMENT":return { count: state.count - 1 };default:return state;}
}
 
(2) 创建 store
 
const store = createStore(counterReducer);
 
(3) 订阅状态变化
store.subscribe(() => {console.log("State changed:", store.getState());
});
 
(4) 触发 action 更新 state
 
store.dispatch({ type: "INCREMENT" }); // State changed: { count: 1 }
store.dispatch({ type: "INCREMENT" }); // State changed: { count: 2 }
store.dispatch({ type: "DECREMENT" }); // State changed: { count: 1 }
 
4. 实现 Redux 中间件机制(类似 applyMiddleware)
 
Middleware(中间件)用于增强 dispatch,例如:日志、异步操作等。
function applyMiddleware(store, middlewares) {let dispatch = store.dispatch;middlewares.forEach(middleware => {dispatch = middleware(store)(dispatch);});return { ...store, dispatch };
}// 示例:日志中间件
const loggerMiddleware = store => next => action => {console.log("Action:", action);let result = next(action);console.log("New State:", store.getState());return result;
};// 创建 store 并应用中间件
const storeWithMiddleware = applyMiddleware(store, [loggerMiddleware]);storeWithMiddleware.dispatch({ type: "INCREMENT" });
 
5. Redux 的异步处理(Thunk 实现)
Redux 本身是同步的,要处理异步操作(如 API 请求),可以使用 redux-thunk。
实现 thunkMiddleware
const thunkMiddleware = store => next => action => {if (typeof action === "function") {return action(store.dispatch, store.getState);}return next(action);
};// 使用 thunkMiddleware
const storeWithThunk = applyMiddleware(store, [thunkMiddleware]);// 异步 action
const asyncIncrement = () => (dispatch) => {setTimeout(() => {dispatch({ type: "INCREMENT" });}, 1000);
};// 触发异步 action
storeWithThunk.dispatch(asyncIncrement());
 
6. 总结
| 功能 | 核心 API | 作用 | 
|---|---|---|
| 创建 Store | createStore(reducer) | 创建全局状态存储 | 
| 获取状态 | getState() | 获取当前 state | 
| 更新状态 | dispatch(action) | 触发 reducer,计算新 state | 
| 订阅变化 | subscribe(listener) | 监听状态变化 | 
| 支持中间件 | applyMiddleware() | 增强 dispatch,添加日志、异步支持等 | 
🔹 通过 createStore 创建 Redux,结合 middleware 处理异步,最终实现一个完整的 Redux! 🚀
66.useRoutes的原理是什么?
白话回答:useRoutes是一个hook,原理是将声明式路由配置(json的路由表)转化为动态的路由匹配与组件渲染,通过 React Router 的上下文和内部匹配算法,实现 URL 到组件的映射。其中使用到matchRoutes和
 useLocation将从浏览器url中获取到的路由和json中进行match匹配,并将匹配到的路由对应的组件进行映射渲染
 匹配结果通过 renderMatches(matches) 进行递归渲染:
- 从最外层父路由开始,逐层渲染匹配的 
element。 - 递归传递子路由 通过 
Outlet组件完成嵌套渲染。 
useRoutes 是 React Router v6 中引入的一个核心钩子,用于在函数式组件中声明式地定义路由配置。它的原理可以拆解为以下几个关键点:
1. 基于配置的路由匹配
useRoutes 的核心是将路由配置转化为 React Router 内部的路由匹配逻辑。它接受一个路由配置对象数组(类似 Route 组件的结构化数据),例如:
const routes = [{ path: '/', element: <Home /> },{ path: 'users', element: <Users />, children: [{ path: ':id', element: <UserProfile /> }]}
];
 
useRoutes(routes) 会解析当前 URL,按配置的路径(path)和嵌套关系(children)进行匹配,最终确定需要渲染的组件。
2. 依赖 React Router 的上下文
useRoutes 必须工作在 React Router 的上下文(<BrowserRouter> 或 <HashRouter>)中,因为它依赖以下机制:
- 路由状态:通过 
useLocation()获取当前 URL 的location对象。 - 匹配算法:使用 
matchPath()等内部方法,将location.pathname与配置的path进行模式匹配(支持动态参数、通配符等)。 - 嵌套路由:通过递归处理 
children配置,构建嵌套的路由层级结构。 
3. 生成 React 元素
useRoutes 的返回值是一个 React 元素(或 null),代表当前 URL 匹配到的组件树。例如:
- 当 URL 为 
/users/123时,useRoutes会依次匹配users和:id,最终返回<Users><UserProfile /></Users>的嵌套结构。 - 若未匹配到路由,返回 
null(通常需要配置path: '*'作为兜底路由)。 
4. 动态参数与状态传递
- 参数解析:动态路径(如 
:id)的参数会被提取,并通过useParams()传递给组件。 - 状态传递:
useRoutes自动处理路由状态(如location.state),确保子组件能通过useLocation()获取。 
5. 性能优化
useRoutes 内部通过 Memoization 优化路由匹配过程,避免不必要的重新渲染。只有当 location 或路由配置变化时,才会重新计算匹配结果。
源码简析(简化版)
useRoutes 的核心逻辑类似于以下伪代码:
function useRoutes(routes) {const location = useLocation();// 递归匹配路由,生成匹配结果const matches = matchRoutes(routes, location);// 将匹配结果转换为 React 元素(嵌套结构)return renderMatches(matches);
}
 
其中:
matchRoutes():遍历路由配置,找到与当前location匹配的路由。renderMatches():将匹配的路由按嵌套关系渲染为 React 元素。
总结
可回答 useRoutes的原理应该是将路由配置对象转换为React元素,利用React Router的上下文和路由匹配机制,动态地根据当前URL决定渲染哪个组件。内部可能使用React的context API来传递路由状态,并通过匹配算法找到对应的路由配置,然后生成相应的组件结构。
useRoutes 的原理是将声明式路由配置转化为动态的路由匹配与组件渲染,通过 React Router 的上下文和内部匹配算法,实现 URL 到组件的映射。它简化了路由定义,同时保持了与 React 组件模型的深度集成。
67.react18中,为什么选择messagechannel来让出执行权?
React在更新时需要进行任务调度,确保高优先级的任务优先执行,同时不阻塞主线程,保持应用的响应性。 在React 18之前,可能使用的是requestIdleCallback或者setTimeout来进行任务调度。但requestIdleCallback的兼容性和触发时机可能不够可靠,而setTimeout有最小延迟时间4ms,可能不够及时。所以React需要一种更高效的方式来调度任务。 MessageChannel属于宏任务,它的回调会在当前事件循环的末尾执行,这样可以让出主线程,让浏览器有机会处理用户输入等紧急任务。相比setImmediate,MessageChannel的兼容性更好,而相比requestAnimationFrame,它更适合处理非渲染相关的任务。 另外,React可能希望更精细地控制任务的调度,使用MessageChannel可以更主动地安排任务的执行时机,避免被其他宏任务阻塞。同时,结合Scheduler包,React能够实现时间切片和任务的中断恢复,提升并发模式下的性能。
 在 React 18 中,选择 MessageChannel 作为调度器(Scheduler)中让出主线程(Yield to Main Thread)的核心机制,是为了实现更精细的时间切片(Time Slicing)和并发渲染(Concurrent Rendering) ,其背后的设计原理可以拆解如下:
1. 为什么需要让出执行权?
React 的并发模式需要将长任务拆分为可中断的微任务块,避免阻塞主线程导致页面卡顿(如点击事件、动画无法及时响应)。传统同步渲染模式下,组件树的渲染是连续不可中断的,而并发模式下需要主动让出主线程,让浏览器有机会处理更高优先级的任务。
2. MessageChannel 的核心优势
a. 宏任务(Macro Task)的特性
MessageChannel 的回调属于宏任务,其执行时机在当前事件循环的末尾,与微任务(如 Promise)相比:
- 更可控的让出时机:宏任务会在浏览器完成当前任务(如渲染、事件处理)后执行,确保主线程有喘息机会。
 - 避免微任务的饥饿问题:微任务队列会一次性全部执行,若递归触发微任务可能导致主线程长期被占用。
 
b. 对比其他宏任务方案
setTimeout(fn, 0):存在最小延迟(~4ms) ,且可能被浏览器节流(如后台标签页)。requestAnimationFrame:与渲染帧强绑定,不适合非渲染相关的任务调度。setImmediate:仅 IE/Node.js 支持,兼容性差。MessageChannel:无延迟、无兼容性问题(主流浏览器均支持)、触发时机精准。
3. React 调度器的实现逻辑
React 的调度器(Scheduler)使用 MessageChannel 实现任务队列的调度,核心流程如下:
- 任务入队:将 React 的渲染任务(如组件更新)加入调度队列。
 - 触发宏任务:通过 
MessageChannel.port.postMessage触发一个宏任务。 - 执行任务块:在宏任务回调中,执行一段任务(如 5ms 的时间切片)。
 - 让出主线程:若任务超时,将剩余任务重新入队,等待下一个宏任务继续执行。
 
// 简化的调度逻辑
const channel = new MessageChannel();
channel.port2.onmessage = () => {// 执行任务块,若超时则让出主线程if (needsYield()) {channel.port1.postMessage(null); // 触发下一个宏任务return;}processNextTask();
};
 
4. 与浏览器事件循环的协同
通过 MessageChannel,React 的调度器能够:
- 精准控制任务中断点:在每段任务执行后检查是否需要让出主线程。
 - 避免阻塞用户交互:即使渲染未完成,点击、滚动等事件也能在下一个事件循环中及时响应。
 - 兼容时间切片:实现类似“可中断渲染”的效果,为并发模式(Concurrent Mode)提供基础。
 
5. 性能与兼容性权衡
- 性能:MessageChannel 的触发速度比 
setTimeout更快,且不受浏览器节流影响。 - 兼容性:MessageChannel 在 IE11 及现代浏览器中均可使用,而 
setImmediate或requestIdleCallback存在兼容性问题。 
6. 源码中的关键代码
在 React 源码的 Scheduler 模块中,以下代码体现了 MessageChannel 的使用:
// React Scheduler 中的调度逻辑
const performWorkUntilDeadline = () => {// 执行任务...if (hasMoreWork) {// 通过 MessageChannel 调度下一个任务块schedulePerformWorkUntilDeadline();}
};const channel = new MessageChannel();
channel.port1.onmessage = performWorkUntilDeadline;// 触发任务调度
schedulePerformWorkUntilDeadline = () => {channel.port2.postMessage(null);
};
 
总结
React 18 选择 MessageChannel 作为让出执行权的机制,是因为它结合了宏任务的非阻塞特性、精准的触发时机和良好的兼容性,使得 React 能够在并发模式下实现高效的时间切片,平衡渲染性能与用户体验。这是 React 调度器设计中的关键决策,为复杂应用的流畅运行奠定了基础。
 微任务和宏任务的区别,为什么选择宏任务而不是微任务。微任务会在当前任务结束后立即执行,可能导致长时间占用主线程,而宏任务可以让出主线程,让浏览器有机会处理其他事件。
 React 18选择MessageChannel可能是因为它作为宏任务能够有效让出主线程,提供更可靠的调度时机,同时兼容性较好,适合实现时间切片和并发模式的需求。
5. MessageChannel vs. setTimeout vs. requestIdleCallback
 
| API | 执行时间 | 适用场景 | React 适配 | 
|---|---|---|---|
setTimeout(fn, 0) | 4ms 延迟(最小值) | 低优先级任务 | ❌ 太慢 | 
requestAnimationFrame | 下一帧执行 | 动画、布局计算 | ❌ 不能保证及时执行 | 
requestIdleCallback | 浏览器空闲时执行 | 后台任务 | ❌ 不适用于高优先级任务 | 
MessageChannel | 微任务队列,尽快执行 | 任务调度、React 并发渲染 | ✅ 最优选择 | 
69.RN和react在跨端架构上有什么区别?
React 和 React Native(RN)在跨端架构上的主要区别可以从以下几个方面来分析:
1. 渲染机制
React(Web 端)
- React 主要运行在浏览器,通过 Virtual DOM (VDOM) 进行高效的 UI 更新。
 - 使用 
ReactDOM.render()渲染组件,最终通过 HTML + CSS 在浏览器中显示。 - 依赖 CSS、HTML、JavaScript,主要针对 Web 平台。
 
示例
function App() {return <div>Hello, Web!</div>;
}
 
最终会被转换成:
<div>Hello, Web!</div>
 
React Native(移动端)
- 不使用 DOM,而是使用 Native 组件(如 iOS 的 
UIView、Android 的View)。 - 使用 React Native Bridge 将 JavaScript 调用转换为原生渲染指令。
 - 组件如 
<View>、<Text>会在 iOS 和 Android 上映射到原生组件。 
示例
function App() {return <View><Text>Hello, Mobile!</Text></View>;
}
 
在 iOS 上会被转换为:
UIView -> UILabel("Hello, Mobile!")
 
在 Android 上会被转换为:
View -> TextView("Hello, Mobile!")
 
✅ 核心区别:
- React 依赖 DOM,使用 HTML/CSS。
 - RN 直接调用原生组件,不使用 HTML,而是通过 桥接(Bridge) 与原生交互。
 
2. 样式与布局
React (Web)
- 使用 CSS 进行样式布局。
 - 采用 Flexbox(但实现和 RN 有些不同)。
 - 依赖 
px、rem、em、vh、vw等单位。 
.container {display: flex;justify-content: center;align-items: center;
}
 
React Native
- 没有 CSS,只能用 
StyleSheet定义样式。 - 所有样式基于 Flexbox(不支持 
grid、float)。 - 单位默认为 dp(独立像素) ,不使用 
px。 
const styles = StyleSheet.create({container: {flex: 1,justifyContent: 'center',alignItems: 'center',}
});
 
✅ 核心区别:
- React 用 CSS,支持多种布局方式。
 - RN 只支持 Flexbox,并使用 
StyleSheet定义样式。 
3. 事件处理
React (Web)
- 依赖 DOM 事件,比如 
onClick、onMouseMove。 - 事件是合成事件(Synthetic Event) ,在 
document级别进行事件代理。 
<button onClick={() => alert('Clicked!')}>Click Me</button>
 
React Native
- 没有 DOM,所以事件是直接绑定在原生组件上。
 - 主要事件有 
onPress(代替onClick)、onTouchStart、onScroll等。 
<TouchableOpacity onPress={() => alert('Clicked!')}><Text>Click Me</Text>
</TouchableOpacity>
 
✅ 核心区别:
- React 使用 DOM 事件,React Native 直接绑定到原生事件。
 - RN 没有 
onClick,而是onPress。 
4. 跨端运行方式
React(Web 端)
- 只在浏览器中运行,不需要 
Bridge。 - 使用 ReactDOM 渲染,最终依赖 HTML + CSS + JavaScript。
 
React Native(移动端)
-  
需要一个 JavaScript 线程 + 原生线程。
 -  
通过 React Native Bridge 进行通信:
- JS 线程 运行 React 代码。
 - Bridge 传输数据(JS <-> Native)。
 - Native 线程 负责渲染 UI。
 
 
✅ 核心区别:
- React 直接在浏览器运行,React Native 需要 Bridge 连接 JS 和 Native 代码。
 
5. 跨端兼容
React(Web 跨端)
- 主要依赖 React Native Web,将 RN 组件转换为 HTML + CSS 组件。
 - Web 端用 
react-router-dom进行路由管理。 
import { BrowserRouter as Router, Route } from 'react-router-dom';
 
React Native(移动端跨端)
- 主要依赖 React Navigation,提供 
StackNavigator和TabNavigator。 
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
 
✅ 核心区别:
- React 依赖 
react-router-dom进行 Web 路由。 - RN 依赖 
react-navigation进行移动端导航。 
6. 代码复用性
React
- 主要针对 Web,跨端需要借助 React Native Web 或 Electron。
 
React Native
- 通过 
Platform进行 按平台渲染: 
import { Platform } from 'react-native';const styles = StyleSheet.create({container: {padding: Platform.OS === 'ios' ? 20 : 10}
});
 
- 可以使用 
react-native-web让 RN 代码在 Web 端运行。 
✅ 核心区别:
- React 不能直接用于移动端,React Native 代码可以通过 
react-native-web兼容 Web。 
总结
| 维度 | React(Web) | React Native(移动端) | 
|---|---|---|
| 渲染方式 | DOM + Virtual DOM | 原生组件 + Bridge | 
| 样式 | CSS | StyleSheet(无 CSS) | 
| 事件 | onClick、onMouseMove | onPress、onTouchStart | 
| 运行环境 | 浏览器 | iOS/Android | 
| 跨端能力 | 需要 React Native Web | 直接支持 iOS 和 Android | 
| 代码复用 | 主要针对 Web | 可通过 Platform 区分平台 | 
✅ 核心结论
- React 面向 Web,依赖 HTML + CSS + JS,而 RN 面向移动端,依赖原生组件。
 - React Native 使用 
MessageQueue进行 JS-Native 通信,而 React 直接运行在浏览器。 - React 代码可通过 
react-native-web兼容 RN,但 RN 代码比 Web 更通用。 
🚀 面试官可能会追问:
-  
RN 如何优化 Bridge 速度?
- 通过 Fabric(新架构)+ TurboModule 提高原生通信效率。
 
 -  
RN 的
Hermes引擎是什么?Hermes是一个优化的 JS 引擎,提高 RN 启动速度。
 -  
Web 和 RN 组件能共享吗?
- 可以,使用 
react-native-web实现跨端共享。 
 - 可以,使用 
 
总结一句话:React 适用于 Web,React Native 适用于移动端,RN 通过 Bridge 实现 JS 和原生交互,二者在跨端架构上有本质区别! 🚀
70.常见的redux中间件有哪些
Redux 常见的中间件主要用于 异步处理、日志记录、调试工具、错误处理 等,
- redux-thunk(异步 Action 处理)
 - redux-saga(更强大的异步流控制)
 - redux-logger(日志中间件)
 - redux-devtools-extension(开发者工具)
 - redux-promise(Promise 处理)
作用:基于 ES6 Generator 实现的异步流管理,比redux-thunk更适合 复杂的异步逻辑(如监听多个 action、并发请求、取消任务等)。 
