2-Vue 的实现

来源:互联网 发布:访问网站出现 php探针 编辑:程序博客网 时间:2024/06/05 09:11

1.声明式渲染

DOM应尽可能是一个函数式到状态的映射。
DOM状态只是数据状态的一个映射。

声明式渲染
如图所示,所有的逻辑尽可能在状态的层面去进行
当状态改变时,View应该是在框架帮助下自动更新到合理的状态,而不是当你观测到数据变化之后手动选择一个元素,再命令式地去改动它的属性。

在模板语法上,Vue跟Angular是比较相似。
把模板直接做成在浏览器里面 parse 成 DOM 树,然后去遍历这个树,提取其中的各种绑定。

声明式渲染原理

Vue的编译器在编译模板之后,会把这些模板编译成一个渲染函数。
而函数被调用的时候就会渲染并且返回一个虚拟DOM的树。
这个树非常轻量,它的职责就是描述当前界面所应处的状态。当我们有了这个虚拟的树之后,再交给一个patch 函数,负责把这些虚拟 DOM 真正施加到真实的 DOM上。

在这个过程中,Vue自身的响应式系统侦测在渲染过程中所依赖到的数据来源。侦测到的数据来源之后,就可以精确感知数据源的变动。
当数据发生变化时,可以根据需要重新进行渲染。
当重新进行渲染之后,会生成一个新的树,将新树与旧树进行对比,就可以最终得出应施加到真实DOM上的改动。最后再通过 patch 函数施加改动。

这样做的主要原因是:在浏览器当中,JavaScript的运算在现代的引擎中非常快,但DOM本身是非常缓慢的东西。当你调用原生DOM API的时候,浏览器需要在JavaScript引擎的语境下去接触原生的DOM的实现,这个过程有相当的性能损耗。所以,本质的考量是,要把耗费时间的操作尽量放在纯粹的计算中去做,保证最后计算出来的需要实际接触真实DOM的操作是最少的。

关于渲染函数:

<div class= 'message'>hello world</div>// 经过 Vue 编译后会变成render( h ){    return h (    'div',        { class: 'message'},        [' hello word ']        )  }

以上就是渲染函数内部的运作机制,
渲染函数,返回的就是一个虚拟 DOM 树
JSX 实际就是一套用于让我们更简单地去描述树状结构的语法糖。

这个函数类似于创建一个虚拟元素的函数,我们可以给它一个名字,给它描述应该有的属性特性和可能其他的数据。后面的参数是个数组,包含了该虚拟元素的子元素。

当然,你也可以选择直接跳过模板这一层去手写渲染函数,同时也有可选JSX支持。

  • JSX和直接渲染函数 更灵活更强大,能够表达更复杂的逻辑
    因为是真正的JavaScript,拥有这个语言本身的所有的能力,可以进行复杂的逻辑判断,进行选择性的返回最终要返回的DOM结构,能够实现一些在模板的语法限制下,很难做到的一些事情。

  • 模板 书写便利、易读 更接近文档和 Css
    模板更贴近我们的 HTML 可以让我们更直观地思考语义结构

Vue 里两个都是可以选择的,在绝大部分情况下使用模板,但是在需要复杂逻辑的情况下,使用渲染函数。
在 Vue2.0 的路由和内部的一些实践上,都大量地应用渲染函数做复杂的抽象组件,比如过渡动画组件以及路由里面的link组件,都是用渲染函数实现的,同时还保留了它本身的依赖追踪系统。

Vue的依赖追踪通过ES5的 Object.defineProperty 方法实现。
比如,我们给它一个原生对象,Vue会遍历这个数据对象的属性,然后进行属性转换。每一个属性会被转换为一个 getter 和一个 setter。同时每个组件会有一个对应的 watcher 对象,这个对象的职责就是在当前组件被渲染的时候,记录数据上面的哪些属性被用到了。

例如,在渲染函数里面用到A.B的时候,这个就会触发对应的 getter。整个渲染流程具体要点如下:
当某个数据属性被用到时,触发 getter,这个属性就会被作为依赖被 watcher 记录下来。
整个函数被渲染完的时候,每一个被用到的数据属性都会被记录。
相应的数据变动时,例如给它一个新的值,就会触发 setter,通知数据对象对应数据有变化。
此时会通知对应的组件,其数据依赖有所改动,需要重新渲染。
对应的组件再次调动渲染函数,生成 Virtual DOM,实现 DOM 更新。

如下图所示,在Vue里面由于依赖追踪系统的存在,当任意数据变动的时,Vue的每一个组件都精确地知道自己是否需要重绘,所以并不需要手动优化。用Vue渲染这些组件的时候,数据变了,对应的组件基本上去除了手动优化的必要性。

2. 组件系统

把UI结构映射到恰当的组件树,如下图所示。

组件

在Vue中,父子组件之间的通信是通过 props 传递。
从父向子单向传递;而如果子组件想要在父组件作用里面产生副作用,就需要去派发事件。这样就形成一个基本的父子通信模式,在涉及大规模状态管理的时候会有额外的方案,这个后面会提到。

这里写图片描述

Vue的组件引入构建工具之后有一个单文件组件概念
在同一个Vue文件里,可以同时写 template, script 和 style,三个东西放在一个里面。同时,Vue的单文件组件和 Web Components 有一个本质不同,它是基于构建工具实现。这样的好处是有了一个构建的机会,可以对这些单文件组件做更多的分析处,在每一个语言块里可以单独使用不同的处理器,这点后面还会讲到。

3. 客户端路由

在做一个界面复杂度非常高的应用时,它会有很多的状态,这样的应用显然不可能在每做一次操作后都刷新一个页面作为用户反馈。
这就要这个应用有多个复杂的状态,同时这些状态还要对应到URL。
有一个重要的功能叫做 deep-linking,也就是当用户浏览到一个URL,然后把它传给另外的人或者复制重新打开,应用需要直接渲染出这个URL对应的状态。
这就意味着应用的URL和组件树的状态之间有一个映射关系,客户端路由的职责就是让这个映射关系声明式地对应起来。

路由
客户端路由涉及很多更复杂的问题,如:
这路由

可能同一层的路由有多个不同的出口,还有着复杂的URL匹配规则,等等。这些问题如果都由自己去一一实现,那么复杂度是非常高的。而Vue基本都有对应的解决方案(router.vuejs.org)。配合Webpack还可以实现基于路由的懒加载,一条路径所对应的组件在打包的时候,会分离成另外一块,只有当该路由被访问的时候,才会被加载出来。这有相应的解决方案,同时也有实例。

4. 状态管理

说到状态管理,可以把整个应用抽象: 行为到数据到视图的循环

图中的这三个东西是一个单向数据流,State 驱动 View 的渲染,而用户对 View 进行操作产生 Action,会使State产生变化,从而导致 View 重新渲染。
<循环

一个单独的Vue的组件,其实就已经是这样的结构。
当多个这样的组件来配套的时候,会遇到一个问题:每个组件都有它自己的状态,但整个应用的状态,跟组件之间并不一定存在一一对应的关系。这个状态可能是一个全局状态。那么状态到底放在哪里?
大部分解决方案是把这个状态从组件树中提取出来,放在一个全局的 Store 里面。Vue在这种做法上做了一些改动,如下图:

哈哈

我们看到最左边就是Vue的组件,这些组件在大部分情况下,就不再有私有的状态,而是从全局的 Store 里面获取状态。
Actions 和 Mutations 比较难用一两句话说清楚,大致就是当应用状态进行改变的时候,需要通过 Mutations 去显式地触发,而 Actions 则是负责异步和其他副作用。由于 Mutations 会被记录下来,我们可以把这些记录发到工具里面去做分析,甚至进行回滚。当发现 bug 的时候,这使得我们可以更好地理解大型应用中的状态变化。

5.构建工具

Vue有官方的,全局安装的 vue-cli 构建工具
全局安装之后,我们就可以用 vue 命令创建一个新的项目

Vue 的 CLI 跟其他 CLI 不同之处在于,有多个可选模板,有简单的也有复杂的。极简的配置,更快的安装,可以更快的上手。它也有一个更完整的模板,包括单元测试在内的各种内容都涵盖,但是,它的复杂度也更高,这又涉及到根据用例来选择恰当复杂度的问题。所有的模板在创建之后,构建脚本都是通过 npm 脚本来执行,在国内安装 npm 依赖的时候有点卡,可以用 yarn 或者推荐用淘宝的 npm 镜象源,可以很大地提升安装速度。