文章目录
-
-
- vue2与vue3的区别
-
- 1.源码组织上的变化
- 2.组合式API
- 3.运作机制的变化
- 组件渲染
-
- 根组件模板编译
- 对象组件渲染成真实DOM
-
- vue3初始化根组件
- 创建vnode
- 渲染成真实的组件
-
reactivity: 响应式系统
runtime-core:与平台无关的运行时核心 (可以创建针对特定平台的运行时 - 自定义渲染器)
runtime-dom: 针对浏览器的运行时。包括DOM API,属性,事件处理等
runtime-test:用于测试server-renderer:用于服务器端渲染
compiler-core:与平台无关的编译器核心compiler-dom: 针对浏览器的编译模块compiler-ssr: 针对服务端渲染的编译模块compiler-sfc: 针对单文件解析size-check:用来测试代码体积
template-explorer:用于调试编译器输出的开发工具
shared:多个包之间共享的内容vue:完整版本,包括运行时和编译器
vue2与vue3的区别
1.源码组织上的变化
vue2中,所有的源码都存在src目录下complier是模板编译的相关代码,core是与平台无关的通用运行时代码,platforms是平台专有代码,shared是共享工具代码。
vue3相对于vue2使用了monorepo的方式进行包管理,每个包独立负责一块核心功能的实现,有各自的API,类型定义和测试,使得vue3源码职责更加清晰。
compiler-core负责与平台无关层的渲染器底层,对外提供统一调用的函数,没不通过完整的测试用例保障功能的稳定性,complier-dom和complier-ssr依托于complier-core分别实现浏览器和服务端的渲染器上层逻辑。
2.组合式API
组合式API是一系列API的集合,使我们可以使用函数而不是声明选项的方式书写vue组件。在大型项目中,会产生大量状态逻辑的维护甚至跨组件的逻辑复用,更适合用组合式API。
3.运作机制的变化
之前通过new vue()来创建vue对象的方式已经变成了createApp,在响应式部分也由原来object.defineProperty改成了现在的 Proxy API 实现,针对响应式依赖收集的内容,在 Vue 2.x版本中是收集了 watcher ,而到了 Vue 3中则成了 effect 。
组件渲染
根组件模板编译
.vue类型的文件无法在web端直接加载,我们通常会在weback的编译阶段,通过vue-loader编译生成组件相关的 js 和 css,并把 template 部分编译转换成 render函数添加到组件对象的属性中.
对象组件渲染成真实DOM
vue3初始化根组件
createApp过程
export const createApp=(...args)=>{
const app =ensureRenderer().createApp(...args);
//....
return app;
}
ensureRenderer().createApp(…args) 这个链式函数执行完成后肯定返回了 mount 函数,ensureRenderer 就是构造了一个带有createApp 函数的渲染器 renderer 对象.
function ensureRenderer() {
// 如果 renderer 有值的话,那么以后都不会初始化了
return (
renderer ||
(renderer = createRenderer(rendererOptions)
)
}
// renderOptions 包含以下函数:
const renderOptions = {
createElement,
createText,
setText,
setElementText,
patchProp,
insert,
remove,
}
这里返回的 renderer 对象,可以认为是一个跨平台的渲染器对象,针对不同的平台,会创建出不同的 renderer 对象,上述是创建浏览器环境的 renderer 对象,对于服务端渲染的场景,则会创建 server render 的 renderer
let enabledHydration = false
function ensureHydrationRenderer(){
renderer = enabledHydration
? renderer
: createHydrationRenderer(rendererOptions)
enabledHydration = true
return renderer
}
createRenderer返回的对象
export function createRenderer(options) {
// ...
// 这里不介绍 hydrate 模式
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate),
}
}
renderer 对象上包含了 createApp 和 render 方法
createApp方法
function createAppAPI(render, hydrate) {
// createApp createApp 方法接收的两个参数:根组件的对象和 prop
return function createApp(rootComponent, rootProps = null) {
const app = {
// ... 省略很多不需要在这里介绍的属性
_component: rootComponent,
_props: rootProps,
mount(rootContainer, isHydrate, isSVG) {
// ...
}
}
return app
}
}
入口文件 createApp 真正执行的内容就是这里的 createAppAPI 函数中的 createApp 函数
该函数接收了 组件作为根组件 rootComponent
返回了一个包含 mount 方法的 app 对象
mount的内部实现
mount(rootContainer, isHydrate, isSVG) {
if (!isMounted) {
// ...
// 1. 创建根组件的 vnode
const vnode = createVNode(
rootComponent,
rootProps
)
// 2. 渲染根组件
render(vnode, rootContainer, isSVG)
isMounted = true
}
}
一个组件想要真正渲染生成DOM的步骤
1.创建vnode
2.渲染vnode
3.生成DOM
创建vnode
vnode节点就是将真实的DOM以普通对象形式的数据结构来表达,简化了很多DOM中的内容
const vnode={
type:"div",
props:{
'class':'helloWorld'
},
children:'helloWorld'
}
根节点如何被创建成一个vnode,根据下列createVNode函数
// packages/runtime-core/src/vnode.ts
function createBaseVNode(...) {
const vnode = {
type,
props,
key: props && normalizeKey(props),
children,
component: null,
shapeFlag,
patchFlag,
dynamicProps,
dynamicChildren: null,
// ... 一些其他属性
}
// ...
return vnode
}
function createVNode(type, props = null, children = null) {
if (props) {
// 如果存在 props 则需要对 props 进行一些处理,这里先省略
}
// ...
// 处理 shapeFlag 类型
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT
: __FEATURE_SUSPENSE__ && isSuspense(type)
? ShapeFlags.SUSPENSE
: isTeleport(type)
? ShapeFlags.TELEPORT
: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT
: isFunction(type)
? ShapeFlags.FUNCTIONAL_COMPONENT
: 0
// ...
return createBaseVNode(
type,
props,
children,
patchFlag,
dynamicProps,
shapeFlag,
isBlockNode,
true
)
}
根据上述代码,当进行根组件渲染的时候,createVNode的第一个入参 type 是App 对象,也就是一个 Object,所以得到的 shapeFlag 的值是 STATEFUL_COMPONENT,代表的是一个有状态组件对象。
也就是说,现在为止,vue完成了对根组件Vnode对象的创建。
引入vnode,可以把渲染过程抽象化,从而使组件的抽象能力得到提升
但是,使用vnode并不意味着不用操作DOM了
渲染成真实的组件
回到mount函数中,进行对vnode的渲染工作
render(vnode,rootContainer)
这里的render函数是在调用createAppAPI时传进来的,而createAppAPI是在创建renderer渲染器的时候调用的
render函数的实现
// packages/runtime-core/src/renderer.ts
const render = (vnode, container) => {
if (vnode == null) {
// 如果 vnode 不存在,表示需要卸载组件
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
// 否则进入更新流程(初始化创建也是特殊的一种更新)
patch(container._vnode || null, vnode, container)
}
// 缓存 vnode
container._vnode = vnode
}
对于初始化根组件的过程中,传入了一个根组件的 vnode 对象,所以这里会执行 patch 相关的动作。patch 本意是补丁的意思,可以理解成为更新做一些补丁的活儿,其实初始的过程也可以看作是一个全量补丁,一种特殊的更新操作
// packages/runtime-core/src/renderer.ts
function patch(n1,n2,container = null,anchor = null,parentComponent = null) {
// 对于类型不同的新老节点,直接进行卸载
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1)
unmount(n1, parentComponent, parentSuspense, true)
n1 = null
}
// 基于 n2 的类型来判断
// 因为 n2 是新的 vnode
const { type, shapeFlag } = n2;
switch (type) {
case Text:
// 处理文本节点
break;
// 其中还有几个类型比如: static fragment comment
default:
// 这里就基于 shapeFlag 来处理
if (shapeFlag & ShapeFlags.ELEMENT) {
// 处理普通 DOM 元素
processElement(n1, n2, container, anchor, parentComponent);
} else if (shapeFlag & ShapeFlags.COMPONENT) {
// 处理 component
processComponent(n1, n2, container, parentComponent);
} else if {
// ... 处理其他元素
}
}
}
初始渲染主要做的两件事:渲染组件生成subTree,把subTree挂载到container中