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

怀化冰山涯IT网站建设公司网站维护提示代码

怀化冰山涯IT网站建设公司,网站维护提示代码,在哪个网站上做预收款报告,电子商城网站建设公司性能优化一直是前端领域讨论的一个热门问题#xff0c;但在平时沟通及code review过程中发现很多人对于React中性能优化理解很模糊#xff0c;讲不清楚组件什么时候更新#xff0c;为什么会更新#xff0c;关于React性能优化的文章虽然比较多#xff0c;但大多数都是在罗列…性能优化一直是前端领域讨论的一个热门问题但在平时沟通及code review过程中发现很多人对于React中性能优化理解很模糊讲不清楚组件什么时候更新为什么会更新关于React性能优化的文章虽然比较多但大多数都是在罗列一些优化的点本文将以React底层更新过程为基础层层递进将性能优化相关的用法、原理串联起来让读者真正理解为什么需要性能优化以及如何使用。 更新流程 React使得前端开发者不再跟DOM打交道只需要控制组件及其状态来完成应用开发。React在这背后做的最主要的工作就是保持组件状态与用户界面的一致性将组件状态构建成用于描述用户界面的UI Tree(或者叫Virtual Dom)并反应的浏览器中这也是React更新的一个最宏观的过程。 我们向下一层看一下当更新发生时的具体过程。 如下图所示假设用户蓝色的结点中发生了一次onClick对应组件触发了相应setStateReact就会重新开始构建整颗UI Tree。因为构建都是从根节点发生的所以会先调用getRootForUpdateFiber找到根节点并触发ScheduleUpdateOnFiber进入Scheduler进行调度开始更新过程更新主要分为两个阶段Render Phase和Commit Phase其中 Render阶段就是根据每个组件中的状态构建出一个新的UI Tree也叫WorkInProgress Tree并为每一个结点对应的操作打上EffectTag即更新、删除、新增。全部构建完成后就进入下一阶段。 Commit阶段就是将构建好的WIP Tree反应到浏览器中即React为我们自动进行相应的dom操作保持UI一致性。 当提交完成后实际上一次更新就完成了用户可以进行交互可能又会触发新的更新从而循环这个过程。 上图的这个过程实际上就是React更新的核心Loop。开发者或者是React团队本身所做的所有关于性能优化的事情本质上都是通过加速这个loop的过程从而实现用户界面的高响应。比如Scheduler中Time Slice机制实际上就是减少每次循环的工作量当然这个过程对我们开发者是无感的不再详细展开我们今天重点的关注是作为开发者如何加速这一过程也就是实现性能优化。 如何加速 这里我们再向下一层看一下Render阶段到底是如何构建UI Tree的。因为结点会组成一个树结构所以构建的过程本身是一个遍历的过程每个阶段结点都会经历beginWork和completeWork大致遍历过程如下图所示 遍历过程并非本篇重点过程有简化 默认优化策略 然而对于一个实际的应用来说涉及节点众多而实际一个用户操作往往只会影响个别的节点如果挨个遍历全部重新构建一遍显然会有些浪费当然React自己也知道这件事具体是怎么优化的呢 我们干脆再深入一层直接到源码看看React在beginWork里都做了些什么 为方便读者理解源码有一定程度简化 // ReactFiberBeginWork.new.js function beginWork(current, workInPrgress, renderLanes) {// 检查props和context是否发生改变if (oldProps ! newProps || hasLegacyContextChanged()) { didReceiveUpdate true;} else { // props或者context都未改变的时候检查是否有pending中的updateconst hasScheduledUpdateOrContext checkScheduledUpdateOrContext(current,renderLanes,);if (!hasScheduledUpdateOrContext) {didReceiveUpdate false;// 当前Fiber可以复用进入bailout流程return attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes);}}// 无法bailout真正进入beginWork流程 } 代码中关于这部分可以看到名称叫LegacyContext所以这里的context实际上是指旧版的context新版的context是否发生变化实际上会反应到pending update中但这里直接理解成context并不影响本文内容因此不再展开 可以看到React在真正进入beginWork构建之前实际上会有一层逻辑判断这就是React自带的性能优化策略。对于那些props statepending update) context没有发生变化的结点会进入bailout流程中文翻译过来为“急救”可以简单理解成这个结点没有发生变化还可以抢救一下没必要让他重生。 我们再进一步看看attemptEarlyBailoutIfNoScheduledUpdate里面做了什么核心其实下面这个方法主要是看子节点state是否发生变化如果没有直接返回null代表当前节点的子树都可以bailout也就是跳过构建 function bailoutOnAlreadyFinishedWork( current: Fiber | null,workInProgress: Fiber,renderLanes: Lanes, ): Fiber | null {// 检查下children是否有pending workif (!includesSomeLane(renderLanes, workInProgress.childLanes)) {return null}// 当前结点无任务工作要做但是子树有克隆子结点继续Reconciler的过程cloneChildFibers(current, workInProgress);return workInProgress.child; } 为什么看子树有没有发生变化时为什么没有比较props 到这里我们就搞明白了React自带的性能优化策略简单概括下如下 接下来我们看一个非常简单的demo思考一下当点击update按钮时Child中的child render会被打印吗 const Child () {console.log(child render);return divI am child/div; };export default function App() {const [count, setCount] useState(0);return (button onClick{() setCount(count 1)}update/buttonChild //); } 根据我们刚才的分析当Child组件进入beginWork流程时因为props state context都没有变应该会被跳过即走Bailout流程才对。但是实际上可以看到每次点击Child组件都会重新渲染这是为什么呢 让我们带着疑问直接到源码里面调试一下 可以看到Child组件确实没有满足React默认优化策略的条件而不满足的原因是props发生了变化。 我们知道JSX本质上是React.createElement的语法糖所以调用Child的地方App组件内本质上是调用React.createElement传递的props为一个空对象App两次渲染传递给子组件的props并不相等 {} ! {} export default function App() {const [count, setCount] useState(0);return (button onClick{() setCount(count 1)}update/button{React.createElement(Child, {})}Child //); } 手动跳过构建 既然Child与状态count无关理论上来讲肯定是可以被跳过的重新渲染呢怎么做呢这里就要引入第一个性能优化API React.memo如下面的例子通过memo包裹来组件之后发现这时候点击后Child不会再重新渲染了。 const Child memo(() {console.log(child render);return divI am child/div; });export default function App() {const [count, setCount] useState(0);return (button onClick{() setCount(count 1)}update/buttonChild //); } 我们直接进入源码中看看memo到底做了什么 类似效果的API还有 PureComponent 、ShouldComponentUpdate由于现在基本都已经拥抱函数式组件此处以memo为例 function updateSimpleMemoComponent( current: Fiber | null,workInProgress: Fiber,Component: any,nextProps: any,renderLanes: Lanes, ): null | Fiber {const prevProps current.memoizedProps;// 使用浅比较代替了全等比较if (shallowEqual(prevProps, nextProps)) { didReceiveUpdate false;}// 检查是否有pending的更新if (!checkScheduledUpdateOrContext(current, renderLanes)) {return bailoutOnAlreadyFinishedWork(current,workInProgress,renderLanes,);}// 无法bailout, 即进入render阶段return updateFunctionComponent(current,workInProgress,Component,nextProps,renderLanes,); } 从源码中我们可以看到当自动bailout不满足时memo实际上提供了另一条路径进入bailout而要求跟默认优化策略非常类似唯一的区别是第10行用shallowEqual替换了原先的props全等比较。而对于我们上面demo的情况由于新旧props都是空对象因此通过浅比较就满足了优化策略从而跳过了构建过程。 那是不是给每个组件都包裹一下memo来尽可能的命中bailout 第三条路 上面的问题显然答案是否定的因为如果是的话那React为什么不直接默认给所有组件都包裹一下还需要开发者手动来不是多此一举么 不这么做的原因是memo并不是免费的shallowEqual会去挨个遍历props并进行比较这个成本可要比全等大多了那有没有办法不使用memo又能命中bailout的第三条路呢这里给大家介绍两种方式 Move state down - 状态下放 还是拿刚才那个例子我们可以看到App组件更新的原因是内部的count发生了变化而Child虽然跟count没有任何关系但是由于同属于一个组件也被带着重新渲染了 const Child () {console.log(child render);return divI am child/div; };export default function App() {const [count, setCount] useState(0);return (button onClick{() setCount(count 1)}update/buttonChild //); } 我们稍微改造一下把count及其相关的逻辑抽离到另外一个子组件Counter中 const Child () {console.log(child render);return divI am child/div; };const Counter () {const [count, setCount] useState(0);return button onClick{() setCount(count 1)}update/button; }export default function App() {return (Counter /Child //); } 可以实际测试一下上面的代码虽然只是简单调整了一下组件结构Child居然不再重新渲染了 原因就在于变化的内容现在在Counter内部App组件会由于满足了默认的性能优化策略不再重新渲染因此传递给Child的props就不会发生变化从而Child也就满足了默认的性能优化策略这种逻辑是具有传递性的即如果Child还有子组件也会因为Child没有重渲染继续满足默认性能优化策略而都被跳过。 大家是否有听说过这样的建议要避免大组件将组件的粒度控制要尽可能的细。 在React中组件本质上就是函数函数有单一职责原则组件也适用 。 想必通过上面的例子我们对于这句话能有更深的理解这么做不仅仅是便于维护而是会直接影响到性能优化。 Lift content up - 内容提升 当然也有状态下放不适用的情况比如但是当遇到下面这个caseChild的外层div中也用到了count如果将Child全部拆分过去到Counter中实际Counter变化Child还是会重新渲染这时候就可以用另外一种方法 内容提升 const Child () {console.log(child render);return divI am child/div; };export default function App() {const [count, setCount] useState(0);return (div classname{count}button onClick{() setCount(count 1)}update/buttonChild //div); } 简而言之就是虽然将Child拆分到Counter中但是Child得渲染不依赖任何Counter的内容可以将Child提升到App中以children的方式进行传递 const Child () {console.log(child render);return divI am child/div; };const Counter ({children}) {const [count, setCount] useState(0);return (div classname{count}button onClick{() setCount(count 1)}update/button{children} /div ); }export default function App() {return (CounterChild //Counter); } 通过上面这种方式其实Child也不会重新渲染调试一下看看Counter的props发现children实际上就是Child能看到两次渲染Child内容并没有变 原因是Child现在是作为Counter组件的propsprops的内容是在App组件中传递的因此可以理解成Child依然是直接依赖于App组件由于App没有重新渲染因此Child也满足了默认的性能优化策略。 跳过局部构建 通过上面的内容我们知道了每次更新时会去重新构建组件树当然我们也可以通过命中bailout来避免组件重新构建但组件内确实发生了状态变化就无法bailout这时候就会进入一个组件的render阶段 function updateSimpleMemoComponent( current: Fiber | null,workInProgress: Fiber,Component: any,nextProps: any,renderLanes: Lanes, ): null | Fiber {// ...// 无法bailout, 即进入render阶段return updateFunctionComponent(current,workInProgress,Component,nextProps,renderLanes,); } 跟随代码继续走我们可以看到render阶段实际上就是调用组件的渲染方法 export function renderWithHooks(current: Fiber | null,workInProgress: Fiber,Component: (p: Props, arg: SecondArg) any,props: Props,secondArg: SecondArg,nextRenderLanes: Lanes, ): any {// ...let children Component(props, secondArg);// ... } 到这里就要轮到两个性能优化的API出场了 useMemo useCallback 背后的逻辑是如果一个组件必须重新绘制时我们可以尽可能加速这个绘制的过程。 useMemo的基本用法相关资料很多这里不再赘述重点说明下使用useMemo的两种场景 避免耗时的逻辑重复计算 如下面的例子所示heavyCalc是一个耗时比较严重的逻辑运算我们期望与state2无关的reRender能够跳过这次运算通过useMemo包裹heavyCalc能够实现只有当state2变化时才重新计算val。 const Comp () {const [state1, setState1] useState();const [state2, setState2] useState();const val heavyCalc(state2);return // ...); } 防止子组件的缓存击穿 list由于是Child组件的props而每次Comp更新都会生成一个全新的对象这会导致Child即使使用了性能优化策略如使用React.memo也无法命中bailout而通过userMemo返回list可以实现每次渲染都返回同一个值。 const Comp () {// ...// 通过缓存这个值来避免子组件memo失效const list []return // ...Child list{list} /); } useMemo是如何做到的呢我们直接看看源码 function updateMemo(nextCreate, deps) {// ...const prevDeps: Arraymixed | null prevState[1];// 对于数组中的每一项全等比较if (areHookInputsEqual(nextDeps, prevDeps)) {return prevState[0];}// 调用函数返回新创建的值const nextValue nextCreate();hook.memoizedState [nextValue, nextDeps];return nextValue; } 实现比较简单最关键的就是第四行做的事情就是将依赖项中的每一个挨个和上一次渲染时传递的依赖项进行全等比较如果都没有发生变化直接将存储的缓存值进行返回否则重新计算。 那我们再看看顺便看看useCallback的实现 function updateCallback(callback, deps) {// ...const nextDeps deps undefined ? null : deps;const prevState hook.memoizedState;// 对于数组中的每一项全等比较const prevDeps: Arraymixed | null prevState[1];if (areHookInputsEqual(nextDeps, prevDeps)) {return prevState[0];}// 直接返回callbackhook.memoizedState [callback, nextDeps];return callback; } 可以看出两者唯一的区别在于一个存储函数的本身(useCallback) 一个存储函数返回的值useMemo) 从实现上我们其实能看出useCallback中不包含任何运算逻辑因此使用场景要比useMemo更少只适用于第二种场景即防止子组件的缓存击穿通过将父组件中声明的回调函数进行缓存来保持子组件props的不变。 const Comp () {// ...// 通过缓存这个值来避免子组件memo失效const handleClick useCallback(() {}, []);return // ...Child onClick{handleClick} /); } 总结和建议 回顾 先简单回顾一下整体的更新逻辑当用户触发更新操作时 React会首先尝试应用默认的性能优化策略尝试对组件进行bailout这一阶段我们在编码过程中可以尽可能的应用状态下放和内容提升满足默认性能优化策略条件提高bailout命中率 默认bailout不满足时我们也可以使用像PureComponent ShouldComponentUpdate React.memo 来降低匹配条件再次进行bailout 当必不可少的需要重新渲染时我们可以使用useMemo useCallback来减少渲染的时间 当全部渲染完成后实际上就构建好了一颗新的UI TreeReact会去对比新旧两颗Tree来找出需要对哪些dom结点进行何种操作这个过程也被称为reconcile大家比较熟悉的diff算法就是发生在这里但是作为开发者这一做的事情很有限我们唯一可以做的事是在通过循环添加组件时注意为组件添加有效的key来让React进行diff的时候少做一些比较减少不必要的dom操作。 建议 了解这些性能优化的手段后迫不及待在自己的组件中整改一顿这边的建议是注意不要过度优化 为什么这么说呢 一方面我们能够发现useMemo和useCallback本身也不是免费的需要开辟空间去存储依赖并且每次都要去比较 另外从实际的开发体验上在组件中大量使用useMemo和useCallback会导致代码比较臃肿可读性变差对开发者心智要求比较高维护依赖项 我们要知道React本身的性能优化已经做的很好了正确的逻辑应该是当实际性能问题发生时我们需要去定位发生问题的组件再应用上文提到的性能优化方法进行优化。 最后出现性能问题时的组件定位这边推荐一下React Profiler相关文档比较清楚这边不再赘述简单分享些小tip可以勾一下这两选项可以方便的看到组件重新渲染的原因和操作时发生重新渲染的组件 以上就是本次分享的全部内容读者如果觉得有帮助可以顺手点个赞觉得哪里有不理解的也可以评论区留言讨论:) 最后 最近还整理一份JavaScript与ES的笔记一共25个重要的知识点对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识提升工作效率。 有需要的小伙伴可以点击下方卡片领取无偿分享
http://www.hkea.cn/news/14444567/

相关文章:

  • 威海好的网站建设公司微信公众平台公众号
  • 邯郸网站设计在哪里姚孟信通网站开发中心
  • jsp做网站实例软件培训公司排名
  • 襄阳市做网站的公司韩国设计教程网站
  • 网站制作大概多少钱做网站一般需要多少钱
  • 美食网站建设背景免费素材网png
  • 公司做网站一般多少钱运营百度蜘蛛池
  • 外贸公司查询济南做网站优化价格
  • 网站建设与管理专业学什么装修网站设计案例
  • 免费网站制作多少钱郑州网站建
  • 厦门营销型网站建设公司广东网络公司网站
  • 陕西建设厅执业资格注册中心网站怎样做艾条艾柱网站
  • 城固网站建设wordpress 博客主机
  • 温州网站关键词推广最佳的资源磁力搜索引擎
  • 网站建设在哪里进行企业组网方案
  • 网站建设外包还是自建物联网工程是干什么的
  • 百度站长平台诊断上海seo方案
  • 南昌做兼职的网站设计网站开发设置网页端口
  • 周大福网站设计特点个人网站备案信息填写
  • 怎么区分营销型和展示型的网站东莞关键词排名推广
  • 网站开发设计书籍wordpress产品参数多图
  • 赣州网站建设好么深圳营销网站建设服务
  • 建设一个地方门户网站福建省住房城乡建设厅网站
  • 手套网站模板建设响应式网站有哪些好处
  • 如何提高网站的点击量自己怎么做网站游戏
  • 免费个人自助建站网站 备案 中国 名字吗
  • 申请个人网站做seo必须有自己网站吗
  • 杭州营销网站建设平台视频网站建设工具
  • iis 网站属性北京亦庄做网站公司
  • 哪些网站做科技专题app开发公司有什么部门