组件化——组件的实现原理

渲染器主要负责将虚拟 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 函数的第一个参数传递。

经过上述两步工作后,我们就实现了对组件自身状态的支持,以及在渲染函数内访问组件自身状态的能力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/160663.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

iperf3 网络测试

iperf3 测试网络的上下行带宽 下载地址 https://iperf.fr/iperf-download.php 开启服务器 开启客户端 常用命令 -c 代表客户端-s 代表服务端-u 代表 udp-r 代表数据方向是否反向 https://baijiahao.baidu.com/s?id1731514357681464971&wfrspider&forpc

C++学习 --queue

目录 1, 什么是queue 2, 创建queue 2-1, 标准数据类型 2-2, 自定义数据类型 2-3, 其他创建方式 3, 操作stack 3-1, 赋值 3-2, 插入元素(push) 3-3, 查询元素 3…

Python简直是万能的,这5大主要用途你一定要知道!

从2015开始国内就开始慢慢接触Python了,从16年开始Python就已经在国内的热度更高了,目前也可以算的上"全民Python"了。 众所周知小学生的教材里面已经有Python了,国家二级计算机证也需要学习Python了! 因为Python简单…

编程语言发展史:布尔代数和机器语言

布尔代数是一种数学理论,用于描述和分析逻辑和布尔值的关系。它是由英国数学家George Boole在19世纪中期发明的,被认为是现代计算机科学的基础之一。布尔代数的发明使得逻辑运算可以被表示为代数运算,从而为计算机科学的发展奠定了基础。 在…

PTA 7-4 数列求和-加强版

7-4 数列求和-加强版 分数 20 全屏浏览题目 作者 DS课程组 单位 浙江大学 给定某数字A(1≤A≤9)以及非负整数N(0≤N≤100000),求数列之和SAAAAAA⋯AA⋯A(N个A)。例如A1, N3时,S1…

Unity、UE和Godot的优劣对比

先占位。。。。。。 首先说Unity和UE这两家公司,是行业的两座灯塔,对整个游戏引擎的这个行业的发展具有这种指导性的这种作作用。这两个引擎我从2016年开始就一直在用,结合一下业内的共识,一般来说认为呢,Unity更擅长移…

2023全球边缘计算大会深圳站-核心PPT资料下载

一、峰会简介 边缘计算,是指在靠近物或数据源头的一侧,采用网络、计算、存储、应用核心能力为一体的开放平台,就近提供最近端服务。其应用程序在边缘侧发起,产生更快的网络服务响应,满足行业在实时业务、应用智能、安…

LeetCode算法题解(动态规划,背包问题)|LeetCode416. 分割等和子集

LeetCode416. 分割等和子集 题目链接:416. 分割等和子集 题目描述: 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 示例 1: 输入:nums [1,5,…

Linux中的进程程序替换

Linux中的进程程序替换 1. 替换原理2. 替换函数3. 函数解释4. 命名理解程序替换的意义 1. 替换原理 替换原理 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的…

[Docker]九.Docker compose讲解

docker-compose 是 docker 官方的一个开源项目,可以实现对 docker 容器集群的快速编排, docker-compose 通过一个 配置文件 来管理多个 Docker 容器,在配置文件中,所有的容器通过 services 来定义,然后使用 docker-compose脚本 来 启动&am…

Nuxt.js Next.js Nest.js

Nuxt.js和Next.js都是服务端渲染框架(SSR),属于前端框架,Nest.js则是node框架,属于后端框架。 其中Nuxt.js是vue的ssr框架,Next.js是react的ssr框架。 都是比vue和react更上层的前端框架。 文章目录 1.SSR2.Nuxt2.1 Nuxt的下载2.2 Nuxt的集成2.3 Nuxt…

HuggingFace-利用BERT预训练模型实现中文情感分类(下游任务)

准备数据集 使用编码工具 首先需要加载编码工具,编码工具可以将抽象的文字转成数字,便于神经网络后续的处理,其代码如下: # 定义数据集 from transformers import BertTokenizer, BertModel, AdamW # 加载tokenizer token Ber…

cobol基本动词

cobol基本动词 基本动词用于过程部中的数据处理。每个语句总是以cobol动词开头。 input(输入)/output(输出) 输入输出动词用于从用户获取数据。并显示cobol程序的输出。 accept 用于从操作系统或者用户获取数据,例如日…

langchain 部署组件-LangServe

原文:🦜️🏓 LangServe | 🦜️🔗 Langchain LangServe 🚩 We will be releasing a hosted version of LangServe for one-click deployments of LangChain applications. Sign up here to get on the wa…

OpenLayers入门,OpenLayers6的WebGLPointsLayer图层样式和运算符详解,四种symbolType类型案例

专栏目录: OpenLayers入门教程汇总目录 前言 本章讲解使用OpenLayers6的WebGL图层显示大量点情况下,列举出所有WebGLPointsLayer图层所支持的所有样式运算符大全。 补充说明 本篇主要介绍OpenLayers6.x版本的webgl图层,OpenLayers7.x和OpenLayers8.x主要更新内容就是webgl…

GB28181学习(十七)——基于jrtplib实现tcp被动和主动发流

前言 GB/T28181-2022实时流的传输方式介绍:https://blog.csdn.net/www_dong/article/details/134255185 基于jrtplib实现tcp被动和主动收流介绍:https://blog.csdn.net/www_dong/article/details/134451387 本文主要介绍下级平台或设备发流功能&#…

生活如果真能像队列一样的话

生活如果真能像队列一样,那该多好啊。 —————————————————————————————————————————— 背包,队列 可以先看他们的API:都含有一个无参构造函数,添加单个元素的方法,测试集合…

php项目从宝塔面板切换转到phpstudy小皮面板

宝塔面板转phpstudy面板 版本 宝塔面板8.0.1 phpstudy面板8.1.1.3 步骤 1、宝塔面板,找到项目文件夹,打包、下载到本地、解压 2、本地windows系统安装phpstudy面板,选择尽可能一样的配置 比如宝塔php7.4.33,可能phpstudy面板只有php7.4.3,也行 大环境一定要一致,比如…

力扣算法练习BM46—最小的K个数

题目 给定一个长度为 n 的可能有重复值的数组,找出其中不去重的最小的 k 个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4(任意顺序皆可)。 数据范围:0≤k,n≤10000,数组中每个数的大小0≤val≤1000 要…

linux signal 机制

ref: Linux操作系统学习笔记(十六)进程间通信之信号 | Ty-Chens Home https://www.cnblogs.com/renxinyuan/p/3867593.html 当执行kill -9 PID时系统发生了什么 -