三合一网站建站,ip分享网站,北京论坛建站模板,wordpress 4.8 主题36.Fiber的更新机制
React Fiber 更新机制详解
React Fiber 是 React 16 引入的核心架构重构#xff0c;旨在解决可中断渲染和优先级调度问题#xff0c;提升复杂应用的流畅性。其核心思想是将渲染过程拆分为可控制的工作单元#xff0c;实现更细粒度的任务管理。以下是其…36.Fiber的更新机制
React Fiber 更新机制详解
React Fiber 是 React 16 引入的核心架构重构旨在解决可中断渲染和优先级调度问题提升复杂应用的流畅性。其核心思想是将渲染过程拆分为可控制的工作单元实现更细粒度的任务管理。以下是其核心机制 一、Fiber 架构的设计目标
可中断与恢复允许渲染过程被高优先级任务如用户输入打断后续恢复。增量渲染将渲染任务拆分为多个小任务时间分片避免阻塞主线程。优先级调度根据任务类型如动画、数据加载分配不同优先级。并发模式支持为 Suspense、Transition 等特性提供底层支持。 二、Fiber 节点工作单元的基础
每个 Fiber 节点对应一个组件或 DOM 节点构成链表树结构包含以下关键信息 组件类型函数/类组件、HTML 标签等。 状态与 Propsstate、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。HooksuseLayoutEffect 回调。 切换当前树将 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. 新的根APIcreateRoot 替换旧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 /
/Suspense5. 新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. 过渡APITransitions 区分紧急/非紧急更新通过 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 namename /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 (DocumentSuspense fallback{Loading /}AsyncContent //Suspense/Document);
}2. 资源处理优化
样式表支持
解决问题简化样式管理
自动处理样式表加载顺序支持组件级样式声明
function Component() {return (link relstylesheet hrefstyles.css precedencedefault /div classNamestyled-content.../div/);
}异步脚本支持
解决问题优化脚本加载
自动处理脚本去重优化加载优先级
function MyComponent() {return (divscript async{true} srcwidget.js /divWidget 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{/* 服务器组件可以渲染客户端组件 */}ExpandableNotesList notes{notes} //Expandable/div);
}// 客户端组件
use client
function Expandable({ children }) {const [expanded, setExpanded] useState(false);return (divbutton 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 (divh1{note.title}/h1Suspense 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 useRefHTMLInputElement(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 (articleh1{post.title}/h1Suspense fallback{AuthorSkeleton /}Author authorPromise{authorPromise} //SuspenseContent{post.content}/ContentSuspense 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.stateReact 就无法知道哪些部分发生了变化也就无法进行智能的 diff 和批量更新。例如
this.state.count 10; // 直接修改
this.setState({ count: 10 }); // 通过 setState 更新在 setState() 中React 会比较前后的状态判断是否需要重新渲染组件而直接修改 this.state 则无法触发这种比较。 4. 组件的生命周期和渲染
this.setState() 触发状态更新时React 会在合适的生命周期方法中触发组件的重新渲染。例如在状态更新时shouldComponentUpdate、componentDidUpdate 等生命周期方法会被调用以便开发者可以在状态变化时执行一些操作。如果直接修改 this.stateReact 不会知道组件状态发生变化进而不会触发这些生命周期方法。
这会导致一些问题比如无法对比新旧状态、无法做条件渲染等操作。 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 可以批量处理多个状态更新提升性能。生命周期管理通过 setStateReact 能够触发生命周期钩子确保组件正确更新。性能优化this.setState() 可以通过智能的差异化更新减少不必要的 DOM 操作而直接修改 this.state 会跳过这一过程。
因此直接修改 this.state 会破坏 React 的更新机制导致状态和 UI 不一致影响性能和可维护性。React 的设计理念和架构要求我们通过 this.setState() 来更新状态从而确保更高效和可预测的渲染过程。
40.原来的react虚拟dom树遍历是深度优先遍历还是层序遍历存储是链表存储还是栈存储fiber机制下的是如何存储
React 虚拟DOM遍历与存储机制详解 一、传统虚拟DOMReact 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架构的核心改进
维度传统虚拟DOMFiber架构遍历控制递归不可中断迭代可中断 恢复数据结构隐式调用栈显式链表child/sibling/return任务调度同步执行优先级调度 时间分片性能优化易阻塞主线程增量渲染避免卡顿扩展能力有限支持并发模式Suspense/Transition 四、Fiber遍历流程示例
假设组件树结构为
A
├─ B
│ ├─ C
│ └─ D
└─ E└─ F遍历顺序
进入A → 处理A进入A.child B → 处理B进入B.child C → 处理CC无子节点 → 完成C回溯到B进入B.sibling D → 处理DD无子节点 → 完成D回溯到B → 完成B回溯到A进入A.sibling E → 处理E进入E.child F → 处理FF无子节点 → 完成F回溯到E → 完成E回溯到A → 完成A 五、Fiber架构的优势
可中断渲染高优先级任务如用户输入可打断低优先级渲染。增量更新将渲染任务拆分为多个帧执行避免主线程阻塞。精准副作用提交通过 flags 标记变更一次性提交DOM操作。并发模式支持实现服务端渲染流式输出、Suspense等高级特性。 总结
传统虚拟DOM深度优先遍历 递归调用栈简单但不可中断。Fiber架构深度优先遍历 显式链表结构通过迭代实现可中断渲染结合优先级调度与双缓存机制为React带来革命性性能提升与扩展能力。核心价值将同步渲染转化为异步可调度任务使复杂应用保持流畅交互。
41.React如何创建工程环境jstseject的作用是什么 一、创建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或.tsCRA会自动识别并配置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的标签。 总结
核心HooksuseState、useEffect、useContext、useReducer、useRef。性能优化useMemo、useCallback。高级场景useLayoutEffect、useImperativeHandle。自定义Hook封装可复用的逻辑如useFetch。
43.React中函数式组件如何模拟生命周期
类组件生命周期函数式组件实现方式componentDidMountuseEffect 空依赖数组componentDidUpdateuseEffect 监听依赖项componentWillUnmountuseEffect 的清理函数shouldComponentUpdateReact.memo 或 useMemogetDerivedStateFromPropsuseState 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] useStateApiData | 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;}
}函数组件 useRef Hook 创建可持久化的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中释放资源。 总结
场景方法类组件获取DOMReact.createRef()函数组件获取DOMuseRef动态绑定DOM回调Ref函数形式访问子组件DOMReact.forwardRef控制子组件暴露的实例useImperativeHandle forwardRef
47.React什么是状态提升子父组件如何通信
在React中状态提升State Lifting和子父组件通信是组件间数据交互的核心机制 1. 状态提升State Lifting
概念
状态提升是指将多个子组件需要共享的状态State移动到它们最近的共同父组件中通过props向下传递数据再通过回调函数让子组件通知父组件更新状态。这种方式遵循单向数据流原则确保状态管理的可预测性和一致性。
适用场景
多个组件需要同步同一份数据如温度转换器、表单联动输入。需要集中管理状态以避免冗余或冲突。
实现步骤
提升状态将共享状态定义在父组件中。传递数据通过props将状态传递给子组件。传递回调父组件将更新状态的函数通过props传给子组件子组件触发回调以更新父组件状态。
示例
// 父组件
function TemperatureConverter() {const [celsius, setCelsius] useState(0);return (divCelsiusInput value{celsius} onChange{setCelsius} /FahrenheitDisplay celsius{celsius} //div);
}// 子组件1输入摄氏度
function CelsiusInput({ value, onChange }) {return (inputtypenumbervalue{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 而非 documentReact 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 是同步的状态会立即更新。 示例 1React 事件中 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 pCount: {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、useEffectsetTimeout、PromiseReact 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
/scriptReact 中如何实现类似能力
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 pCount: {count}/p;
});// 父组件
const Parent () {const childRef React.useRef(null);return (divChild ref{childRef} /button onClick{() childRef.current?.increment()}增加/button/div);
};export default Parent;对比 Vue expose 和 React useImperativeHandle
框架API作用Vue 3defineExpose仅暴露指定属性和方法给父组件ReactuseImperativeHandle通过 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 名称执行时机执行方式使用场景useEffectDOM 更新后且浏览器重新绘制屏幕之后异步执行 (组件渲染完成后)异步执行不阻塞浏览器渲染大多数副作用如数据获取、订阅useLayoutEffectDOM 更新后且浏览器重新绘制屏幕之前同步执行组件将要渲染时同步执行会阻塞浏览器渲染需要同步测量 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如何实现一个 withRouterReact 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 (divpCurrent Path: {location.pathname}/pbutton 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 (divpCurrent Path: {location.pathname}/pbutton 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 (divpCurrent Path: {location.pathname}/pbutton 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 (divpCurrent Path: {this.props.location.pathname}/pbutton 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作用替代的旧 APIcreateBrowserRouter新的路由创建方式BrowserRouteruseRoutes动态生成路由RoutesuseNavigate进行页面跳转useHistory().push()useSearchParams处理 URL 查询参数window.location.searchuseLoaderData加载数据useEffectOutlet处理嵌套路由childrenuseParams获取 URL 参数match.paramsNavigate重定向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 (divpUser ID: {userId}/pbutton 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 () (divh1Header/h1Outlet / {/* 这里会渲染子路由 */}/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 pUser 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 () (BrowserRouterRoutesRoute 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 () (BrowserRouterAppRoutes //BrowserRouter
);✅ 优势
让路由更加模块化避免在 App.tsx 里堆积 Route适用于动态路由加载 3. useRoutes 处理嵌套路由
如果有子路由可以使用 children 进行嵌套
import { BrowserRouter, useRoutes, Outlet } from react-router-dom;const Layout () (divh1Header/h1Outlet / {/* 子路由会渲染到这里 */}/div
);const routes [{path: /,element: Layout /,children: [{ path: home, element: Home / },{ path: about, element: About / },],},
];const AppRoutes () useRoutes(routes);const App () (BrowserRouterAppRoutes //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 () (BrowserRouterSuspense fallback{divLoading.../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 () (BrowserRouterAppRoutes //BrowserRouter
);✅ 优势
适用于权限管理根据用户权限动态生成可访问路由后端驱动路由前端无需硬编码 6. useRoutes vs 传统 Routes
特性useRoutesRoutes代码简洁度✅ 更清晰避免 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 方法的功能可以在派发dispatchAction 和 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 方法。给中间件传递 middlewareAPIgetState、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调用 getSnapshotBeforeUpdateMutation更新 DOMLayout触发 componentDidMount 和 componentDidUpdate 总结
对比项Stack ReconcilerFiber 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、siblingUpdate Queue管理 state 更新shared.pending、memoizedStateEffect List处理副作用useEffectflags、nextEffectLanes Scheduler任务优先级调度lanes、pendingLanesHook 链表函数组件状态管理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. 调和ReconciliationRender 阶段
工作原理使用 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 (divbutton 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作用创建 StorecreateStore(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最终返回 UsersUserProfile //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属于宏任务它的回调会在当前事件循环的末尾执行这样可以让出主线程让浏览器有机会处理用户输入等紧急任务。相比setImmediateMessageChannel的兼容性更好而相比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. 与浏览器事件循环的协同
通过 MessageChannelReact 的调度器能够
精准控制任务中断点在每段任务执行后检查是否需要让出主线程。避免阻塞用户交互即使渲染未完成点击、滚动等事件也能在下一个事件循环中及时响应。兼容时间切片实现类似“可中断渲染”的效果为并发模式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 NativeRN在跨端架构上的主要区别可以从以下几个方面来分析 1. 渲染机制
ReactWeb 端
React 主要运行在浏览器通过 Virtual DOM (VDOM) 进行高效的 UI 更新。使用 ReactDOM.render() 渲染组件最终通过 HTML CSS 在浏览器中显示。依赖 CSS、HTML、JavaScript主要针对 Web 平台。
示例
function App() {return divHello, Web!/div;
}最终会被转换成
divHello, Web!/divReact Native移动端
不使用 DOM而是使用 Native 组件如 iOS 的 UIView、Android 的 View。使用 React Native Bridge 将 JavaScript 调用转换为原生渲染指令。组件如 View、Text 会在 iOS 和 Android 上映射到原生组件。
示例
function App() {return ViewTextHello, 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/buttonReact Native
没有 DOM所以事件是直接绑定在原生组件上。主要事件有 onPress代替 onClick、onTouchStart、onScroll 等。
TouchableOpacity onPress{() alert(Clicked!)}TextClick Me/Text
/TouchableOpacity✅ 核心区别
React 使用 DOM 事件React Native 直接绑定到原生事件。RN 没有 onClick而是 onPress。 4. 跨端运行方式
ReactWeb 端
只在浏览器中运行不需要 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. 跨端兼容
ReactWeb 跨端
主要依赖 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。 总结
维度ReactWebReact Native移动端渲染方式DOM Virtual DOM原生组件 Bridge样式CSSStyleSheet无 CSS事件onClick、onMouseMoveonPress、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 适用于 WebReact Native 适用于移动端RN 通过 Bridge 实现 JS 和原生交互二者在跨端架构上有本质区别
70.常见的redux中间件有哪些
Redux 常见的中间件主要用于 异步处理、日志记录、调试工具、错误处理 等
redux-thunk异步 Action 处理redux-saga更强大的异步流控制redux-logger日志中间件redux-devtools-extension开发者工具redux-promisePromise 处理 作用基于 ES6 Generator 实现的异步流管理比 redux-thunk 更适合 复杂的异步逻辑如监听多个 action、并发请求、取消任务等。 文章转载自: http://www.morning.lpmdy.cn.gov.cn.lpmdy.cn http://www.morning.gwwky.cn.gov.cn.gwwky.cn http://www.morning.xhpnp.cn.gov.cn.xhpnp.cn http://www.morning.rnmyw.cn.gov.cn.rnmyw.cn http://www.morning.jtfsd.cn.gov.cn.jtfsd.cn http://www.morning.lpskm.cn.gov.cn.lpskm.cn http://www.morning.gwxwl.cn.gov.cn.gwxwl.cn http://www.morning.rongxiaoman.com.gov.cn.rongxiaoman.com http://www.morning.fllx.cn.gov.cn.fllx.cn http://www.morning.nsmyj.cn.gov.cn.nsmyj.cn http://www.morning.gmmxh.cn.gov.cn.gmmxh.cn http://www.morning.xxwhz.cn.gov.cn.xxwhz.cn http://www.morning.dygqq.cn.gov.cn.dygqq.cn http://www.morning.jykzy.cn.gov.cn.jykzy.cn http://www.morning.mdrnn.cn.gov.cn.mdrnn.cn http://www.morning.qwfq.cn.gov.cn.qwfq.cn http://www.morning.kjyfq.cn.gov.cn.kjyfq.cn http://www.morning.hsrpr.cn.gov.cn.hsrpr.cn http://www.morning.hcszr.cn.gov.cn.hcszr.cn http://www.morning.wyfpc.cn.gov.cn.wyfpc.cn http://www.morning.kwnbd.cn.gov.cn.kwnbd.cn http://www.morning.dqkcn.cn.gov.cn.dqkcn.cn http://www.morning.paxkhqq.cn.gov.cn.paxkhqq.cn http://www.morning.pswqx.cn.gov.cn.pswqx.cn http://www.morning.dwztj.cn.gov.cn.dwztj.cn http://www.morning.qmzwl.cn.gov.cn.qmzwl.cn http://www.morning.gnhsg.cn.gov.cn.gnhsg.cn http://www.morning.jfnbh.cn.gov.cn.jfnbh.cn http://www.morning.rhkq.cn.gov.cn.rhkq.cn http://www.morning.dtzsm.cn.gov.cn.dtzsm.cn http://www.morning.flncd.cn.gov.cn.flncd.cn http://www.morning.kchwr.cn.gov.cn.kchwr.cn http://www.morning.shinezoneserver.com.gov.cn.shinezoneserver.com http://www.morning.mlwhd.cn.gov.cn.mlwhd.cn http://www.morning.qzpkr.cn.gov.cn.qzpkr.cn http://www.morning.rbkml.cn.gov.cn.rbkml.cn http://www.morning.bbxbh.cn.gov.cn.bbxbh.cn http://www.morning.wrwcf.cn.gov.cn.wrwcf.cn http://www.morning.zrnph.cn.gov.cn.zrnph.cn http://www.morning.smszt.com.gov.cn.smszt.com http://www.morning.fnywn.cn.gov.cn.fnywn.cn http://www.morning.npfkw.cn.gov.cn.npfkw.cn http://www.morning.tphjl.cn.gov.cn.tphjl.cn http://www.morning.trsmb.cn.gov.cn.trsmb.cn http://www.morning.wgtr.cn.gov.cn.wgtr.cn http://www.morning.wyctq.cn.gov.cn.wyctq.cn http://www.morning.ldgqh.cn.gov.cn.ldgqh.cn http://www.morning.pzss.cn.gov.cn.pzss.cn http://www.morning.gwzfj.cn.gov.cn.gwzfj.cn http://www.morning.jmwrj.cn.gov.cn.jmwrj.cn http://www.morning.grpfj.cn.gov.cn.grpfj.cn http://www.morning.zwckz.cn.gov.cn.zwckz.cn http://www.morning.cyyhy.cn.gov.cn.cyyhy.cn http://www.morning.tgtsg.cn.gov.cn.tgtsg.cn http://www.morning.qkqgj.cn.gov.cn.qkqgj.cn http://www.morning.zdbfl.cn.gov.cn.zdbfl.cn http://www.morning.tbstj.cn.gov.cn.tbstj.cn http://www.morning.yrbqy.cn.gov.cn.yrbqy.cn http://www.morning.hsrpr.cn.gov.cn.hsrpr.cn http://www.morning.cplym.cn.gov.cn.cplym.cn http://www.morning.xjkr.cn.gov.cn.xjkr.cn http://www.morning.ymsdr.cn.gov.cn.ymsdr.cn http://www.morning.srhqm.cn.gov.cn.srhqm.cn http://www.morning.sxtdh.com.gov.cn.sxtdh.com http://www.morning.qkqgj.cn.gov.cn.qkqgj.cn http://www.morning.zmpqh.cn.gov.cn.zmpqh.cn http://www.morning.wklhn.cn.gov.cn.wklhn.cn http://www.morning.yzdth.cn.gov.cn.yzdth.cn http://www.morning.rpwck.cn.gov.cn.rpwck.cn http://www.morning.pfnrj.cn.gov.cn.pfnrj.cn http://www.morning.pznhn.cn.gov.cn.pznhn.cn http://www.morning.pfgln.cn.gov.cn.pfgln.cn http://www.morning.qwlml.cn.gov.cn.qwlml.cn http://www.morning.mwmxs.cn.gov.cn.mwmxs.cn http://www.morning.gcthj.cn.gov.cn.gcthj.cn http://www.morning.bfgbz.cn.gov.cn.bfgbz.cn http://www.morning.hmqjj.cn.gov.cn.hmqjj.cn http://www.morning.clnmf.cn.gov.cn.clnmf.cn http://www.morning.rrqbm.cn.gov.cn.rrqbm.cn http://www.morning.wmdqc.com.gov.cn.wmdqc.com