专业网站设计服务商,一级a做爰片免费网站黄,上海建设工程招标,免费在线看片渲染器主要负责将虚拟 DOM 渲染为真实 DOM#xff0c;我们只需要使用虚拟 DOM 来描述最终呈现的内容即可。但当我们编写比较复杂的页面时#xff0c;用来描述页面结构的虚拟 DOM 的代码量会变得越来越多#xff0c;或者说页面模板会变得越来越大。这时#xff0c;我们就需要…渲染器主要负责将虚拟 DOM 渲染为真实 DOM我们只需要使用虚拟 DOM 来描述最终呈现的内容即可。但当我们编写比较复杂的页面时用来描述页面结构的虚拟 DOM 的代码量会变得越来越多或者说页面模板会变得越来越大。这时我们就需要组件化的能力。有了组件我们就可以将一个大的页面拆分为多个部分每一个部分都可以作为单独的组件这些组件共同组成完整的页面。组件化的实现同样需要渲染器的支持从现在开始我们将详细讨论 Vue.js 中的组件化。
1、渲染组件
从用户的角度来看一个有状态组件就是一个选项对象如下面的代码所示
01 // MyComponent 是一个组件它的值是一个选项对象
02 const MyComponent {
03 name: MyComponent,
04 data() {
05 return { foo: 1 }
06 }
07 }但是如果从渲染器的内部实现来看一个组件则是一个特殊类型的虚拟 DOM 节点。例如为了描述普通标签我们用虚拟节点的 vnode.type 属性来存储标签名称如下面的代码所示
01 // 该 vnode 用来描述普通标签
02 const vnode {
03 type: div
04 // ...
05 }为了描述片段我们让虚拟节点的 vnode.type 属性的值为Fragment例如
01 // 该 vnode 用来描述片段
02 const vnode {
03 type: Fragment
04 // ...
05 }为了描述文本我们让虚拟节点的 vnode.type 属性的值为Text例如
01 // 该 vnode 用来描述文本节点
02 const vnode {
03 type: Text
04 // ...
05 }渲染器的 patch 函数证明了上述内容如下是我们实现的 patch 函数的代码
01 function patch(n1, n2, container, anchor) {
02 if (n1 n1.type ! n2.type) {
03 unmount(n1)
04 n1 null
05 }
06
07 const { type } n2
08
09 if (typeof type string) {
10 // 作为普通元素处理
11 } else if (type Text) {
12 // 作为文本节点处理
13 } else if (type Fragment) {
14 // 作为片段处理
15 }
16 }可以看到渲染器会使用虚拟节点的 type 属性来区分其类型。对于不同类型的节点需要采用不同的处理方法来完成挂载和更新。
实际上对于组件来说也是一样的。为了使用虚拟节点来描述组件我们可以用虚拟节点的 vnode.type 属性来存储组件的选项对象例如
01 // 该 vnode 用来描述组件type 属性存储组件的选项对象
02 const vnode {
03 type: MyComponent
04 // ...
05 }为了让渲染器能够处理组件类型的虚拟节点我们还需要在patch 函数中对组件类型的虚拟节点进行处理如下面的代码所示
01 function patch(n1, n2, container, anchor) {
02 if (n1 n1.type ! n2.type) {
03 unmount(n1)
04 n1 null
05 }
06
07 const { type } n2
08
09 if (typeof type string) {
10 // 作为普通元素处理
11 } else if (type Text) {
12 // 作为文本节点处理
13 } else if (type Fragment) {
14 // 作为片段处理
15 } else if (typeof type object) {
16 // vnode.type 的值是选项对象作为组件来处理
17 if (!n1) {
18 // 挂载组件
19 mountComponent(n2, container, anchor)
20 } else {
21 // 更新组件
22 patchComponent(n1, n2, anchor)
23 }
24 }
25 }在上面这段代码中我们新增了一个 else if 分支用来处理虚拟节点的 vnode.type 属性值为对象的情况即将该虚拟节点作为组件的描述来看待并调用 mountComponent 和patchComponent 函数来完成组件的挂载和更新。
渲染器有能力处理组件后下一步我们要做的是设计组件在用户层面的接口。这包括用户应该如何编写组件组件的选项对象必须包含哪些内容以及组件拥有哪些能力等等。实际上组件本身是对页面内容的封装它用来描述页面内容的一部分。因此一个组件必须包含一个渲染函数即 render 函数并且渲染函数的返回值应该是虚拟 DOM。换句话说组件的渲染函数就是用来描述组件所渲染内容的接口如下面的代码所示
01 const MyComponent {
02 // 组件名称可选
03 name: MyComponent,
04 // 组件的渲染函数其返回值必须为虚拟 DOM
05 render() {
06 // 返回虚拟 DOM
07 return {
08 type: div,
09 children: 我是文本内容
10 }
11 }
12 }这是一个最简单的组件示例。有了基本的组件结构之后渲染器就可以完成组件的渲染如下面的代码所示
01 // 用来描述组件的 VNode 对象type 属性值为组件的选项对象
02 const CompVNode {
03 type: MyComponent
04 }
05 // 调用渲染器来渲染组件
06 renderer.render(CompVNode, document.querySelector(#app))渲染器中真正完成组件渲染任务的是 mountComponent 函数其具体实现如下所示
01 function mountComponent(vnode, container, anchor) {
02 // 通过 vnode 获取组件的选项对象即 vnode.type
03 const componentOptions vnode.type
04 // 获取组件的渲染函数 render
05 const { render } componentOptions
06 // 执行渲染函数获取组件要渲染的内容即 render 函数返回的虚拟 DOM
07 const subTree render()
08 // 最后调用 patch 函数来挂载组件所描述的内容即 subTree
09 patch(null, subTree, container, anchor)
10 }这样我们就实现了最基本的组件化方案。
2、组件状态与自更新
在上一节中我们完成了组件的初始渲染。接下来我们尝试为组件设计自身的状态如下面的代码所示
01 const MyComponent {
02 name: MyComponent,
03 // 用 data 函数来定义组件自身的状态
04 data() {
05 return {
06 foo: hello world
07 }
08 },
09 render() {
10 return {
11 type: div,
12 children: foo 的值是: ${this.foo} // 在渲染函数内使用组件状态
13 }
14 }
15 }在上面这段代码中我们约定用户必须使用 data 函数来定义组件自身的状态同时可以在渲染函数中通过 this 访问由 data 函数返回的状态数据。
下面的代码实现了组件自身状态的初始化
01 function mountComponent(vnode, container, anchor) {
02 const componentOptions vnode.type
03 const { render, data } componentOptions
04
05 // 调用 data 函数得到原始数据并调用 reactive 函数将其包装为响应式数据
06 const state reactive(data())
07 // 调用 render 函数时将其 this 设置为 state
08 // 从而 render 函数内部可以通过 this 访问组件自身状态数据
09 const subTree render.call(state, state)
10 patch(null, subTree, container, anchor)
11 }如上面的代码所示实现组件自身状态的初始化需要两个步骤
通过组件的选项对象取得 data 函数并执行然后调用reactive 函数将 data 函数返回的状态包装为响应式数据在调用 render 函数时将其 this 的指向设置为响应式数据state同时将 state 作为 render 函数的第一个参数传递。
经过上述两步工作后我们就实现了对组件自身状态的支持以及在渲染函数内访问组件自身状态的能力。