有了‍解信息化专业有哪些工具的吗?说下是哪个做更好些呢?

&figure&&img src=&https://pic4.zhimg.com/v2-d64bf78c2baa1b0a70a69_b.jpg& data-rawwidth=&500& data-rawheight=&280& class=&origin_image zh-lightbox-thumb& width=&500& data-original=&https://pic4.zhimg.com/v2-d64bf78c2baa1b0a70a69_r.jpg&&&/figure&&blockquote&长文预警:原《&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&React Fiber性能优化(内部试讲)&/a&》的完整版&/blockquote&&p&&br&&/p&&blockquote&性能优化是一个系统性的工程,如果只看到局部,引入算法,当然是越快越好; 但从整体来看,在关键点引入缓存,可以秒杀N多算法,或另辟蹊径,探索事件的本质,可能用户要的并不是快……&/blockquote&&p&&br&&/p&&p&React16启用了全新的架构,叫做Fiber,其最大的使命是解决大型React项目的性能问题,再顺手解决之前的一些痛点。&/p&&p&&br&&/p&&h2& 痛点&/h2&&p&主要有如下几个:&/p&&ul&&li&组件不能返回数组,最见的场合是UL元素下只能使用LI,TR元素下只能使用TD或TH,这时这里有一个组件循环生成LI或TD列表时,我们并不想再放一个DIV,这会破坏HTML的语义。&/li&&li&弹窗问题,之前一直使用不稳定的unstable_renderSubtreeIntoContainer。弹窗是依赖原来DOM树的上下文,因此这个API第一个参数是组件实例,通过它得到对应虚拟DOM,然后一级级往上找,得到上下文。它的其他参数也很好用,但这个方法一直没有转正。。。&/li&&li&异常处理,我们想知道哪个组件出错,虽然有了React DevTool,但是太深的组件树查找起来还是很吃力。希望有个方法告诉我出错位置,并且出错时能让我有机会进行一些修复工作&/li&&li&HOC的流行带来两个问题,毕竟是社区兴起的方案,没有考虑到ref与context的向下传递。&/li&&li&组件的性能优化全凭人肉,并且主要集中在SCU,希望框架能干些事情,即使不用SCU,性能也能上去。&/li&&/ul&&p&&br&&/p&&h2&解决进度&/h2&&ul&&li&16.0 让组件支持返回任何数组类型,从而解决数组问题; 推出createPortal API ,解决弹窗问题; 推出componentDidCatch新钩子, 划分出错误组件与边界组件, 每个边界组件能修复下方组件错误一次, 再次出错,转交更上层的边界组件来处理,解决异常处理问题。&/li&&li&16.2 推出Fragment组件,可以看作是数组的一种语法糖。&/li&&li&16.3 推出createRef与forwardRef解决Ref在HOC中的传递问题,推出new Context API,解决HOC的context传递问题(主要是SCU作崇)&/li&&li&而性能问题,从16.0开始一直由一些内部机制来保证,涉及到批量更新及基于时间分片的限量更新。&/li&&/ul&&p&&br&&/p&&h2&一个小实验&/h2&&p&&br&&/p&&p&我们可以通过以下实验来窥探React16的优化思想。&/p&&p&&br&&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&function&/span& &span class=&nx&&randomHexColor&/span&&span class=&p&&(){&/span&
&span class=&k&&return&/span& &span class=&s2&&&#&&/span& &span class=&o&&+&/span& &span class=&p&&(&/span&&span class=&s2&&&0000&&/span&&span class=&o&&+&/span& &span class=&p&&(&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span& &span class=&o&&*&/span& &span class=&mh&&0x1000000&/span& &span class=&o&&&&&/span& &span class=&mi&&0&/span&&span class=&p&&).&/span&&span class=&nx&&toString&/span&&span class=&p&&(&/span&&span class=&mi&&16&/span&&span class=&p&&)).&/span&&span class=&nx&&substr&/span&&span class=&p&&(&/span&&span class=&o&&-&/span&&span class=&mi&&6&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&nx&&setTimeout&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&k&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&kd&&var&/span& &span class=&nx&&root&/span& &span class=&o&&=&/span& &span class=&nb&&document&/span&&span class=&p&&.&/span&&span class=&nx&&getElementById&/span&&span class=&p&&(&/span&&span class=&s2&&&root&&/span&&span class=&p&&);&/span&
&span class=&k&&for&/span&&span class=&p&&(&/span&&span class=&kd&&var&/span& &span class=&nx&&i&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span& &span class=&nx&&i&/span& &span class=&o&&&&/span& &span class=&mi&&10000&/span&&span class=&p&&;&/span& &span class=&nx&&i&/span&&span class=&o&&++&/span&&span class=&p&&){&/span&
&span class=&nx&&k&/span& &span class=&o&&+=&/span& &span class=&k&&new&/span& &span class=&nb&&Date&/span& &span class=&o&&-&/span& &span class=&mi&&0&/span& &span class=&p&&;&/span&
&span class=&kd&&var&/span& &span class=&nx&&el&/span& &span class=&o&&=&/span& &span class=&nb&&document&/span&&span class=&p&&.&/span&&span class=&nx&&createElement&/span&&span class=&p&&(&/span&&span class=&s2&&&div&&/span&&span class=&p&&);&/span&
&span class=&nx&&el&/span&&span class=&p&&.&/span&&span class=&nx&&innerHTML&/span& &span class=&o&&=&/span& &span class=&nx&&k&/span&&span class=&p&&;&/span&
&span class=&nx&&root&/span&&span class=&p&&.&/span&&span class=&nx&&appendChild&/span&&span class=&p&&(&/span&&span class=&nx&&el&/span&&span class=&p&&);&/span&
&span class=&nx&&el&/span&&span class=&p&&.&/span&&span class=&nx&&style&/span&&span class=&p&&.&/span&&span class=&nx&&cssText&/span& &span class=&o&&=&/span& &span class=&sb&&`background:&/span&&span class=&si&&${&/span&&span class=&nx&&randomHexColor&/span&&span class=&p&&()&/span&&span class=&si&&}&/span&&span class=&sb&&;height:40px`&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&p&&},&/span& &span class=&mi&&1000&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&&br&&/p&&p&&br&&/p&&p&这是一个拥有10000个节点的插入操作,包含了innerHTML与样式设置,花掉1000ms。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-1c4aebad9fdfa_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1194& data-rawheight=&552& class=&origin_image zh-lightbox-thumb& width=&1194& data-original=&https://pic2.zhimg.com/v2-1c4aebad9fdfa_r.jpg&&&/figure&&p&&br&&/p&&p&我们再改进一下,分派次插入节点,每次只操作100个节点,共100次,发现性能异常的好!&/p&&p&&br&&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&function&/span& &span class=&nx&&randomHexColor&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&s2&&&#&&/span& &span class=&o&&+&/span& &span class=&p&&(&/span&&span class=&s2&&&0000&&/span& &span class=&o&&+&/span& &span class=&p&&(&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span& &span class=&o&&*&/span& &span class=&mh&&0x1000000&/span& &span class=&o&&&&&/span& &span class=&mi&&0&/span&&span class=&p&&).&/span&&span class=&nx&&toString&/span&&span class=&p&&(&/span&&span class=&mi&&16&/span&&span class=&p&&)).&/span&&span class=&nx&&substr&/span&&span class=&p&&(&/span&&span class=&o&&-&/span&&span class=&mi&&6&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&kd&&var&/span& &span class=&nx&&root&/span& &span class=&o&&=&/span& &span class=&nb&&document&/span&&span class=&p&&.&/span&&span class=&nx&&getElementById&/span&&span class=&p&&(&/span&&span class=&s2&&&root&&/span&&span class=&p&&);&/span&
&span class=&nx&&setTimeout&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span& &span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kd&&function&/span& &span class=&nx&&loop&/span&&span class=&p&&(&/span&&span class=&nx&&n&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&k&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&nx&&n&/span&&span class=&p&&);&/span&
&span class=&k&&for&/span& &span class=&p&&(&/span&&span class=&kd&&var&/span& &span class=&nx&&i&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span& &span class=&nx&&i&/span& &span class=&o&&&&/span& &span class=&mi&&100&/span&&span class=&p&&;&/span& &span class=&nx&&i&/span&&span class=&o&&++&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&k&/span& &span class=&o&&+=&/span& &span class=&k&&new&/span& &span class=&nb&&Date&/span& &span class=&o&&-&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&kd&&var&/span& &span class=&nx&&el&/span& &span class=&o&&=&/span& &span class=&nb&&document&/span&&span class=&p&&.&/span&&span class=&nx&&createElement&/span&&span class=&p&&(&/span&&span class=&s2&&&div&&/span&&span class=&p&&);&/span&
&span class=&nx&&el&/span&&span class=&p&&.&/span&&span class=&nx&&innerHTML&/span& &span class=&o&&=&/span& &span class=&nx&&k&/span&&span class=&p&&;&/span&
&span class=&nx&&root&/span&&span class=&p&&.&/span&&span class=&nx&&appendChild&/span&&span class=&p&&(&/span&&span class=&nx&&el&/span&&span class=&p&&);&/span&
&span class=&nx&&el&/span&&span class=&p&&.&/span&&span class=&nx&&style&/span&&span class=&p&&.&/span&&span class=&nx&&cssText&/span& &span class=&o&&=&/span& &span class=&sb&&`background:&/span&&span class=&si&&${&/span&&span class=&nx&&randomHexColor&/span&&span class=&p&&()&/span&&span class=&si&&}&/span&&span class=&sb&&;height:40px`&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&n&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&setTimeout&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span& &span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&nx&&loop&/span&&span class=&p&&(&/span&&span class=&nx&&n&/span& &span class=&o&&-&/span& &span class=&mi&&1&/span&&span class=&p&&);&/span&
&span class=&p&&},&/span& &span class=&mi&&40&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&nx&&loop&/span&&span class=&p&&(&/span&&span class=&mi&&100&/span&&span class=&p&&);&/span&
&span class=&p&&},&/span& &span class=&mi&&1000&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-2caedd9205f_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&434& data-rawheight=&562& class=&origin_image zh-lightbox-thumb& width=&434& data-original=&https://pic4.zhimg.com/v2-2caedd9205f_r.jpg&&&/figure&&p&&br&&/p&&p&究其原因是因为浏览器是单线程,它将GUI描绘,时间器处理,事件处理,JS执行,远程资源加载统统放在一起。当做某件事,只有将它做完才能做下一件事。如果有足够的时间,浏览器是会对我们的代码进行编译优化(JIT)及进行热代码优化,一些DOM操作,内部也会对reflow进行处理。reflow是一个性能黑洞,很可能让页面的大多数元素进行重新布局。&/p&&p&浏览器的运作流程&/p&&p&&br&&/p&&blockquote&渲染 -& tasks -& 渲染 -& tasks -& 渲染 -& tasks -& ....&/blockquote&&p&&br&&/p&&p&这些tasks中有些我们可控,有些不可控,比如setTimeout什么时候执行不好说,它总是不准时; 资源加载时间不可控。但一些JS我们可以控制,让它们分派执行,tasks的时长不宜过长,这样浏览器就有时间优化JS代码与修正reflow!下图是我们理想中的渲染过程&/p&&figure&&img src=&https://pic3.zhimg.com/v2-8a94ee4aba85a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1344& data-rawheight=&426& class=&origin_image zh-lightbox-thumb& width=&1344& data-original=&https://pic3.zhimg.com/v2-8a94ee4aba85a_r.jpg&&&/figure&&p&&br&&/p&&p&总结一句,&b&就是让浏览器休息好,浏览器就能跑得更快&/b&。&/p&&p&&br&&/p&&h2&如何让代码断开重连&/h2&&p&&br&&/p&&p&JSX是一个快乐出奇蛋,一下子满足你两个愿望:&b&组件化&/b&与&b&标签化&/b&。并且JSX成为组件化的标准化语言。&/p&&p&&br&&/p&&div class=&highlight&&&pre&&code class=&language-html&&&span&&/span&&span class=&p&&&&/span&&span class=&nt&&div&/span&&span class=&p&&&&/span&
&span class=&p&&&&/span&&span class=&nt&&Foo&/span&&span class=&p&&&&/span&
&span class=&p&&&&/span&&span class=&nt&&Bar&/span& &span class=&p&&/&&/span&
&span class=&p&&&/&/span&&span class=&nt&&Foo&/span&&span class=&p&&&&/span&
&span class=&p&&&/&/span&&span class=&nt&&div&/span&&span class=&p&&&&/span&
&/code&&/pre&&/div&&p&&br&&/p&&p&但标签化是天然套嵌的结构,意味着它会最终编译成递归执行的代码。因此React团队称React16之前的调度器为栈调度器,栈没有什么不好,栈显浅易懂,代码量少,但它的坏处不能随意break掉,continue掉。根据我们上面的实验,break后我们还要重新执行,我们需要一种链表的结构。&/p&&p&链表是对异步友好的。链表在循环时不用每次都进入递归函数,重新生成什么执行上下文,变量对象,激活对象,性能当然比递归好。&/p&&p&因此Reat16设法将组件的递归更新,改成链表的依次执行。如果页面有多个虚拟DOM树,那么就将它们的根保存到一个数组中。&/p&&p&&br&&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&ReactDOM.render(&A /&, node1)
ReactDOM.render(&B /&, node2)
//node1与node2不存在包含关系,那么这页面就有两棵虚拟DOM树
&/code&&/pre&&/div&&p&&br&&/p&&p&&br&&/p&&p&如果仔细阅读源码,React这个纯视图库其实也是三层架构。在React15有&code&虚拟DOM层&/code&,它只负责&b&描述&/b&结构与逻辑;&code&内部组件层&/code&,它们负责组件的更新, ReactDOM.render、 setState、 forceUpdate都是与它们打交道,能让你多次setState,只执行一次真实的渲染, 在适合的时机执行你的组件实例的生命周期钩子;
&code&底层渲染层&/code&, 不同的显示介质有不同的渲染方法,比如说浏览器端,它使用元素节点,文本节点,在Native端,会调用oc, java的GUI, 在canvas中,有专门的API方法。。。&/p&&p&虚拟DOM是由JSX转译过来的,JSX的入口函数是React.createElement, 可操作空间不大, 第三大的底层API也非常稳定,因此我们只能改变第二层。&/p&&p&React16将内部组件层改成Fiber这种数据结构,因此它的架构名也改叫Fiber架构。Fiber节点拥有return, child, sibling三个属性,分别对应父节点, 第一个孩子, 它右边的兄弟, 有了它们就足够将一棵树变成一个链表, 实现深度优化遍历。 &/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-453e1f48a4f53356bee021c90ee00bed_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&343& data-rawheight=&272& class=&content_image& width=&343&&&/figure&&p&&br&&/p&&p&&br&&/p&&h2&如何决定每次更新的数量&/h2&&p&&br&&/p&&p&在React15中,每次更新时,都是从根组件或setState后的组件开始,更新整个子树,我们唯一能做的是,在某个节点中使用SCU断开某一部分的更新,或者是优化SCU的比较效率。&/p&&p&React16则是需要将虚拟DOM转换为Fiber节点,首先它规定一个时间段内,然后在这个时间段能转换多少个FiberNode,就更新多少个。&/p&&p&因此我们需要将我们的更新逻辑分成两个阶段,第一个阶段是将虚拟DOM转换成Fiber, Fiber转换成组件实例或真实DOM(不插入DOM树,插入DOM树会reflow)。Fiber转换成后两者明显会耗时,需要计算还剩下多少时间。并且转换实例需要调用一些钩子,如componentWillMount, 如果是重复利用已有的实例,这时就是调用componentWillReceiveProps,
shouldComponentUpdate,
componentWillUpdate,这时也会耗时。&/p&&p&为了让读者能直观了解React Fiber的运作过程,我们简单实现一下ReactDOM.render, 但不保证会跑起来。&/p&&p&首先是一些简单的方法:&/p&&p&&br&&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&queue&/span& &span class=&o&&=&/span& &span class=&p&&[]&/span&
&span class=&nx&&ReacDOM&/span&&span class=&p&&.&/span&&span class=&nx&&render&/span& &span class=&o&&=&/span& &span class=&kd&&function&/span& &span class=&p&&(&/span&&span class=&nx&&root&/span&&span class=&p&&,&/span& &span class=&nx&&container&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&queue&/span&&span class=&p&&.&/span&&span class=&nx&&push&/span&&span class=&p&&(&/span&&span class=&nx&&root&/span&&span class=&p&&)&/span&
&span class=&nx&&updateFiberAndView&/span&&span class=&p&&()&/span&
&span class=&p&&}&/span&
&span class=&kd&&function&/span& &span class=&nx&&getVdomFormQueue&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&queue&/span&&span class=&p&&.&/span&&span class=&nx&&shift&/span&&span class=&p&&()&/span&
&span class=&p&&}&/span&
&span class=&kd&&function&/span& &span class=&nx&&Fiber&/span&&span class=&p&&(&/span&&span class=&nx&&vnode&/span&&span class=&p&&){&/span&
&span class=&k&&for&/span&&span class=&p&&(&/span&&span class=&kd&&var&/span& &span class=&nx&&i&/span& &span class=&k&&in&/span& &span class=&nx&&vnode&/span&&span class=&p&&){&/span&
&span class=&k&&this&/span&&span class=&p&&[&/span&&span class=&nx&&i&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&nx&&vnode&/span&&span class=&p&&[&/span&&span class=&nx&&i&/span&&span class=&p&&]&/span&
&span class=&p&&}&/span&
&span class=&k&&this&/span&&span class=&p&&.&/span&&span class=&nx&&uuid&/span& &span class=&o&&=&/span& &span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span&
&span class=&p&&}&/span&
&span class=&c1&&//我们简单的Fiber目前来看,只比vdom多了一个uuid属性&/span&
&span class=&kd&&function&/span& &span class=&nx&&toFiber&/span&&span class=&p&&(&/span&&span class=&nx&&vnode&/span&&span class=&p&&){&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&o&&!&/span&&span class=&nx&&vnode&/span&&span class=&p&&.&/span&&span class=&nx&&uuid&/span&&span class=&p&&){&/span&
&span class=&k&&return&/span& &span class=&k&&new&/span& &span class=&nx&&Fiber&/span&&span class=&p&&(&/span&&span class=&nx&&vnode&/span&&span class=&p&&)&/span&
&span class=&p&&}&/span&
&span class=&k&&return&/span& &span class=&nx&&vnode&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&&br&&/p&&p&updateFiberAndView要实现React的时间分片,我们先用setTimeout模拟。我们暂时不用理会updateView怎么实现,可能它就是updateComponentOrElement中将它们放到又一个列队,需再出来执行insertBefore, componentDidMount操作呢!&/p&&p&&br&&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&function&/span& &span class=&nx&&updateFiberAndView&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&now&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&nb&&Date&/span& &span class=&o&&-&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&kd&&var&/span& &span class=&nx&&deadline&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&nb&&Date&/span& &span class=&o&&+&/span& &span class=&mi&&100&/span&&span class=&p&&;&/span&
&span class=&nx&&updateView&/span&&span class=&p&&()&/span& &span class=&c1&&//更新视图,这会耗时,因此需要check时间&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&k&&new&/span& &span class=&nb&&Date&/span& &span class=&o&&&&/span& &span class=&nx&&deadline&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&vdom&/span& &span class=&o&&=&/span& &span class=&nx&&getVdomFormQueue&/span&&span class=&p&&()&/span&
&span class=&kd&&var&/span& &span class=&nx&&fiber&/span& &span class=&o&&=&/span& &span class=&nx&&vdom&/span&&span class=&p&&,&/span& &span class=&nx&&firstFiber&/span&
&span class=&kd&&var&/span& &span class=&nx&&hasVisited&/span& &span class=&o&&=&/span& &span class=&p&&{}&/span&
&span class=&k&&do&/span& &span class=&p&&{&/span&&span class=&c1&&//深度优先遍历&/span&
&span class=&kd&&var&/span& &span class=&nx&&fiber&/span& &span class=&o&&=&/span& &span class=&nx&&toFiber&/span&&span class=&p&&(&/span&&span class=&nx&&fiber&/span&&span class=&p&&);&/span&&span class=&c1&&//A处&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&o&&!&/span&&span class=&nx&&firstFiber&/span&&span class=&p&&){&/span&
&span class=&nx&&fibstFiber&/span& &span class=&o&&=&/span& &span class=&nx&&fiber&/span&
&span class=&p&&}&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&o&&!&/span&&span class=&nx&&hasVisited&/span&&span class=&p&&[&/span&&span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&uuid&/span&&span class=&p&&])&/span& &span class=&p&&{&/span&
&span class=&nx&&hasVisited&/span&&span class=&p&&[&/span&&span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&uuid&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&mi&&1&/span&
&span class=&c1&&//根据fiber.type实例化组件或者创建真实DOM&/span&
&span class=&c1&&//这会耗时,因此需要check时间&/span&
&span class=&nx&&updateComponentOrElement&/span&&span class=&p&&(&/span&&span class=&nx&&fiber&/span&&span class=&p&&);&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&child&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&//向下转换&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&newDate&/span& &span class=&o&&-&/span& &span class=&mi&&0&/span& &span class=&o&&&&/span& &span class=&nx&&deadline&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&queue&/span&&span class=&p&&.&/span&&span class=&nx&&push&/span&&span class=&p&&(&/span&&span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&child&/span&&span class=&p&&)&/span&&span class=&c1&&//时间不够,放入栈&/span&
&span class=&k&&break&/span&
&span class=&p&&}&/span&
&span class=&nx&&fiber&/span& &span class=&o&&=&/span& &span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&child&/span&&span class=&p&&;&/span&
&span class=&k&&continue&/span&
&span class=&c1&&//让逻辑跑回A处,不断转换child, child.child, child.child.child&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&c1&&//如果组件没有children,那么就向右找&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&sibling&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&fiber&/span& &span class=&o&&=&/span& &span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&sibling&/span&&span class=&p&&;&/span&
&span class=&k&&continue&/span& &span class=&c1&&//让逻辑跑回A处&/span&
&span class=&p&&}&/span&
&span class=&c1&&// 向上找&/span&
&span class=&nx&&fiber&/span& &span class=&o&&=&/span& &span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&k&&return&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&nx&&fiber&/span& &span class=&o&&===&/span& &span class=&nx&&fibstFiber&/span& &span class=&o&&||&/span& &span class=&o&&!&/span&&span class=&nx&&fiber&/span&&span class=&p&&){&/span&
&span class=&k&&break&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span& &span class=&k&&while&/span& &span class=&p&&(&/span&&span class=&mi&&1&/span&&span class=&p&&)&/span&
&span class=&p&&}&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&queue&/span&&span class=&p&&.&/span&&span class=&nx&&length&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&setTimeout&/span&&span class=&p&&(&/span&&span class=&nx&&updateFiberAndView&/span&&span class=&p&&,&/span& &span class=&mi&&40&/span&&span class=&p&&)&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&&br&&/p&&p&&br&&/p&&p&里面有一个do while循环,每一次都是小心翼翼进行计时,时间不够就将来不及处理的节点放进列队。&/p&&p&&br&&/p&&p&updateComponentOrElement无非是这样:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&function&/span& &span class=&nx&&updateComponentOrElement&/span&&span class=&p&&(&/span&&span class=&nx&&fiber&/span&&span class=&p&&){&/span&
&span class=&kd&&var&/span& &span class=&p&&{&/span&&span class=&nx&&type&/span&&span class=&p&&,&/span& &span class=&nx&&stateNode&/span&&span class=&p&&,&/span& &span class=&nx&&props&/span&&span class=&p&&}&/span& &span class=&o&&=&/span& &span class=&nx&&fiber&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&o&&!&/span&&span class=&nx&&stateNode&/span&&span class=&p&&){&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&k&&typeof&/span& &span class=&nx&&type&/span& &span class=&o&&===&/span& &span class=&s2&&&string&&/span&&span class=&p&&){&/span&
&span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&stateNode&/span& &span class=&o&&=&/span& &span class=&nb&&document&/span&&span class=&p&&.&/span&&span class=&nx&&createElement&/span&&span class=&p&&(&/span&&span class=&nx&&type&/span&&span class=&p&&)&/span&
&span class=&p&&}&/span&&span class=&k&&else&/span&&span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&context&/span& &span class=&o&&=&/span& &span class=&p&&{}&/span&&span class=&c1&&//暂时免去这个获取细节&/span&
&span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&stateNode&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&nx&&type&/span&&span class=&p&&(&/span&&span class=&nx&&props&/span&&span class=&p&&,&/span& &span class=&nx&&context&/span&&span class=&p&&)&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&nx&&stateNode&/span&&span class=&p&&.&/span&&span class=&nx&&render&/span&&span class=&p&&){&/span&
&span class=&c1&&//执行componentWillMount等钩子&/span&
&span class=&nx&&children&/span& &span class=&o&&=&/span& &span class=&nx&&stateNode&/span&&span class=&p&&.&/span&&span class=&nx&&render&/span&&span class=&p&&()&/span&
&span class=&p&&}&/span&&span class=&k&&else&/span&&span class=&p&&{&/span&
&span class=&nx&&children&/span& &span class=&o&&=&/span& &span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&childen&/span&
&span class=&p&&}&/span&
&span class=&kd&&var&/span& &span class=&nx&&prev&/span& &span class=&o&&=&/span& &span class=&kc&&null&/span&&span class=&p&&;&/span&
 &span class=&c1&&//这里只是mount的实现,update时还需要一个oldChildren, 进行key匹配,重复利用已有节点&/span&
&span class=&k&&for&/span&&span class=&p&&(&/span&&span class=&kd&&var&/span& &span class=&nx&&i&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&,&/span& &span class=&nx&&n&/span& &span class=&o&&=&/span& &span class=&nx&&children&/span&&span class=&p&&.&/span&&span class=&nx&&length&/span&&span class=&p&&;&/span& &span class=&nx&&i&/span& &span class=&o&&&&/span& &span class=&nx&&n&/span&&span class=&p&&;&/span& &span class=&nx&&i&/span&&span class=&o&&++&/span&&span class=&p&&){&/span&
&span class=&kd&&var&/span& &span class=&nx&&child&/span& &span class=&o&&=&/span& &span class=&nx&&children&/span&&span class=&p&&[&/span&&span class=&nx&&i&/span&&span class=&p&&];&/span&
&span class=&nx&&child&/span&&span class=&p&&.&/span&&span class=&k&&return&/span& &span class=&o&&=&/span& &span class=&nx&&fiber&/span&&span class=&p&&;&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&o&&!&/span&&span class=&nx&&prev&/span&&span class=&p&&){&/span&
&span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&child&/span& &span class=&o&&=&/span& &span class=&nx&&child&/span&
&span class=&p&&}&/span&&span class=&k&&else&/span&&span class=&p&&{&/span&
&span class=&nx&&prev&/span&&span class=&p&&.&/span&&span class=&nx&&sibling&/span& &span class=&o&&=&/span& &span class=&nx&&child&/span&
&span class=&p&&}&/span&
&span class=&nx&&prev&/span& &span class=&o&&=&/span& &span class=&nx&&child&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&&br&&/p&&p&因此这样Fiber的return, child, sibling就有了,可以happy地进行深度优先遍历了。&/p&&h2&如何调度时间才能保证流畅&/h2&&p&&br&&/p&&p&刚才的updateFiberAndView其实有一个问题,我们安排了100ms来更新视图与虚拟DOM,然后再安排40ms来给浏览器来做其他事。如果我们的虚拟DOM树很小,其实不需要100 如果我们的代码之后, 浏览器有更多其他事要干, 40ms可能不够。IE10出现了setImmediate,requestAnimationFrame这些新定时器,让我们这些前端,其实浏览器有能力让页面更流畅地运行起来。&/p&&p&浏览器本身也不断进化中,随着页面由简单的展示转向WebAPP,它需要一些新能力来承载更多节点的展示与更新。&/p&&p&下面是一些自救措施:&/p&&ul&&li&requestAnimationFrame&/li&&li&requestIdleCallback&/li&&li&web worker&/li&&li&IntersectionObserver&/li&&/ul&&p&&br&&/p&&p&我们依次称为浏览器层面的帧数控制调用,闲时调用,多线程调用, 进入可视区调用。&/p&&p&requestAnimationFrame在做动画时经常用到,jQuery新版本都使用它。web worker在angular2开始就释出一些包,实验性地用它进行diff数据。IntersectionObserver可以用到ListView中。而requestIdleCallback是一个生脸孔,而React官方恰恰看上它。&/p&&p&刚才说updateFiberAndView有出两个时间段,一个给自己的,一个给浏览器的。requestAnimationFrame能帮我们解决第二个时间段,从而确保整体都是60帧或75帧(这个帧数可以在操作系统的显示器刷新频率中设置)流畅运行。&/p&&p&我们看requestIdleCallback是怎么解决这问题的&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-e1ba24e51c372e7c824bdf4df5a41555_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&968& data-rawheight=&712& class=&origin_image zh-lightbox-thumb& width=&968& data-original=&https://pic2.zhimg.com/v2-e1ba24e51c372e7c824bdf4df5a41555_r.jpg&&&/figure&&p&&br&&/p&&p&它的第一个参数是一个回调,回调有一个参数对象,对象有一个timeRemaining方法,就相当于&code&new Date - deadline&/code&,并且它是一个高精度数据, 比毫秒更准确, 至少浏览器到底安排了多少时间给更新DOM与虚拟DOM,我们不用管。第二个时间段也不用管,不过浏览器可能1,2秒才执行这个回调,因此为了保险起见,我们可以设置第二个参数,让它在回调结束后300ms才执行。要相信浏览器,因为都是大牛们写的,时间的调度比你安排更有效率。&/p&&p&于是我们的updateFiberAndView可以改成这样:&/p&&p&&br&&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&function&/span& &span class=&nx&&updateFiberAndView&/span&&span class=&p&&(&/span&&span class=&nx&&dl&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&updateView&/span&&span class=&p&&()&/span& &span class=&c1&&//更新视图,这会耗时,因此需要check时间&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&dl&/span&&span class=&p&&.&/span&&span class=&nx&&timeRemaining&/span&&span class=&p&&()&/span& &span class=&o&&&&/span& &span class=&mi&&1&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&vdom&/span& &span class=&o&&=&/span& &span class=&nx&&getVdomFormQueue&/span&&span class=&p&&()&/span&
&span class=&kd&&var&/span& &span class=&nx&&fiber&/span& &span class=&o&&=&/span& &span class=&nx&&vdom&/span&&span class=&p&&,&/span& &span class=&nx&&firstFiber&/span&
&span class=&kd&&var&/span& &span class=&nx&&hasVisited&/span& &span class=&o&&=&/span& &span class=&p&&{}&/span&
&span class=&k&&do&/span& &span class=&p&&{&/span&&span class=&c1&&//深度优先遍历&/span&
&span class=&kd&&var&/span& &span class=&nx&&fiber&/span& &span class=&o&&=&/span& &span class=&nx&&toFiber&/span&&span class=&p&&(&/span&&span class=&nx&&fiber&/span&&span class=&p&&);&/span&&span class=&c1&&//A处&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&o&&!&/span&&span class=&nx&&firstFiber&/span&&span class=&p&&){&/span&
&span class=&nx&&fibstFiber&/span& &span class=&o&&=&/span& &span class=&nx&&fiber&/span&
&span class=&p&&}&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&o&&!&/span&&span class=&nx&&hasVisited&/span&&span class=&p&&[&/span&&span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&uuid&/span&&span class=&p&&])&/span& &span class=&p&&{&/span&
&span class=&nx&&hasVisited&/span&&span class=&p&&[&/span&&span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&uuid&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&mi&&1&/span&
&span class=&c1&&//根据fiber.type实例化组件或者创建真实DOM&/span&
&span class=&c1&&//这会耗时,因此需要check时间&/span&
&span class=&nx&&updateComponentOrElement&/span&&span class=&p&&(&/span&&span class=&nx&&fiber&/span&&span class=&p&&);&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&child&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&//向下转换&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&dl&/span&&span class=&p&&.&/span&&span class=&nx&&timeRemaining&/span&&span class=&p&&()&/span& &span class=&o&&&&/span& &span class=&mi&&1&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&queue&/span&&span class=&p&&.&/span&&span class=&nx&&push&/span&&span class=&p&&(&/span&&span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&child&/span&&span class=&p&&)&/span&&span class=&c1&&//时间不够,放入栈&/span&
&span class=&k&&break&/span&
&span class=&p&&}&/span&
&span class=&nx&&fiber&/span& &span class=&o&&=&/span& &span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&child&/span&&span class=&p&&;&/span&
&span class=&k&&continue&/span&
&span class=&c1&&//让逻辑跑回A处,不断转换child, child.child, child.child.child&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&c1&&//....略&/span&
&span class=&p&&}&/span& &span class=&k&&while&/span& &span class=&p&&(&/span&&span class=&mi&&1&/span&&span class=&p&&)&/span&
&span class=&p&&}&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&queue&/span&&span class=&p&&.&/span&&span class=&nx&&length&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&requetIdleCallback&/span&&span class=&p&&(&/span&&span class=&nx&&updateFiberAndView&/span&&span class=&p&&,&/span& &span class=&p&&{&/span&
&span class=&nx&&timeout&/span&&span class=&o&&:&/span&&span class=&k&&new&/span& &span class=&nb&&Date&/span& &span class=&o&&+&/span& &span class=&mi&&100&/span&
&span class=&p&&}&/span&
&span class=&p&&)&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&&br&&/p&&p&到这里,ReactFiber基于时间分片的限量更新讲完了。实际上React为了照顾绝大多数的浏览器,自己实现了requestIdleCallback。&/p&&h2&批量更新&/h2&&p&&br&&/p&&p&但React团队觉得还不够,需要更强大的东西。因为有的业务对视图的实时同步需求并不强烈,希望将所有逻辑都跑完才更新视图,于是有了batchedUpdates,目前它还不是一个稳定的API,因此大家使用它时要这样用ReactDOM.unstable_batchedUpdates。&/p&&p&这个东西怎么实现呢?就是搞一个全局的开关,如果打开了,就让updateView不起作用。&/p&&p&&br&&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&var isBatching = false
function batchedUpdates(callback, event) {
let keepbook = isB
isBatching =
return callback(event);
} finally {
isBatching =
if (!isBatching) {
requetIdleCallback(updateFiberAndView, {
timeout:new Date + 1
function updateView(){
if(isBatching){
//更新视图
&/code&&/pre&&/div&&p&&br&&/p&&p&&br&&/p&&p&事实上,当然没有这么简单,考虑到大家看不懂React的源码,大家可以看一下anujs是怎么实现的:&/p&&p&&a href=&https://link.zhihu.com/?target=https%3A//github.com/RubyLouvre/anu/blob/master/packages/fiber/scheduleWork.js%23L94-L113& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&github.com/RubyLouvre/a&/span&&span class=&invisible&&nu/blob/master/packages/fiber/scheduleWork.js#L94-L113&/span&&span class=&ellipsis&&&/span&&/a&&/p&&p&&br&&/p&&p&React内部也大量使用batchedUpdates来优化用户代码,比如说在事件回调中setState,在commit阶段的钩子(componentDidXXX)中setState 。&/p&&p&可以说,&code&setState是对单个组件的合并渲染,batchedUpdates是对多个组件的合并渲染&/code&。合并渲染是React最主要的优化手段。&/p&&p&&br&&/p&&h2&为什么使用深度优化遍历&/h2&&p&&br&&/p&&p&React通过Fiber将树的遍历变成了链表的遍历,但遍历手段有这么多种,为什么偏偏使用DFS?!&/p&&p&这涉及一个很经典的消息通信问题。如果是父子通信,我们可以通过props进行通信,子组件可以保存父的引用,可以随时call父组件。如果是多级组件间的通信,或不存在包含关系的组件通信就麻烦了,于是React发明了上下文对象(context)。&/p&&p&context一开始是一个空对象,为了方便起见,我们称之为&b&unmaskedContext&/b&。&/p&&p&当它遇到一个有getChildContext方法的组件时,那个方法会产生一个新context,与上面的合并,然后将新context作为unmaskedContext往下传。&/p&&p&当它遇到一个有contextTypes的组件,context就抽取一部分内容给这个组件进行实例化。这个只有部分内容的context,我们称之为&b&maskedContext&/b&。&/p&&p&组件总是从unmaskedContext中割一块肉下来作为自己的context。可怜!&/p&&p&如果子组件没有contextTypes,那么它就没有任何属性。&/p&&p&在React15中,为了传递unmaskedContext,于是大部分方法与钩子都留了一个参数给它。但这么大架子的context竟然在文档中没有什么地位。那时React团队还没有想好如何处理组件通信,因此社区一直用舶来品Redux来救命。这情况一直到Redux的作者入主React团队。&/p&&p&还有一个隐患,它可能被SCU比较时是用maskedContext,而不是unmaskedContext。&/p&&p&基于这些问题,终于new Context API出来了。首先,
unmaskedContext
不再像以前那样各个方法中来往穿梭了,有一个独立的contextStack。开始时就push进一个空对象,到达某个组件需要实例化时,就取它第一个。当再次访问这个组件时, 就像它从栈中弹出。因此我们需要深度优先遍历,保证每点节点都访问两次。&/p&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-d629ff51df8b827d67179_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1158& data-rawheight=&901& class=&origin_image zh-lightbox-thumb& width=&1158& data-original=&https://pic4.zhimg.com/v2-d629ff51df8b827d67179_r.jpg&&&/figure&&p&&br&&/p&&p&相同的情况还有container,container是我们某个元素虚拟DOM需要用到的真实父节点。在React15中,它会装在一个containerInfo对象也层层传送。&/p&&p&我们知道,虚拟DOM分成两大类,一种是组件虚拟DOM,type为函数或类,它本身不产生节点,而是生成组件实例,而通过render方法,产生下一级的虚拟DOM。一种是元素虚拟DOM,type为标签名,会产生DOM节点。上面的元素虚拟DOM的stateNode(DOM节点),就是下方的元素虚拟DOM的contaner。&/p&&p&这种独立的栈机制有效地解决了内部方法的参数冗余问题。&/p&&p&但有一个问题,当第一次渲染完毕后,contextStack置为空了。然后我们位于虚拟DOM树的某个组件setState,这时它的context应该如何获取呢?React的解决方式是,每次都是从根开始渲染,通过updateQueue加速跳过没有更新的 节点——每个组件在setState或forceUpdate时,都会创建一个updateQueue属性在它的上面。anujs则是保存它之前的unmaskedContext到实例上,unmaskedContext可以看作是上面所有context的并集,并且一个可以当多个使用。&/p&&p&当我们批量更新时,可能有多少不连续的子组件被更新了,其中两个组件之间的某个组件使用了SCU return false,这个SCU应该要被忽视。 因此我们引用一些变量让它透明化。就像forceUpdate能让组件无视SCU一样。&/p&&p&&br&&/p&&h2&为什么要对生命周期钩子大换血&/h2&&p&&br&&/p&&p&React将虚拟DOM的更新过程划分两个阶段,reconciler阶段与commit阶段。reconciler阶段对应早期版本的diff过程,commit阶段对应早期版本的patch过程。&/p&&p&一些迷你React,如preact会将它们混合在一起,一边diff一边patch(幸好它使用了Promise.then来优化,确保每次只更新一个组件) 。&/p&&p&有些迷你React则是通过减少移动进行优化,于是绞尽脑汁,用上各种算法,最短编辑距离,最长公共子序列,最长上升子序列。。。&/p&&p&其实基于算法的优化是一种绝望的优化,就类似玛雅文明因为找不到铜矿一直停留于石器时代,诞生了伟大的工匠精神把石器打磨得美伦美奂。&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-fa296a08d4ae7a3b14f572_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&426& data-rawheight=&122& class=&origin_image zh-lightbox-thumb& width=&426& data-original=&https://pic2.zhimg.com/v2-fa296a08d4ae7a3b14f572_r.jpg&&&/figure&&p&&br&&/p&&p&之所以这么说,因为diff算法都用于组件的新旧children比较,children一般不会出现过长的情况,有点大炮打蚊子。况且当我们的应用变得非常庞大,页面有上万个组件,要diff这么多组件,再卓绝的算法也不能保证浏览器不会累趴。因为他们没想到浏览器也会累趴,也没有想到这是一个长跑的问题。如果是100米短跑,或者1000米竞赛,当然越快越好。如果是马拉松,就需要考虑到保存体力了,需要注意休息了。性能是一个系统性的工程。&/p&&p&在我们的代码里面,&code&休息&/code&就是检测时间然后断开Fiber链。&/p&&p&updateFiberAndView里面先进行updateView,由于节点的更新是不可控,因此全部更新完,才检测时间。并且我们完全不用担心updateView会出问题,因为updateView实质上是在batchedUpdates中,里面有try catch。而接下来我们基于DFS更新节点,每个节点都要check时间,这个过程其实很害怕出错的, 因为组件在挂载过程中会调三次钩子/方法(constructor, componentWillMount, render), 组件在更新过程中会调4次钩子 (componentWillReceiveProps, shouldUpdate, componentWillUpdate), 总不能每个方法都用try
catch包起来,这样会性能很差。而constructor, render是不可避免的,于是对三个willXXX动刀了。&/p&&p&在早期版本中,componentWillMount与componentWillReceiveProps会做内部优化,执行多次setState都会延后到render时进行合并处理。因此用户就肆意setState了。这些willXXX还可以让用户任意操作DOM。 操作DOM会可能reflow,这是官方不愿意看到的。于是官方推出了getDerivedStateFromProps,让你在render设置新state,你主要返回一个新对象,它就主动帮你setState。由于这是一个静态方法,你不能操作instance,这就阻止了你多次操作setState。由于没有instance,也就没有&a href=&https://link.zhihu.com/?target=http%3A//instance.refs.xxx& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&instance.refs.xxx&/span&&span class=&invisible&&&/span&&/a&,你也没有机会操作DOM了。这样一来,getDerivedStateFromProps的逻辑应该会很简单,这样就不会出错,不会出错,就不会打断DFS过程。&/p&&p&getDerivedStateFromProps取代了原来的componentWillMount与componentWillReceiveProps方法,而componentWillUpdate本来就是可有可无,以前完全是为了对称好看。&/p&&p&在即使到来的异步更新中,reconciler阶段可能执行多次,才执行一次commit,这样也会导致willXXX钩子执行多次,违反它们的语义,它们的废弃是不可逆转的。&/p&&p&在进入commi阶段时,组件多了一个新钩子叫getSnapshotBeforeUpdate,它与commit阶段的钩子一样只执行一次。&/p&&p&如果出错呢,在componentDidMount/Update后,我们可以使用componentDidCatch方法。于是整个流程变成这样:&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-00ed58fde002fec930f6_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1082& data-rawheight=&634& class=&origin_image zh-lightbox-thumb& width=&1082& data-original=&https://pic3.zhimg.com/v2-00ed58fde002fec930f6_r.jpg&&&/figure&&p&&br&&/p&&p&reconciler阶段的钩子都不应该操作DOM,最好也不要setState,我们称之为&b&&i&轻量钩子&/i&*。commit阶段的钩子则对应称之为&/b&重量钩子**。&/p&&p&&br&&/p&&h2&任务系统&/h2&&p&&br&&/p&&p&updateFiberAndView是位于一个requestIdleCallback中,因此它的时间很有限,分给DFS部分的时间也更少,因此它们不能做太多事情。这怎么办呢,标记一下,留给commit阶段做。于是产生了一个任务系统。&/p&&p&每个Fiber分配到新的任务时,就通过位操作,累加一个sideEffect。sideEffect字面上是副作用的意思,非常重FP流的味道,但我们理解为任务更方便我们的理解。&/p&&p&每个Fiber可能有多个任务,比如它要插入DOM或移动,就需要加上Replacement,需要设置样式,需要加上Update。&/p&&p&怎么添加任务呢?&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&effectTag&/span& &span class=&o&&|=&/span& &span class=&nx&&Update&/span&
&/code&&/pre&&/div&&p&&br&&/p&&p&怎么保证不会重复添加相同的任务?&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&effectTag&/span& &span class=&o&&&=&/span& &span class=&o&&~&/span&&span class=&nx&&DidCapture&/span&&span class=&p&&;&/span&
&/code&&/pre&&/div&&p&&br&&/p&&p&在commit阶段,怎么知道它包含了某项任务?&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&nx&&fiber&/span&&span class=&p&&.&/span&&span class=&nx&&effectTag&/span& &span class=&o&&&&/span& &span class=&nx&&Update&/span&&span class=&p&&){&/span& &span class=&cm&&/*操作属性*/&/span&&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&&br&&/p&&p&&br&&/p&&p&React内置这么多任务,从DOM操作到Ref处理到回调唤起。。。&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-bbe243c77ba3a918ca475b_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&480& data-rawheight=&429& class=&origin_image zh-lightbox-thumb& width=&480& data-original=&https://pic3.zhimg.com/v2-bbe243c77ba3a918ca475b_r.jpg&&&/figure&&p&&br&&/p&&p&顺便说一下anu的任务名,是基于素数进行乘除。&/p&&p&&a href=&https://link.zhihu.com/?target=https%3A//github.com/RubyLouvre/anu/blob/master/packages/fiber/commitWork.js& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&github.com/RubyLouvre/a&/span&&span class=&invisible&&nu/blob/master/packages/fiber/commitWork.js&/span&&span class=&ellipsis&&&/span&&/a&&/p&&p&&br&&/p&&p&无论是位操作还是素数,我们只要保证某个Fiber的相同性质任务只执行一次就行了。&/p&&p&此外,任务系统还有另一个存在意义,保证一些任务优先执行,某些任务是在另一些任务之前。我们称之为任务分拣。这就像快递的仓库管理一样,有了归类才好进行优化。比如说,元素虚拟DOM的插入移动操作必须在所有任务之前执行,移除操作必须在componentWillUnmount后执行。这些任务之所以是这个顺序,因为这样做才合理,都经过高手们的严密推敲,经过React15时代的大众验证。&/p&&p&&br&&/p&&h2&Fiber的连体婴结构&/h2&&p&&br&&/p&&p&连体婴是一个可怕的名词,想想就不舒服,因为事实上Fiber就是一个不寻常的结构,直到现在我的anujs还没有很好实现这结构。Fiber有一个叫alternate的属性,你们称之为备胎,替死鬼,替身演员。你也可以视它为git的开发分支,稳定没错的那个则是master。每次 setState时,组件实例stateNode上有一个_reactInternalFiber的对象,就是master分支,然后立即复制一个一模一样的专门用来踩雷的alternate对象。&/p&&p&alternate对象会接受上方传递下来的新props,然后从getDerivedStateFromProps得到新state,于是render不一样的子组件,子组件再render,渐渐的,master与alternate的差异越来越大,当某一个子组件出错,于是我们又回滚到该边界组件的master分支。&/p&&p&可以说,React16通过Fiber这种数据结构模拟了git的三种重要操作, git add, git commit, git revert。&/p&&p&有关连体婴结构的思考,可以参看我另一篇文章&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&《从错误边界到回滚到MWI》&/a&,这里就不再展开。&/p&&p&&br&&/p&&h2&中间件系统&/h2&&p&&br&&/p&&p&说起中间件系统,大家可能对koa与redux里面的洋葱模型比较熟悉。&/p&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-97f0a2fadcafccec58b0_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&478& data-rawheight=&435& class=&origin_image zh-lightbox-thumb& width=&478& data-original=&https://pic4.zhimg.com/v2-97f0a2fadcafccec58b0_r.jpg&&&/figure&&p&&br&&/p&&p&早在React15时代,已经有一个叫Transaction的东西,与洋葱模型一模一样。在 Transaction 的源码中有一幅特别的 ASCII 图,形象的解释了 Transaction 的作用。&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-2a02bf591fbf291f481709ac_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&655& data-rawheight=&551& class=&origin_image zh-lightbox-thumb& width=&655& data-original=&https://pic2.zhimg.com/v2-2a02bf591fbf291f481709ac_r.jpg&&&/figure&&p&&br&&/p&&p&简单地说,一个Transaction 就是将需要执行的 method 使用 wrapper 封装起来,再通过 Transaction 提供的 perform 方法执行。而在 perform 之前,先执行所有 wrapper 中的 initialize 方法;perform 完成之后(即 method 执行后)再执行所有的 close 方法。一组 initialize 及 close 方法称为一个 wrapper,从上面的示例图中可以看出 Transaction 支持多个 wrapper 叠加。&/p&&p&这个东西有什么用呢? 最少有两个用处,在更新DOM时,收集当前获取焦点的元素与选区,更新结束后,还原焦点与选区(因为插入新节点会引起焦点丢失,document.activeElement变成body,或者是autoFocus,让焦点变成其他input,导致我们正在输入的input的光标不见了,无法正常输入)。在更新时,我们需要保存一些非受控组件,在更新后,对非受控组件进行还原(非受控组件是一个隐涩的知识点,目的是让那些没有设置onChange的表单元素无法手动改变它的值)。当然了,contextStack, containerStack的初次入栈与清空也可以做成中间件。中间件就是分布在batchedUpdates的两侧,一种非常易于扩展的设计,为什么不多用用呢!&/p&&p&&br&&/p&&h2&总结&/h2&&p&&br&&/p&&p&React Fiber是对React来说是一次革命,解决了React项目严重依赖于手工优化的痛点,通过系统级别的时间调度,实现划时代的性能优化。鬼才般的Fiber结构,为异常边界提供了退路,也为限量更新提供了下一个起点。React团队的人才济济,创造力非凡,别出心裁,从更高的层次处理问题,这是其他开源团队不可多见。这也是我一直选择与学习React的原因所在。&/p&&p&但是和所有人一样,我最初学习React16的源码是非常痛苦的。后来观看他们团队的视频,深刻理解时间分片与Fiber的链表结构后,渐渐明确整个思路,不需要对React源码进行断点调试,也能将大体流程复制出来。俗话说,看不如写(就是写anujs,欢迎大家加star, &a href=&https://link.zhihu.com/?target=https%3A//github.com/RubyLouvre/anu& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&github.com/RubyLouvre/a&/span&&span class=&invisible&&nu&/span&&span class=&ellipsis&&&/span&&/a&),与不如再复述出教会别人。于是便有了本文。&/p&&p&&br&&/p&&p&******************&/p&&p&&a href=&https://link.zhihu.com/?target=https%3A//github.com/RubyLouvre/anu/blob/master/BC.jpg& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&自觉打赏:钦成&/a& &/p&&p&******************&/p&
长文预警:原《》的完整版 性能优化是一个系统性的工程,如果只看到局部,引入算法,当然是越快越好; 但从整体来看,在关键点引入缓存,可以秒杀N多算法,或另辟蹊径,探索事件的本质,可能用户要的并不是快…… React16启…
&figure&&img src=&https://pic3.zhimg.com/v2-68a3a6b23964a7ffb2f1c96072c30ddf_b.jpg& data-rawwidth=&2832& data-rawheight=&1238& class=&origin_image zh-lightbox-thumb& width=&2832& data-original=&https://pic3.zhimg.com/v2-68a3a6b23964a7ffb2f1c96072c30ddf_r.jpg&&&/figure&&h2&1 引言&/h2&&p&精读原文是 typescript 2.0-2.9 的文档:&/p&&p&&a href=&http://link.zhihu.com/?target=http%3A//www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&2.0-2.8&/a&,&a href=&http://link.zhihu.com/?target=https%3A//blogs.msdn.microsoft.com/typescript//announcing-typescript-2-9-rc/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&2.9 草案&/a&.&/p&&p&我发现,许多写了一年以上 Typescript 开发者,对 Typescript 对理解和使用水平都停留在入门阶段。造成这个现象的原因是,Typescript 知识的积累需要 &b&刻意练习&/b&,使用 Typescript 的时间与对它的了解程度几乎没有关系。&/p&&p&这篇文章精选了 TS 在 &code&2.0-2.9&/code& 版本中最重要的功能,并配合实际案例解读,帮助你快速跟上 TS 的更新节奏。&/p&&p&对于 TS 内部优化的用户无感部分并不会罗列出来,因为这些优化都可在日常使用过程中感受到。&/p&&h2&2 精读&/h2&&p&由于 Typescript 在严格模式下的许多表现都与非严格模式不同,为了避免不必要的记忆,建议只记严格模式就好了!&/p&&h2&严格模式导致的大量边界检测代码,已经有解了&/h2&&p&直接访问一个变量的属性时,如果这个变量是 &code&undefined&/code&,不但属性访问不到,js 还会抛出异常,这几乎是业务开发中最高频的报错了(往往是后端数据异常导致的),而 typescript 的 &code&strict&/code& 模式会检查这种情况,不允许不安全的代码出现。&/p&&p&在 &code&2.0&/code& 版本,提供了 “非空断言标志符” &code&!.&/code& 解决明确不会报错的情况,比如配置文件是静态的,那肯定不会抛出异常,但在 &code&2.0&/code& 之前的版本,我们可能要这么调用对象:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&const config = {
port: 8000
if (config) {
console.log(config.port);
&/code&&/pre&&/div&&p&有了 &code&2.0&/code& 提供的 “非空断言标志符”,我们可以这么写了:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&console.log(config!.port);
&/code&&/pre&&/div&&p&在 &code&2.8&/code& 版本,ts 支持了条件类型语法:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&type TypeName&T& = T extends string ? &string&
&/code&&/pre&&/div&&p&当 T 的类型是 string 时,TypeName 的表达式类型为 &string&。&/p&&p&这这时可以构造一个自动 “非空断言” 的类型,把代码简化为:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&console.log(config.port);
&/code&&/pre&&/div&&p&前提是框架先把 &code&config&/code& 指定为这个特殊类型,这个特殊类型的定义如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&export type PowerPartial&T& = {
[U in keyof T]?: T[U] extends object ? PowerPartial&T[U]& : T[U]
&/code&&/pre&&/div&&p&也就是 &code&2.8&/code& 的条件类型允许我们在类型判断进行递归,把所有对象的 key 都包一层 “非空断言”!&/p&&blockquote&此处灵感来自 &a href=&http://link.zhihu.com/?target=https%3A//github.com/whxaxes/blog/issues/12& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&egg-ts 总结&/a&&/blockquote&&h2&增加了 &code&never&/code& &code&object&/code& 类型&/h2&&p&当一个函数无法执行完,或者理解为中途中断时,TS &code&2.0&/code& 认为它是 &code&never&/code& 类型。&/p&&p&比如 &code&throw Error&/code& 或者 &code&while(true)&/code& 都会导致函数返回值类型时 &code&never&/code&。&/p&&p&和 &code&null&/code& &code&undefined&/code& 特性一样,&code&never&/code& 等于是函数返回值中的 &code&null&/code& 或 &code&undefined&/code&。&b&它们都是子类型&/b&,比如类型 &code&number&/code& 自带了 &code&null&/code& 与 &code&undefined&/code& 这两个子类型,是因为任何有类型的值都有可能是空(也就是执行期间可能没有值)。&/p&&p&这里涉及到很重要的概念,就是预定义了类型不代表类型一定如预期,就好比函数运行时可能因为 &code&throw Error&/code& 而中断。所以 ts 为了处理这种情况,&b&将&/b& &code&&b&null&/b&&/code& &code&&b&undefined&/b&&/code& &b&设定为了所有类型的子类型&/b&,而从 &code&2.0&/code& 开始,函数的返回值类型又多了一种子类型 &code&never&/code&。&/p&&p&TS &code&2.2&/code& 支持了 &code&object&/code& 类型, 但许多时候我们总把 &code&object&/code& 与 &code&any&/code& 类型弄混淆,比如下面的代码:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&const persion: object = {
console.log(persion.age); // Error: Property 'age' does not exist on type 'object'.
&/code&&/pre&&/div&&p&这时候报错会出现,有时候闭个眼改成 &code&any&/code& 就完事了。其实这时候只要把 &code&object&/code& 删掉,换成 TS 的自动推导就搞定了。那么问题出在哪里?&/p&&p&首先 &code&object&/code& 不是这么用的,它是 TS &code&2.3&/code& 版本中加入的,用来描述一种非基础类型,所以一般用在类型校验上,比如作为参数类型。如果参数类型是 &code&object&/code&,那么允许任何对象数据传入,但不允许 &code&3&/code& &code&&abc&&/code& 这种非对象类型:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&declare function create(o: object | null):
create({ prop: 0 }); // 正确
create(null); // 正确
create(42); // 错误
create(&string&); // 错误
create(false); // 错误
create(undefined); // 错误
&/code&&/pre&&/div&&p&而一开始 &code&const persion: object&/code& 这种用法,是将能精确推导的对象类型,扩大到了整体的,模糊的对象类型,TS 自然无法推断这个对象拥有哪些 &code&key&/code&,因为对象类型仅表示它是一个对象类型,在将对象作为整体观察时是成立的,但是 &code&object&/code& 类型是不承认任何具体的 &code&key&/code& 的。&/p&&h2&增加了修饰类型&/h2&&p&TS 在 &code&2.0&/code& 版本支持了 &code&readonly&/code& 修饰符,被它修饰的变量无法被修改。&/p&&p&在 TS &code&2.8&/code& 版本,又增加了 &code&-&/code& 与 &code&+&/code& 修饰修饰符,有点像副词作用于形容词。举个例子,&code&readonly&/code& 就是 &code&+readonly&/code&,我们也可以使用 &code&-readonly&/code& 移除只读的特性;也可以通过 &code&-?:&/code& 的方式移除可选类型,因此可以延伸出一种新类型:&code&Required&T&&/code&,将对象所有可选修饰移除,自然就成为了必选类型:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&type Required&T& = { [P in keyof T]-?: T[P] };
&/code&&/pre&&/div&&h2&可以定义函数的 this 类型&/h2&&p&也是 TS &code&2.0&/code& 版本中,我们可以定制 &code&this&/code& 的类型,这个在 &code&vue&/code& 框架中尤为有用:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&function f(this: void) {
// make sure `this` is unusable in this standalone function
&/code&&/pre&&/div&&p&&code&this&/code& 类型是一种假参数,所以并不会影响函数真正参数数量与位置,只不过它定义在参数位置上,而且永远会插队在第一个。&/p&&h2&引用、寻址支持通配符了&/h2&&p&简单来说,就是模块名可以用 &code&*&/code& 表示任何单词了:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&declare module &*!text& {
const content:
&/code&&/pre&&/div&&p&它的类型可以辐射到:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&import fileContent from &./xyz.txt!text&;
&/code&&/pre&&/div&&p&这个特性很强大的一个点是用在拓展模块上,因为包括 &code&tsconfig.json&/code& 的模块查找也支持通配符了!举个例子一下就懂:&/p&&p&最近比较火的 &code&umi&/code& 框架,它有一个 &code&locale&/code& 插件,只要安装了这个插件,就可以从 &code&umi/locale&/code& 获取国际化内容:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&import { locale } from &umi/locale&;
&/code&&/pre&&/div&&p&其实它的实现是创建了一个文件,通过 &code&webpack.alias&/code& 将引用指了过去。这个做法非常棒,那么如何为它加上类型支持呢?只要这么配置 &code&tsconfig.json&/code&:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&{
&compilerOptions&: {
&paths&: {
&umi/*&: [&umi&, &&somePath&&]
&/code&&/pre&&/div&&p&将所有 &code&umi/*&/code& 的类型都指向 &code&&somePath&&/code&,那么 &code&umi/locale&/code& 就会指向 &code&&somePath&/locale.ts&/code& 这个文件,如果插件自动创建的文件名也恰好叫 &code&locale.ts&/code&,那么类型就自动对应上了。&/p&&h2&跳过仓库类型报错&/h2&&p&TS 在 &code&2.x&/code& 支持了许多新 &code&compileOptions&/code&,但 &code&skipLibCheck&/code& 实在是太耀眼了,笔者必须单独提出来说。&/p&&p&&code&skipLibCheck&/code& 这个属性不但可以忽略 npm 不规范带来的报错,还能最大限度的支持类型系统,可谓一举两得。&/p&&p&拿某 UI 库举例,某天发布的小版本 &code&d.ts&/code& 文件出现一个漏洞,导致整个项目构建失败,你不再需要提 PR 催促作者修复了!&code&skipLibCheck&/code& 可以忽略这种报错,同时还能保持类型的自动推导,也就是说这比 &code&declare module &ui-lib&&/code& 将类型设置为 &code&any&/code& 更强大。&/p&&h2&对类型修饰的增强&/h2&&p&TS &code&2.1&/code& 版本可谓是针对类型操作革命性的版本,我们可以通过 &code&keyof&/code& 拿到对象 key 的类型:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&interface Person {
type K1 = keyof P // &name& | &age&
&/code&&/pre&&/div&&p&基于 &code&keyof&/code&,我们可以增强对象的类型:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&type NewObjType&T& = { [P in keyof T]: T[P] };
&/code&&/pre&&/div&&p&Tips:在 TS &code&2.8&/code& 版本,我们可以以表达式作为 &code&keyof&/code& 的参数,比如 &code&keyof (A & B)&/code&。 Tips:在 TS &code&2.9&/code& 版本,&code&keyof&/code& 可能返回非 &code&string&/code& 类型的值,因此从一开始就不要认为 &code&keyof&/code& 的返回类型一定是 &code&string&/code&。&/p&&p&&code&NewObjType&/code& 原封不动的将对象类型重新描述了一遍,这看上去没什么意义。但实际上我们有三处拓展的地方:&/p&&ul&&li&左边:比如可以通过 &code&readonly&/code& 修饰,将对象的属性变成只读。&/li&&li&中间:比如将 &code&:&/code& 改成 &code&?:&/code&,将对象所有属性变成可选。&/li&&li&右边:比如套一层 &code&Promise&T[P]&&/code&,将对象每个 &code&key&/code& 的 &code&value&/code& 类型覆盖。&/li&&/ul&&p&基于这些能力,我们拓展出一系列上层很有用的 &code&interface&/code&:&/p&&ul&&li&Readonly。把对象 key 全部设置为只读,或者利用 &code&2.8&/code& 的条件类型语法,实现递归设置只读。&/li&&li&Partial。把对象的 key 都设置为可选。&/li&&li&Pick&T, K&。从对象类型 T 挑选一些属性 K,比如对象拥有 10 个 key,只需要将 K 设置为 &code&&name& | &age&&/code& 就可以生成仅支持这两个 key 的新对象类型。&/li&&li&Extract&T, U&。是 Pick 的底层 API,直到 &code&2.8&/code& 版本才内置进来,可以认为 Pick 是挑选对象的某些 key,Extract 是挑选 key 中的 key。&/li&&li&Record&K, U&。将对象某些属性转换成另一个类型。比较常见用在回调场景,回调函数返回的类型会覆盖对象每一个 key 的类型,此时类型系统需要 &code&Record&/code& 接口才能完成推导。&/li&&li&Exclude&T, U&。将 T 中的 U 类型排除,和 Extract 功能相反。&/li&&li&Omit&T, K&(未内置)。从对象 T 中排除 key 是 K 的属性。可以利用内置类型方便推导出来:&code&type Omit&T, K& = Pick&T, Exclude&keyof T, K&&&/code&&/li&&li&NonNullable。排除 &code&T&/code& 的 &code&null&/code& 与 &code&undefined&/code& 的可能性。&/li&&li&ReturnType。获取函数 &code&T&/code& 返回值的类型,这个类型意义很大。&/li&&li&InstanceType。获取一个构造函数类型的实例类型。&/li&&/ul&&blockquote&以上类型都内置在 lib.d.ts 中,不需要定义就可直接使用,可以认为是 Typescript 的 utils 工具库。&/blockquote&&p&单独拿 &code&ReturnType&/code& 举个例子,体现出其重要性:&/p&&p&Redux 的 Connect 第一个参数是 &code&mapStateToProps&/code&,这些 Props 会自动与 React Props 聚合,我们可以利用 &code&ReturnType&typeof currentMapStateToProps&&/code& 拿到当前 Connect 注入给 Props 的类型,就可以打通 Connect 与 React 组件的类型系统了。&/p&&h2&对 Generators 和 async/await 的类型定义&/h2&&p&TS &code&2.3&/code& 版本做了许多对 Generators 的增强,但实际上我们早已用 async/await 替代了它,所以 TS 对 Generators 的增强可以忽略。需要注意的一块是对 &code&for..of&/code& 语法的异步迭代支持:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&async function f() {
for await (const x of fn1()) {
console.log(x);
&/code&&/pre&&/div&&p&这可以对每一步进行异步迭代。注意对比下面的写法:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&async function f() {
for (const x of await fn2()) {
console.log(x);
&/code&&/pre&&/div&&p&对于 &code&fn1&/code&,它的返回值是可迭代的对象,并且每个 item 类型都是 Promise 或者 Generator。对于 &code&fn2&/code&,它自身是个异步函数,返回值是可迭代的,而且每个 item 都不是异步的。举个例子:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&function fn1() {
return [Promise.resolve(1), Promise.resolve(2)];
function fn2() {
return [1, 2];
&/code&&/pre&&/div&&p&在这里顺带一提,对 &code&Array.map&/code& 的每一项进行异步等待的方法:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&await Promise.all(
arr.map(async item =& {
return await item.run();
&/code&&/pre&&/div&&p&如果为了执行顺序,可以换成 &code&for..of&/code& 的语法,因为数组类型是一种可迭代类型。&/p&&h2&泛型默认参数&/h2&&p&了解这个之前,先介绍一下 TS &code&2.0&/code& 之前就支持的函数类型重载。&/p&&p&首先 JS 是不支持方法重载的,Java 是支持的,而 TS 类型系统一定程度在对标 Java,当然要支持这个功能。好在 JS 有一些偏方实现伪方法重载,典型的是 redux 的 &code&createStore&/code&:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&export default function createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === &function& && typeof enhancer === &undefined&) {
enhancer = preloadedS
preloadedState =
&/code&&/pre&&/div&&p&既然 JS 有办法支持方法重载,那 TS 补充了函数类型重载,两者结合就等于 Java 方法重载:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&declare function createStore(
reducer: Reducer,
preloadedState: PreloadedState,
enhancer: Enhancer
declare function createStore(reducer: Reducer, enhancer: Enhancer);
&/code&&/pre&&/div&&p&可以清晰的看到,&code&createStore&/code& 想表现的是对参数个数的重载,如果定义了函数类型重载,TS 会根据函数类型自动判断对应的是哪个定义。&/p&&p&而在 TS &code&2.3&/code& 版本支持了泛型默认参数,&b&可以某些场景减少函数类型重载的代码量&/b&,比如对于下面的代码:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&declare function create(): Container&HTMLDivElement, HTMLDivElement[]&;
declare function create&T extends HTMLElement&(element: T): Container&T, T[]&;
declare function create&T extends HTMLElement, U extends HTMLElement&(
element: T,
children: U[]
): Container&T, U[]&;
&/code&&/pre&&/div&&p&通过枚举表达了范型默认值,以及 U 与 T 之间可能存在的关系,这些都可以用泛型默认参数解决:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&declare function create&T extends HTMLElement = HTMLDivElement, U = T[]&(
element?: T,
children?: U
): Container&T, U&;
&/code&&/pre&&/div&&p&尤其在 React 使用过程中,如果用泛型默认值定义了 &code&Component&/code&:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&.. Component&Props = {}, State = {}& ..
&/code&&/pre&&/div&&p&就可以实现以下等价的效果:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&class Component extends React.PureComponent&any, any& {
class Component extends React.PureComponent {
&/code&&/pre&&/div&&h2&动态 Import&/h2&&p&TS 从 &code&2.4&/code& 版本开始支持了动态 Import,同时 Webpack4.0 也支持了这个语法(在 &a href=&http://link.zhihu.com/?target=https%3A//github.com/dt-fe/weekly/blob/master/47.%25E7%25B2%25BE%25E8%25AF%25BB%25E3%Awebpack4.0%%258D%%25BA%25A7%25E6%258C%%258D%%B.md& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&精读《webpack4.0%20 升级指南》&/a& 有详细介绍),这个语法就正式可以用于生产环境了:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&const zipUtil = await import(&./utils/create-zip-file&);
&/code&&/pre&&/div&&blockquote&准确的说,动态 Import 实现于 webpack 2.1.0-beta.28,最终在 TS &code&2.4&/code& 版本获得了语法支持。&/blockquote&&p&在 TS &code&2.9&/code& 版本开始,支持了 &code&import()&/code& 类型定义:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&const zipUtil: typeof import('./utils/create-zip-file') = await import('./utils/create-zip-file')
&/code&&/pre&&/div&&p&也就是 &code&typeof&/code& 可以作用于 &code&import()&/code& 语法,而不真正引入 js 内容。不过要注意的是,这个 &code&import('./utils/create-zip-file')&/code& 路径需要可被推导,比如要存在这个 npm 模块、相对路径、或者在 &code&tsconfig.json&/code& 定义了 &code&paths&/code&。&/p&&p&好在 &code&import&/code& 语法本身限制了路径必须是字面量,使得自动推导的成功率非常高,只要是正确的代码几乎一定可以推导出来。好吧,所以这也从另一个角度推荐大家放弃 &code&require&/code&。&/p&&h2&Enum 类型支持字符串&/h2&&p&从 Typescript &code&2.4&/code& 开始,支持了枚举类型使用字符串做为 value:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&enum Colors {
Red = &RED&,
Green = &GREEN&,
Blue = &BLUE&
&/code&&/pre&&/div&&p&笔者在这提醒一句,这个功能在纯前端代码内可能没有用。因为在 TS 中所有 &code&enum&/code& 的地方都建议使用 &code&enum&/code& 接收,下面给出例子:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// 正确
type: monaco.languages.types.F
&/code&&/pre&&/div&&p&不仅是可读性,&code&enum&/code& 对应的数字可能会改变,直接写 &code&75&/code& 的做法存在风险。&/p&&p&但如果前后端存在交互,前端是不可能发送 &code&enum&/code& 对象的,必须要转化成数字,这时使用字符串作为 value 会更安全:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&enum types {
Folder = &FOLDER&
fetch(`/api?type=${monaco.languages.types.Folder}`);
&/code&&/pre&&/div&&h2&数组类型可以明确长度&/h2&&p&最典型的是 chart 图,经常是这样的二维数组数据类型:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&[[1, 5.5], [2, 3.7]

我要回帖

更多关于 什么又称信息化的生产工具 的文章

 

随机推荐