bt365体育投注.主頁欢迎您!!

    <acronym id="zvmrr"></acronym>
    <td id="zvmrr"></td>
  • <tr id="zvmrr"><label id="zvmrr"></label></tr>
  • <acronym id="zvmrr"></acronym>
  • rocky191

    rocky191 查看完整档案

    北京编辑内蒙古民族大学  |  计算机科学与技术 编辑北京xxxx公司  |  前端工程师 编辑 rocky-191.github.io/ 编辑
    编辑
    _ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

    个人动态

    rocky191 关注了专栏 · 1月17日

    被代码虐

    关注 3059

    rocky191 关注了用户 · 2020-12-05

    敖丙 @aobing

    关注 5801

    rocky191 关注了专栏 · 2020-11-25

    前端森林公众号

    一个有温度的前端号,关注行业前沿。从基础到架构,携手你我共同成长。

    关注 9901

    rocky191 赞了文章 · 2020-11-20

    链家网前端总架构师杨永林:我的8年架构师成长之路

    杨永林,人称“教主”,八年前端开发经验,原新浪微博前端技术专家,现任链家网前端总架构师。长期研究Web访问性能优化和前端框架搭建。
    作为初始团队成员,教主参与了新浪微博所有PC版本的开发,其中4~6版以架构师的身份设计了微博PC版的前端架构。在新浪微博任职期间,教主设计实现了流水线加载技术与模块化代码组织,达到了在提高访问性能的同时极大降低了开发成本的目的。主要研究方向是Web访问性能优化与框架组织。在国内为数不多地实现了BigPipe技术,极大地提升了微博的访问速度。同时,微博的前端代码基础包、前端框架和构建工具均出自教主之手。
    2015年年底,教主加入链家网,负责前端的整体架构工作。
    在8年的前端开发生涯中,教主是如何一步一步地成为知名前端架构师的呢?为何选择加入了链家网呢?

    您在微博和链家都是前端架构师,能说说前端架构师这个工种具体是做什么的吗?

    杨永林:我对架构师所担任的职责的认识是一步步变化,慢慢深入的。

    在刚参加工作的时候,我觉得架构师就是代码写得又快又好的人,是工程师的晋级版本。

    工作过一些年以后,我发现仅仅提高自身的开发效率是远远不够的,团队需要整体的提升。发现这一点后,我开始制作并完善各种开发工具,编写开发框架。

    最近几年,随着迭代开发了一些产品本,我又发现之前能够提升效率的框架工具很有可能在后来成了产品发展的绊脚石。这时,我开始考虑架构设计的指导原则,开始考虑取舍。一些在短期内能够提升效率但不符合原则的东西,我就选择不做或者想办法在原则的指导下进行改进。比如我相信可变化的代码才是有生命力的代码,在架构设计上我也会趋向于让项目的代码可以一点一点的变化演进,不是那种一言不合就重构到状态。所以我认为前端架构师就是那种在前端领域提出开发的指导原则,在原则下设计开发框架和开发工具,让更多的开发者可以协同工作的人。

    您在新浪微博的时候设计了前端架构,能否介绍一下包括了哪些组成部分,有什么关键技术?

    杨永林:主要是代码基础包,页面加载框架和前端构建工具。

    早期前端开发面临两个主要问题是浏览器兼容和API不够丰富,基础包一般都是用来解决这两个问题。当时新浪有一个自己的Sina包,但是代码比较零散,模式也不统一,各产品线有自己的扩展,同样的功能可能有多种实现,不太好维护。后来我用业余时间开发了一个带有命名空间管理功能的基础包,特点就是简单清晰,易于使用,被团队采纳作为了微博的基础包使用至今。

    页面加载框架是被需倒逼着产生的,2010年微博业务膨胀,页面展示的内容越来越多,这使得页面响应速度也变得越来越慢。我所在的团队接到的需求是要求在内容变多的情况下将响应速度变得更快。

    这个时候Facebook推出了BigPipe技术,我们觉得这个理念正好能够解决我们应对的问题,所以决定实施,但当时Facebook只是分享了他们的做法,并没有提供实现,所以对我来说也是巨大的挑战。我当时将页面划分成多个独立的子模块,模块是完全可以自主运行的,模块可以嵌套,所以页面就是一批模块的树形堆叠。服务端用Chunked的方式将模块的信息以JavaScript代码块的方式传输到页面,而前端需要做的很重要的工作是管理每个模块的生命周期。

    我很荣幸那时能有机会和团队成员一起开发了这个加载框架,我们可能是国内第一个在大型互联网应用上全面使用这项技术的。之后的一年我一直致力于此项技术的优化工作,比如支持服务端乱序输出,保证服务端可以使用并行策略,压缩,减少前置依赖条件等,并在2013年与@Laruence(鸟哥)合作实施了CBigPipe(并行的BigPipe)技术,进一步提高了这项技术的性能。微博的V5版的加载性能也达到了顶峰,页面的加载速度几乎相当于静态网页。

    前端构建工具是这几年才开始流行,其实早在2008年的时候,新浪就已经使用前端小文件开发,使用构建工具进行开发,测试和上线。现在想想应该是比较超前了,不过那时的版本是需要PHP、Python和Java环境,团队维护起来比较困难,而且使用的是字符串替换方案,功能比较有限。2012年我将这个工具进行了改造,使其仅需要Node环境,同时支持开发、测试部署和打包上线。由于使用了UglifyJS,有了语法树,我加了一些以前没有的功能,比如预编译的模版引擎、支持模版嵌套和母模版、代码健康度检测、冗余模块分析等。

    前端构建工具前后有Grunt/Gulp、Webpack、npm scripts等,您对这些工具有什么看法,哪个更好?如何选择适合公司产品的工具?应从哪些方面考虑?

    杨永林:我觉得这些工具有效地解决了前端开发效率的问题,它们的出现都是对技术的推动,如果在我做工具的时候有这些项目的出现,会减少我很多的工作量。至于哪个更好,我觉得,你能掌握哪个,哪个就是最好的。因为说到底,工具是为你的业务服务的,你可能需要对它做些改造或者是写一些扩展,在这个时候你发现你对他的熟悉变的很重要。构建工具的迁移成本还是挺高的,我不太推荐频繁地变更它,所以最好不要追着流行走,还是要根据自己团队的特点,因地制宜地选择一款合适的。如果不是超大型的应用,其实build的结果的影响并没有太大的差异,与其想着哪个更好哪个更牛逼,不如将其中一个玩熟玩透。

    如何保证团队成员不会踩到同样的坑?在设计框架和构建工具时有无这方面的考虑?请举例说明。

    杨永林:首先,制定规范、分享经验是免不了的,但纸上得来终觉浅吧,很多时候,亲身踩一次坑,得到的经验才会深刻。而我所要做的是在团队成员踩到坑的时候降低这件事造成的后果。比如我提供的开发环境是可以完全模拟线上环境的,测试代码和线上保持一致,很多意外情况都可以在开发、测试期被发现。同时,制定的开发规范要由工具检测来保证,不符合规范的代码不能够打包上线。对于规范代码可以使用工具计算出业务影响范围,能有效保证测试覆盖面。总的来说,踩坑不要紧,架构来帮你兜底,爬出坑的过程就成为了团队成员所得到的财富。

    您认为对Web访问性能的优化需要关注哪些方面?其中,最值得关注的点是什么?为什么?

    杨永林:我觉得性能优化需要方方面面都要兼顾,包括网络时间、服务器计算时间、页面请求数、下载量、页面载入模型等。而这里面任何一项的性能提升可能都需要你修改大量代码或者调整架构来实现,但是得到的效果可能就是一点点。因此很少见到银弹,一般都是一点一点地做出来的。我这里谈两个我觉得比较值得关注但很容易被忽视的点吧。

    一是你所服务产品的形态,用户关心什么,这是一些工程师比较容易忽略的。有些产品需要用户打开时很快,有些需要用户使用时流畅;有些产品用户可以容忍看旧数据,而有些则必须是新内容;有些产品用户一天打开很多次,而有些看一次就关掉了。这些产品需求的差异都会影响你的决策。

    二是评测标准,用什么来测量性能的好坏。一些人认为请求数或者请求量减少了,访问就快了,其实这是不一定的,有可能你花了很大精力做的事情在用户看来并没有什么太大变化。所以,找一个评测标准让每一个优化在数据上有所体现是很重要的。

    度量前端性能的指标有哪些?如何对Web访问性能进行监控?

    杨永林:我所服务的产品一般都关注访问性能,也就是用户看到内容的快慢,所以我们一般用首屏时间来评估,一般的性能检测服务商都能提供这个指标。

    选这个指标有两点考虑:一是因为它并不是一个技术指标,而是一个感知指标,所以更接近人类的感受。二是旁路检测,它并不在系统内,不是系统汇报上来的数据,这样就有效的规避了幸存者偏差的问题。当然它也有些不足:一是数据采样小,二是可以被欺骗。所以可能需要一点儿统计学功底和性能监控的正确认识。

    在监控的过程中,一是要关注长期趋势的变化,如果不是突发状况,单点的数据的绝对值是没有意义的,要收集长期的数据,分析其中的变化,当有变更的时候尤其要关注数据的变化。二是关注最差25%的状况,有些人,会在公司内网刷自己的产品,感觉挺快,其实不论你用什么手段,只要网快,用户的体验都不会太差,体验的差异在于最差那部分用户。三是从不同维度分析数据,如地区、网络、时段、运行环境等。

    前端工程师如何成为前端架构师,除了编程能力和架构知识,还需要培养哪些能力?

    杨永林:我想,大部分领域的架构师工作都是差不多的,就是搭建一个解决问题的框架,让团队成员能在框架下良好的配合工作,完成产品的开发需求。

    我们知道,解决一个问题的手段有很多,在这个过程中取舍就很重要了,我们也知道,没有银弹,很少能遇见那种全面优势的解决方案,大部分方案都是牺牲掉一部分东西来换取一部分东西。因此,作为架构师,不仅要对各个技术方案的特点、成本要熟知(也就是编程能力和架构知识),还要学会如何选择。显然,架构师需要根据产品的特点和发展方向做出决定,在前端领域的架构要能让配合的团队对接的顺畅。那么在这个过程中,良好的沟通能力、同理心、利他的思维方式,就显得很重要了。因为我们不仅要完成开发任务,也要思考在自己的领域内如何帮助项目解决问题。

    据说有些同事在对技术的讨论中以“击败”您为荣,您是如何看待的?这对团队及其个人的发展带来了哪些影响?

    杨永林:这是我一个毛病,喜欢给别人的方案着茬。我觉得这是一个思辨的过程,通过从不同角度分析问题,去挑战解决方案的合理性,才能让问题解决的更稳妥。在知识的获取中也是这样,一次一次地去问为什么,去追根溯源,才能让知识体系更牢固。

    我很喜欢在团队内扮演一种“反派”的角色,从反面的角度分析问题,去挑战别人的方案。其实,我不是真的去否定他,而是希望他的方案是经过反复推敲、深思熟虑产生的,这样的方案会更健壮。时间长了,他们会觉得我是一个爱抬杠的人,就会做足准备来“挑战”我。能把我说得接不上话来,他们会觉得很开心。这个结果是我想看到的,因为这说明团队成员在解决问题时进行了充分的思考。

    您为什么放弃了在之前新浪微博的元老级身份,而选择加入链家网?

    杨永林:这可能源自我对工作的看法吧,我觉得人生活在社会上,工作是在为社会创造价值和财富,这和他具体从事哪种职业没有直接关系。现在行业里有一种风气,就是觉得程序员写好代码就好了,不用关心自己做的事情是什么。甚至社会上也给程序员打一些什么“木讷”、“情商低”之类的标签。我觉得不应该是这样的,程序员也是社会人,也有他的社会责任,也有家庭责任,也需要陪伴他的伴侣,照顾他的小孩,不是每天只是面对代码而不管其他的事。人不要因为群体印象就把自己限制住,人的生活就应该是多种多样、丰富多采的,人生应该是有意义的。

    就我个人而言,在过去的几年,我所服务的产品不仅加深了人们之间的沟通和理解,也使得国家的信息变得更透明。而我所做的工作对这样的一个产品做出了贡献,可以说我的工作让世界变得美好了那么一点点。这让我觉得我的人生增添了那么一点意义。而当我搭建起前端框架后,我个人能起的作用变得越来越小,我能继续创造的价值也越来越少,所以需要另一个平台来继续发挥我的能量。

    这时我有机会接触到链家网,这家公司致力于解决人们的居住问题,它让中国最大的市场变得透明、有序。我觉得链家网做的是很有意义的事,同时,它仅仅用了不到两年的时间,就集结了一批各领域的牛人,维护了国内规模最大的房地产交易系统,用技术手段让房屋的买卖变的更轻松、透明、快捷。在与链家网的接触中,我感受到了那种积极解决问题的活力和务实做事的态度。再加上链家网中大部分技术人,在之前也都是各个大型互联网公司的中坚力量,我想没有什么比与志同道合的人来一起改变世界更令人激动的了。此时,鸟哥专门来邀请我加入链家网,我就毫不犹豫地同意了。

    教主答群友问:
    Q:您对初级前端人员有什么好的建议吗?
    A:多尝试一些解决方案,多想想这些方案的特点,对别人的方案深究原理。
    Q:前端学习方面有什么书籍可以介绍吗?
    A:现在前端书籍都挺多挺好多呀,我一般推荐高级程序设计,图解CSS3。
    Q:您在担任架构师这个角色中遇到的最严重的线上事故是什么?当时是怎么解决的?
    A:工作这么多年,在前端应该就只有一次B级故障,做非前端的时候倒是通过大篓子,因为上线速度比较快,而且大问题也都是很明显的解决方案,所以就是快速上线了。这个要感谢测试同学,很给力,避免了很多线上故障。
    Q:学前端是否去参加商业培训更见效?亦或是这种商业培训反而更会僵化思维?这样流水线培训出来的学员在企业认可度如何。
    A:我没参见过商业培训,也不太好评价,我是觉得被灌输的知识可能不如自己躺坑得来的扎实吧。企业认可这方面,我基本只看能力。
    Q:对于您来说技术比较重要还是产品比较重要?因为刚才您说是因为觉得链家的“产品”比较有意义才考虑去的,那能理解为你觉得产品比技术更重要吗?
    A:我所说的产品不是“产品人员”,是公司的产出的服务。显然对于一家公司来说,产品是最重要的,技术是如何实现产品的手段啊。
    Q:您觉得什么样的代码才算是可变化的代码?这方面又做出了哪些实践?有哪些系统化的产出?
    A:我说变化的代码其实代码是可控的,可以方便的去调整项目,可以一步一步的改造项目而不是重构,我做开发一致遵循这个理念。
    Q:前面提到搭建团队可用的框架,但我感觉这个工作量非常巨大而且需要很多改进和测试,教主当时有同感吗?怎么解决这么大的工作量问题?
    A:我可能比较幸运,曾经有一段时间来调整结构,我是这样想的,每当我向前迈一步的时候,我就是在进步,所以我没有急于让架构搭建一次到位,我会想好调整的步骤,每一步都会让架构进步,把大问题拆解成小问题一步一步做。
    Q:小公司开发前端,由于缺少项目管理经验,导致许多冗余性的工作,请问教主在管理方面有何建议?
    A:这个不同公司的情况都不一样吧,不太好建议。
    Q:多尝试一些解决方案和“一步一步逐步改进产品”是否矛盾?
    A:不矛盾啊,多尝试不代表多实施啊。

    查看原文

    赞 75 收藏 46 评论 4

    rocky191 关注了用户 · 2020-11-20

    小齐本齐 @xiaoqibenqi

    公众号:码农田小齐
    回复【666】查看往期文章分类。
    回复【进群】有技术交流群/自习打卡群/好文分享群。
    回复【01-05】可以获取计算机精选书籍、个人刷题笔记、大厂面经、面试资料等资源~

    愿我们一起成为更好的人,期待与你相遇??

    关注 3384

    rocky191 关注了用户 · 2020-11-04

    宗恩 @yzn

    关注新科技

    联系邮箱 yzn@sifou.com

    交流微信:yyuuuuusjjjd

    关注 47

    rocky191 关注了专栏 · 2020-09-19

    Jerry Wang的SAP技术专栏

    SAP成都研究院开发专家,SAP社区导师,SAP中国技术大使

    关注 51

    rocky191 发布了文章 · 2020-09-05

    Promise链式调用特性总结

    相信各位前端小伙伴对于Promise应该很熟悉了吧,日常工作中,100%会用到的一个东西,除非你还在用callback解决异步,那我就太佩服了。话不多说,进入正题。

    提前声明一下,以下代码在node环境中实现,你可以创建一个文件,使用nodemon这个工具执行这个文件,就可以进行监听更新,真香。

    首先你要创建一个promise

    let p=new Promise((resolve,reject)=>{
      resolve('first resolve')
    })

    方式一、通过return传递结果

    p.then(res=>{
      return res;
    }).then(res=>{
      console.log(res)
    })

    控制台就会输出:first resolve

    方式二、通过返回新的promise resolve结果

    p.then(res=>{
      return res;
    }).then(res=>{
      return new Promise((resolve,reject)=>{
        resolve('second resolve: '+res)
      })
    }).then(res=>{
      console.log(res)
    })

    控制台就会输出:second resolve: first resolve
    如果在返回的promise里加一个异步比如settimeout呢,结果会是什么样?

    p.then(res=>{
      return res;
    }).then(res=>{
      return new Promise((resolve,reject)=>{
        // resolve('second resolve: '+res)
        setTimeout(()=>{
          resolve('second resolve: '+res)
        },2000)
      })
    }).then(res=>{
      console.log(res)
    })

    控制台等待2s后输出:second resolve: first resolve

    方式三、通过返回新的promise reject 原因

    既然可以通过新的promise resolve,那么reject应该也可以。

    p.then(res=>{
      return res;
    }).then(res=>{
      return new Promise((resolve,reject)=>{
        setTimeout(()=>{
          reject('error')
        },2000)
      })
    }).then(res=>{
      console.log(res)
    },err=>{
      console.log('error: '+err)
    })

    控制台等待2s后输出:error: error

    方式四、then函数走了失败回调继续走then

    紧接着上一步,失败后,reject出原因,继续后面then

    p.then(res=>{
      return res;
    }).then(res=>{
      return new Promise((resolve,reject)=>{
        setTimeout(()=>{
          reject('error')
        },2000)
      })
    }).then(res=>{
      console.log(res)
    },err=>{
      console.log('error: '+err)
      // 默认 return undefined
    }).then(res=>{
      console.log('second then success: '+res)
    },err=>{
      console.log('second then error: '+err)
    })

    控制台会输出两行内容:error: error,second then success: undefined。这就表明在reject 后面继续then会执行下一步的resolve,如果上一步没有返回值,默认接收undefined。

    方式五、then中使用throw new Error情况

    如果在then中抛出异常呢,如何显示?

    p.then(res=>{
      return res;
    }).then(res=>{
      return new Promise((resolve,reject)=>{
        setTimeout(()=>{
          reject('error')
        },2000)
      })
    }).then(res=>{
      console.log(res)
    },err=>{
      console.log('error: '+err)
    }).then(res=>{
      throw new Error('happend error')
    }).then(res=>{
      console.log('third then success'+res)
    },err=>{
      console.log('third then error '+err)
    })

    控制台会输出:
    error: error
    third then error Error: happend error
    这表明throw error抛出异常类似reject,会由下一步的then方法中的错误方法处理。

    方式六、在promise中使用catch进行错误捕获

    p.then(res=>{
      return res;
    }).then(res=>{
      return new Promise((resolve,reject)=>{
        setTimeout(()=>{
          reject('error')
        },2000)
      })
    }).then(res=>{
      console.log(res)
    },err=>{
      console.log('error: '+err)
    }).then(res=>{
      throw new Error('happend error')
    }).then(res=>{
      console.log('third then success'+res)
    }).catch(err=>{
      console.log('catched '+err)
    })

    控制台会输出:
    error: error
    catched Error: happend error

    如果在catch方法的前面then中有对上一步错误的处理办法会怎么样呢?

    p.then(res=>{
      return res;
    }).then(res=>{
      return new Promise((resolve,reject)=>{
        // resolve('second resolve: '+res)
        setTimeout(()=>{
          reject('error')
        },2000)
      })
    }).then(res=>{
      console.log(res)
    },err=>{
      console.log('error: '+err)
    }).then(res=>{
      throw new Error('happend error')
    }).then(res=>{
      console.log('third then success'+res)
    },err=>{
      console.log('third then error '+ err)
    }).catch(err=>{
      console.log('catched '+err)
    })

    控制台会输出:
    error: error
    third then error Error: happend error
    这说明catch捕获,如果catch前面有error处理函数,catch不会捕获异常的。

    如果在catch后面继续then呢?

    p.then(res=>{
      return res;
    }).then(res=>{
      return new Promise((resolve,reject)=>{
        // resolve('second resolve: '+res)
        setTimeout(()=>{
          reject('error')
        },2000)
      })
    }).then(res=>{
      console.log(res)
    },err=>{
      console.log('error: '+err)
    }).then(res=>{
      throw new Error('happend error')
    }).then(res=>{
      console.log('third then success'+res)
    }).catch(err=>{
      console.log('catched '+err)
      return 'catched error'
    }).then(res=>{
      console.log('catched then '+res)
    })

    控制台会输出:
    error: error
    catched Error: happend error
    catched then catched error
    这说明catch后面是可以继续调用then的,catch 在promise的源码里面其实也是一个then,catch遵循then的运行规则。

    总结

    promise链式调用,具体是失败还是成功,取决于以下情况:

    成功的条件

    • then return 一个普通的js 值
    • then return 一个新的promise成功态的结果 resolve处理

    失败的条件

    • then return 一个新的promise失败态的原因 error
    • then throw抛出异常

    以上就是promise链式调用的一些实践总结,复习复习基础知识。欢迎大家交流。

    参考资料:

    查看原文

    赞 2 收藏 2 评论 0

    rocky191 关注了用户 · 2020-08-19

    刘小夕 @liuyan666

    本人微信公众号: 前端宇宙

    写文不易,Star支持一下?

    【github】https://github.com/YvetteLau/...

    关注 1865

    rocky191 赞了文章 · 2020-08-11

    React Fiber 源码解析

    图片作者:Artem Sapegin,来源:https://unsplash.com/photos/b...

    本文作者:刘鹏

    前言

    在 React v16.13 版本中,正式推出了实验性的 Concurrent Mode,尤其是提供一种新的机制 Suspense,非常自然地解决了一直以来存在的异步副作用问题。结合前面 v16.8 推出的 Hooks,v16.0 底层架构 Fiber,React 给开发者体验上带来了极大提升以及一定程度上更佳的用户体验。所以,对 React 17,你会有什么期待?

    Stack Reconciler 和 ?Fiber Reconciler

    我们知道,Stack?Reconciler 是 React v15 及之前版本使用的协调算法。而 React Fiber 则是从 v16 版本开始对 Stack Reconciler 进行的重写,是 v16 版本的核心算法实现。
    Stack?Reconciler 的实现使用了同步递归模型,该模型依赖于内置堆栈来遍历。React 团队 Andrew 之前有提到:

    如果只依赖内置调用堆栈,那么它将一直工作,直到堆栈为空,如果我们可以随意中断调用堆栈并手动操作堆栈帧,这不是很好吗? 这就是 React Fiber 的目标。Fiber 是内置堆栈的重新实现,专门用于 React 组件,可以将一个 fiber 看作是一个虚拟堆栈帧。

    正是由于其内置 Stack Reconciler 天生带来的局限性,使得 DOM 更新过程是同步的。也就是说,在虚拟 DOM 的比对过程中,如果发现一个元素实例有更新,则会立即同步执行操作,提交到真实 DOM 的更改。这在动画、布局以及手势等领域,可能会带来非常糟糕的用户体验。因此,为了解决这个问题,React 实现了一个虚拟堆栈帧。实际上,这个所谓的虚拟堆栈帧本质上是建立了多个包含节点和指针的链表数据结构。每一个节点就是一个 fiber 基本单元,这个对象存储了一定的组件相关的数据域信息。而指针的指向,则是串联起整个 fibers 树。重新自定义堆栈带来显而易见的优点是,可以将堆栈保留在内存中,在需要执行的时候执行它们,这使得暂停遍历和停止堆栈递归成为可能。

    Fiber 的主要目标是实现虚拟 DOM 的增量渲染,能够将渲染工作拆分成块并将其分散到多个帧的能力。在新的更新到来时,能够暂停、中止和复用工作,能为不同类型的更新分配优先级顺序的能力。理解 React 运行机制对我们更好理解它的设计思想以及后续版本新增特性,比如 v17 版本可能带来的异步渲染能力,相信会有很好的帮助。本文基于 React?v16.8.6 版本源码,输出一些浅见,希望对你也有帮助,如有不对,还望指正。

    基础概念

    在了解 React Fiber 架构的实现机制之前,有必要先把几个主要的基础概念抛出来,以便于我们更好地理解。

    Work

    在 React?Reconciliation 过程中出现的各种必须执行计算的活动,比如 state update,props update 或 refs update 等,这些活动我们可以统一称之为 work。

    Fiber 对象

    文件位置:packages/react-reconciler/src/ReactFiber.js

    每一个 React 元素对应一个 fiber 对象,一个 fiber 对象通常是表征 work 的一个基本单元。fiber 对象有几个属性,这些属性指向其他 fiber 对象。

    • child: 对应于父 fiber 节点的子 fiber
    • sibling: 对应于 fiber 节点的同类兄弟节点
    • return: 对应于子 fiber 节点的父节点

    因此 fibers 可以理解为是一个包含 React 元素上下文信息的数据域节点,以及由 child,?sibling 和 return 等指针域构成的链表结构。

    fiber 对象主要的属性如下所示:

    Fiber = {
        // 标识 fiber 类型的标签,详情参看下述 WorkTag
        tag: WorkTag,
    
        // 指向父节点
        return: Fiber | null,
    
        // 指向子节点
        child: Fiber | null,
    
        // 指向兄弟节点
        sibling: Fiber | null,
    
        // 在开始执行时设置 props 值
        pendingProps: any,
    
        // 在结束时设置的 props 值
        memoizedProps: any,
    
        // 当前 state
        memoizedState: any,
    
        // Effect 类型,详情查看以下 effectTag
        effectTag: SideEffectTag,
    
        // effect 节点指针,指向下一个 effect
        nextEffect: Fiber | null,
    
        // effect list 是单向链表,第一个 effect
        firstEffect: Fiber | null,
    
        // effect list 是单向链表,最后一个 effect
        lastEffect: Fiber | null,
    
        // work 的过期时间,可用于标识一个 work 优先级顺序
        expirationTime: ExpirationTime,
    };

    从 React 元素创建一个 fiber 对象

    文件位置:react-reconciler/src/ReactFiber.js
    export function createFiberFromElement(
        element: ReactElement,
        mode: TypeOfMode,
        expirationTime: ExpirationTime
    ): Fiber {
        const fiber = createFiberFromTypeAndProps(type, key, pendingProps, owner, mode, expirationTime);
        return fiber;
    }

    workTag

    文件位置:shared/ReactWorkTags.js

    上述 fiber 对象的 tag 属性值,称作 workTag,用于标识一个 React 元素的类型,如下所示:

    export const FunctionComponent = 0;
    export const ClassComponent = 1;
    export const IndeterminateComponent = 2; // Before we know whether it is function or class
    export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
    export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
    export const HostComponent = 5;
    export const HostText = 6;
    export const Fragment = 7;
    export const Mode = 8;
    export const ContextConsumer = 9;
    export const ContextProvider = 10;
    export const ForwardRef = 11;
    export const Profiler = 12;
    export const SuspenseComponent = 13;
    export const MemoComponent = 14;
    export const SimpleMemoComponent = 15;
    export const LazyComponent = 16;
    export const IncompleteClassComponent = 17;
    export const DehydratedSuspenseComponent = 18;
    export const EventComponent = 19;
    export const EventTarget = 20;
    export const SuspenseListComponent = 21;

    EffectTag

    文件位置:shared/ReactSideEffectTags.js

    上述 fiber 对象的 effectTag 属性值,每一个 fiber 节点都有一个和它相关联的 effectTag 值。
    我们把不能在 render 阶段完成的一些 work 称之为副作用,React 罗列了可能存在的各类副作用,如下所示:

    export const NoEffect = /*              */ 0b000000000000;
    export const PerformedWork = /*         */ 0b000000000001;
    
    export const Placement = /*             */ 0b000000000010;
    export const Update = /*                */ 0b000000000100;
    export const PlacementAndUpdate = /*    */ 0b000000000110;
    export const Deletion = /*              */ 0b000000001000;
    export const ContentReset = /*          */ 0b000000010000;
    export const Callback = /*              */ 0b000000100000;
    export const DidCapture = /*            */ 0b000001000000;
    export const Ref = /*                   */ 0b000010000000;
    export const Snapshot = /*              */ 0b000100000000;
    export const Passive = /*               */ 0b001000000000;
    
    export const LifecycleEffectMask = /*   */ 0b001110100100;
    export const HostEffectMask = /*        */ 0b001111111111;
    
    export const Incomplete = /*            */ 0b010000000000;
    export const ShouldCapture = /*         */ 0b100000000000;

    Reconciliation 和 Scheduling

    协调(Reconciliation):
    简而言之,根据 diff 算法来比较虚拟 DOM,从而可以确认哪些部分的 React 元素需要更改。

    调度(Scheduling):
    可以简单理解为是一个确定在什么时候执行 work 的过程。

    Render 阶段和 Commit 阶段

    相信很多同学都看过这张图,这是 React 团队作者 Dan Abramov 画的一张生命周期阶段图,详情点击查看。他把 React 的生命周期主要分为两个阶段:render 阶段和 commit 阶段。其中 commit 阶段又可以细分为 pre-commit 阶段和 commit 阶段,如下图所示:

    image.png

    从 v16.3 版本开始,在 render 阶段,以下几个生命周期被认为是不安全的,它们将在未来的版本中被移除,可以看到这些生命周期在上图中未被包括进去,如下所示:

    • [UNSAFE_]componentWillMount (deprecated)
    • [UNSAFE_]componentWillReceiveProps (deprecated)
    • [UNSAFE_]componentWillUpdate (deprecated)

    在 React 官网中明确提到了废弃的原因,这些被标记为不安全的生命周期由于常常被开发者错误理解甚至被滥用,比如一些开发人员会倾向于将带有请求数据等副作用的逻辑放在这些生命周期方法中,认为能带来更好的性能,而实际上真正带来的收益几乎可以忽略。在未来, React 逐步推崇异步渲染模式下,这很有可能会因为不兼容而带来很多问题。

    在 render 阶段,React 可以根据当前可用的时间片处理一个或多个 fiber 节点,并且得益于 fiber 对象中存储的元素上下文信息以及指针域构成的链表结构,使其能够将执行到一半的工作保存在内存的链表中。当 React 停止并完成保存的工作后,让出时间片去处理一些其他优先级更高的事情。之后,在重新获取到可用的时间片后,它能够根据之前保存在内存的上下文信息通过快速遍历的方式找到停止的 fiber 节点并继续工作。由于在此阶段执行的工作并不会导致任何用户可见的更改,因为并没有被提交到真实的 DOM。所以,我们说是 fiber 让调度能够实现暂停、中止以及重新开始等增量渲染的能力。相反,在 commit 阶段,work 执行总是同步的,这是因为在此阶段执行的工作将导致用户可见的更改。这就是为什么在 commit 阶段, React 需要一次性提交并完成这些工作的原因。

    Current 树和 WorkInProgress 树

    首次渲染之后,React 会生成一个对应于 UI 渲染的 fiber 树,称之为 current 树。实际上,React 在调用生命周期钩子函数时就是通过判断是否存在 current 来区分何时执行 componentDidMount 和 componentDidUpdate。当 React 遍历 current 树时,它会为每一个存在的 fiber 节点创建了一个替代节点,这些节点构成一个 workInProgress 树。后续所有发生 work 的地方都是在 workInProgress 树中执行,如果该树还未创建,则会创建一个 current 树的副本,作为 workInProgress 树。当 workInProgress 树被提交后将会在 commit 阶段的某一子阶段被替换成为 current 树。

    这里增加两个树的主要原因是为了避免更新的丢失。比如,如果我们只增加更新到 workInProgress 树,当 workInProgress 树通过从 current 树中克隆而重新开始时,一些更新可能会丢失。同样的,如果我们只增加更新到 current 树,当 workInProgress 树被提交后会被替换为 current 树,更新也会被丢失。通过在两个队列都保持更新,可以确保更新始终是下一个 workInProgress 树的一部分。并且,因为 workInProgress 树被提交成为 current 树,并不会出现相同的更新而被重复应用两次的情况。

    Effects list

    effect list 可以理解为是一个存储 effectTag 副作用列表容器。它是由 fiber 节点和指针 nextEffect 构成的单链表结构,这其中还包括第一个节点 firstEffect,和最后一个节点 lastEffect。如下图所示:

    image.png

    React 采用深度优先搜索算法,在 render 阶段遍历 fiber 树时,把每一个有副作用的 fiber 筛选出来,最后构建生成一个只带副作用的 effect list 链表。
    在 commit 阶段,React 拿到 effect list 数据后,通过遍历 effect list,并根据每一个 effect 节点的 effectTag 类型,从而对相应的 DOM 树执行更改。

    更多 effect list 构建演示流程,可以点击查看动画 《Effect List —— 又一个 Fiber 链表的构建过程》

    Render 阶段

    在本文中,我们以类组件为例,假设已经开始调用了一个 setState 方法。

    enqueueSetState

    每个 React 组件都有一个相关联的 updater,作为组件层和核心库之间的桥梁。
    react.Component 本质上就是一个函数,在它的原型对象上挂载了 setState 方法

    文件位置:react/src/ReactBaseClasses.js
    // Component函数
    function Component(props, context, updater) {
        this.props = props;
        this.context = context;
        this.updater = updater || ReactNoopUpdateQueue;
    }
    
    // Component原型对象挂载 setState
    Component.prototype.setState = function (partialState, callback) {
        this.updater.enqueueSetState(this, partialState, callback, 'setState');
    };

    React 给 work 大致分成以下几种优先级类型,其中 immediate 比较特殊,它的优先级最高,可以理解为是同步调度,调度过程中不会被中断。

    export const NoPriority = 0;
    export const ImmediatePriority = 1;
    export const UserBlockingPriority = 2;
    export const NormalPriority = 3;
    export const LowPriority = 4;
    export const IdlePriority = 5;

    React 有一套计算逻辑,根据不同的优先级类型为不同的 work 计算出一个过期时间 expirationTime,其实就是一个时间戳。所谓的 React 在新的更新到来时,能为不同类型的更新分配优先级顺序的能力,本质上是根据过期时间 expirationTime 的大小来确定优先级顺序,expirationTime 数值越小,则优先级越高。在相差一定时间范围内的 work,React 会认为它们是同一个批次(batch)的,因此这一批次的 work 会在一次更新中完成。

    文件位置:react-reconciler/src/ReactFiberClassComponent.js
    const classComponentUpdater = {
        enqueueSetState(inst, payload, callback) {
            // 获取 fiber 对象
            const fiber = getInstance(inst);
            const currentTime = requestCurrentTime();
    
            // 计算到期时间 expirationTime
            const expirationTime = computeExpirationForFiber(currentTime, fiber, suspenseConfig);
    
            const update = createUpdate(expirationTime, suspenseConfig);
            // 插入 update 到队列
            enqueueUpdate(fiber, update);
            // 调度 work 方法
            scheduleWork(fiber, expirationTime);
        },
    };

    renderRoot

    文件位置:react-reconciler/src/ReactFiberWorkLoop.js

    协调过程总是 renderRoot 开始,方法调用栈:scheduleWork -->??scheduleCallbackForRoot? -->?renderRoot

    代码如下:

    function renderRoot(
      root: FiberRoot,
      expirationTime: ExpirationTime,
      isSync: boolean,
    ) | null {
      do {
        // 优先级最高,走同步分支
        if (isSync) {
          workLoopSync();
        } else {
          workLoop();
        }
      } while (true);
    }
    
    // 所有的fiber节点都在workLoop 中被处理
    function workLoop() {
      while (workInProgress !== null && !shouldYield()) {
        workInProgress = performUnitOfWork(workInProgress);
      }
    }

    performUnitOfWork

    所有的 fiber 节点都在 workLoop 方法处理。协调过程总是从最顶层的 hostRoot 节点开始进行 workInProgress 树的遍历。但是,React 会跳过已经处理过的 fiber 节点,直到找到还未完成工作的节点。例如,如果在组件树的深处调用 setState,React 将从顶部开始,但会快速跳过父节点,直到到达调用了 setState 方法的组件。整个过程采用的是深度优先搜索算法,处理完当前 fiber 节点后,workInProgress 将包含对树中下一个 fiber 节点的引用,如果下一个节点为 null 不存在,则认为执行结束退出 workLoop 循环并准备进行一次提交更改。

    方法调用栈如下:
    performUnitOfWork? -->??beginWork -->? updateClassComponent --> finishedComponent --> completeUnitOfWork

    代码如下所示:

    文件位置:react-reconciler/src/ReactFiberWorkLoop.js
    function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
        const current = unitOfWork.alternate;
    
        let next;
        next = beginWork(current, unitOfWork, renderExpirationTime);
    
        // 如果没有新的 work,则认为已完成当前工作
        if (next === null) {
            next = completeUnitOfWork(unitOfWork);
        }
    
        return next;
    }

    了解树的深度优先搜索算法,可点击参考该示例 《js-ntqfill》

    completeUnitOfWork

    文件位置:react-reconciler/src/completeUnitOfWork.js

    在 completeUnitOfWork 方法中构建 effect-list 链表,该 effect list 在下一个 commit 阶段非常重要,关于 effect list 上述有介绍。

    如下所示:

    function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
        // 深度优先搜索算法
        workInProgress = unitOfWork;
        do {
            const current = workInProgress.alternate;
            const returnFiber = workInProgress.return;
    
            /*
            构建 effect-list部分
        */
            if (returnFiber.firstEffect === null) {
                returnFiber.firstEffect = workInProgress.firstEffect;
            }
            if (workInProgress.lastEffect !== null) {
                if (returnFiber.lastEffect !== null) {
                    returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
                }
                returnFiber.lastEffect = workInProgress.lastEffect;
            }
    
            if (returnFiber.lastEffect !== null) {
                returnFiber.lastEffect.nextEffect = workInProgress;
            } else {
                returnFiber.firstEffect = workInProgress;
            }
            returnFiber.lastEffect = workInProgress;
    
            const siblingFiber = workInProgress.sibling;
            if (siblingFiber !== null) {
                // If there is more work to do in this returnFiber, do that next.
                return siblingFiber;
            }
            // Otherwise, return to the parent
            workInProgress = returnFiber;
        } while (workInProgress !== null);
    }

    至此,一个 render 阶段大概流程结束。

    Commit 阶段

    commit 阶段是 React 更新真实 DOM 并调用 pre-commit phase 和 commit phase 生命周期方法的地方。与 render 阶段不同,commit 阶段的执行始终是同步的,它将依赖上一个 render 阶段构建的 effect list 链表来完成。

    commitRootImpl

    commit 阶段实质上被分为如下三个子阶段:

    • before mutation
    • mutation phase
    • layout phase

    mutation 阶段主要做的事情是遍历 effect-list 列表,拿到每一个 effect 存储的信息,根据副作用类型 effectTag 执行相应的处理并提交更新到真正的 DOM。所有的 mutation effects 都会在 layout phase 阶段之前被处理。当该阶段执行结束时,workInProgress 树会被替换成 current 树。因此在 mutation phase 阶段之前的子阶段 before mutation,是调用 getSnapshotBeforeUpdate 生命周期的地方。在 before mutation 这个阶段,真正的 DOM 还没有被变更。最后一个子阶段是 layout phase,在这个阶段生命周期 componentDidMount/Update 被执行。

    文件位置:react-reconciler/src/ReactFiberWorkLoop.js

    如下所示:

    function commitRootImpl(root) {
        if (firstEffect !== null) {
            // before mutation 阶段,遍历 effect list
            do {
                try {
                    commitBeforeMutationEffects();
                } catch (error) {
                    nextEffect = nextEffect.nextEffect;
                }
            } while (nextEffect !== null);
    
            // the mutation phase 阶段,遍历 effect list
            nextEffect = firstEffect;
            do {
                try {
                    commitMutationEffects();
                } catch (error) {
                    nextEffect = nextEffect.nextEffect;
                }
            } while (nextEffect !== null);
    
            // 将 work-in-progress 树替换为 current 树
            root.current = finishedWork;
    
            // layout phase 阶段,遍历 effect list
            nextEffect = firstEffect;
            do {
                try {
                    commitLayoutEffects(root, expirationTime);
                } catch (error) {
                    captureCommitPhaseError(nextEffect, error);
                    nextEffect = nextEffect.nextEffect;
                }
            } while (nextEffect !== null);
    
            nextEffect = null;
        } else {
            // No effects.
            root.current = finishedWork;
        }
    }

    commitBeforeMutationEffects

    before mutation 调用链路:commitRootImpl -->??commitBeforeMutationEffects -->?commitBeforeMutationLifeCycles

    代码如下:

    function commitBeforeMutationLifeCycles(
      current: Fiber | null,
      finishedWork: Fiber,
    ): void {
      switch (finishedWork.tag) {
        case FunctionComponent:
        case ForwardRef:
        case SimpleMemoComponent:
        ...
        // 属性 stateNode 表示对应组件的实例
        // 在这里 class 组件实例执行 instance.getSnapshotBeforeUpdate()
        case ClassComponent: {
          if (finishedWork.effectTag & Snapshot) {
            if (current !== null) {
              const prevProps = current.memoizedProps;
              const prevState = current.memoizedState;
              const instance = finishedWork.stateNode;
              const snapshot = instance.getSnapshotBeforeUpdate(
                finishedWork.elementType === finishedWork.type
                  ? prevProps
                  : resolveDefaultProps(finishedWork.type, prevProps),
                prevState,
              );
    
              instance.__reactInternalSnapshotBeforeUpdate = snapshot;
            }
          }
          return;
        }
        case HostRoot:
        case HostComponent:
        case HostText:
        case HostPortal:
        case IncompleteClassComponent:
          ...
      }
    }

    commitMutationEffects

    文件位置:react-reconciler/src/ReactFiberWorkLoop.js

    mutation phase 阶段调用链路:
    commitRootImpl -->??commitMutationEffects -->?commitWork

    代码如下:

    function commitMutationEffects() {
      while (nextEffect !== null) {
        const effectTag = nextEffect.effectTag;
    
        let primaryEffectTag = effectTag & (Placement | Update | Deletion);
        switch (primaryEffectTag) {
          case Placement:
            ...
          case PlacementAndUpdate:
            ...
          case Update: {
            const current = nextEffect.alternate;
            commitWork(current, nextEffect);
            break;
          }
          case Deletion: {
            commitDeletion(nextEffect);
            break;
          }
        }
      }
    }

    commitLayoutEffects

    文件位置:react-reconciler/src/ReactFiberCommitWork.js

    layout phase 调用链路:commitRootImpl -->??commitLayoutEffects -->?commitLifeCycles

    代码如下:

    function commitLifeCycles(
      finishedRoot: FiberRoot,
      current: Fiber | null,
      finishedWork: Fiber,
      committedExpirationTime: ExpirationTime,
    ): void {
      switch (finishedWork.tag) {
        case FunctionComponent:
        case ForwardRef:
        case SimpleMemoComponent:
          ...
        case ClassComponent: {
          // 属性 stateNode 表示对应组件的实例
          // 在这里 class 组件实例执行 componentDidMount/DidUpdate
          const instance = finishedWork.stateNode;
          if (finishedWork.effectTag & Update) {
            // 首次渲染时,还没有 current 树
            if (current === null) {
              instance.componentDidMount();
            } else {
              const prevProps =
                finishedWork.elementType === finishedWork.type
                  ? current.memoizedProps
                  : resolveDefaultProps(finishedWork.type, current.memoizedProps);
              const prevState = current.memoizedState;
              instance.componentDidUpdate(
                prevProps,
                prevState,
                instance.__reactInternalSnapshotBeforeUpdate,
              );
            }
          }
          const updateQueue = finishedWork.updateQueue;
          if (updateQueue !== null) {
            commitUpdateQueue(
              finishedWork,
              updateQueue,
              instance,
              committedExpirationTime,
            );
          }
          return;
        }
        case HostRoot:
        case HostComponent:
        case HostText:
        case HostPortal:
        case Profiler:
        case SuspenseComponent:
        case SuspenseListComponent:
          ...
      }
    }

    扩展

    以下是一些关于 Fiber 的扩展内容。

    调用链路

    如下图所示,根据 React 源码绘制的调用链路图,主要罗列了一些比较重要的函数方法,可作为大家了解 Fiber 的参考。源码调试过程可以找到对应的函数方法打断点,以了解实际运行的过程,便于更好梳理出各个逻辑方法之间的关系。

    fiber调用链路.jpg

    requestIdleCallback

    之前有文章在总结 React Fiber 的调度原理时提到,客户端线程执行任务时会以帧的形式划分,在两个执行帧之间,主线程通常会有一小段空闲时间,在这个空闲期触发 requestIdleCallback 方法,能够执行一些优先级较低的 work。

    据说在早期的 React 版本上确实是这么做的,但使用 requestIdleCallback 实际上有一些限制,执行频次不足,以致于无法实现流畅的 UI 渲染,扩展性差。因此 React 团队放弃了 requestIdleCallback 用法,实现了自定义的版本。比如,在发布 v16.10 版本中,推出实验性的 Scheduler,尝试使用 postMessage 来代替 requestAnimationFrame。更多了解可以查看 React 源码 packages/scheduler 部分。

    小结

    Fiber 由来已久,可以说是 React 设计思想的一个典型表现。相比业界其他流行库更多采用当新数据到达时再计算模式,React 坚持拉取模式,即能够把计算资源延迟到必要时候再用,并且它知道,什么时候更适合执行,什么时候不执行。看起来虽然只是微小的区别,却意义很大。随着后续异步渲染能力等新特性的推出,我们有理由相信,在未来,React 将会在人机交互的应用中给我们带来更多的惊喜。

    参考

    本文发布自 网易云音乐大前端团队,文章未经授权禁止任何形式的转载。我们常年招收前端、iOS、Android,如果你准备换工作,又恰好喜欢云音乐,那就加入我们 grp.music-fe(at)corp.netease.com!
    查看原文

    赞 19 收藏 9 评论 4

    认证与成就

    • 获得 62 次点赞
    • 获得 28 枚徽章 获得 4 枚金徽章, 获得 7 枚银徽章, 获得 17 枚铜徽章

    擅长技能
    编辑

    开源项目 & 著作
    编辑

    (??? )
    暂时没有

    注册于 2015-11-07
    个人主页被 1.8k 人浏览

    bt365体育投注