当前位置: 首页 > news >正文

教育视频网站开发软件项目管理工具

教育视频网站开发,软件项目管理工具,哈尔滨做网站数据平台的公司,教学直播平台网站建设费用1. 对 React 的理解、特性 React 是靠数据驱动视图改变的一种框架#xff0c;它的核心驱动方法就是用其提供的 setState 方法设置 state 中的数据从而驱动存放在内存中的虚拟 DOM 树的更新 更新方法就是通过 React 的 Diff 算法比较旧虚拟 DOM 树和新虚拟 DOM 树之间的 Chan…1. 对 React 的理解、特性 React 是靠数据驱动视图改变的一种框架它的核心驱动方法就是用其提供的 setState 方法设置 state 中的数据从而驱动存放在内存中的虚拟 DOM 树的更新 更新方法就是通过 React 的 Diff 算法比较旧虚拟 DOM 树和新虚拟 DOM 树之间的 Change 然后批处理这些改变。 遵循组件设计模式、声明式编程范式和函数式编程概念以使前端应用程序更高效 使用虚拟 DOM 来有效地操作 DOM遵循从高阶组件到低阶组件的单向数据流 声明式编程React采用声明式编程范式允许开发者描述UI应该呈现的状态而非具体的操作步骤。这种抽象简化了开发过程并且有利于React在内部执行高效的DOM更新。虚拟DOMReact引入了虚拟DOM的概念即在内存中维护一个与实际DOM相对应的数据结构。每当组件的状态变化时React首先更新虚拟DOM然后通过高效的Diff算法找出最小化的DOM更新操作再将这些变化反映到实际DOM上从而极大提升了渲染性能。组件化React强调组件化开发鼓励将UI拆分成可复用的独立单元。组件具有明确的输入Props和内部状态State使得代码易于组织和测试。单向数据流与可预测性React推崇单向数据流的设计理念数据从父组件流向子组件通过props传递而子组件通过回调函数通知父组件状态变化这种设计有助于理解和调试应用状态。函数组件与Hooks随着React 16.8版本引入的Hooks函数组件获得了状态管理和生命周期功能使得无需类就能写出完整功能的组件进一步简化了代码结构提高了可读性和可复用性。React FiberReact 16 引入的调度算法改进提供了更细粒度的任务划分与优先级调度增强了应用在复杂场景下的流畅性。兼容性与扩展性React生态丰富支持服务端渲染SSR、静态站点生成SSG、移动应用开发React Native等多种应用场景具备良好的兼容性和扩展能力。 2. React 生命周期 React组件的生命周期在过去的不同版本中有所调整以下是React类组件的经典生命周期钩子React v16及之前版本和现代函数组件使用的Hook形式生命周期方法的对比表 类组件生命周期方法经典生命周期 阶段 生命周期钩子 描述 初始化/挂载 constructor(props) 构造函数在组件实例化时调用用于设置初始状态或绑定实例方法 挂载前/实例化后 static getDerivedStateFromProps(props, state) 可选在每次渲染前调用返回新的state以响应props更改但不推荐过度依赖此方法 挂载前 render() 必须定义的方法用于返回jsx元素React根据此方法渲染DOM 挂载后 componentDidMount() 组件挂载到DOM后调用常用于网络请求、订阅或手动操作DOM 更新前 shouldComponentUpdate(nextProps, nextState) 可选在props或state即将更改时调用返回布尔值决定是否重新渲染 更新前 static getSnapshotBeforeUpdate(prevProps, prevState) 在最新的渲染被提交到DOM之前获取一些信息返回值将在 componentDidUpdate 中作为第三个参数 更新 render() 同上在props或state更改时再次调用 更新后 componentDidUpdate(prevProps, prevState, snapshot) 组件完成更新并重新渲染到DOM后调用 卸载前 componentWillUnmount() 组件从DOM移除之前调用用于清理工作如取消定时器、解绑事件监听器等 函数组件生命周期钩子使用React Hooks 阶段 Hook 方法 描述 初始化/挂载 useState() 初始化状态并在每次渲染时返回一对值当前状态和更新状态的函数 初始化/挂载 useEffect(fn, deps) 类似于 componentDidMount 和 componentDidUpdate 的合并以及 componentWillUnmount 功能fn 函数在组件渲染后运行deps 是依赖数组控制何时重新运行该效果 初始化/挂载 useLayoutEffect(fn, deps) 类似 useEffect但在所有 DOM 变更之后同步调用 初始化/挂载 useMemo(() result, deps) 记忆化计算结果仅当依赖项deps改变时重新计算 初始化/挂载 useCallback(fn, deps) 记忆化函数引用避免不必要的函数重创建 卸载 useCleanup(returnFn) 返回的函数在组件卸载时执行用于资源清理 注意useEffect、useMemo 和 useCallback 的依赖数组可以帮助确定何时重新执行钩子逻辑。 由于React Hooks的引入函数组件现在可以直接处理大部分原本需要生命周期方法才能完成的任务使得组件更加简洁和易于维护。 旧的生命周期流程图如下 通过两个图的对比可以发现新版的生命周期减少了以下三种方法 componentWillMount componentWillReceiveProps componentWillUpdate 其实这三个方法仍然存在只是在前者加上了UNSAFE_前缀如UNSAFE_componentWillMount并不像字面意思那样表示不安全而是表示这些生命周期的代码可能在未来的 react版本可能废除 同时也新增了两个生命周期函数 getDerivedStateFromProps getSnapshotBeforeUpdate 3. React 性能优化的手段 React 中进行性能优化的手段可以从多个维度进行分类以下是一些关键类别及其对应的优化策略 1. 组件优化 使用PureComponent或React.memo对于仅根据props和state改变才重新渲染的组件使用React.PureComponent或者对其包装一层React.memo它们都能通过浅比较props来避免不必要的重新渲染。shouldComponentUpdate/React Hooks中的useMemo/useCallback在类组件中实现shouldComponentUpdate生命周期方法来手动控制是否更新组件。在函数组件中使用useMemo缓存计算结果useCallback缓存回调函数防止因依赖项不变而引起的无效渲染。 2. 状态管理与变更 减少不必要的setState调用合并多次对同一状态的修改例如使用useState hook时可以利用函数式的setState来一次性更新多个状态值。选择性地更新state只在props或state真正发生变化时才进行更新避免频繁或大面积的state变更引发大量子组件重新渲染。 3. Virtual DOM与Diff算法优化 合理构建组件层级保持组件树扁平化减少不必要的嵌套层次使React的diff算法更高效。利用key属性为列表元素提供稳定的唯一key帮助React识别并最小化DOM变动。少用 dom 层级 多使用箭头标签替代 4. 事件处理优化 使用合成事件React的合成事件系统可以减少全局事件监听器的数量提高事件处理效率。避免内联函数绑定在事件处理函数中避免每次渲染时创建新的函数引用而是使用箭头函数或者useCallback来缓存函数引用。 5. 懒加载与代码分割 动态导入使用React.lazy和Suspense来按需加载组件减轻初始加载负担提高首屏加载速度。使用优先级加载CSS、JavaScript和图片资源。 6. 优化渲染过程 使用ReactDOM.createPortal将某些组件渲染到根DOM之外比如渲染到document.body可以避免不必要的re-render。CSS动画与交互优化配合requestAnimationFrame等API来处理复杂的动画减少不必要的布局重排和重绘。 7、工具辅助 Profiler工具利用React DevTools的Profiler面板分析组件渲染性能瓶颈。性能监控与警告设置性能指标监控点及时发现和修复潜在性能问题。 8、前端通用优化 静态资源压缩与HTTP缓存优化CSS、JavaScript文件大小合理设置HTTP缓存策略。服务端渲染(SSR)针对SEO友好和首屏加载速度结合Next.js等框架进行服务器端渲染。 4. React如何捕获错误 错误边界Error Boundaries React 16及更高版本引入了错误边界这一概念它是一种特殊的React组件能够在其子组件树中捕获任何渲染错误或其他JavaScript错误。当错误边界内的任何子组件抛出错误时错误边界能够捕获这个错误记录日志并且可以选择性地显示恢复界面而不是让整个应用程序崩溃。 除此之外还可以通过window.onerror或unhandledrejection事件监听器在全局范围内捕获未处理的错误。 window.addEventListener(error, function(event) { ... }) window.addEventListener(unhandledrejection, function(event) { ... }) 5. tsx转换成真实DOM过程 TypeScript 编译阶段 .tsx 文件包含了 TypeScript 类型注解和 JSX 语法首先通过 TypeScript 编译器如tsc或者配合Babel的babel/preset-typescript插件进行编译。TypeScript 编译器负责检查类型注解的正确性并且把包含类型信息的 TypeScriptJSX 代码转换为普通的 JavaScript 代码同时保持 JSX 结构不变。 JSX编译阶段 开发者使用JSX编写React组件。JSX并非浏览器原生支持的语法所以需要通过像Babel这样的编译器将其转换为标准的JavaScript代码。Babel将JSX转换成React.createElement(component, props, ...children)的调用形式其中component是组件名称或原生DOM元素标签名props是组件属性对象children是子元素数组。 创建虚拟DOMVDOM React.createElement()调用会产生一个虚拟DOM节点对象它是一个轻量级的JavaScript对象模拟了DOM节点的结构和属性。整个组件树会转换成由这些虚拟DOM节点构成的虚拟DOM树。 渲染虚拟DOM 当调用ReactDOM.render()方法时React接收虚拟DOM树作为参数。React会将这个虚拟DOM树与实际DOM进行比较首次渲染时不存在比较直接创建。 DIFF算法与更新DOM 在组件状态或props更改导致重新渲染时React会生成一个新的虚拟DOM树并与旧的虚拟DOM树进行高效差异比较称为Reconciliation或Diffing过程。Diff算法找出最小化的DOM操作集合增删改查节点。React随后将这些操作应用到实际的DOM上仅更新需要改变的部分而不是完全替换整个DOM树。 真实DOM更新 最终React通过ReactDOM模块与浏览器底层交互执行必要的DOM操作将虚拟DOM树的改动反映到浏览器的真实DOM中 6. 对Fiber架构的理解 Fiber 即是React新的调度算法 在数据更新时react生成了一棵更大的虚拟dom树给第二步的diff带来了很大压力——我们想找到真正变化的部分这需要花费更长的时间。js占据主线程去做比较渲染线程便无法做其他工作用户的交互得不到响应所以便出现了react fiber。 React 为了解决这个问题根据浏览器的每一帧执行的特性构思出了 Fiber 来将一次任务拆解成单元以划分时间片的方式按照Fiber的自己的调度方法根据任务单元优先级分批处理或吊起任务将一次更新分散在多次时间片中另外, 在浏览器空闲的时候, 也可以继续去执行未完成的任务, 充分利用浏览器每一帧的工作特性。 一次更新任务是分时间片执行的直至完成某次更新。 这样 React 更新任务就只能在规定时间内占用浏览器线程了, 如果说在这个时候用户有和浏览器的页面交互浏览器也是可以及时获取到交互内容。 Fiber架构出现之前 react 存在的问题 主线程阻塞在React 15及更早版本中当组件树发生更新时React会通过递归算法一次性完成整个组件树的渲染过程这个过程如果涉及大量组件会导致主线程长时间阻塞无法处理其他的UI交互从而造成卡顿和延迟降低用户体验。无法中断与恢复渲染原有的渲染过程不具备中断和恢复的能力一旦开始渲染就必须等到整个过程结束即使在中间有更高优先级的任务也需要等待。无法实现增量渲染以往的React无法有效区分渲染任务的重要性和紧急程度所有更新任务都被视为同等重要的无法做到逐步、增量地渲染UI。资源优化不足旧版React无法根据应用的具体需求动态分配资源无法高效利用有限的CPU周期来优化渲染性能。 jsx 形成 dom 的以下几个步骤react 在虚拟 dom 之前做了一层数据结构设计将ReactElement 转换为 fiberNode 写JSX来描述React组件的结构和内容。JSX被Babel转译成React.createElement(__jsx或__jsxs)的调用生成ReactElement。在React的协调过程中ReactElement被转换成FiberNode。FiberNode是React用来进行高效渲染和更新的数据结构它支持并发渲染和优先级调度。最终FiberNode的信息被用来更新浏览器中的真实DOM从而呈现用户界面。 Fiber数据结构是一个链表这样就为Fiber架构可中断渲染提供可能 js 复制代码 function FiberNode(){this.tag tag; //元素类型this.key key;//元素的唯一标识。this.elementType null; //元素类型 this.type null;//元素类型this.stateNode null;//元素实例的状态节点// Fiberthis.return null;//该组件实例的父级。this.child null;//该组件实例的第一个子级。this.sibling null;//该组件实例的下一个兄弟级this.index 0;//该组件实例在父级的子级列表中的位置。this.ref null;//该组件实例的ref属性this.refCleanup null;//ref的清理函数this.pendingProps pendingProps;//待处理的props最新的this.memoizedProps null;//处理后的props上一次this.updateQueue null;//TODOthis.memoizedState null;//类组件保存state信息函数组件保存hooks信息this.dependencies null;//该组件实例的依赖列表this.mode mode;//该组件实例的模式 (DOM模式和Canvas模式)// Effectsxthis.flags NoFlags$1;//副作用标签 之前的版本是effectTagthis.subtreeFlags NoFlags$1;//子节点副作用标签。this.deletions null;//待删除的子树列表。this.lanes NoLanes;//任务更新的优先级区分this.childLanes NoLanes;//子树任务更新的优先级区分this.alternate null;//组件实例的备份实例用于记录前一次更新的状态。更新时候 workInProgress会复用当前值} tag tag用来标识Fiber节点的类型。不同的tag值代表了不同类型的React元素比如函数组件、类组件、DOM元素等。React会根据tag的值来决定如何处理该Fiber节点。 key key是一个可选的字符串用于在兄弟元素之间建立唯一的身份。当列表重新排序或元素添加/删除时key帮助React识别哪些元素发生了变化从而高效地更新UI。在Fiber节点中key用于在协调过程中识别节点的身份。 child child指向Fiber节点的第一个子节点。通过child属性React可以遍历Fiber树执行渲染和更新操作。 sibling sibling指向Fiber节点的下一个兄弟节点。当React遍历完一个Fiber节点的所有子节点后它会通过sibling属性移动到下一个兄弟节点继续遍历。 return return指向Fiber节点的父节点。通过return属性React可以在Fiber树中向上回溯这对于错误处理和优先级调度等功能非常重要。 Fiber树生成 首先调用createRoot方法创建FiberRoot(应用根节点)、RootFiber(Fiber树的根节点)目前对应节点上的数据都是空的生成的数据结构如下 FiberRoot{tag: 1,//ConcurrentRoot containerInfo: div#root,//挂载的dom节点current: { // RooFiber tag: 3,//标记Fiber的类型如类组件、函数组件、DOM组件等key: null,//用于在列表或其他需要区分子元素的场景中识别Fiber的键。elementType: null,//通常与type相同但在某些情况下如懒加载组件可能不同。它指的是要渲染的元素类型。type: null,//组件的类型函数、类等或DOM节点的类型如divstateNode: null,//对于DOM组件这是实际的DOM节点对于类组件这是组件的实例return: null,//指向父Fiber的指针child: null,//指向子Fiber的指针sibling: null,// 指向兄弟Fiber的指针index: 0,//在父Fiber的子节点列表中的索引ref: null,//用于获取DOM节点或类组件实例的引用refCleanup: null,pendingProps: null,//新的或待处理的propsmemoizedProps: null,//上一次渲染使用的propsupdateQueue: null,//存储状态更新和回调的队列memoizedState: null,//上一次渲染时的状态dependencies: null,mode: 3,//表示Fiber的渲染模式如并发模式、阻塞模式等flags: 0,//用于跟踪Fiber位字段的状态和效果的位字段subtreeFlags: 0,//用于跟踪Fiber子树的状态和效果的位字段deletions: null,//指向要删除的Fiber子树的指针lanes: 0,//与优先级和并发渲染相关的内部字段childLanes: 0,//与优先级和并发渲染相关的内部字段alternate: null,//在双缓冲系统中指向对应Fiber的指针用于新旧树之间的比较actualDuration: 0,actualStartTime: -1,selfBaseDuration: 0,treeBaseDuration: 0,},//...} 调用render方法创建对应Fiber节点的信息因为上一波生成都是空我们需要把组件App(),dev节点都构建成Fiber node。后面你就需要知道React Fiber是如何工作的 React Fiber工作原理详解 双缓冲技术 React Fiber使用了类似于图形渲染中的双缓冲技术。这意味着在构建新的UI树时React会同时在内存中维护两棵树当前屏幕上显示的树current tree和正在构建的树work-in-progress tree。只有当新的树完全构建完成后它才会被一次性地渲染到屏幕上从而实现更加流畅的用户体验。 任务调度 React Fiber引入了任务调度的概念允许将渲染工作拆分成多个较小的任务单元。这些任务单元可以被中断和恢复从而实现并发渲染。React根据任务的优先级来决定它们的执行顺序确保高优先级的任务如用户交互能够优先执行。 运行方式: Reconciliation阶段 当React决定要更新UI时它会启动reconciliation协调过程。在这个阶段React会比较新旧两棵树之间的差异并为需要更新的组件生成相应的Fiber节点。这个过程是异步的可以被中断和恢复。Commit阶段 当所有的Fiber节点都被处理完毕后React会进入commit阶段。在这个阶段React会将之前在render阶段计算出的所有变化一次性应用到DOM上并触发相关的生命周期方法如useEffectuseLayoutEffect方法。这个过程是不可中断的因为它涉及到实际的DOM操作。优先级调度 React Fiber通过优先级调度来管理任务的执行顺序。每个Fiber节点都有一个与之关联的优先级React会根据节点的优先级来决定哪些节点需要先更新。高优先级的任务如用户交互会打断低优先级的任务如定时器回调并优先执行从而实现更流畅的用户体验。 Reconciliation阶段 在Reconciliation阶段React会遍历Fiber树并执行每个Fiber节点的更新逻辑。这个过程可以被分为两个阶段beginWork和completeWork。在beginWork阶段React会执行组件的渲染逻辑并计算副作用side effects。在completeWork阶段是向上归并的过程如果有兄弟节点会返回 sibling兄弟没有返回 return 父级一直返回到 fiebrRoot 期间可以形成effectList对于初始化流程会创建 DOM 对于 DOM 元素进行事件收集处理styleclassName等这个阶段并不直接更新DOM或触发任何用户可见的更改而是为后续的Commit阶段做准备。 Reconciliation阶段 在Reconciliation阶段React会遍历Fiber树并执行每个Fiber节点的更新逻辑。这个过程可以被分为两个阶段beginWork和completeWork。在beginWork阶段React会执行组件的渲染逻辑并计算副作用side effects。在completeWork阶段是向上归并的过程如果有兄弟节点会返回 sibling兄弟没有返回 return 父级一直返回到 fiebrRoot 期间可以形成effectList对于初始化流程会创建 DOM 对于 DOM 元素进行事件收集处理styleclassName等这个阶段并不直接更新DOM或触发任何用户可见的更改而是为后续的Commit阶段做准备。 beginWork做了什么 对于组件执行部分生命周期执行 render 得到最新的 children 。向下遍历调和 children 复用 oldFiber ( diff 算法)diff 流程。打不同的副作用标签 effectTag 比如类组件的生命周期或者元素的增加删除更新 Commit阶段 在Commit阶段React将根据在Reconciliation阶段生成的更新计划来执行实际的DOM更新。这个过程包括更新DOM节点、处理生命周期方法如在类组件中的useEffect以及执行其他与渲染相关的副作用。此阶段是同步执行的意味着一旦开始就会一口气完成不会被其他任务打断 对于组件执行部分生命周期执行 render 得到最新的 children 。向下遍历调和 children 复用 oldFiber ( diff 算法)diff 流程。打不同的副作用标签 effectTag 比如类组件的生命周期或者元素的增加删除更新 那 React Fiber 是怎么实现的 主要是通过两个原生的 API 来实现的 requestAnimationFrame 和 requestIdleCallback 显示器每秒 60 帧我们看着才不会感觉到卡嘛比如动画的时候一帧的时间内布局和绘制结束还有剩余时间JS 就会拿到主线程使用权如果 JS 某个任务执行过长动画下一帧开始时 JS 还没有执行完就会导致掉帧出现卡顿。 所以就通过把 JS 任务分成更小的任务块分到每一帧上的方式一帧时间到先暂停 JS 执行然后下一帧绘制任完成再把主线程交给 JS在每一帧绘制之前调用 requestAnimationFrame在每一帧空间阶段就是一帧动画任务完成下一帧还没到开始时间这中间还有时间的话就调用 requetIdleCallback执行它里面的任务 Fiber具体都做了什么 React Fiber架构是React在16版本推出的一种全新的、革命性的调度算法和组件更新机制。它对React核心算法进行了重构以满足更流畅的UI渲染、更灵活的任务调度以及更好的交互体验。 react fiber使得diff阶段有了被保存工作进度的能力 Fiber架构通过引入可中断和恢复的渲染机制以及基于任务优先级的调度系统使得React能够更灵活地管理渲染任务实现增量渲染和精细化调度从而显著提升了性能和用户体验。此外Fiber架构也为React后期支持并发渲染和异步数据流等功能打下了基础 核心特点与优势 精细化任务调度 在Fiber架构之前React的更新是同步且整体的一旦开始更新直到结束才会释放浏览器主线程这可能导致长时间阻塞无法响应用户的其他交互。Fiber架构则采用了增量Incremental和可暂停Interruptible的渲染方式将渲染任务分解为一个个小的工作单元Fiber节点并可以根据优先级和浏览器环境的空闲时间灵活调度确保UI的渲染不会阻塞用户交互。 可恢复更新 Fiber架构允许React在渲染过程中暂停和恢复这意味着React可以在执行更新的过程中临时切换去处理高优先级的任务如响应用户的鼠标点击或滚动事件然后再回到之前的渲染任务这样就保证了用户界面始终能够快速响应用户的操作。 优先级调度 Fiber架构支持任务优先级排序不同的更新任务可以根据其影响范围、交互重要性等因素赋予不同优先级优先处理对用户体验影响更大的更新。 协调Reconciliation算法优化 Fiber架构改进了React的协调算法使其在对比新老虚拟DOM树时更为智能能够更快地找出需要更新的DOM节点从而提高渲染性能。 并发模式与Suspense 基于Fiber架构React得以实现并发模式通过时间切片Time Slicing技术分配渲染任务未来还可以更好地处理异步数据加载如Suspense组件。 总之React Fiber架构在很大程度上提升了React的性能表现和用户体验使得React能够在更复杂的场景下仍然保持高效、流畅和可预测的行为。同时也为React未来的演进提供了坚实的基础和更多可能性。 底层原理 我们要找到前后状态变化的部分必须把所有节点遍历。 在老的架构中节点以树的形式被组织起来每个节点上有多个指针指向子节点。要找到两棵树的变化部分最容易想到的办法就是深度优先遍历规则如下 从根节点开始依次遍历该节点的所有子节点当一个节点的所有子节点遍历完成才认为该节点遍历完成 如果你系统学习过数据结构应该很快就能反应过来这不过是深度优先遍历的后续遍历。根据这个规则在图中标出了节点完成遍历的顺序。 这种遍历有一个特点必须一次性完成。假设遍历发生了中断虽然可以保留当下进行中节点的索引下次继续时我们的确可以继续遍历该节点下面的所有子节点但是没有办法找到其父节点——因为每个节点只有其子节点的指向。断点没有办法恢复只能从头再来一遍。 以该树为例 在遍历到节点2时发生了中断我们保存对节点2的索引下次恢复时可以把它下面的3、4节点遍历到但是却无法找回5、6、7、8节点。 在新的架构中每个节点有三个指针分别指向第一个子节点、下一个兄弟节点、父节点。这种数据结构就是fiber它的遍历规则如下 从根节点开始依次遍历该节点的子节点、兄弟节点如果两者都遍历了则回到它的父节点当一个节点的所有子节点遍历完成才认为该节点遍历完成 根据这个规则同样在图中标出了节点遍历完成的顺序。跟树结构对比会发现虽然数据结构不同但是节点的遍历开始和完成顺序一模一样。不同的是当遍历发生中断时只要保留下当前节点的索引断点是可以恢复的——因为每个节点都保持着对其父节点的索引。 同样在遍历到节点2时中断fiber结构使得剩下的所有节点依旧能全部被走到。 这就是react fiber的渲染可以被中断的原因。树和fiber虽然看起来很像但本质上来说一个是树一个是链表。 fiber是纤程 这种数据结构之所以被叫做fiber因为fiber的翻译是纤程它被认为是协程的一种实现形式。协程是比线程更小的调度单位它的开启、暂停可以被程序员所控制。具体来说react fiber是通过requestIdleCallback这个api去控制的组件渲染的“进度条”。 requesetIdleCallback是一个属于宏任务的回调就像setTimeout一样。不同的是setTimeout的执行时机由我们传入的回调时间去控制requesetIdleCallback是受屏幕的刷新率去控制。本文不对这部分做深入探讨只需要知道它每隔16ms会被调用一次它的回调函数可以获取本次可以执行的时间每一个16ms除了requesetIdleCallback的回调之外还有其他工作所以能使用的时间是不确定的但只要时间到了就会停下节点的遍历。 使用方法如下 const workLoop (deadLine) {let shouldYield false;// 是否该让出线程while(!shouldYield){console.log(working)// 遍历节点等工作shouldYield deadLine.timeRemaining()1;}requestIdleCallback(workLoop) } requestIdleCallback(workLoop); requestIdleCallback的回调函数可以通过传入的参数deadLine.timeRemaining()检查当下还有多少时间供自己使用。上面的demo也是react fiber工作的伪代码。 但由于兼容性不好加上该回调函数被调用的频率太低react实际使用的是一个polyfill(自己实现的api)而不是requestIdleCallback。 现在可以总结一下了React Fiber是React 16提出的一种更新机制使用链表取代了树将虚拟dom连接使得组件更新的流程可以被中断恢复它把组件渲染的工作分片到时会主动让出渲染主线程。 react fiber带来的变化 使用新架构后动画变得流畅宽度的变化不会卡顿使用新架构后用户响应变快鼠标悬停时颜色变化更快 动画变流畅的根本原因一定是一秒内可以获得更多动画帧。但是当我们使用react fiber时并没有减少更新所需要的总时间。 上面是使用旧的react时获得每一帧的时间点下面是使用fiber架构时获得每一帧的时间点因为组件渲染被分片完成一帧更新的时间点反而被推后了我们把一些时间片去处理用户响应了。 这里要注意不会出现“一次组件渲染没有完成页面部分渲染更新”的情况react会保证每次更新都是完整的。 但页面的动画确实变得流畅了这是为什么呢 我们现在已经知道了react fiber是在弥补更新时“无脑”刷新不够精确带来的缺陷。这是不是能说明react性能更差呢 并不是。孰优孰劣是一个很有争议的话题在此不做评价。因为vue实现精准更新也是有代价的一方面是需要给每一个组件配置一个“监视器”管理着视图的依赖收集和数据更新时的发布通知这对性能同样是有消耗的另一方面vue能实现依赖收集得益于它的模版语法实现静态编译这是使用更灵活的JSX语法的react做不到的。 在react fiber出现之前react也提供了PureComponent、shouldComponentUpdate、useMemo,useCallback等方法给我们来声明哪些是不需要连带更新子组件。 react因为先天的不足——无法精确更新所以需要react fiber把组件渲染工作切片而vue基于数据劫持更新粒度很小没有这个压力react fiber这种数据结构使得节点可以回溯到其父节点只要保留下中断的节点索引就可以恢复之前的工作进度 7. diff的原理 React中的diff算法是其核心优化策略之一用于比较新旧两个虚拟DOM树之间的差异并找出最小化的DOM操作集以更新真实DOM。以下是React中diff算法的大致步骤概述 树结构比较: React并不会简单地递归遍历整颗新旧虚拟DOM树进行全量比较而是采用分层比较的思想仅比较同层级的节点。它首先会比较树的根节点如果根节点不同则直接替换整个根节点对应的真实DOM元素。 同层级节点比较: 对于同层级的子节点React会尝试找到旧树中与新树中每一个节点相匹配的节点主要依据是它们的key属性。 如果找到了具有相同key的节点则认为它们是同一个“实体”只需更新属性或内容。如果没有找到相同key的节点则视为新增或删除节点。 属性比较与更新: 对于找到匹配节点的情况React会进一步比较它们的属性是否有变化如果有变化则更新相应的真实DOM元素的属性。 文本节点与组件节点处理: 文本节点的比较相对简单直接比较文本内容即可。组件节点则根据组件类型类或函数组件以及props的变化决定是否需要重新渲染。 子节点递归比较: 当节点匹配成功后继续对其子节点进行同样的分层级比较直至所有子节点都已比较完毕。 跳过跨层级操作: React假设在一次更新中一个节点及其子节点不会随意地在DOM树中跨层级移动。因此它在寻找匹配节点时只会对兄弟节点进行比较而不是在整个树中搜索大大降低了算法复杂度。 删除与插入操作: 在比较过程中React收集到的所有需要删除的旧节点和需要插入的新节点会在比较结束后统一进行DOM操作避免频繁地增删DOM元素。 优化策略: React还有一系列优化策略例如先处理移动或更新的节点尽量减少DOM元素的移动次数对组件类型不同的节点直接进行替换不尝试深入比较等。 通过以上步骤React的diff算法能够在O(n)的时间复杂度内完成虚拟DOM树的比较从而实现在大量DOM更新时依然保持较高的性能。 8. 提高组件的渲染效率的避免不必要的render 在React中提高组件渲染效率并避免不必要的渲染主要有以下几个策略 使用PureComponent或React.memo React.PureComponent自动进行浅比较(shallow comparison)只有当props或state发生改变时才会触发组件重新渲染。继承自React.PureComponent的组件会默认检查props和state对象是否严格相等。对于函数组件可以使用React.memo对其进行包裹React.memo同样会进行浅比较只有当props发生变化时才会重新渲染组件。 自定义shouldComponentUpdate生命周期方法 在class组件中可以覆盖shouldComponentUpdate(nextProps, nextState)方法根据传入的新props和新state判断是否有必要调用render方法从而避免不必要的渲染。 使用React Hooks进行优化 使用React.useState和React.useReducer时可以根据业务逻辑精确控制state的变化避免不必要的状态更新。使用React.useMemo来缓存计算结果仅当依赖的props或state改变时才重新计算。使用React.useCallback来缓存函数引用避免在props没变的情况下因为回调函数引用变了而导致不必要的子组件重渲染。 优化数据结构 避免在props或state中传递深度嵌套的对象或数组因为React的默认浅比较无法检测到深层数据的变化。若必须使用复杂数据结构应当在适当的地方使用shouldComponentUpdate、useMemo或手动进行深比较。 优化事件处理器 将事件处理器封装在useCallback中保证其在props不变时引用始终一致避免无意义的组件重渲染。 减少不必要的state和props更新 只有当数据实际变化时才更新state避免频繁调用setState。使用Context API或者Redux等状态管理库时确保只在数据变化时触发全局状态更新。 通过以上这些方法可以最大程度地减少不必要的组件渲染从而提升React应用的性能。在实践中需要根据组件的具体情况进行权衡和选择最适合的优化方案。 9. React render方法的原理 React的render方法是React组件的核心方法之一它的基本原理和作用在于将组件的状态和属性转化为可以在浏览器中渲染的虚拟DOM表示然后将这个虚拟DOM转化为实际的DOM操作最终更新到浏览器的真实DOM中。 以下是render方法的基本原理和过程 首次渲染 当组件实例化并初次插入到DOM中时React会调用该组件的render方法。这个方法必须返回一个React元素可以是原生DOM元素、组件元素或FragmentReact会根据这个返回值创建一个虚拟DOM树。虚拟DOM的创建与比对 render方法返回的是一个虚拟DOM树这是一个轻量级的JavaScript对象结构与实际的DOM树结构相似但并非真实的DOM节点。当组件的props或state发生变化时React会重新执行render方法生成新的虚拟DOM树。React使用其内部的高效算法——虚拟DOM Diff算法比较新旧两棵虚拟DOM树的差异。 最小化DOM操作 根据虚拟DOM的比较结果React确定最少必要的DOM操作如添加、更新或删除DOM节点而不是每次都完全重建DOM树。这种增量更新机制极大提高了React应用的性能因为它避免了频繁地直接操作DOM带来的性能损耗。 DOM更新 最终React将这些最小化的DOM操作应用到实际DOM树上确保用户界面得到准确、高效的更新。 React render方法的作用就是将组件的状态和属性转化为虚拟DOM通过虚拟DOM Diff算法来决定实际DOM的最小更新操作从而实现高性能的用户界面更新。在类组件中render方法是必需的而在函数组件中函数体本身充当了render方法的角色。 10. Redux原理 从Flux中衍生来的单一数据源单向数据流 官方解释Redux 是 JavaScript 状态容器,提供可预测化的状态管理。我的理解是redux是为了解决react组件间通信和组件间状态共享而提出的一种解决方案主要包括3个部分store action reducer。 store用来存储当前react状态机state的对象。connect后store的改变就会驱动react的生命周期循环从而驱动页面状态的改变State: store对象包含的所有数据action: 用于接受state的改变命令是改变state的唯一途径和入口。一般使用时在当前组件里面调用相关的action方法通常把和后端的通信(ajax)函数放在这里reducer: action的处理器用于修改store中state的值返回一个新的state值Dispatch: view发出action的唯一方法 主要解决什么问题 1、组件间通信 由于connect后各connect组件是共享store的所以各组件可以通过store来进行数据通信当然这里必须遵守redux的一些规范比如遵守 view - aciton - reducer的改变state的路径 2、通过对象驱动组件进入生命周期 对于一个react组件来说只能对自己的state改变驱动自己的生命周期或者通过外部传入的props进行驱动。通过redux可以通过store中改变的state来驱动组件进行update 3、方便进行数据管理和切片 redux通过对store的管理和控制可以很方便的实现页面状态的管理和切片。通过切片的操作可以轻松的实现redo之类的操作 11. redux中间件 Redux 中间件Middleware是 Redux 库中一个强大的扩展机制它位于 action 被发出dispatched和到达 reducer 处理这两个阶段之间。中间件可以看作是对 Redux dispatch 流程的一种拦截器它允许开发者在 action 传播的过程中执行额外的操作例如日志记录、异步处理、事务控制、取消操作、异常处理等。 redux中间件接受一个对象作为参数对象的参数上有两个字段 dispatch 和 getState分别代表着 Redux Store 上的两个同名函数。柯里化函数两端一个是 middewares一个是store.dispatch 中间件通过链式调用的方式来组织形成一个中间件栈。当一个 action 被 dispatch 时它会依次经过中间件栈中的每一个中间件。每个中间件都有机会查看 action对其进行操作如修改、延迟 dispatch 或发起异步请求然后决定是否将 action 传递给下一个中间件或者直接发送给 reducers 进行状态更新。 中间件的结构通常遵循一个标准的函数签名即接受 store 的 dispatch 方法和 getState 方法作为参数然后返回一个新的增强过的 dispatch 函数。这个新的 dispatch 函数会在执行原先的 dispatch 行为前后插入自定义的逻辑。 Redux 中常见的中间件如 redux-thunk 和 redux-saga 分别用于简化异步操作的处理。redux-thunk 允许 dispatch 一个函数而不是单纯的 action 对象这个函数可以在运行时生成和 dispatch 多个 action。而 redux-saga 通过生成器函数实现复杂的异步流程控制它可以监听 actions 并触发一系列的异步操作然后再 dispatch 回结果 action。 12. 对React Hooks的理解 hooks的出现使函数组件的功能得到了扩充拥有了类组件相似的功能在我们日常使用中使用hooks能够解决大多数问题并且还拥有代码复用机制因此优先考虑hooks 解决老的函数式组件在React Hook出现之前函数式组件也称为无状态组件的主要特点与优缺点如下 早期函数式组件优点 简洁性函数式组件代码结构简单易于阅读和理解因为它仅负责接收props并基于props返回JSX元素不涉及复杂的生命周期方法和状态管理。 效率由于没有内部状态和生命周期方法函数式组件在每次props改变时都会重新渲染而这种简单的渲染方式往往更快减少了不必要的计算和DOM操作。 易测试由于它们是纯函数不依赖外部状态或上下文因此单元测试更加容易和可靠。 记忆化React在某些情况下能够利用PureComponent或shouldComponentUpdate优化减少不必要的渲染即使对于无状态组件。 函数式组件缺点 无状态最大的限制在于它们不能拥有自身的state所有数据必须由父组件通过props传递难以实现局部状态管理。 无生命周期方法这意味着无法在组件挂载、更新、卸载等阶段执行自定义操作比如数据获取、订阅、清理等。 逻辑复用困难若需复用包含副作用或状态相关的逻辑往往需要借助高阶组件HOC或Render Props等模式这会导致组件层级过深代码组织不够直观。 Hooks 是 useState、useEffect、useMemo 等 hook方法的总称提供了一种在函数组件中实现状态逻辑、生命周期方法、副作用处理以及其他各种功能的方法使得函数组件也能拥有原本只有类组件才能拥有的能力。 Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性 至于为什么引入hook官方给出的动机是解决长时间使用和维护react过程中常遇到的问题例如 【状态复用困难】难以重用和共享组件中的与状态相关的逻辑 【可维护性差】逻辑复杂的组件难以开发与维护当我们的组件需要处理多个互不相关的 local state 时每个生命周期函数中可能会包含着各种互不相关的逻辑在里面 【this 心智负担高】类组件中的this增加学习成本类组件在基于现有工具的优化上存在些许问题 【函数组件太弱】由于业务变动函数组件不得不改为类组件等等 为函数式组件赋能 在以前函数组件也被称为无状态的组件只负责渲染的一些工作 因此现在的函数组件也可以是有状态的组件内部也可以维护自身的状态以及做一些逻辑方面的处理 在React Hooks推出之前函数式组件非常适合于那些只需要根据传入props进行渲染的简单场景但对于复杂的交互逻辑和状态管理开发者不得不转向类组件或采用间接的方式来弥补这些不足。React Hooks的引入极大地增强了函数式组件的能力使得它们既能保持简洁又能拥有状态管理和生命周期功能有效地解决了上述缺点。 13. 高阶组件 React 中的高阶组件Higher-Order Component简称 HOC是一种高级的React组件抽象概念它本质上是一个函数此函数接受一个React组件作为参数并返回一个新的封装过的React组件。高阶组件主要用于代码复用、逻辑抽象和交叉关注点的处理比如权限控制、数据预取、主题样式切换等场景。 什么是高阶组件 在函数式编程的概念中高阶函数是指接受函数作为输入或者输出函数的函数。在React中高阶组件遵循同样的原则它是接收组件并返回新组件的函数这样可以使得我们能够在不修改原始组件代码的情况下为其增加额外的功能。 怎么编写高阶组件 下面是一个基础的高阶组件示例它接收一个组件WrappedComponent并返回一个新的组件为传入的组件增加了某些通用逻辑或特性 function withEnhancement(WrappedComponent) {return class EnhancedComponent extends React.Component {componentDidMount() {// 在这里添加额外的生命周期逻辑}render() {// 可能会修改或添加props或者包裹WrappedComponentconst newProps { ...this.props, extraProp: someValue };return WrappedComponent {...newProps} /;}}; }// 使用高阶组件 const EnhancedMyComponent withEnhancement(MyComponent); 应用场景 跨组件状态管理当多个组件需要共享相同的数据源或状态时可以通过高阶组件统一管理和分发状态。Props代理HOC可以拦截和修改传入子组件的props例如注入依赖、处理副作用逻辑或进行数据转换。生命周期方法的封装可以集中处理诸如数据加载、订阅和取消订阅等生命周期事件。组件装饰为组件添加全局功能如路由、错误边界、日志记录、性能检测等。权限控制根据用户的权限信息决定是否渲染某个组件或者限制组件的部分功能。响应式数据获取在组件挂载前或更新时自动获取数据然后将其作为props传递给被包裹的组件。主题切换允许在不同主题间切换通过高阶组件动态修改组件的样式或其他主题相关属性。 总结起来高阶组件提供了一种强大的抽象手段帮助开发者更好地组织和复用代码保持组件层级扁平化并且能够集中处理与特定业务逻辑或框架无关的通用需求。随着React Hooks的引入虽然很多原本通过HOC实现的需求可以转由自定义Hooks完成但在某些情况下特别是在处理复杂的组件组合和扩展时高阶组件依然是一种有效的设计模式。 通过上面的了解高阶组件能够提高代码的复用性和灵活性在实际应用中常常用于与核心业务无关但又在多个模块使用的功能如权限控制、日志记录、数据校验、异常处理、统计上报等 数据流管理例如在Redux中connect函数就是一个典型的高阶组件用来连接React组件与Redux Store。 生命周期管理为多个组件提供统一的生命周期逻辑如初始化、清理资源、错误处理等。 条件渲染根据某些条件动态决定是否渲染某个组件或者渲染不同的组件版本。 API调用封装在组件渲染前后执行API调用并将数据注入到组件props中。 权限控制 日志记录 数据校验 异常处理 统计上报 14. 受控组件和非受控组件 React 受控组件Controlled Components 受控组件是指组件内部的状态完全由React的state管理用户输入的值立即反映到组件的state中。在受控组件中表单元素如input、textarea或select的值不是直接由DOM本身管理而是通过React组件的state进行控制。例如input元素的值由value属性关联到组件的state变量每次用户输入时都会触发onChange事件此时组件会根据事件处理函数更新state进而更新value属性始终保持DOM元素的值与state同步。 特点 值由React组件控制用户输入不会直接修改DOM元素的值。必须提供onChange事件处理函数以更新组件状态。表单提交时可以通过组件自身的state获取用户输入的最新数据。 优点 提供了对用户输入行为的完全控制方便在表单验证、实时反馈等方面做出快速反应。状态管理集中有利于组件间的协作和数据一致性。 缺点 需要额外的代码来处理onChange事件和更新state增加了开发复杂度。不适用于某些不需要实时响应用户输入或者希望由DOM自身处理的场景。 常用场景 实现复杂的表单验证逻辑需要实时获取并校验用户输入的表单。用户输入内容需要与其他组件或数据源紧密联动例如搜索框实时查询。 React 非受控组件Uncontrolled Components 非受控组件则是指DOM元素的值由浏览器本身管理不受React组件的state直接影响。在非受控组件中表单元素的值是由DOM自身的defaultValue属性初始化用户输入的改变并不会立刻同步到React组件的state中。获取非受控组件的值通常通过ref来访问DOM节点的value属性。 特点 用户输入直接改变DOM元素的值不依赖React组件的state。不需要提供onChange事件处理函数来更新state。获取当前值时通常需要使用ref。 优点 开发简单更接近原生HTML表单的使用方式。适用于只需要在表单提交时获取用户输入值的简单场景。 缺点 无法实时跟踪表单值的变化不利于实现即时验证或联动其他组件的功能。多个组件共享数据时需要额外考虑数据同步问题。 常用场景 简单的表单仅在提交时才关心用户输入的值而不关心输入过程。需要保留浏览器默认行为比如在文本框中粘贴内容时保持原有格式。 总结来说受控组件适合于需要对用户输入实时响应和严格控制的场景而非受控组件更适合于简单的表单处理或者是需要利用原生DOM特性的场合。开发者应根据具体需求灵活选择合适的方式来处理表单输入。 15. React refs 的理解 React 中的 ref 是一种在组件之间直接访问 DOM 节点或在函数组件中访问类组件实例的能力。React 的 ref 底层原理涉及以下几个关键点 创建和分配 Refs React 提供了几种创建 ref 的方式如 React.createRef()适用于类组件、useRef() Hook适用于函数组件以及 forwardRef()用于在函数组件之间传递 ref。创建 ref 后可以通过 ref{myRef} 的方式将 ref 分配给具体的 DOM 元素或类组件实例。 React 内部对 Ref 的处理 当 React 渲染组件时它会注意到带有 ref 属性的元素并将 ref 的 .current 属性指向相应的 DOM 节点或组件实例。对于 DOM 节点React 在组件挂载时将 ref 的 .current 设置为对应的 DOM 元素。对于类组件.current 指向的是组件的实例对象。对于函数组件通过 forwardRef 结合 useImperativeHandle Hook可以让函数组件暴露特定方法或属性给父组件。 生命周期中的 Ref 更新 当组件的生命周期发生变化如挂载、更新或卸载时React 会更新 ref 的 .current 属性。在组件卸载时.current 会被设置为 null以避免对已不存在的 DOM 节点或组件实例进行引用。 Ref 的传播与转发 使用 React.forwardRef API我们可以创建一个能够将接收到的 ref 传递给其内部子组件的组件这对于函数组件尤为有用因为它允许函数组件也能获得和操作内部 DOM 节点或子组件实例。 底层实现 在 React 的 Fiber 架构中refs 是在调度和 reconciliation 过程中进行处理的。React 通过 commitWork 阶段将更新的 ref 信息应用到实际的 DOM 树中。 总而言之React 的 ref 系统通过对组件生命周期的精细控制和对 DOM 操作的抽象为开发者提供了便捷地访问和操作底层 DOM 节点的能力同时也支持组件间的直接交互。React 内部通过巧妙的数据结构和算法确保了 ref 更新的高效和准确。 Ref的底层绑定 当React在渲染过程中遇到带有ref属性的元素时会通过内部机制将ref对象与对应的DOM节点或组件实例建立联系。对于DOM元素React会在组件挂载后立即将ref对象的current属性指向对应的DOM节点。 对于类组件ref的current属性将会指向组件的实例。这样就可以访问类组件的实例方法和内部状态。 对于函数组件通过forwardRef和useImperativeHandle可以定制函数组件对外暴露的实例行为使其也可以拥有类似类组件实例的行为。 生命周期中的Ref管理在组件生命周期的不同阶段React会适当地更新ref的current属性。例如在组件卸载时React会将ref的current设为null以防止对已卸载的DOM节点或组件实例进行引用。Ref的更新和同步React的Fiber架构在调度和Reconciliation过程中处理ref。在确定了DOM更新计划后React会在commit阶段把ref的更新同步到实际的DOM树中。React Fiber与Ref的关系React Fiber通过commitRootImpl等内部方法在DOM更新阶段遍历Fiber节点当遇到有ref的节点时会将ref.current指向正确的DOM节点或组件实例。   总的来说React通过一套内部机制在不影响React组件抽象的同时为开发者提供了一种间接但灵活地访问和操作DOM节点的方式。通过ref开发者可以在React的声明式编程模型中实现对DOM的命令式操作这对处理聚焦、测量尺寸、动画等场景至关重要。 16. 组件之间如何通信 父组件向子组件通信父组件通过 props 向子组件传递需要的信息。 子组件向父组件通信: props回调的方式。 跨级组件的通信方式 Context API React的Context API提供了一种在组件树中共享数据的方法而无需手动通过每个级别的props传递。你可以创建一个Context对象并使用MyContext.Provider在组件树中的某个位置提供数据。然后任何子组件无论多深都可以使用MyContext.Consumer或useContext Hook来访问这些数据。Redux Redux是一个流行的状态管理库它允许你在应用程序的任何地方管理和访问状态。通过使用Redux你可以创建一个全局可访问的store来存储应用程序的状态并使用connect函数或useSelector和useDispatch Hooks将组件与store连接起来。props React Hooks如useState和useReducer与自定义Hooks 对于较简单的跨级通信你可以使用useState和useReducer Hooks在父组件中管理状态并通过自定义Hooks或props将状态和方法传递给子组件。自定义Hooks允许你封装和重用状态逻辑。MobX MobX是另一个状态管理库它提供了一种更响应式的方法来管理和更新应用程序的状态。MobX鼓励使用可观察的对象和反应机制来自动更新UI。事件总线Event Bus或发布-订阅模式 你可以实现一个简单的事件总线或使用现有的库如mitt、tiny-emitter等允许组件订阅事件并在事件发生时接收通知。这种方法在需要解耦组件时特别有用。全局状态容器如window对象 虽然不推荐作为常规做法但有时你可以将状态附加到全局对象如window上以实现跨级通信。这种方法应该谨慎使用因为它可能导致难以追踪的状态更新和潜在的命名冲突。父组件回调 通过父组件向子组件传递回调函数子组件可以在需要时调用这些函数来通知父组件状态的变化。这种方法适用于较简单的场景和较浅的组件层次结构。使用第三方库 除了Redux和MobX之外还有其他一些第三方库可以帮助实现跨级组件通信如reactn、unstated、zustand等。这些库提供了不同的抽象和机制来处理状态管理。 父组件获取子组件的状态和方法 回调函数使用 refs 1、通常建议遵循React数据流向单向数据绑定的原则尽量避免直接访问子组件的状态。 2、使用回调函数是一种更符合React设计理念的方式它促进了组件之间的解耦和可复用性。 3、Refs 主要用于获取DOM节点或在必要时获取子组件实例进行一些特殊操作而不鼓励常规情况下频繁获取子组件的状态。 17. hook 的跨级组件的通信方式 Context API with useContext Hook 顶层组件创建createContext 包裹组件子组件使用useContext useReducer with useContext 首先在顶层组件或接近顶层的位置创建一个Context并通过 useReducer 初始化和管理状态。然后将 useReducer 返回的状态和dispatch方法作为值放入Context中。这样一来任何需要访问或修改这个状态的子组件不管它们在层级结构中的位置有多深只要通过 useContext 消费这个Context就能直接获取到状态和dispatch方法实现跨层级的状态共享和更新 Redux or MobX with their respective Hooks 如果你的应用规模较大可以使用Redux或MobX配合它们的React Hooks如useSelector、useDispatchRedux Toolkit或useObservable、useComputedMobX来进行跨级甚至全局范围内的状态管理。自定义Hook Context 你可以创建一个自定义Hook封装对Context的消费然后在任何层级的组件中使用这个自定义Hook。事件总线Event Bus 使用第三方库如pubsub-js或者自己实现一个简单的事件发布订阅系统通过发布和订阅事件来实现跨级通信。但在React中这种方法相比Context API等更为低效且不易于维护故不太推荐。祖先组件作为中介 如果组件层次结构相对清晰可以向上寻找共同的祖先组件作为通信的媒介祖先组件可以使用useReducer、useState等方式维护共享状态再通过props向下传递给需要通信的子组件。 18. hook 模拟生命周期 类组件生命周期方法 对应的 Hooks 功能 constructor N/A直接在函数组件中初始化状态即可如使用 useState 初始化状态 componentDidMount useEffect(fn, []) 传入一个空数组作为依赖项表示在组件挂载后执行一次 componentDidUpdate(prevProps, prevState) useEffect(fn, [dependencies]) 传入依赖项数组当这些依赖项变化时执行 componentWillUnmount useEffect 返回的清理函数在组件卸载前执行清理操作 shouldComponentUpdate(nextProps, nextState) React.memo 对于函数组件用于优化不必要的渲染 或者在 useEffect 的依赖数组中精确定义需要监听的变化 getDerivedStateFromProps 避免在函数组件中使用派生状态。推荐使用 useState 或 useReducer 并在 useEffect 中根据props更新state。 补充说明 对于 componentDidUpdateReact Hooks并没有直接对应的Hook来模拟但可以通过在 useEffect 的依赖数组中声明需要观察的props或state变量来实现类似的效果。若要模拟类组件生命周期中的状态更新逻辑可以结合 useState、useReducer 以及 useEffect 等Hooks来实现。 19. react 的批处理 在最新版本的React 18中批处理机制得到了进一步增强和改进。批处理主要是指React能够有效地合并多个连续的状态更新从而减少不必要的渲染次数。 在React 17及以前版本中React已经实现了半自动的批处理即在React事件处理程序内部发生的多个setState调用会被合并在一起并且在事件循环结束前一次性执行这些更新而非分别触发多次渲染。 React 18引入了更全面的自动批处理机制其中的核心改变在于 自动批处理Automatic Batching 在React 18中批处理不仅限于事件处理程序而是扩展到了整个React的工作流程中。这意味着任何异步上下文中例如Promise回调、setTimeout回调等发起的多个状态更新请求都会被自动合并即使它们不在同一个事件处理器中。 Concurrent Mode下的批处理 React 18引入了Concurrent Mode其中的批处理变得更加智能它可以更好地利用浏览器的异步能力将多个更新放入微任务队列进行合并然后在一个批次中执行从而大大减少了视觉层面上不必要的界面刷新。 createRoot API 要启用React 18的全部批处理功能推荐使用ReactDOM.createRoot()方法替换原有的ReactDOM.render()方法来挂载根组件。这样做是为了兼容新的批处理逻辑以及并发渲染特性。 新增的过渡API React 18还提供了新的API如startTransition允许开发者标记某个状态更新为可延迟的这将进一步帮助React进行有效的批处理和优化渲染过程特别是对于那些不影响UI关键路径的状态变更。 总之React 18通过更加精细和广泛的批处理机制增强了其性能优化能力使得应用程序能够在状态频繁变动的情况下仍然保持高效和流畅的用户体验。 20. setState 到页面重新渲染经历了什么 setState 方法在 React 中调用后直到页面重新渲染之间经历了一系列的步骤。以下是这个过程的详细解释 状态合并 当调用 setState 时React 不会立即改变组件的状态而是将传入的新状态对象与当前状态进行合并。合并通常是浅层合并这意味着如果新状态包含深层次的对象属性更改那么只有第一层属性会合并深层对象的更改可能不会生效除非显式替换整个深层对象。 异步处理 React 将 setState 操作视为异步的特别是当在事件处理器或生命周期方法中调用时。这意味着调用 setState 并不会立即导致重新渲染。实际上React 可能会把多个连续的 setState 调用合并成一个以减少不必要的渲染次数。 批处理 React 有一个批处理过程它可以收集在一个事件循环 tick 内的多个 setState 调用然后一次性去更新状态。这样有助于在高并发更新时优化性能。 状态更新调度 React 会在微任务队列中安排状态更新的任务这通常发生在事件处理结束、生命周期钩子调用之后或者在某些异步操作如网络请求完成之后。 组件生命周期方法 在状态更新前React 可能会触发 componentWillReceiveProps在旧版 React 中或 getDerivedStateFromProps在 React 16.3 版本中接着是 shouldComponentUpdate如果有实现的话来决定是否需要继续渲染。 重新渲染决策 如果根据新的 props 或 stateReact 确定组件需要重新渲染它会进入 render 方法来生成新的虚拟 DOM 树。 虚拟 DOM 比较 新的虚拟 DOM 与旧的虚拟 DOM 进行对比找出最小化的差异diffing算法确定哪些实际 DOM 节点需要更新、添加或删除。 DOM 更新 根据 diff 结果React 更新实际的 DOM 树仅做必要的改动。 生命周期方法调用 在 DOM 更新完成后React 会触发 getSnapshotBeforeUpdate如果有的话来抓取更新前后的状态差异随后触发 componentDidUpdate 生命周期方法这时组件已经反映出了新的状态和 UI。 21. React 事件代理原理 事件绑定在顶级容器React 在组件的最外层容器比如在ReactDOM.render()方法挂载的DOM节点上绑定一个单一的事件监听器而不是为每个子组件分别绑定。合成事件系统React 创建了自己的合成事件SyntheticEvent它是对浏览器原生事件的一层抽象无论底层浏览器如何合成事件的行为都是一致的。当用户交互发生时相应的原生事件会冒泡到顶层。事件分发当事件冒泡到顶层的事件监听器时React 根据事件的目标event.target来判断哪个组件应当处理这个事件并调用相应组件上绑定的事件处理器。映射关系React 维护了一种映射关系记录了每个组件内部定义的事件处理器与其对应的实际DOM节点之间的联系。当合成事件触发时React能够准确地找到应当处理这个事件的组件实例并调用相应的处理函数 React 16 之前事件代理确实是在document级别进行的这样做的确有可能引起全局事件冲突和性能问题。React 17 引入了一个重要的改进它改变了事件绑定的位置。React 17开始事件监听器会直接附加到React组件挂载的DOM节点上而不是像以前那样固定在document上。这样可以避免与外部库或其他独立于React的事件监听器产生冲突同时减少不必要的全局事件监听。 22. React.PureComponent 和React.memo React.PureComponent 应用场景React.PureComponent 是一个内置的类组件它继承自 React.Component并且重写了 shouldComponentUpdate 生命周期方法用于决定何时需要更新组件。 原理shouldComponentUpdate(nextProps, nextState) 方法默认执行浅比较shallow comparison比较当前组件的 props 和 state 与即将接收到的新 props 和 state 是否相等。如果两者完全相等浅比较下引用不变或值类型相等则返回 false阻止组件进行重新渲染否则返回 true组件将会进行正常渲染。 注意浅比较意味着对于复杂的数据结构如嵌套对象或数组如果内部的值发生了变化但是引用地址没变PureComponent 无法检测到这些变化会导致组件不会更新。 React.memo 应用场景React.memo 是一个高阶组件HOC专门用于优化函数组件的性能相当于函数组件版本的 PureComponent。 原理React.memo 会包裹提供的函数组件并为其添加一层优化机制。当组件的 props 发生变化时React.memo 也会进行类似的浅比较。 默认情况下React.memo 会比较前后两次传递给组件的 props 对象是否相等如果不相等则重新渲染组件如果相等则跳过渲染。 自定义比较函数React.memo 允许传入第二个参数作为自定义的比较函数这个函数接收新旧两个props对象作为参数由开发者自行决定是否应该触发组件重新渲染。 React.PureComponent 适用于类组件而 React.memo 适用于函数组件它们通过浅比较来决定组件是否需要重新渲染从而达到性能优化的目的。但请注意这两种方式都不适用于含有深层嵌套数据结构或依赖内部状态变更的组件优化。在这种情况下应手动进行深比较或者使用更高级别的优化手段。 23. 触发 React 重新渲染 状态变化State Update 当组件内部调用 setState() 方法更新状态时React会触发该组件及其后代组件的重新渲染流程除非 shouldComponentUpdate()、getDerivedStateFromProps() 或者 useEffect() Hook 中指定了特殊的优化条件。 Props变化Props Change 当父组件向子组件传递的 props 发生变化时子组件会接收到新的 props进而触发重新渲染。 Context变化Context Update 如果组件订阅了某个Context当Context的值发生变化时相关组件也会重新渲染。 Force Update 使用 forceUpdate() 方法会强制组件重新渲染不论其状态或props是否发生变化。这是一种特殊情况通常不推荐使用因为它绕过了React的优化机制。 React.memo PureComponent 虽然 React.memo 和 React.PureComponent 旨在防止不必要的渲染但如果它们的props比较得出的结果是不相等的对于 React.memo 可以自定义比较函数那么包裹的组件也会重新渲染。 useState() Hook 更新 在函数组件中使用 useState() Hook 更新状态时同样会引起组件重新渲染。 UseReducer Dispatch 使用 useReducer() Hook 时dispatch动作后reducer返回新的state会触发组件重新渲染。 生命周期钩子 在某些生命周期方法中进行状态更改如在 componentDidMount、componentDidUpdate 或 useEffect 中的异步操作完成后。 自定义Hook引起的副作用 在自定义Hook内部使用 useState、useReducer 或 useContext 等Hook更新状态同样会触发使用了这些自定义Hook的组件重新渲染。 Web API事件 例如当组件监听到浏览器的某些事件如窗口大小变化、存储事件等并在事件处理函数中做了状态更新这也可能引发组件重新渲染。 24. 能render时访问refs吗 不能。这是因为refs是在组件挂载到DOM之后填充的并且在render()执行期间React还没有完成这个过程。 refs会在组件渲染完成后才被赋值即在组件生命周期的某个阶段比如componentDidMount或getDerivedStateFromProps已弃用后的一个回调钩子中才能保证已经被正确设置并指向真实的DOM节点或组件实例。 因此如果你尝试在render()方法内部访问ref它可能会是null或未定义的因为在那个时刻React还没有机会将其关联到实际的DOM元素或组件实例上。 正确的做法是在生命周期方法中或者其他合适的时机例如事件处理函数中访问refs。例如在componentDidMount或useEffect Hook对于函数组件中可以安全地通过this.refName.current对于类组件或 useRef Hook 返回的 .current 属性对于函数组件来访问refs指向的内容。 25. 性能优化在哪个生命周期原理 React 中进行性能优化的关键生命周期方法是 shouldComponentUpdate(nextProps, nextState)这是在类组件中可用的一个生命周期方法。另外在函数组件中可以通过 React.memo 或者 useMemo、useCallback Hooks 来实现相似的效果。 shouldComponentUpdate(nextProps, nextState) 这个方法在组件接收到新的 props 或 state 更新后但在实际渲染前被调用。默认情况下React 组件在接收到新的 props 或 state 后都会重新渲染。然而如果组件的渲染成本较高或者状态变化并不影响视图我们可以通过重写 shouldComponentUpdate 方法根据 nextProps 和 nextState 与当前的 props 和 state 进行比较如果两者相同则返回 false阻止不必要的渲染从而优化性能。 优化原理 shouldComponentUpdate 的优化原理是基于组件的局部更新理念。通过检查组件是否真的需要因 props 或 state 的变化而更新视图避免了对整个组件树的不必要的 diff 过程和 DOM 更新。当返回 false 时React 不会进一步递归更新该组件及其子组件节省了 CPU 计算和浏览器 DOM 操作的资源。 函数组件优化 React.memo用于函数组件的性能优化它是一个高阶组件通过记忆最近一次的 props 值然后在下次渲染时比较新旧 props 是否有变化如果没有变化就不执行组件的渲染逻辑。useMemo用于缓存昂贵计算结果的 Hook当依赖项数组中任何一个值发生变化时才会重新计算 memoized 值。useCallback类似于 useMemo但它用于缓存函数确保当依赖项没有改变时返回同一个函数引用从而避免不必要的子组件重新渲染。 26. React-Router的实现原理是什么 基于 hash 的路由通过监听hashchange事件感知 hash 的变化 改变 hash 可以直接通过 location.hashxxx 基于 H5 history 路由 改变 url 可以通过 history.pushState 和 resplaceState 等会将URL压入堆栈同时能够应用 history.go() 等 API监听 url 的变化可以通过自定义事件触发实现 基于 Hash 的路由Hash-Based Routing 优点 兼容性良好几乎在所有浏览器中都可以使用即使是老旧的浏览器包括不支持HTML5 History API的浏览器。无需服务器配置由于Hash改变并不会触发页面刷新所以浏览器不会向服务器发送新的请求这就意味着无需对服务器进行任何特定配置以支持前端路由。 缺点 URL 不够美观URL中包含哈希符号#例如 http://example.com/#/about这在一定程度上影响了用户体验和SEO搜索引擎优化。浏览器历史记录管理受限每个Hash变化都被浏览器认为是同一页面的不同状态所以在浏览器的历史记录栈中会产生大量条目不利于用户导航回退。无法进行真正的网页跳转由于hash变化不会触发页面加载所以不能通过改变hash实现真正的页面跳转和前进后退功能需要借助onhashchange事件自行管理。 基于 HTML5 History 的路由History-Based Routing 优点 美观的URL能够提供更直观、更友好的URL例如 http://example.com/about这种URL看起来更像是传统web应用的链接。更好的用户体验可以利用浏览器的前进、后退按钮进行导航且不会在历史记录中产生过多条目。有利于SEO对于需要搜索引擎优化的应用History API可以让每个路由映射到唯一的真实URL便于搜索引擎索引和理解页面结构。 缺点 兼容性限制需要现代浏览器支持HTML5 History API老版本浏览器如IE9及以下版本可能不支持。需要服务器支持因为切换路由时会改变URL路径当用户直接访问这些路径或刷新页面时服务器需要配置重定向或服务端渲染以避免404错误。实现相对复杂相较于基于Hash的路由History API的实现稍微复杂一些需要更多的代码来处理浏览器的路由变化和页面跳转。 27. react 中怎么实现重定向 重定向Redirect在Web开发中指的是将用户的请求从一个URL转向另一个URL的行为通常是当用户访问某个页面时服务器或前端框架决定将用户引导至另一个页面。 使用Redirect组件适用于React Router v4及更高版本 在一个路由配置或组件内部使用Redirect组件当组件渲染时它会立刻执行重定向。编程式重定向 通过history对象或者useHistory Hook可以在组件内部进行动态重定向。在路由配置中实现重定向 在设置路由规则时可以直接配置重定向规则 28. react-router 里的 Link 标签和 a 标签的区别 HTML a 标签 原生HTML元素点击后会触发浏览器的默认行为即发送一个新的HTTP请求到指定的URL页面会发生整页刷新。通过 href 属性指定目标URL。 React Router Link 标签 是React Router提供的一个组件专为SPASingle Page Applications设计。点击后不会导致整页刷新而是通过React Router的内部机制来改变当前应用的状态只重新渲染需要更新的部分实现页面局部刷新。通过 to 属性指定目标路由而不是URL可以是绝对路径或相对路径。Link 标签同样支持激活样式activeClassName、activeStyle等功能可根据当前路由是否匹配来添加特定样式。 a 标签用于传统的、页面间全量加载的导航。Link 标签则是为React Router中的路由导航设计用于在SPA中实现无刷新的页面跳转能够更好地整合到React应用的客户端路由系统中。 29. React-Router 4的Switch有什么用 React Router 4 中的 Switch 组件用于渲染一组 Route 组件它会遍历其子 Route 组件并依次检查它们是否与当前的location匹配。重点在于Switch 会确保只渲染第一个匹配路径的 Route一旦找到匹配的 Route它会停止遍历剩余的 Route 子组件。 这样做的好处是可以避免在多个路径同时匹配时多个组件同时被渲染的情况确保任何时候路由匹配时只有一个组件是活跃并渲染出来的。这对于构建更有序和明确的路由逻辑非常有用尤其是在有嵌套路由或多分支路由的情况下能够确保页面展现的正确性和一致性 30. useEffect 与 useLayoutEffect 的区别 useEffect 和 useLayoutEffect 都是 React 中的 Hook用于处理副作用例如订阅事件、执行异步操作、更新DOM属性等。它们的主要区别在于执行时机和浏览器渲染流水线的影响 执行时机 useEffect在所有的 DOM 变更已经完成并同步到浏览器界面之后浏览器的事件循环下一次迭代中异步执行。这意味着在 useEffect 里的代码运行时用户已经可以看到渲染的结果。useLayoutEffect在所有的 DOM 变更完成后立即同步执行紧接在 DOM 更新之前。这意味着 useLayoutEffect 内部的代码更改会影响到当前帧的渲染结果因此在它执行期间浏览器会暂停布局和绘制工作等待同步代码执行完毕。 案例树状组件渲染完后修补对齐线div 宽高改变时使用 useLayoutEffect 要特别谨慎因为如果执行的副作用中有耗时操作可能会导致页面的“卡顿”。 对渲染性能的影响 useEffect由于其异步执行的特性不会阻塞浏览器的渲染流程所以不会引起明显的卡顿或布局闪烁。useLayoutEffect由于其同步执行若在其回调函数中修改了样式或其他影响布局的属性将会强制浏览器重新计算布局这可能造成性能上的影响特别是在循环或大量元素上进行同步更新时。 何时使用 useEffect适用于大部分副作用场景尤其是涉及网络请求、设置定时器、订阅事件等不直接影响布局的任务。useLayoutEffect在那些需要在渲染后立即操作DOM并确保DOM更新与之同步的场景中使用比如某些动画效果、依赖DOM尺寸变动的操作或者为了防止UI闪烁而需要在绘制前调整样式的情况。 总结来说useEffect 更适合非阻塞渲染管线的异步操作而 useLayoutEffect 则用于那些需要严格保证在渲染完成前执行并可能影响布局的操作。在大部分情况下优先考虑使用 useEffect只有在特定场景下当确实需要同步更新并避免布局抖动时才应该选择 useLayoutEffect。 31. Hooks需要注意的问题 1不要在循环条件或嵌套函数中调用Hook必须始终在 React函数的顶层使用Hook 2使用useState时候使用pushpopsplice等直接更改数组对象的坑 3useState设置状态的时候只有第一次生效后期需要更新状态必须通过useEffect 4善用useCallback 32. useState实现原理 以下是useState在React中的底层实现原理的大致步骤概述 注册状态 当React在渲染函数组件的过程中遇到useState(initialState)时它会在组件实例的内部创建一个新的状态槽slot并将initialState作为初始值存入该槽位。由于函数组件没有实例的概念React通过Fiber节点来模拟实例行为所以实际上是给对应的Fiber节点添加了状态。状态钩子的创建 React会返回一个状态对——当前状态值和更新函数。状态值就是从状态槽中读取的初始值更新函数则是由React自动生成的一个闭包函数它能够找到正确的状态槽并更新其值。状态更新 当调用setState(newState)时React不会立即修改状态而是创建一个更新任务将其加入到一个优先级队列中。这是因为React采用了异步更新策略以批量处理多个状态更新提高性能。调度过程 调度器Reconciler/Renderer会按照一定的优先级顺序处理队列中的更新任务。当轮到处理某个组件的状态更新时调度器会查看新旧状态是否不同如果不同则标记该组件及其子孙组件为需要重新渲染。渲染阶段 在渲染阶段React会重新执行函数组件体以获取最新的UI描述虚拟DOM。此时useState会返回上一次渲染时的状态值但如果在本次渲染周期中有待处理的更新则会返回已更新的状态值。差异比较与DOM更新 React使用虚拟DOM diff算法找出前后两次渲染之间的差异然后只对实际DOM进行必要的更新操作。闭包的作用setState函数通过闭包绑定到了正确的组件实例即Fiber节点这样在任何地方调用这个函数都能够准确地修改相应组件的状态而不影响其他组件。 综上所述useState在React的底层是通过一套复杂但高效的系统实现的包括状态槽的管理、更新任务的调度、异步更新机制、以及闭包的应用等技术手段从而使得函数组件也能具备维护自身状态的能力。 fiber 角度下的 useState 在Fiber架构下React为每个函数组件实例即Fiber节点内部维护了一个状态对象。当调用 useState(initialState) 时 状态创建 React为当前渲染的函数组件Fiber节点创建一个状态槽并将传入的 initialState 保存在这个槽位中。状态访问与更新 useState 返回一对值当前状态和一个更新状态的函数。这个更新函数通过闭包捕获了与当前Fiber节点关联的状态槽因此可以准确地定位和更新状态。调度与更新 当调用 setState(newState) 时React并不会立即更新状态和重新渲染组件而是将更新操作添加到一个优先级队列中。React Fiber调度器会依据任务优先级决定何时执行这些更新。在重新渲染过程中Fiber节点的状态会根据 useState 返回的新状态值进行更新。记忆化与回收 React在处理Fiber树时会利用记忆化机制确保状态和副作用函数与正确的Fiber节点关联并在组件卸载时正确地清理资源。 因此useState 能够在函数组件中管理状态得益于React Fiber架构对其内部状态管理机制的改进和增强。Fiber不仅负责调度和更新组件还提供了在函数组件中存储和更新状态的能力。 33. useEffect 实现 useEffect是React Hooks中的一个重要Hook用于处理副作用、订阅数据源和执行DOM操作等。以下是useEffect在React中的底层实现原理的大致步骤 注册效应函数 当React在渲染过程中遇到useEffect(callback, deps)时它会捕获当前作用域下的callback函数副作用函数以及依赖数组deps。每次组件渲染完毕后React都会检查是否存在已经注册过的效应函数并且比较新的依赖项列表与旧的是否发生变化。首次渲染时执行 在组件的首次渲染完成后React会立刻执行useEffect中传入的回调函数不论依赖项数组是否为空。这相当于“挂载”阶段执行的生命周期方法如componentDidMount。依赖变化时执行 在后续的渲染周期中React会对比新旧依赖项数组。如果数组内容发生了变化例如引用变更或者数组元素内容有变React就会认为依赖关系有所更新并触发新的副作用函数执行。这对应于类组件中的componentDidUpdate生命周期方法。清理工作 如果在副作用函数中返回了一个清理函数比如取消定时器、解绑事件监听器等那么在下一次副作用函数执行前React会确保先调用上一次的清理函数保证资源的有效释放。这相当于类组件中的componentWillUnmount生命周期方法。优化选项 可以通过传递空数组[]作为useEffect的第二个参数告诉React这个副作用函数只在组件挂载和卸载时运行不依赖任何props或state的变化。这有助于避免不必要的重复执行。 useEffect Hook的工作机制基于渲染周期后的回调执行和依赖项变化的追踪利用这种机制React能够在函数组件中有效地管理和调度各种副作用操作保持组件逻辑清晰的同时也实现了与类组件生命周期方法类似的灵活性和控制力。 任务调度 在 React Fiber 架构下所有的更新任务包括 useEffect 回调函数的执行都被视为可中断和可恢复的工作单元。当组件渲染完成后React Fiber 会根据优先级调度执行 useEffect 回调函数。这意味着即使在组件树的深度遍历过程中也可以暂停当前的工作并转而去处理更高优先级的任务如处理浏览器事件或进行渲染。依赖收集与更新 当 useEffect 被调用时React 会记录下其依赖项数组。在后续的渲染周期中React 通过 Fiber 结构跟踪和比较依赖项的变化决定是否需要重新执行 useEffect 回调函数。Fiber 节点存储了组件的状态信息和副作用信息这是决定何时执行 useEffect 的关键。清理工作与调度 useEffect 回调函数可以返回一个清理函数用于在组件卸载或 useEffect 更新前执行清理操作。React Fiber 在调度任务时会管理这些清理函数的执行确保资源的释放和状态的一致性。并发模式支持 Fiber 架构支持 React 的并发模式Concurrent Mode在这种模式下useEffect 会更加智能地配合调度系统适应可暂停、可恢复的渲染过程使得多个 useEffect 可以并发执行从而提高应用的整体性能和响应性。 34. 为什么不能不要滥用useContext useContext 是React提供的一个Hook它允许组件无需通过props层层传递就能直接访问Context中的数据。然而在使用过程中需要注意适度避免滥用的原因主要有以下几点 过度耦合过多地使用 useContext 可能导致各个组件与全局状态高度耦合。即使这些组件原本不需要关心全局状态也可能因为直接消费Context而导致维护性和可读性降低同时也增加了组件重构和复用的难度。难以追踪依赖当多个组件都通过 useContext 访问同一个Context时一旦这个全局状态发生改变所有依赖它的组件都有可能被迫重新渲染即便它们并不关心这次状态变化的具体内容。这可能导致不必要的性能损失。架构复杂度随着应用规模扩大如果随意创建和使用Context会导致全局状态管理变得混乱且难以维护。合理的做法通常是集中管理和划分不同作用域的Context而不是处处使用。不利于逻辑解耦过度依赖全局上下文可能导致业务逻辑难以拆分和独立管理。理想的做法是将相关性强的状态和逻辑封装在自包含的组件或Redux等状态管理库中保持每个模块的内聚性。 因此在实际开发中应当审慎地使用 useContext只在必要的全局共享状态场景下使用并结合其他最佳实践如Redux、 Recoil、MobX等以及React的其他特性如 useReducer、useState 等合理规划和组织应用的状态管理结构。 35. react 最新版本不同 React 16.x的三大新特性 Time Slicing、Suspense、 hooks Time Slicing 萨拉森解决CPU速度问题使得在执行任务的期间可以随时暂停跑去干别的事情这个特性使得react能在性能极其差的机器跑时仍然保持有良好的性能Suspense 瑟斯半死解决网络IO问题和lazy配合实现异步加载组件。 能暂停当前组件的渲染 当完成某件事以后再继续渲染解决从react出生到现在都存在的「异步副作用」的问题而且解决得非的优雅使用的是 T异步但是同步的写法这是最好的解决异步问题的方式提供了一个内置函数componentDidCatch当有错误发生时可以友好地展示 fallback 组件; 可以捕捉到它的子元素包括嵌套子元素抛出的异常; 可以复用错误组件。 1React16.8 加入hooks让React函数式组件更加灵活hooks之前React存在很多问题 在组件间复用状态逻辑很难复杂组件变得难以理解高阶组件和函数组件的嵌套过深。class组件的this指向问题 难以记忆的生命周期 hooks很好的解决了上述问题hooks提供了很多方法 useState 返回有状态值以及更新这个状态值的函数useEffect 接受包含命令式可能有副作用代码的函数。useContext 接受上下文对象从 React.createContext返回的值并返回当前上下文值useReducer useState 的替代方案。接受类型为 stateaction newState的reducer并返回与dispatch方法配对的当前状态。useCalLback 返回一个回忆的memoized版本该版本仅在其中一个输入发生更改时才会更改。纯函数的输入输出确定性 o useMemo 纯的一个记忆函数 o useRef 返回一个可变的ref对象其Current 属性被初始化为传递的参数返回的 ref 对象在组件的整个生命周期内保持不变。useImperativeMethods 自定义使用ref时公开给父组件的实例值useMutationEffect 更新兄弟组件之前它在React执行其DOM改变的同一阶段同步触发useLayoutEffect DOM改变后同步触发。使用它来从DOM读取布局并同步重新渲染 2React16.9 重命名 Unsafe 的生命周期方法。新的 UNSAFE_前缀将有助于在代码 review 和 debug 期间使这些有问题的字样更突出废弃 javascrip:形式的 URL。以javascript:开头的URL 非常容易遭受攻击造成安全漏洞。废弃Factory组件。 工厂组件会导致 React 变大且变慢。act也支持异步函数并且你可以在调用它时使用 await。使用 React.ProfiLer 进行性能评估。在较大的应用中追踪性能回归可能会很方便 3React16.13.0 支持在渲染期间调用setState但仅适用于同一组件可检测冲突的样式规则并记录警告废弃 unstable_createPortal使用CreatePortal将组件堆栈添加到其开发警告中使开发人员能够隔离bug并调试其程序这可以清楚地说明问题所在并更快地定位和修复错误。 36. react 中状态提升是什么使用场景有哪些 在React中状态提升State Lifting是一种管理状态state的设计模式它是指当多个组件需要共享相同状态时而不是各自在各自的组件内维护独立的状态副本应该将该状态集中管理将其移到这些组件的最近共同父组件或创建一个新的容器组件专门用来托管此状态中去维护。这样父组件可以通过props向下传递状态以及更新状态的方法给子组件子组件通过调用这些方法通知父组件来间接更改和同步状态从而保证所有依赖此状态的子组件能够得到及时和一致的更新。 使用场景举例 表单联动例如有两个相关的输入框当在一个输入框中输入内容时另一个输入框的内容需要同步更新这时就可以将这两个输入框的状态提升到它们的父组件中统一管理。全局应用状态比如导航栏的显示/隐藏状态、主题颜色、登录状态等需要在整个应用范围内共享的状态这些状态通常会被提升到顶层组件如App组件中统一管理。列表项的选择状态在一个列表组件中有多个可勾选的子项这些子项的选择状态需要被集中管理以便进行全选、反选等操作这时可以把选择状态提升到列表组件中。跨组件通信当两个没有直接关联的子组件需要基于同一份数据进行操作时可以通过将这份数据放入它们的公共父组件中来实现在更高层级上的状态控制。 通过状态提升可以确保状态的一致性和避免组件之间的不必要的直接耦合符合React中单向数据流的原则使应用程序的数据流更易于理解和调试。 37. react中页面重新加载时怎样保留数据 Redux将页面的数据存储在redux中在重新加载页面时获取Redux中的数据data.js使用webpack构建的项目可以建一个文件data.js将数据保存data.js中跳转页面后获取sessionStorge在进入选择地址页面之前componentWillUnMount的时候将数据存储到sessionStorage中每次进入页面判断sessionStorage中有没有存储的那个值有则读取渲染数据没有则说明数据是初始化的状态。返回或进入除了选择地址以外的页面清掉存储的sessionStorage保证下次进入是初始化的数据history APIHistory API 的 pushState 函数可以给历史记录关联一个任意的可序列化 state所以可以在路由 push 的时候将当前页面的一些信息存到 state 中下次返回到这个页面的时候就能从 state 里面取出离开前的数据重新渲染。react-router 直接可以支持。这个方法适合一些需要临时存储的场景。 38. 没有看到使用react却需要引入react 本质上来说JSX是React.createElement(component, props, ...children)方法的语法糖。在React 17之前如果使用了JSX其实就是在使用React babel 会把组件转换为 CreateElement 形式。在React 17之后就不再需要引入因为 babel 已经可以帮我们自动引入react。 39. 高阶组件运用了什么设计模式 使用了装饰模式高阶组件的运用 装饰模式的特点是不需要改变 被装饰对象 本身而只是在外面套一个外壳接口。JavaScript 目前已经有了原生装饰器的提案其用法如下 40. useMome 和react.memo区别 React.memo(): 用途 用于优化函数组件通过记忆缓存组件的渲染结果当组件的 props 没有发生变化时避免不必要的重新渲染。实现React.memo() 是一个高阶组件用于包装函数组件返回一个新的记忆化的组件。在组件的渲染时它会检查当前的 props 是否和上一次渲染时的 props 相同如果相同则直接返回上一次的渲染结果。使用场景 适用于纯函数组件尤其是当组件渲染开销较大且 props 变化不频繁时。 useMemo(): 用途 用于记忆化计算结果以避免在每次渲染时都重新计算某个值。它可以缓存计算结果只有在依赖项发生变化时才重新计算。实现useMemo() 是一个 Hook接收一个回调函数和一个依赖数组返回一个记忆化的值。只有当依赖数组中的值发生变化时才会重新计算值否则它会直接返回上一次计算的结果。使用场景 适用于那些在渲染期间需要进行昂贵计算的值尤其是在处理复杂的逻辑或计算的情况下。 区别 应用对象不同 React.memo() 用于优化整个组件的重新渲染。useMemo() 用于优化特定值的计算而不涉及整个组件的渲染。 处理方式不同 React.memo() 处理整个组件的 props 是否发生变化。useMemo() 处理值的计算和依赖项数组。 返回结果不同 React.memo() 返回一个记忆化的组件。useMemo() 返回一个记忆化的值。 应用场景不同 React.memo() 适用于整个组件的渲染优化特别是对于纯函数组件。useMemo() 适用于计算昂贵值以减少不必要的计算开销特别是在处理复杂逻辑的情况下。 在实际使用中这两个工具可能会同时使用以确保组件和值的性能都得到了优化。 41. 事件合成 React引入了“合成事件”SyntheticEvent的概念这是一种跨浏览器的事件抽象层它模拟了W3C标准事件行为并提供了统一的接口给开发者。 事件委派 React并不直接将事件处理器绑定到各个DOM元素上而是采用事件委派的策略将所有的事件监听器绑定到最顶层的容器元素上在React 17之前是document17及以后版本默认绑定在根DOM节点#root上。当任何子元素触发事件时事件会按照浏览器的事件传播机制冒泡到顶层容器此时React的事件监听器会捕获这些事件。 事件对象标准化 React创建了自己的事件对象——合成事件当原生事件发生时React会创建一个与原生事件相对应的SyntheticEvent对象并将其传递给相应的事件处理函数。SyntheticEvent对象是对原生事件对象的包装它具有与原生事件相同的接口和属性但确保了在各种浏览器下的行为一致性。 事件池复用 为了提高性能React采用了事件对象池的技术。每次事件触发后SyntheticEvent对象会被重置并放回池中以备下次使用而不是每次触发事件都创建新的对象从而减少内存分配和垃圾回收的压力。 42. 说下批处理 在React中批处理Batching是一种优化策略主要用于提升React组件状态更新时的性能。批处理的核心概念是将一系列连续的状态更新操作合并到一起然后一次性去触发视图的重新渲染而非每更新一次状态就立即引起一次渲染。 具体来说 在React的早期版本中每当通过setState或使用Hooks中的useState、useReducer等API更改状态时通常会触发组件及其子组件的重新渲染。但如果短时间内有多个状态更新操作每个更新都会导致渲染流水线上的额外开销。 为了避免这种频繁渲染React内部实现了一种机制能够识别出在单个事件处理器如onClick回调、生命周期方法如componentDidUpdate或者React Suspense上下文中的一系列状态变更并将它们合并到同一个批处理中。这样即使在短时间内多次调用setStateReact也会等待这些调用全部结束然后才进行一次实际的组件树比较reconciliation和渲染过程。在React 18中批处理得到了进一步增强尤其是对于异步渲染的支持推出了并发模式。在这种模式下React可以更好地控制何时开始和结束渲染周期以及如何处理中间状态。例如React 18会自动批处理在事件处理器内的状态更新并且支持Suspense边界内的异步状态更新批处理。针对那些需要立即强制同步更新场景React 18还引入了flushSync API允许开发者手动同步执行状态更新绕过批处理机制确保状态变化立即反映到DOM中。 总的来说批处理是React用来优化性能的关键手段之一旨在减少不必要的渲染次数提高应用的整体响应速度。 43. 介绍一下React的patch流程React的patch流程即React的虚拟DOMVirtual DOM更新机制主要包括以下几个步骤 生成新的虚拟DOM树 当组件的state或props发生变化时React会重新执行render方法生成新的虚拟DOM树。虚拟DOM是一种轻量级的JavaScript对象表示它模仿了实际DOM结构。比较Diffing React使用了一种优化过的算法通常称为“ reconciliation algorithm”或“ diff算法”来比较新的虚拟DOM树与上一次渲染后的虚拟DOM树。这个算法并不是简单地对整个DOM树进行深比较而是采取了一些优化策略如 仅对同级节点进行比较不同层级的节点变化不会相互影响。对于相同类型的组件仅比较props和state的变化。对于列表通过key来高效地识别新增、移除和移动的元素。 生成差异对象Delta Diff算法得出哪些部分需要更新后会生成一个差异对象其中包含了需要在实际DOM中进行更新的具体操作。执行更新操作Patching React根据差异对象对实际DOM进行最小化操作。这些操作可能包括 更新元素的属性。插入、删除或移动DOM元素。更新文本内容。 批处理更新 React还会将多个组件的更新操作进行批处理batching在事件循环的下一个tick统一执行以提高性能。 在整个patch流程中React通过高效的Diff算法和最小化DOM操作策略最大程度地减少了对实际DOM的直接操作从而提高应用的性能和响应速度。在React Fiber架构下这个流程变得更加灵活能够支持异步渲染和优先级调度使得UI更新更加平滑和高效。
http://www.yayakq.cn/news/5851/

相关文章:

  • 广东微信网站推广哪家专业wordpress 七牛云图床
  • 网站建设和管理是教什么科目杭州服装设计公司
  • 好的网站搭建公司沈阳免费seo关键词优化排名
  • 驾校视频网站模板哪家公司网站做得好
  • 网站建设作业做一个简单的网站成品网站 售卖
  • 泰安可信的网站建设wordpress表单制作
  • 中国国际贸易网站西安手机网站案例
  • 网站专题怎么做小微企业建站
  • app推广平台网站学网站开发培训机构
  • 网站负责人 法人北京seo专业团队
  • 网站程序方面建筑业大数据服务平台官网
  • 爱妮微如何做网站链接的网址北京做网站设计招聘
  • 武清区网站建设中国铁建华南建设有限公司网站
  • 网站宣传制作深圳防疫今天最新规定
  • 网站建设 字体版权大学网站建设的目标与思路
  • 天津做网站外包公司互联网推广平台有哪些
  • 做大型网站需要多少钱单页网站案例分析
  • 网站首页弹出图片餐厅网站模版
  • 用jsp做视频网站银川哪家网络公司做网站做得好
  • 提高工作效率的句子搜索引擎优化工具
  • wordpress多站点功能wordpress 占内存
  • 红色餐饮网站源码wordpress 相关文章 tag
  • 河北建设工程造价信息网站分销网站制作条件
  • 政务网站模版培训机构招生7个方法
  • 制作网站的页面设计怎么做wordpress 主页幻灯片
  • asp简单购物网站源码网站图片上的水印怎么做
  • 迅睿cms建站教程做信息图网站
  • 网站目录结构 权限郴州做网站的公司
  • 住宅城乡建设部门户网站平台建设上线网站
  • 购物网站开发介绍wordpress 用户角色插件