【美高梅开户网址】创设视图框架毫不相关的数据层,老树发新芽

听别人说 MobX 创设视图框架非亲非故的数据层-与 Vue 的构成

2018/07/09 · JavaScript
· mobx

原稿出处:
kuitos   

mobx-vue 近期已进入 mobxjs
官方组织,欢迎试用求 star!

几周前作者写了一篇文章描述了 mobx 与 angularjs 结合使用的不二法门及目标(老树发新芽—使用 mobx 加快你的 AngularJS
应用),本次介绍一下如何将
MobX 跟 Vue 结合起来。

老树发新芽—使用 mobx 加快你的 AngularJS 应用

2018/05/23 · JavaScript
· AngularJS,
mobx

原稿出处:
kuitos   

11月首的时候,Angular 官方博客公布了一则音讯:

AngularJS is planning one more significant release, version 1.7,
and on July 1, 2018 it will enter a 3 year Long Term Support period.

即在 十六月四日 AngularJS 揭橥 1.7.0 版本之后,AngularJS 将进入贰个期限 3
年的 LTS 时代。也便是说 二零一八年11月十五日 起至 2021年1月1三十日,AngularJS
不再统一其余会造成 breaking changes 的 features 或
bugfix,只做须要的题材修复。详细消息见这里:Stable AngularJS and Long
Term
Support

看到那则音信时自身大概感动颇多的,作为本人的前端启蒙框架,笔者从 AngularJS
上搜查缉获到了那些多的滋养。固然 AngularJS 作为一款不错的前端 MVW
框架已经可以的完毕了投机的历史义务,但考虑到正是到了 2018
年,许多供销社依照 AngularJS 的类型依然处于服役阶段,结合笔者过去一年多在
mobx 上的商讨和实践,小编决定给 AngularJS
强行再续一波命。(搭车求治推延症良方,三月尾起草的文章二月份才写完,消息都要过期了)

浅析vue是怎么着促成数量变动革新视图的.

正文主就算指导大家解析$mount。

安装

npm i mobx-vue -S

1
npm i mobx-vue -S

预备干活

在起来从前,大家要求给 AngularJS 搭配上一些现代化 webapp
开发套件,以便后边能更方便地装载上 mobx 引擎。

前记

$mount所做的办事从大体上来讲至关心器重要分为3步:

使用

mobx-vue 的利用格外简单,只供给利用 connect 将您用 mobx 定义的 store 跟
vue component 连接起来即可:

<template> <section> <p v-text=”amount”></p>
<p v-for=”user in users” :key=”user.name”>{{user.name}}</p>
</section> </template> <script lang=”ts”> import {
Connect } from “mobx-vue”; import Vue from “vue”; import Component from
“vue-class-component”; class ViewModel { @observable users = [];
@computed get amount() { return this.users.length } <a
href=’;
fetchUsers() {} } @Connect(new ViewModel()) @Component() export default
class App extends Vue { mounted() { this.fetchUsers(); } }
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
    <section>
        <p v-text="amount"></p>
        <p v-for="user in users" :key="user.name">{{user.name}}</p>
    </section>
</template>
 
<script lang="ts">
    import { Connect } from "mobx-vue";
    import Vue from "vue";
    import Component from "vue-class-component";
    class ViewModel {
        @observable users = [];
        @computed get amount() { return this.users.length }
        <a href=’http://www.jobbole.com/members/Francesco246437′>@action</a> fetchUsers() {}
    }
 
    @Connect(new ViewModel())
    @Component()
    export default class App extends Vue {
        mounted() {
            this.fetchUsers();
        }
    }
</script>

AngularJS 配合 ES6/next

后天是二〇一八年,使用 ES6 开发使用已经成为事实标准(有恐怕的引荐直接上 TS
)。如何将 AngularJS 搭载上 ES6
那里不再赘言,能够看笔者前边的那篇小说:Angular1.x + ES6
开发风格指南

三个月前看了vue源码来分析哪些做到响应式数据的,
小说名字叫vue源码之响应式数据, 最终分析到,
数据变动后会调用沃特cher的update()方法.
那么时隔四月让大家继承看看update()做了什么样.
(那八个月用react-native做了个档次, 也无意总计了, 因为接近太容易了).

1.比方你的option里面没有 render 函数,那么,通过 compileToFunctions
将HTML模板编写翻译成能够生成VNode的Render函数。

Why MobX/mobx-vue

大家驾驭,mobx 跟 vue 都以依据 数据威迫&注重收集
的主意来贯彻响应式机制的。mobx 官方也反复提到
inspired by vue,那么大家为啥还要将七个大概相同的事物组成起来吧?

Yes, it’s weird.

2014年本人在营造公司级组件库的时候开始考虑3个难题,大家什么样在代码库基于某一框架的状态下,能以尽恐怕小的代价在今后将零件库迁移到别的框架/库
下?总无法根据新的技能全体重写一回呢,那也太浪费生命了。且不说对于基础控件而言,交互/行为
逻辑基本上是可明确的,最多也正是 UI
上的部分调动,而且只是为了尝试新技巧开支公司人力物力将基础库推导重写也是老大不工作的做法。那么我们只可以承受被框架绑架而只可以沦为某一技术栈从此泥潭深陷吗?对于前端那种框架半衰期越发短的小圈子而言肯定是不可接受的,结果唯有正是要么本人跑路坑后来人,要么招不到人来一起填坑…
简单的话大家鞭长莫及享用新技巧带来的各个红利。

在 MVVM 架构视角下,越是重型的选拔其复杂度越是集中在 M(Model) 跟
VM(ViewModel) 那两层,尤其是 Model
层,理论上应该是能脱离上层视图独立运行独立宣布独立测试的留存。而相应的不比视图框架只是使用了分化绑定语法的动态模板引擎而已,这些视角作者在前头的几篇小说里都讲述过。所以一旦我们将视图层做的很薄,我们迁移的开支自然会降到贰个可接受的局面,甚至有大概因此工具在编写翻译期自动生成分裂框架的视图层代码。

要完结 Model 甚至 ViewModel
独立可复用,大家要求的是一种能够援助大家讲述各数据模型间正视关系图且框架中立的通用状态管理方案。那里面作者尝试过
ES6 accessor、redux、rxjs 等方案,但都壮志未酬。accessor
过于底层且异步不友好、redux
开发体验太差(参考Redux数据流管理架构有何样致命缺陷,将来会怎么着革新?)、rxjs
过重等等。直到后来看看 MobX:MobX 语法丰硕简单、弱主张(unopinioned)、oop
向、框架中立等特征恰恰吻合本人的须要。

在过去的一年多里,笔者分别在 react、angularjs、angular 上尝试过基于 MobX
营造 VM/M
层,在这之中有多个上线项目,三个私家项目,实践意义基本上也实现了自家的预料。在架设上,大家只要求接纳相应的
connector,就能遵照相同数据层,在不相同框架下自如的切换。那样看来,那套思路今后就剩
Vue 没有被证实了。

在 mobx-vue 在此之前,社区早就有一对完好无损的 connector 完结,如
movue
vue-modex 等,但中央都以根据 vue
的插件机制且 inspired by
vue-rx【美高梅开户网址】创设视图框架毫不相关的数据层,老树发新芽。,除了选用起来相对繁琐的题材外,最大的标题是其落到实处焦点都是凭借
Vue.util.defineReactive 来做的,也便是说依然依据 Vue
自有的响应式机制,那在一定水平不仅浪费了 MobX 的reactive
能力,而且会为搬迁到此外视图框架下埋下了不分明的种子(毕竟你不能保险是
Vue 照旧 MobX 在响应状态变化)。

参考:why mobx-vue

不错图景下相应是由 mobx 管理数据的重视性关系,vue 针对 mobx 的响应做出
re render 动作即可,vue 只是二个仅仅的动态模板渲染引擎,就像 react
一样。

在这么的三个背景下,mobx-vue
诞生了。

根据组件的使用架构

AngularJS 在 1.5.0 版本后新增了一多重动人心魄的特点,如 onw-way
bindings、component lifecycle hooks、component definition
等,基于那个特征,大家得以便宜的将 AngularJS
系统构建成一个纯组件化的使用(尽管你对那几个特色很纯熟可直接跳过至 AngularJS
搭配
mobx)。大家三个个来看:

  • onw-way bindings 单向绑定
    AngularJS
    中使用 来定义组件的单向数据绑定,例如我们这样定义一个组件:
angular .module('app.components', \[\]) .directive('component', ()
=&gt; ({ restrict: 'E', template: '&lt;p&gt;count:
{{$ctrl.count}}&lt;/p&gt;&lt;button ng-click="$ctrl.count =
$ctrl.count + 1"&gt;increase&lt;/button&gt;' scope: { count: '&lt;'
}, bindToController: true, controllerAs: '$ctrl', })

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6aab02f2c952921585-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f2c952921585-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f2c952921585-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f2c952921585-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f2c952921585-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f2c952921585-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f2c952921585-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f2c952921585-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f2c952921585-9">
9
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f2c952921585-10">
10
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f2c952921585-11">
11
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6aab02f2c952921585-1" class="crayon-line">
angular
</div>
<div id="crayon-5b8f6aab02f2c952921585-2" class="crayon-line crayon-striped-line">
    .module('app.components', [])
</div>
<div id="crayon-5b8f6aab02f2c952921585-3" class="crayon-line">
    .directive('component', () =&gt; ({
</div>
<div id="crayon-5b8f6aab02f2c952921585-4" class="crayon-line crayon-striped-line">
        restrict: 'E',
</div>
<div id="crayon-5b8f6aab02f2c952921585-5" class="crayon-line">
        template: '&lt;p&gt;count: {{$ctrl.count}}&lt;/p&gt;&lt;button ng-click=&quot;$ctrl.count = $ctrl.count + 1&quot;&gt;increase&lt;/button&gt;'
</div>
<div id="crayon-5b8f6aab02f2c952921585-6" class="crayon-line crayon-striped-line">
        scope: {
</div>
<div id="crayon-5b8f6aab02f2c952921585-7" class="crayon-line">
            count: '&lt;'
</div>
<div id="crayon-5b8f6aab02f2c952921585-8" class="crayon-line crayon-striped-line">
        },
</div>
<div id="crayon-5b8f6aab02f2c952921585-9" class="crayon-line">
        bindToController: true,
</div>
<div id="crayon-5b8f6aab02f2c952921585-10" class="crayon-line crayon-striped-line">
        controllerAs: '$ctrl',
</div>
<div id="crayon-5b8f6aab02f2c952921585-11" class="crayon-line">
    })
</div>
</div></td>
</tr>
</tbody>
</table>


使用时:



{{app.count}} component count="app.count"&gt;component&gt;

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6aab02f35150522417-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f35150522417-2">
2
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6aab02f35150522417-1" class="crayon-line">
{{app.count}}
</div>
<div id="crayon-5b8f6aab02f35150522417-2" class="crayon-line crayon-striped-line">
component count=&quot;app.count&quot;&gt;component&gt;
</div>
</div></td>
</tr>
</tbody>
</table>

当我们点击组件的 increase 按钮时,可以看到组件内的 count 加 1
了,但是 `app.count`并不受影响。

区别于 AngularJS
赖以成名的双向绑定特性 `scope: { count: '='}`,单向数据绑定能更有效的隔离操作影响域,从而更方便的对数据变化溯源,降低
debug 难度。  
双向绑定与单向绑定有各自的优势与劣势,这里不再讨论,有兴趣的可以看我这篇回答:[单向数据绑定和双向数据绑定的优缺点,适合什么场景?](https://www.zhihu.com/question/49964363/answer/136022879)
  • component lifecycle hooks 组件生命周期钩子1.5.3
    开首新增了多少个零件的生命周期钩子(目标是为更便于的向 Angular2+
    迁移),分别是 $onInit $onChanges $onDestroy $postLink $doCheck(1.5.3日增),写起来差不多长这么:
class Controller { $onInit() { // initialization }
$onChanges(changesObj) { const { user } = changesObj; if(user &&
!user.isFirstChange()) { // changing } } $onDestroy() {} $postLink()
{} $doCheck() {} } angular .module('app.components', \[\])
.directive('component', () =&gt; ({ controller: Controller, ... }))

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6aab02f3a441625873-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3a441625873-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f3a441625873-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3a441625873-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f3a441625873-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3a441625873-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f3a441625873-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3a441625873-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f3a441625873-9">
9
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3a441625873-10">
10
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f3a441625873-11">
11
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3a441625873-12">
12
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f3a441625873-13">
13
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3a441625873-14">
14
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f3a441625873-15">
15
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3a441625873-16">
16
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f3a441625873-17">
17
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3a441625873-18">
18
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f3a441625873-19">
19
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3a441625873-20">
20
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f3a441625873-21">
21
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3a441625873-22">
22
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f3a441625873-23">
23
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3a441625873-24">
24
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f3a441625873-25">
25
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3a441625873-26">
26
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6aab02f3a441625873-1" class="crayon-line">
class Controller {
</div>
<div id="crayon-5b8f6aab02f3a441625873-2" class="crayon-line crayon-striped-line">
    
</div>
<div id="crayon-5b8f6aab02f3a441625873-3" class="crayon-line">
    $onInit() {
</div>
<div id="crayon-5b8f6aab02f3a441625873-4" class="crayon-line crayon-striped-line">
        // initialization
</div>
<div id="crayon-5b8f6aab02f3a441625873-5" class="crayon-line">
    }
</div>
<div id="crayon-5b8f6aab02f3a441625873-6" class="crayon-line crayon-striped-line">
    
</div>
<div id="crayon-5b8f6aab02f3a441625873-7" class="crayon-line">
    $onChanges(changesObj) {
</div>
<div id="crayon-5b8f6aab02f3a441625873-8" class="crayon-line crayon-striped-line">
        const { user } = changesObj;
</div>
<div id="crayon-5b8f6aab02f3a441625873-9" class="crayon-line">
        if(user &amp;&amp; !user.isFirstChange()) {
</div>
<div id="crayon-5b8f6aab02f3a441625873-10" class="crayon-line crayon-striped-line">
            // changing
</div>
<div id="crayon-5b8f6aab02f3a441625873-11" class="crayon-line">
        }
</div>
<div id="crayon-5b8f6aab02f3a441625873-12" class="crayon-line crayon-striped-line">
    }
</div>
<div id="crayon-5b8f6aab02f3a441625873-13" class="crayon-line">
    
</div>
<div id="crayon-5b8f6aab02f3a441625873-14" class="crayon-line crayon-striped-line">
    $onDestroy() {}
</div>
<div id="crayon-5b8f6aab02f3a441625873-15" class="crayon-line">
    
</div>
<div id="crayon-5b8f6aab02f3a441625873-16" class="crayon-line crayon-striped-line">
    $postLink() {}
</div>
<div id="crayon-5b8f6aab02f3a441625873-17" class="crayon-line">
    
</div>
<div id="crayon-5b8f6aab02f3a441625873-18" class="crayon-line crayon-striped-line">
    $doCheck() {}   
</div>
<div id="crayon-5b8f6aab02f3a441625873-19" class="crayon-line">
}
</div>
<div id="crayon-5b8f6aab02f3a441625873-20" class="crayon-line crayon-striped-line">
 
</div>
<div id="crayon-5b8f6aab02f3a441625873-21" class="crayon-line">
angular
</div>
<div id="crayon-5b8f6aab02f3a441625873-22" class="crayon-line crayon-striped-line">
    .module('app.components', [])
</div>
<div id="crayon-5b8f6aab02f3a441625873-23" class="crayon-line">
    .directive('component', () =&gt; ({
</div>
<div id="crayon-5b8f6aab02f3a441625873-24" class="crayon-line crayon-striped-line">
     controller: Controller,
</div>
<div id="crayon-5b8f6aab02f3a441625873-25" class="crayon-line">
     ...
</div>
<div id="crayon-5b8f6aab02f3a441625873-26" class="crayon-line crayon-striped-line">
 }))
</div>
</div></td>
</tr>
</tbody>
</table>

事实上在 1.5.3
之前,我们也能借助一些机制来模拟组件的生命周期(如 `$scope.$watch`、`$scope.$on('$destroy')`等),但基本上都需要借助`$scope`这座‘‘桥梁’’。但现在我们有了框架原生
lifecycle 的加持,这对于我们构建更纯粹的、框架无关的 ViewModel
来讲有很大帮助。更多关于 lifecycle 的信息可以看官方文档:[AngularJS
lifecycle
hooks](https://code.angularjs.org/1.6.7/docs/api/ng/service/%24compile#life-cycle-hooks)
  • component definitionAngularJS 1.5.0
    后扩张了 component 语法用于更方便人民群众清楚的定义三个零部件,如上述例子中的组件大家得以用component语法改写成:
JavaScript

angular .module('app.components', \[\]) .component('component', {
template: '&lt;p&gt;count: {{$ctrl.count}}&lt;/p&gt;&lt;button
ng-click="$ctrl.onUpdate({count: $ctrl.count +
1})"&gt;increase&lt;/button&gt;' bindings: { count: '&lt;',
onUpdate: '&' }, })

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6aab02f3e495620996-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3e495620996-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f3e495620996-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3e495620996-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f3e495620996-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3e495620996-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f3e495620996-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f3e495620996-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f3e495620996-9">
9
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6aab02f3e495620996-1" class="crayon-line">
angular
</div>
<div id="crayon-5b8f6aab02f3e495620996-2" class="crayon-line crayon-striped-line">
    .module('app.components', [])
</div>
<div id="crayon-5b8f6aab02f3e495620996-3" class="crayon-line">
    .component('component', {
</div>
<div id="crayon-5b8f6aab02f3e495620996-4" class="crayon-line crayon-striped-line">
        template: '&lt;p&gt;count: {{$ctrl.count}}&lt;/p&gt;&lt;button ng-click=&quot;$ctrl.onUpdate({count: $ctrl.count + 1})&quot;&gt;increase&lt;/button&gt;'
</div>
<div id="crayon-5b8f6aab02f3e495620996-5" class="crayon-line">
        bindings: {
</div>
<div id="crayon-5b8f6aab02f3e495620996-6" class="crayon-line crayon-striped-line">
            count: '&lt;',
</div>
<div id="crayon-5b8f6aab02f3e495620996-7" class="crayon-line">
     onUpdate: '&amp;'
</div>
<div id="crayon-5b8f6aab02f3e495620996-8" class="crayon-line crayon-striped-line">
        },
</div>
<div id="crayon-5b8f6aab02f3e495620996-9" class="crayon-line">
    })
</div>
</div></td>
</tr>
</tbody>
</table>

本质上`component`就是`directive`的语法糖,bindings
是 `bindToController + controllerAs + scope` 的语法糖,只不过`component`语法更简单语义更明了,定义组件变得更方便,与社区流行的风格也更一致(熟悉
vue 的同学应该已经发现了😆)。更多关于 AngularJS 组件化开发的 best
practice,可以看官方的开发者文档:[Understanding
Components](https://code.angularjs.org/1.6.7/docs/guide/component)

正文叙事方式为树藤摸瓜, 顺着看源码的逻辑走三回, 查看的vue的本子为2.5.2.
自家fork了一份源码用来记录注释.

2.new 一个 Watcher 实例,触发 updateComponent 方法。

mobx-vue 是怎么样运行的

既是大家的指标是将 vue 变成3个不过的模版渲染引擎(vdom),并且使用 mobx
响应式机制取代 vue 的响应式,那么只要大家威胁到 Vue
的零部件装载及立异方法,然后在组件装载的时候收集依赖,在依靠发生变更时更新组件即可。

以下内容与其叫做 mobx-vue 是怎么着运营的,不如叫 Vue 源码解析:

咱俩知道 Vue 日常是那般开首化的:

new Vue({ el: ‘#app’, render: h => h(App)});

1
new Vue({ el: ‘#app’, render: h => h(App)});

那正是说找到 Vue 的构造函数,

function Vue (options) { …… this._init(options) }

1
2
3
4
function Vue (options) {
  ……
  this._init(options)
}

跟进到_init办法,除了一多级组件初步化行为外,最重假使终极一有的的
$mount 逻辑:

if (vm.$options.el) { vm.$mount(vm.$options.el) }

1
2
3
if (vm.$options.el) {
  vm.$mount(vm.$options.el)
}

跟进 $mount 方法,以 web runtime 为例:

if (process.env.NODE_ENV !== ‘production’ && config.performance &&
mark) { updateComponent = () => { … } } else { updateComponent = ()
=> { vm._update(vm._render(), hydrating) } } vm._watcher = new
Watcher(vm, updateComponent, noop)

1
2
3
4
5
6
7
8
9
10
11
if (process.env.NODE_ENV !== ‘production’ && config.performance && mark) {
    updateComponent = () => {
        …
    }
} else {
    updateComponent = () => {
        vm._update(vm._render(), hydrating)
    }
}
 
vm._watcher = new Watcher(vm, updateComponent, noop)

从此处可以见见,updateComponent 方法将是组件更新的重庆大学入口,跟进
Watcher 构造函数,看 Vue 怎么调用到这几个方法的:

constructor ( vm: Component, expOrFn: string | Function, cb: Function,
options?: Object ) { … this.expression = process.env.NODE_ENV !==
‘production’ ? expOrFn.toString() : ” // parse expression for getter if
(typeof expOrFn === ‘function’) { this.getter = expOrFn } else {
this.getter = parsePath(expOrFn) … } this.value = this.lazy ?
undefined : this.get()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ) {
    …
    this.expression = process.env.NODE_ENV !== ‘production’
      ? expOrFn.toString()
      : ”
    // parse expression for getter
    if (typeof expOrFn === ‘function’) {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      …
    }
    this.value = this.lazy
      ? undefined
      : this.get()

get () { … try { value = this.getter.call(vm, vm) } catch (e) { … }

1
2
3
4
5
6
7
get () {
    …
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      …
  }

观看那里,我们能觉察,组件 装载/更新 的发起者是:
value = this.getter.call(vm, vm) ,而笔者辈只要通过 vm._watcher.getter
的章程就能收获相应的措施引用, 即
updateComponent := vm._watcher.getter。所以大家假设在 $mount 前将
MobX 管理下的数额植入组件上下文供组件直接动用,在$mount 时让 MobX
收集相应的依靠,在 MobX 检查和测试到依靠更新时调用 updateComponent
即可。那样的话既能让 MobX 的响应式机制通过一种简易的不二法门 hack 进 Vue
种类,同时也能担保组件的原生行为不面临震慑(生命周期钩子等)。

核心绪想就是用 MobX 的响应式机制接管 Vue 的 沃特cher,将 Vue
降级成三个纯粹的装载 vdom 的零件渲染引擎。

主题达成很粗大略:

const { $mount } = Component.prototype; Component.prototype.$mount =
function (this: any, …args: any[]) { let mounted = false; const
reactiveRender = () => { reaction.track(() => { if (!mounted) {
$mount.apply(this, args); mounted = true; } else {
this._watcher.getter.call(this, this); } }); return this; }; const
reaction = new Reaction(`${name}.render()`, reactiveRender); dispose =
reaction.getDisposer(); return reactiveRender(); };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const { $mount } = Component.prototype;
 
Component.prototype.$mount = function (this: any, …args: any[]) {
    let mounted = false;
    const reactiveRender = () => {
        reaction.track(() => {
            if (!mounted) {
                $mount.apply(this, args);
                mounted = true;
            } else {
                this._watcher.getter.call(this, this);
            }
        });
 
        return this;
    };
    const reaction = new Reaction(`${name}.render()`, reactiveRender);
    dispose = reaction.getDisposer();
    return reactiveRender();
};

全体代码在此地:

AngularJS 搭配 mobx

预备干活做了一堆,大家也该起来进入本文的大旨,即什么给 AngularJS 搭载上
mobx 引擎(本文假诺你对 mobx
中的基础概念已经有早晚程度的刺探,假若不打听可以先活动 mobx
repo mobx official
doc):

目的

3.生成vnode,经过patch,把vnode更新到dom上。
由于篇幅有限,那里先说前两步,第1步下篇说。
好,下边具体的说。首先,大家过来 $mount 函数,如下图:

最后

尤大大此前说过:mobx + react 是更繁琐的 Vue,本质上来看真便是这么的,mobx

  • react 组合提供的能力恰好是 Vue 与生俱来的。而 mobx-vue
    做的业务则正好相反:将 Vue 降级成 react 然后再合营 MobX 升级成 Vue
    。那着实很蹊跷。但自笔者想说的是,大家的初衷并不是说 Vue
    的响应式机制落到实处的不佳从而要用 MobX 替换掉,而是期待借助 MobX
    这一个相对中立的景况管理平台,面向分裂视图层技术提供一种相对通用的数据层编制程序范式,从而尽量抹平不一致框架间的语法及技术栈差别,以便为开发者提供越多的视图技术的决策权及恐怕,而不至于被某一框架绑架裹挟。

PS: 这篇是连串小说的第二篇,前边将有越来越多关于
如何基于 MobX 构建视图框架无关的数据层
的架构范式及实施的内容,敬请期待!

1 赞 1 收藏
评论

美高梅开户网址 1

1. mobx-angularjs

引入 mobx-angularjs 库连接
mobx 和 angularjs 。

npm i mobx-angularjs -S

1
npm i mobx-angularjs -S

显然检察方向才能直至目的, 先说一下对象作为:
数据变动之后推行了哪些方法来更新视图的.
那么准备开首以这一个方向为指标从vue源码的进口开首找答案.

美高梅开户网址 2 

2. 定义 ViewModel

在行业内部的 MVVM 框架结构里,ViewModel/Controller
除了创设视图自个儿的景况数据(即局地景况)外,作为视图跟工作模型之间联络的大桥,其主要职务是将业务模型适配(转换/组装)成对视图更友好的数据模型。因而,在
mobx 视角下,ViewModel 主要由以下几有的构成:

  • 视图(局部)状态对应的 observable data
class ViewModel { @observable isLoading = true; @observable
isModelOpened = false; }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6aab02f45684167140-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f45684167140-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f45684167140-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f45684167140-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f45684167140-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f45684167140-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f45684167140-7">
7
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6aab02f45684167140-1" class="crayon-line">
class ViewModel {
</div>
<div id="crayon-5b8f6aab02f45684167140-2" class="crayon-line crayon-striped-line">
    @observable
</div>
<div id="crayon-5b8f6aab02f45684167140-3" class="crayon-line">
    isLoading = true;
</div>
<div id="crayon-5b8f6aab02f45684167140-4" class="crayon-line crayon-striped-line">
 
</div>
<div id="crayon-5b8f6aab02f45684167140-5" class="crayon-line">
 @observable
</div>
<div id="crayon-5b8f6aab02f45684167140-6" class="crayon-line crayon-striped-line">
 isModelOpened = false;
</div>
<div id="crayon-5b8f6aab02f45684167140-7" class="crayon-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>

可观察数据(对应的 observer 为
view),即视图需要对其变化自动做出响应的数据。在 mobx-angularjs
库的协助下,通常 observable data 的变化会使关联的视图自动触发
rerender(或触发网络请求之类的副作用)。ViewModel 中的 observable data
通常是视图状态(UI-State),如 isLoading、isOpened 等。

  • 由 应用/视图 状态衍生的 computed data

    Computed values are values that can be derived from the existing
    state or other computed values.

class ViewModel { @computed get userName() { return
\`${this.user.firstName} ${this.user.lastName}\`; } }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6aab02f49065322680-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f49065322680-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f49065322680-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f49065322680-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f49065322680-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f49065322680-6">
6
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6aab02f49065322680-1" class="crayon-line">
class ViewModel {
</div>
<div id="crayon-5b8f6aab02f49065322680-2" class="crayon-line crayon-striped-line">
    @computed
</div>
<div id="crayon-5b8f6aab02f49065322680-3" class="crayon-line">
    get userName() {
</div>
<div id="crayon-5b8f6aab02f49065322680-4" class="crayon-line crayon-striped-line">
        return `${this.user.firstName} ${this.user.lastName}`;
</div>
<div id="crayon-5b8f6aab02f49065322680-5" class="crayon-line">
    }
</div>
<div id="crayon-5b8f6aab02f49065322680-6" class="crayon-line crayon-striped-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>

计算数据指的是由其他 observable/computed data
转换而来,更方便视图直接使用的衍生数据(derived
data)。 **在重业务轻交互的 web 类应用中(通常是各种企业服务软件),
computed data 在 ViewModel 中应该占主要部分,且基本是由业务 store
中的数据(即应用状态)转换而来。** computed
这种数据推导关系描述能确保我们的应用遵循 single source of truth
原则,不会出现数据不一致的情况,这也是 RP 编程中的基本原则之一。
  • action
    ViewModel 中的 action
    除了一小部分改变视图状态的一言一动外,超过四分之二应当是一向调用 Model/Store
    中的 action 来完结工作情状的流浪。建议把具有对 observable data
    的操作都放到被 aciton 装饰的法子下举行。

mobx 同盟下,贰个针锋相对完好的 ViewModel 大约长这么:

import UserStore from ‘./UserStore’; class ViewModel {
@inject(UserStore) store; @observable isDropdownOpened = false;
@computed get userName() { return `${this.store.firstName}
${this.store.lastName}`; } <a
href=”;
toggel() { this.isDropdownOpened = !isDropdownOpened; }
updateFirstName(firstName) { this.store.updateFirstName(firstName); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import UserStore from ‘./UserStore’;
  
class ViewModel {
      
    @inject(UserStore)
    store;
    @observable
    isDropdownOpened = false;
 
@computed
get userName() {
     return `${this.store.firstName} ${this.store.lastName}`;
}
  
<a href="http://www.jobbole.com/members/Francesco246437">@action</a>
toggel() {
     this.isDropdownOpened = !isDropdownOpened;
}
      
    updateFirstName(firstName) {
        this.store.updateFirstName(firstName);
    }
}

从之前的下结论开始

我们啊能够观察,代码首先判断option里面有没有render函数,没有的话,进一步认清有没有template,没有的话就用dom成分的outerHTML。得到template未来怎么了呢?如下图。

3. 连接 AngularJS 和 mobx

<section mobx-autorun> <counter
value=”$ctrl.count”></counter> <button type=”button”
ng-click=”$ctrl.increse()”>increse</button> </section>

1
2
3
4
<section mobx-autorun>
<counter value="$ctrl.count"></counter>
    <button type="button" ng-click="$ctrl.increse()">increse</button>
</section>

import template from ‘./index.tpl.html’; class ViewModel { @observable
count = 0; <a
href=”;
increse() { this.count++; } } export default angular .module(‘app’,
[]) .component(‘container’, { template, controller: Controller, })
.component(‘counter’, { template:
‘<section><header>{{$ctrl.count}}</header></section>’
bindings: { value: ‘<‘ } }) .name;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import template from ‘./index.tpl.html’;
class ViewModel {
    @observable count = 0;
<a href="http://www.jobbole.com/members/Francesco246437">@action</a> increse() {
     this.count++;
}
}
 
export default angular
    .module(‘app’, [])
    .component(‘container’, {
     template,
     controller: Controller,
})
    .component(‘counter’, {
     template: ‘<section><header>{{$ctrl.count}}</header></section>’
     bindings: { value: ‘<‘ }
})
.name;

能够见见,除了正常的依照 mobx 的 ViewModel
定义外,我们只要求在模板的根节点加上 mobx-autorun 指令,大家的
angularjs 组件就能很好的运作的 mobx 的响应式引擎下,从而自动的对
observable state 的变迁执行 rerender。

先来复习一下事先的定论:

美高梅开户网址 3  

mobx-angularjs 加快应用的魔法

从上文的言传身教代码中大家能够观望,将 mobx 跟 angularjs
衔接运营起来的是 mobx-autorun一声令下,大家翻下 mobx-angularjs 代码:

const link: angular.IDirectiveLinkFn = ($scope) => { const {
$$watchers = [] } = $scope as any const debouncedDigest =
debounce($scope.$digest.bind($scope), 0); const dispose = reaction( ()
=> […$$watchers].map(watcher => watcher.get($scope)), () =>
美高梅开户网址,!$scope.$root.$$phase && debouncedDigest() ) $scope.$on(‘$destroy’,
dispose) }

1
2
3
4
5
6
7
8
9
10
11
12
const link: angular.IDirectiveLinkFn = ($scope) => {
 
  const { $$watchers = [] } = $scope as any
  const debouncedDigest = debounce($scope.$digest.bind($scope), 0);
 
  const dispose = reaction(
    () => […$$watchers].map(watcher => watcher.get($scope)),
    () => !$scope.$root.$$phase && debouncedDigest()
  )
 
  $scope.$on(‘$destroy’, dispose)
}

能够看来 基本代码 其实就三行:

reaction( () => […$$watchers].map(watcher =>
watcher.get($scope)), () => !$scope.$root.$$phase &&
debouncedDigest()

1
2
3
reaction(
    () => […$$watchers].map(watcher => watcher.get($scope)),
    () => !$scope.$root.$$phase && debouncedDigest()

思路极度简单,即在指令 link 之后,遍历2次当前 scope 上挂载的 watchers
并取值,由于这几个动作是在 mobx reaction 执行上下文中展开的,由此 watcher
里注重的具备 observable 都会被采集起来,那样当下次当中任何三个observable 爆发转移时,都会触发 reaction 的副作用对 scope 举办digest,从而完结自动更新视图的指标。

咱俩知道,angularjs 的习性被广为诟病并不是因为 ‘脏检查’ 本身慢,而是因为
angularjs 在历次异步事件发生时都以无脑的从根节点起首向下
digest,从而会造成一些不须要的 loop 造成的。而当我们在搭载上 mobx 的
push-based 的 change propagation
机制时,只有当被视图真正使用的数量发生变化时,相关联的视图才会触发局地digest (能够驾驭为唯有 observable data 存在 subscriber/observer
时,状态变化才会触发关联正视的重算,从而防止不必要财富消耗,即所谓的
lazy)
,差异于异步事件触发即无脑地 $rootScope.$apply,
那种措施分明更敏捷。

vue构造的时候会在data(和部分其他字段)上建立Observer对象,
getter和setter被做了阻碍, getter触发注重收集, setter触发notify.

咱俩能够看来,调用了 compileToFunctions
将template转成render函数。这里面有五个经过:

进而压榨质量

作者们驾驭 angularjs 是透过威吓各样异步事件然后从根节点做 apply
的,那就导致只要我们用到了会被 angularjs 威逼的表征就会触发
apply,别的的比如 $http $timeout 都好说,大家有广大代表方案,可是 ng-click 那类事件监听指令大家不能够制止,就如上文例子中相同,借使大家能杜绝潜藏的根节点
apply,想必使用的属性升高能更为。

思路很粗大略,我们只要把 ng-click 之流替换来不触发 apply
的本子即可。比如把原来的 ng event
实现如此这般改一下:

forEach( ‘click dblclick mousedown mouseup mouseover mouseout mousemove
mouseenter mouseleave keydown keyup keypress submit focus blur copy cut
paste’.split(‘ ‘), function(eventName) { var directiveName =
directiveNormalize(‘native-‘ + eventName);
ngEventDirectives[directiveName] = [‘$parse’, ‘$rootScope’,
function($parse, $rootScope) { return { restrict: ‘A’, compile:
function($element, attr) { var fn = $parse(attr[directiveName], /*
interceptorFn */ null, /* expensiveChecks */ true); return function
ngEventHandler(scope, element) { element.on(eventName, function(event) {
fn(scope, {$event:event}) }); }; } }; }]; } );

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
forEach(
  ‘click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste’.split(‘ ‘),
  function(eventName) {
    var directiveName = directiveNormalize(‘native-‘ + eventName);
    ngEventDirectives[directiveName] = [‘$parse’, ‘$rootScope’, function($parse, $rootScope) {
      return {
        restrict: ‘A’,
        compile: function($element, attr) {
          var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
          return function ngEventHandler(scope, element) {
            element.on(eventName, function(event) {
              fn(scope, {$event:event})
            });
          };
        }
      };
    }];
  }
);

时间监听的回调中只是不难触发一下绑定的函数即可,不再 apply,bingo!

另三个目标是Watcher, 注册watch的时候会调用一遍watch的对象,
那样触发了watch对象的getter, 把信赖收集到近年来Watcher的deps里,
当任何dep的setter被触发就会notify当前沃特cher来调用沃特cher的update()方法.

  • 将template解析成ast语法树。
  • 因而ast语法树生成render函数。

注意事项/ best practise

在 mobx 同盟 angularjs 开发进程中,有一些点大家或者会 遇到/须要考虑:

  • 避免 TTL
    单向数据流优点很多,大多数景色下大家会先行使用 one-way binding
    格局定义组件。平时你会写出如此的代码:
class ViewModel { @computed get unCompeletedTodos() { return
this.store.todos.filter(todo =&gt; !todo.compeleted) } }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6aab02f63385526810-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f63385526810-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f63385526810-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f63385526810-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f63385526810-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f63385526810-6">
6
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6aab02f63385526810-1" class="crayon-line">
class ViewModel {
</div>
<div id="crayon-5b8f6aab02f63385526810-2" class="crayon-line crayon-striped-line">
    @computed
</div>
<div id="crayon-5b8f6aab02f63385526810-3" class="crayon-line">
    get unCompeletedTodos() {
</div>
<div id="crayon-5b8f6aab02f63385526810-4" class="crayon-line crayon-striped-line">
        return this.store.todos.filter(todo =&gt; !todo.compeleted)
</div>
<div id="crayon-5b8f6aab02f63385526810-5" class="crayon-line">
    }
</div>
<div id="crayon-5b8f6aab02f63385526810-6" class="crayon-line crayon-striped-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>



&lt;section mobx-autorun&gt; &lt;todo-panel
todos="$ctrl.unCompeletedTodos"&gt;&lt;/todo-panel&gt;
&lt;/section&gt;

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6aab02f67732098935-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f67732098935-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f67732098935-3">
3
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6aab02f67732098935-1" class="crayon-line">
&lt;section mobx-autorun&gt;
</div>
<div id="crayon-5b8f6aab02f67732098935-2" class="crayon-line crayon-striped-line">
    &lt;todo-panel todos=&quot;$ctrl.unCompeletedTodos&quot;&gt;&lt;/todo-panel&gt;
</div>
<div id="crayon-5b8f6aab02f67732098935-3" class="crayon-line">
&lt;/section&gt;
</div>
</div></td>
</tr>
</tbody>
</table>

`todo-panel` 组件使用单向数据绑定定义:



angular .module('xxx', \[\]) .component('todoPanel', { template:
'&lt;ul&gt;&lt;li ng-repeat="todo in $ctrl.todos track by
todo.id"&gt;{{todo.content}}&lt;/li&gt;&lt;/ul&gt;' bindings: {
todos: '&lt;' } })

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6aab02f6a192201026-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f6a192201026-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f6a192201026-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f6a192201026-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f6a192201026-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f6a192201026-6">
6
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6aab02f6a192201026-1" class="crayon-line">
angular
</div>
<div id="crayon-5b8f6aab02f6a192201026-2" class="crayon-line crayon-striped-line">
    .module('xxx', [])
</div>
<div id="crayon-5b8f6aab02f6a192201026-3" class="crayon-line">
    .component('todoPanel', {
</div>
<div id="crayon-5b8f6aab02f6a192201026-4" class="crayon-line crayon-striped-line">
     template: '&lt;ul&gt;&lt;li ng-repeat=&quot;todo in $ctrl.todos track by todo.id&quot;&gt;{{todo.content}}&lt;/li&gt;&lt;/ul&gt;'
</div>
<div id="crayon-5b8f6aab02f6a192201026-5" class="crayon-line">
     bindings: { todos: '&lt;' }
</div>
<div id="crayon-5b8f6aab02f6a192201026-6" class="crayon-line crayon-striped-line">
 })
</div>
</div></td>
</tr>
</tbody>
</table>

看上去没有任何问题,但是当你把代码扔到浏览器里时就会收获一段
angularjs 馈赠的 TTL
错误:`Error: $rootScope:infdigInfinite $digest Loop`。实际上这并不是
mobx-angularjs 惹的祸,而是 angularjs 目前未实现 one-way binding 的
deep comparison
导致的,由于每次 `get unCompeletedTodos` 都会返回一个新的数组引用,而`又是基于引用作对比,从而每次 prev === current` 都是
false,最后自然报 TTL 错误了(具体可以看这里 [One-way bindings +
shallow
watching](https://github.com/angular/angular.js/issues/14039) )。

不过好在 mobx
优化手段中恰好有一个方法能间接的解决这个问题。我们只需要给 computed
加一个表示要做深度值对比的 modifier 即可:



@computed.struct get unCompeletedTodos() { return
this.store.todos.filter(todo =&gt; !todo.compeleted) }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6aab02f6e965400248-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f6e965400248-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f6e965400248-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f6e965400248-4">
4
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6aab02f6e965400248-1" class="crayon-line">
@computed.struct
</div>
<div id="crayon-5b8f6aab02f6e965400248-2" class="crayon-line crayon-striped-line">
get unCompeletedTodos() {
</div>
<div id="crayon-5b8f6aab02f6e965400248-3" class="crayon-line">
    return this.store.todos.filter(todo =&gt; !todo.compeleted)
</div>
<div id="crayon-5b8f6aab02f6e965400248-4" class="crayon-line crayon-striped-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>

本质上还是对 unCompeletedTodos 的
memorization,只不过对比基准从默认的值对比(===)变成了结构/深度
对比,因而在第一次 get unCompeletedTodos
之后,只要计算出来的结果跟前次的结构一致(只有当 computed data 依赖的
observable 发生变化的时候才会触发重算),后续的 getter
都会直接返回前面缓存的结果,从而不会触发额外的 diff,进而避免了 TTL
错误的出现。
  • $onInit 和 $onChanges 触发顺序的标题
    普通状态下大家期待在 ViewModel 中凭借组件的 lifecycle
    钩子做一些事情,比如在 $onInit 中触发副成效(互连网请求,事件绑定等),在 $onChanges 里监听传入数据变动做视图更新。
class ViewModel { $onInit() { this.store.fetchUsers(this.id); }
$onChanges(changesObj) { const { id } = changesObj; if(id &&
!id.isFirstChange()) { this.store.fetchUsers(id.currentValue) } } }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6aab02f71226980952-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f71226980952-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f71226980952-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f71226980952-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f71226980952-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f71226980952-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f71226980952-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f71226980952-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f71226980952-9">
9
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f71226980952-10">
10
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f71226980952-11">
11
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f71226980952-12">
12
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f71226980952-13">
13
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6aab02f71226980952-1" class="crayon-line">
class ViewModel {
</div>
<div id="crayon-5b8f6aab02f71226980952-2" class="crayon-line crayon-striped-line">
    
</div>
<div id="crayon-5b8f6aab02f71226980952-3" class="crayon-line">
    $onInit() {
</div>
<div id="crayon-5b8f6aab02f71226980952-4" class="crayon-line crayon-striped-line">
     this.store.fetchUsers(this.id);  
</div>
<div id="crayon-5b8f6aab02f71226980952-5" class="crayon-line">
    }
</div>
<div id="crayon-5b8f6aab02f71226980952-6" class="crayon-line crayon-striped-line">
    
</div>
<div id="crayon-5b8f6aab02f71226980952-7" class="crayon-line">
    $onChanges(changesObj) {
</div>
<div id="crayon-5b8f6aab02f71226980952-8" class="crayon-line crayon-striped-line">
        const { id } = changesObj;
</div>
<div id="crayon-5b8f6aab02f71226980952-9" class="crayon-line">
        if(id &amp;&amp; !id.isFirstChange()) {
</div>
<div id="crayon-5b8f6aab02f71226980952-10" class="crayon-line crayon-striped-line">
            this.store.fetchUsers(id.currentValue)
</div>
<div id="crayon-5b8f6aab02f71226980952-11" class="crayon-line">
        }
</div>
<div id="crayon-5b8f6aab02f71226980952-12" class="crayon-line crayon-striped-line">
    }
</div>
<div id="crayon-5b8f6aab02f71226980952-13" class="crayon-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>

可以发现其实我们在 `$onInit` 和 `$onChanges` 中做了重复的事情,而且这种写法也与我们要做视图框架无关的数据层的初衷不符,借助
mobx 的 observe 方法,我们可以将上面的代码改造成这种:



import { ViewModel, postConstruct } from 'mmlpx'; @ViewModel class
ViewModel { @observable id = null; @postConstruct onInit() {
observe(this, 'id', changedValue =&gt;
this.store.fetchUsers(changedValue)) } }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6aab02f74018689164-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f74018689164-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f74018689164-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f74018689164-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f74018689164-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f74018689164-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f74018689164-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f74018689164-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f74018689164-9">
9
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f74018689164-10">
10
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f74018689164-11">
11
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f74018689164-12">
12
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6aab02f74018689164-1" class="crayon-line">
import { ViewModel, postConstruct } from 'mmlpx';
</div>
<div id="crayon-5b8f6aab02f74018689164-2" class="crayon-line crayon-striped-line">
@ViewModel
</div>
<div id="crayon-5b8f6aab02f74018689164-3" class="crayon-line">
class ViewModel {
</div>
<div id="crayon-5b8f6aab02f74018689164-4" class="crayon-line crayon-striped-line">
    
</div>
<div id="crayon-5b8f6aab02f74018689164-5" class="crayon-line">
    @observable
</div>
<div id="crayon-5b8f6aab02f74018689164-6" class="crayon-line crayon-striped-line">
    id = null;
</div>
<div id="crayon-5b8f6aab02f74018689164-7" class="crayon-line">
    
</div>
<div id="crayon-5b8f6aab02f74018689164-8" class="crayon-line crayon-striped-line">
    @postConstruct
</div>
<div id="crayon-5b8f6aab02f74018689164-9" class="crayon-line">
    onInit() {
</div>
<div id="crayon-5b8f6aab02f74018689164-10" class="crayon-line crayon-striped-line">
        observe(this, 'id', changedValue =&gt; this.store.fetchUsers(changedValue))
</div>
<div id="crayon-5b8f6aab02f74018689164-11" class="crayon-line">
    }
</div>
<div id="crayon-5b8f6aab02f74018689164-12" class="crayon-line crayon-striped-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>

熟悉 angularjs 的同学应该能发现,事实上 observe
做的事情跟 `$scope.$watch` 是一样的,但是为了保证数据层的 UI
框架无关性,我们这里用 mobx 自己的观察机制来替代了 angularjs 的
watch。
  • 记不清您是在写
    AngularJS,把它正是3个粗略的动态模板引擎
    不论是大家品尝将
    AngularJS 应用 ES6/TS 化依旧引入 mobx
    状态管理库,实际上大家的初衷都以将大家的 Model 甚至 ViewModel
    层做成视图框架非亲非故,在借助 mobx 管理数据的里边的依靠关系的同时,通过
    connector 将 mobx observable data
    与视图连接起来,从而达成视图重视的意况产生变化自动触发视图的立异。在那么些进度中,angularjs
    不再扮演3个框架的剧中人物影响总体系统的框架结构,而唯有是用作2个动态模板引擎提供
    render 能力而已,后续咱们全然可以透过配套的 connector,将 mobx
    管理的数量层连接到分化的 view library 上。方今 mobx 官方针对
    React/Angular/AngularJS 均有相应的 connector,社区也有针对性 vue
    的化解方案,并不供给我们从零开端。在借助 mobx
    营造数据层之后,我们就能真的完结规范 MVVM 中描述的那么,在 Model
    甚至 VIewModel 不改一行代码的前提下轻松适配别的视图。view library
    的语法、机制差距不再成为视图层 升级/替换
    的界限,大家能透过改很微量的代码来填平它,毕竟只是替换一个动态模板引擎而已。

那就是说那里就从挂号渲染相关的沃特cher起始.

现实的将template解析成ast语法树在本文就隐瞒了,有时光独自开3个章节分析。好,那下我们获得render函数了,那么接下去一步干什么了呢?没错,就起首mountComponent 了。如下图:

Why MobX

React and MobX together are a powerful combination. React renders the
application state by providing mechanisms to translate it into a tree
of renderable components. MobX provides the mechanism to store and
update the application state that React then uses.

Both React and MobX provide optimal and unique solutions to common
problems in application development. React provides mechanisms to
optimally render UI by using a virtual DOM that reduces the number of
costly DOM mutations. MobX provides mechanisms to optimally
synchronize application state with your React components by using a
reactive virtual dependency state graph that is only updated when
strictly needed and is never stale.

MobX 官方的牵线,把下边一段介绍中的 React 换到自由其余(
Vue/Angular/AngularJS ) 视图框架/库(VDOM 部分适当调整一下)
也都适用。得益于 MobX
的概念不难及独立性,它相当适合作为视图中立的景观管理方案。简言之是视图层只做拿多少渲染的办事,状态流转由
MobX 帮您管理。

找到了文件在src/core/instance/lifecycle.js中.

美高梅开户网址 4  

Why Not Redux

Redux 很好,而且社区也有广大跟除 React
之外的视图层集成的推行。单纯的比较 Redux 跟 MobX
大约须要再写一篇小说来演讲,那里只简不难单说几点与视图层集成时的出入:

  1. 即便如此 Redux 本质也是三个观看者模型,但是在 Redux
    的兑现下,状态的生成并不是经过数据 diff
    得出而是 dispatch(action) 来手动通告的,而实在的 diff
    则交由了视图层,那不单造成恐怕的渲染浪费(并不是有着 library 都有
    vdom),在处理各个急需在扭转时触发副成效的风貌也会来得过分繁琐。
  2. 鉴于第②条 Redux 不做多少
    diff,因而咱们不能够在视图层接手数据前搜查缉获哪个局地被更新,进而不能够更便捷的选拔性更新视图。
  3. Redux 在 store 的布置性上是 opinionated
    的,它推广 单一 store 原则。应用能够完全由气象数据来叙述、且情形可管制可回溯
    那或多或少上本人一直不观点,但并不是只有单一 store这一条出路,多 store
    如故能达到规定的标准这一目的。显明 mobx 在那一点上是 unopinionated
    且灵活性更强。
  4. Redux
    概念太多而自笔者做的又太少。能够相比一下 ngRedux 跟 mobx-angularjs 看看完结复杂度上的出入。
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)

可以从上海体育场地看到,程序注解了三个 updateComponent 方法,那个是快要被
沃特cher 实例调用的更新组件的章程,过一会分析到 沃特cher
的时候将会看到。至于何以会有个判断语句来依据规则表明 updateComponent
方法,其实从 performance 能够看看,在那之中2个办法是用来测试 render 和
update 质量的。好我们终归该到 沃特cher 了,先看那句代码:

最后

除外给 AngularJS
搭载上更快速、精确的快速引擎之外,大家最要紧的目标只怕为了将
业务模型层甚至 视图模型层(统称为运用数据层) 做成 UI
框架毫不相关,那样在直面差别的视图层框架的搬迁时,才或者实现轻车熟路。而
mobx 在这一个工作上是一个很好的精选。

说到底想说的是,若是基准允许的话,如故提出将 angularjs 系统升级成
React/Vue/Angular
之一,究竟大多数时候基于新的视图技术开发应用是能推动真正的纯收入的,如
质量提高、开发成效提高 等。即使你长时间内不或许交替掉
angularjs(二种因素,比如曾经依照 angularjs 开发/使用
了一套完整的组件库,代码容量太大改造资金过高),你还是能够在一部分使用
mobx/mobx-angularjs 改造使用或开发新功用,在 mobx-angularjs
协理您升官利用质量的同时,也给你继续的升官安插创造了恐怕。

PS: mobx-angularjs 近来由自个儿和另3个US 小哥全力有限支撑,假如有别的利用上的难题,欢迎随时联系。

1 赞 收藏
评论

美高梅开户网址 5

mountComponent

// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */);

渲染相关的Watcher是在mountComponent()那一个方式中调用的,
那么大家搜一下这么些方法是在哪儿调用的. 唯有2处,
分别是src/platforms/web/runtime/index.js和src/platforms/weex/runtime/index.js,
以web为例:

我们先来分析一下注解里所说的 _watcher 是甚玩意儿呢?其实看看 forceupdate
的代码就理解了:

Vue.prototype.$mount = function (
 el?: string | Element,
 hydrating?: boolean
): Component {
 el = el && inBrowser ? query(el) : undefined
 return mountComponent(this, el, hydrating)
}
Vue.prototype.$forceUpdate = function () {
 var vm = this;
 if (vm._watcher) {
  vm._watcher.update();
 }
 };

原来是这样, 是$mount()方法调用了mountComponent(),
(大概在vue构造时钦赐el字段也会自行调用$mount()方法),
因为web和weex(什么是weex?在此之前其他小说介绍过)渲染的标的物分化,
所以在布告的时候应该引入了差异的公文最后发不成分化的dist(那个题材留给之后来研商vue的任何流程).

哪怕调用那些vm的 _watcher 的 update
方法。用来强制更新。为啥叫强制更新呢?vue里面有咬定,要是新值 ==
旧值, 那么就不触发watcher更新视图了~ 所以,借使非要更新就要调用
forceupdate 来强制更新了。好,让咱们来看一看传进去的参数吧:

下面是mountComponent方法:

  • vm:当前的vm实例
  • updateComponent 那一个那几个主要,用来在末端将vnode更新到dom上的。
  • noop 无意义的函数
  • null option选项,没有则为null
  • true
    首借使用来判定是哪些watcher的。因为computed计算属性和假诺你要在options里面配备watch了扳平也是利用了
    new 沃特cher ,加上这一个用以分裂那三者。好,大家来探望 new 沃特cher
    都做了怎么样事,如下图。
export function mountComponent (
 vm: Component,
 el: ?Element,
 hydrating?: boolean
): Component {
 vm.$el = el // 放一份el到自己的属性里
 if (!vm.$options.render) { // render应该经过处理了, 因为我们经常都是用template或者vue文件
 // 判断是否存在render函数, 如果没有就把render函数写成空VNode来避免红错, 并报出黄错
 vm.$options.render = createEmptyVNode
 if (process.env.NODE_ENV !== 'production') {
  /* istanbul ignore if */
  if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
  vm.$options.el || el) {
  warn(
   'You are using the runtime-only build of Vue where the template ' +
   'compiler is not available. Either pre-compile the templates into ' +
   'render functions, or use the compiler-included build.',
   vm
  )
  } else {
  warn(
   'Failed to mount component: template or render function not defined.',
   vm
  )
  }
 }
 }
 callHook(vm, 'beforeMount')

 let updateComponent
 /* istanbul ignore if */
 if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
 // 不看这里的代码了, 直接看else里的, 行为是一样的
 updateComponent = () => {
  const name = vm._name
  const id = vm._uid
  const startTag = `vue-perf-start:${id}`
  const endTag = `vue-perf-end:${id}`

  mark(startTag)
  const vnode = vm._render()
  mark(endTag)
  measure(`vue ${name} render`, startTag, endTag)

  mark(startTag)
  vm._update(vnode, hydrating)
  mark(endTag)
  measure(`vue ${name} patch`, startTag, endTag)
 }
 } else {
 updateComponent = () => {
  vm._update(vm._render(), hydrating)
 }
 }

 // we set this to vm._watcher inside the watcher's constructor
 // since the watcher's initial patch may call $forceUpdate (e.g. inside child
 // component's mounted hook), which relies on vm._watcher being already defined
 // 注册一个Watcher
 new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
 hydrating = false

 // manually mounted instance, call mounted on self
 // mounted is called for render-created child components in its inserted hook
 if (vm.$vnode == null) {
 vm._isMounted = true
 callHook(vm, 'mounted')
 }
 return vm
}

美高梅开户网址 6 

这段代码其实只做了3件事:

第叁,大家看出代码有个这一个论断

  • 调用beforeMount钩子
  • 建立Watcher
  • 调用mounted钩子
if (isRenderWatcher) {
 vm._watcher = this;
}

(哈哈哈)那么实际上主题正是树立沃特cher了.

能够见到,假若注脚这几个watcher的上下文是用来渲染视图的,也正是说是在
mountComponent 那里调用的 new 沃特cher
的时候,才会把this赋值给_watcher。然后把 watcher push到 _watchers
里面,目标是等到零部件销毁时顺便把watcher也销毁掉。然后正是起初化watcher的成员,代码如下:

看一下沃特cher的参数: vm是this, updateComponent是三个函数, noop是空,
null是空, true代表是Render沃特cher.

this.deep = this.user = this.lazy = this.sync = false;<br />

在Watcher里看了isRenderWatcher:

接下去,便是赋值给 getter , this.getter = expOrFn 。还记得刚才传过来的
updateComponent 函数么,没错,正是以此赋值给自个儿 getter 。然后我们就到了:

if (isRenderWatcher) {
  vm._watcher = this
 }
this.value = this.lazy
 ? undefined
 : this.get();

正确,
只是复制了一份用来在watcher第③次patch的时候判断一些事物(从注释里看的,
笔者今后还不掌握是干嘛的).

跻身到 get 方法里面,我们看看毕竟做了何等。get代码如下图:

那么只有叁个标题没化解便是updateComponent是个什么东西.

美高梅开户网址 7 

updateComponent

大家能够看出,首先它执行的是 pushTarget(this) , pushTarget(this)
代码如下:

在沃特cher的构造函数的第二个参数字传送了function,
那么那几个函数就成了watcher的getter. 聪明的您应该早就猜到,
在这一个updateComponent里肯定调用了视图中颇具的多少的getter,
才能在watcher中树立注重从而让视图响应数据的变化.

function pushTarget (_target) {
 if (Dep.target) { targetStack.push(Dep.target); }
 Dep.target = _target;
}
updateComponent = () => {
  vm._update(vm._render(), hydrating)
 }

也正是说倘若当前有 Dep.target 的话,就把target放到 targetStack
里面,若是没有的话,就设为当前的target,也便是以此watcher。
接着,就是履行了它的 getter 属性,也正是刚刚传入 updateComponent
函数。而 updateComponent 正是大家开篇提到第一步了。

那么就去找vm._update()和vm._render().

总结

在src/core/instance/render.js找到了._render()方法.

如上所述是笔者给咱们介绍的vue中的$mount,希望对我们有着支持,假使大家有别的疑问请给自家留言,作者会及时还原我们的。在此也非凡感谢大家对剧本之家网站的扶助!

Vue.prototype._render = function (): VNode {
 const vm: Component = this
 const { render, _parentVnode } = vm.$options // todo: render和_parentVnode的由来

 // reset _rendered flag on slots for duplicate slot check
 if (process.env.NODE_ENV !== 'production') {
  for (const key in vm.$slots) {
  // $flow-disable-line
  vm.$slots[key]._rendered = false
  }
 }

 if (_parentVnode) {
  vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject
 }

 // set parent vnode. this allows render functions to have access
 // to the data on the placeholder node.
 vm.$vnode = _parentVnode
 // render self
 let vnode
 try {
  vnode = render.call(vm._renderProxy, vm.$createElement)
 } catch (e) {
  // catch其实不需要看了, 都是做异常处理, _vnode是在vm._update的时候保存的, 也就是上次的状态或是null(init的时候给的)
  handleError(e, vm, `render`)
  // return error render result,
  // or previous vnode to prevent render error causing blank component
  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
  if (vm.$options.renderError) {
   try {
   vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
   } catch (e) {
   handleError(e, vm, `renderError`)
   vnode = vm._vnode
   }
  } else {
   vnode = vm._vnode
  }
  } else {
  vnode = vm._vnode
  }
 }
 // return empty vnode in case the render function errored out
 if (!(vnode instanceof VNode)) {
  if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
  warn(
   'Multiple root nodes returned from render function. Render function ' +
   'should return a single root node.',
   vm
  )
  }
  vnode = createEmptyVNode()
 }
 // set parent
 vnode.parent = _parentVnode
 return vnode
 }
}

您可能感兴趣的稿子:

  • Vue实例中生命周期created和mounted的界别详解

其一措施做了:

  • 听大人说近来vm的render方法来生成VNode.
    (render方法只怕是依照template或vue文件编写翻译而来,
    所以推论直接写render方法功用最高)
  • 假使render方法有题目, 那么首先调用renderError方法,
    再不行就读取上次的vnode或是null.
  • 倘使有父节点就放到自身的.parent属性里.
  • 最后回来VNode

因此基本是那句:

vnode = render.call(vm._renderProxy, vm.$createElement)

其中的render(), vm._renderProxy, vm.$createElement都不驾驭是什么.

先看vm._renderProxy: 是initMixin()的时候设置的, 在生育环境再次来到vm,
开发条件重回代理, 那么大家觉得她是三个能够debug的vm(正是vm),
细节之后再看.

vm.$createElement的代码在vdom文件夹下, 看了下是一个办法,
重临值四个VNode.

render有点复杂, 能还是无法从此探讨,
不问可见正是把template或然vue单文件和mount目的parse成render函数.

小总结: vm._render()的重回值是VNode, 根据当下vm的render函数

接下去看vm._update()

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
 const vm: Component = this
 if (vm._isMounted) {
  callHook(vm, 'beforeUpdate')
 }
 // 记录update之前的状态
 const prevEl = vm.$el
 const prevVnode = vm._vnode
 const prevActiveInstance = activeInstance
 activeInstance = vm
 vm._vnode = vnode
 // Vue.prototype.__patch__ is injected in entry points
 // based on the rendering backend used.
 if (!prevVnode) { // 初次加载, 只有_update方法更新vm._vnode, 初始化是null
  // initial render
  vm.$el = vm.__patch__( // patch创建新dom
  vm.$el, vnode, hydrating, false /* removeOnly */,
  vm.$options._parentElm,
  vm.$options._refElm
  )
  // no need for the ref nodes after initial patch
  // this prevents keeping a detached DOM tree in memory (#5851)
  vm.$options._parentElm = vm.$options._refElm = null
 } else {
  // updates
  vm.$el = vm.__patch__(prevVnode, vnode) // patch更新dom
 }
 activeInstance = prevActiveInstance
 // update __vue__ reference
 if (prevEl) {
  prevEl.__vue__ = null
 }
 if (vm.$el) {
  vm.$el.__vue__ = vm
 }
 // if parent is an HOC, update its $el as well
 if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
  vm.$parent.$el = vm.$el
 }
 // updated hook is called by the scheduler to ensure that children are
 // updated in a parent's updated hook.
 }

咱俩关切的片段其实就是__patch()的部分, __patch()做了对dom的操作,
在_update()里判断了是还是不是是初次调用, 假诺是的话成立新dom,
不是的话传入新旧node进行比较再操作.

结论

vue的视图渲染是一种奇特的Watcher, watch的剧情是三个函数,
函数运维的进程调用了render函数,
render又是由template大概el的dom编写翻译成的(template中包括一些被observe的数据).
所以template中被observe的数量有转变触发沃特cher的update()方法就会重新渲染视图.

遗留

render函数是在何地被编译的
vue源码公布时引入差异平台最终打成dist的流水生产线是什么样
__patch__和VNode的分析

你恐怕感兴趣的篇章:

  • Vue2.0用户权限决定消除方案的以身作则
  • vue-router路由懒加载和权力控制详解
  • Vue通过U途观L传参怎么着控制全局console.log的开关详解
  • Vue-Access-Control
    前端用户权限决定化解方案
  • Vue2.0用户权限决定消除方案
  • 详解基于vue-router的动态权限决定完毕方案
  • vue-router
    权限控制的言传身教代码
  • 依照Vue完毕后台系统权限控制的以身作则代码
  • vue2.0结合Element达成select动态控制input禁止使用实例
  • 详解VUE的气象控制与延时加载刷新

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图