Omi架构与React Fiber

原文连接-https://github.com/AlloyTeam/omi/tree/master/tutorialhtml

写在前面

Omi框架在架构设计的时候就决定把update的控制权交给了开发者,视灵活性比生命还重要。否则的话,若是遇到React Fiber要解决的这类问题的话,就须要推翻原有架构从新搞了。node

React Fiber

先引用下咱们团队小鲜肉Stark伟-复旦大四 / 腾讯@AlloyTeam知乎上的回答react

React 的核心思想是每次对于界面 state 的改动,都会从新渲染整个 virtual dom,而后新老的两个 virtual dom 树进行 diff,对比出变化的地方,而后经过 renderer 渲染到实际的UI界面(这里多是浏览器的DOM,也多是native组件)。这样实质上就是把界面变成一个纯粹的状态机,React 的做用就是把这个状态机之间的状态转换高效率地运行出来。可是存在如下问题:git

  • 一、不是每一次状态的变化都要马上执行。
  • 二、不一样的状态变化之间是有轻重缓急之分的,好比『动画』这种状态变化的优先级,出于对用户体验的考量,为了不动画卡顿或者掉帧,通常比『改变页面数据』的优先级更高。
  • 三、咱们如今的作法只是调用 setState 触发从新渲染,而后 React 会收集一个 tick 内的 state 变化,而后执行,因此有可能大量的计算会在同一时刻阻塞进程。但咱们无法控制 React 运算的时序问题,也不太可能经过手工声明让动画的优先级比数据变动更高。而 React 做为一个用户交互的框架,它本应该能让程序员能控制这些东西。因此这个破事要怎么解决咧?( ⊙ o ⊙ )咱们知道,任何的函数调用都会有本身的调用栈,好比对于 v = f(d) 这个函数来讲,函数 f 可能又调用了一系列其它的函数,这些函数就包括在 f 的调用栈中。关键的问题在于,这种原生的调用栈是基本不可延迟的,它会当即执行,若是计算量很大的话就会阻塞住进程,让界面失去响应,这种事情常常发生在 React 的渲染过程当中。

或者看颜什么都不记得适的回答程序员

状态转移时,是在一次 tick 中递归遍历组件树,找出须要更新的节点 rerender。可是这样形成了一些问题:github

  • 在 UI 中,不是全部的状态转移都须要当即执行。大量的同时计算可能会致使资源的浪费,以至出现掉帧的情况,下降用户体验。
  • 不一样类型的状态转移应有不一样的优先级,好比点击按钮出现动画的优先级应该比 Fetch API 要高。
  • React 是 pull-based 实现的,事务的时序所有由 React 决定。咱们没办法操控执行事务的时序。

Omi component update

Omi有上面的问题吗? 没有。web

Omi的卖点之一即是:更自由的更新,每一个组件都有update方法,自由选择你认为最佳的时机进行更新。这样设计的一大好处是更加灵活,若是想要自动更新集成个mobx或者obajs即可,进可功退可守护。
数据和视图虽然是关系密切,可是解耦的设计仍是很是必要,这样能够应付更多的场景。好处:浏览器

  • 你能够等某个动画播放完成再进行update
  • 你能够控制update顺序
  • 你能够update先后干一些事情而不须要利用生命周期的钩子函数(有的时候钩子函数让连续的逻辑打得粉碎...)

component update说完了吗?没有... Omi不只仅有component update!还有更增强大的 updateSelf。架构

Omi component updateSelf

先说下二者的区别:框架

  • update: 更新组件树
  • updateSelf: 更新组件(不包含任何子节点)

以下图所示:

标红的表明会进行更新的节点。

场景模拟

class TestComponent extends Omi.Component {
    render () {
        return `<div>
                    <h3>{{title}}</h3>
                    <List  name="list" data="listData" />
                </div>`;
    }
}

组件结构上面代码所示:

  • 若是调用组件实例的update的话,会更新组件自己以及 List组件
  • 若是调用组件实例的updateSelft的话,会更新组件自己,不会更新List组件

好比咱们仅仅修改了this.data.title,就能够调用this.updateSelf方法,虽然通常状况下无脑update也能达到一样的结果,虽然morphdom的DOM diff已经足够轻量快速,可是必定没有updateSelf方法快速。上面的例子updateSelf优点可能不明显,若是这样呢:

class TestComponent extends Omi.Component {
    render () {
        return `<div>
                    <h3>{{title}}</h3>
                    <List  name="list" data="listData" />
                    <List  name="list" data="listData" />
                    <Content  name="list" data="listData" />
                    <Slider  name="list" data="listData" />
                </div>`;
    }
}

再或者Content、Slider里面再嵌套了子组件,子组件又嵌套了子组件,若是仅仅只是须要修改title的话,updateSelf优点就尽显无疑。

实现细节

这里主要说一说updateSelf的实现细节。主要包含两点:

  • 不从新render的状况下拿到子组件的完整的HTML
  • 关闭子组件的DOM diff

进行updateSelf的时候,就算子组件的data发生了变化,也不去改变子组件。由于updateSelf就意思就是更新自身。
因此子组件的HTML不须要使用模板和data生成,只须要component.node.outerHTML就能够了。outerHTML在古老的firefox是不支持的,能够经过建立节点插入而后读innerHTML进行polyfill。

组件自己的HTML是须要使用模板和data生成,子组件就使用刚刚的outerHTML替换即可。可是问题来了,子组件的DOM diff实际上是没有必要的,虽然morphdom的DOM diff已经足够轻量快速。可是子组件他们原本就是如出一辙,没有必要的开销。因此须要关闭DOM diff~~。而后morphdom没有ignore相关的配置....

扩展 morphdom

API:

morphdom(node, newNodeHTML, {
                        ignoreAttr: ['attr1','attr2']
                    } )

好比上面表明只要标记了attr1或者attr2的就是忽略,固然为了规避错误,这里须要严格的匹配才会ignore DOM diff。怎么算严格的匹配?就是:

  • 当一样的attr的DOM,而且该attr在ignoreAttr里才会ignore DOM diff

Omi Store体系addSelfView

Omi Store体系之前经过addView进行视图收集,store进行update的时候会调用组件的update。

与此同时,Omi Store体系也新增了addSelfView的API。

  • addView 收集该组件视图,store进行update的时候会调用组件的update
  • addSelfView 收集该组件自己的视图,store进行update的时候会调用组件的updateSelf

固然,store内部会对视图进行合并,好比addView里面加进去的全部视图有父子关系的,会把子组件去掉。爷孙关系的会把孙组件去掉。addSelfView收集的组件在addView里已经收集的也去进行合并去重,等等一系列合并优化。

Omi相关

相关文章
相关标签/搜索