Vue-next源码新鲜出炉⼀
vue3 作为⽬前最⽕的技术之⼀,除了学会使⽤以外,肯定是想在深⼊到源码⾥⾯,了解其实现原理,取其精华,提⾼⾃⼰的核⼼竞争⼒,⽆奈代码量太⼤,不知从何处下⼿,推荐开课吧花果⼭崔⽼师的,虽然源码有些改动,但解读思路是⼀样的。
准备⼯作
1、TypeScript学习,Vue 3采⽤TS构建,学会TS很有必要
2、ES6+相关知识,如、、Symbol、泛型等。
├── packages
│├── compiler-core // 编译器核⼼
│├── compiler-dom // dom解析&编译
│├── compiler-sfc // ⽂件编译系统│├── compiler-ssr // 服务端渲染
│├── reactivity // 数据响应
│├── runtime-core // 虚拟DOM渲染-核⼼
│├── runtime-dom // dom即时编译
│├── runtime-test // 测试runtime
│├── server-renderer // ssr
│├── shared // 帮助
│├── size-check // runtime包size检测
│├── template-explorer
│└── vue // 构建vue
这个流程将从⽤户使⽤的 api createApp 来作为⼊⼝点,分析vue 的内部执⾏流程
1、createApp
作⽤流程
进⾏初始化,基于 rootComponent ⽣成 vnode,进⾏ render
使⽤
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 注册路由
setupRouter(app)
router.isReady().then(() => {
})
createAppAPI源码部分
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
vue中reactivereturn function createApp(rootComponent, rootProps = null) {
const context = createAppContext()
// 当前 vue 的实例,全局唯⼀
const app: App = (context.app = {
// 属性
_uid: uid++,
_component: rootComponent as ConcreteComponent, // 存放整个组件原始配置树
_props: rootProps,
_container: null, // 根容器 , 虚拟dom ⼊⼝节点
_context: context, // 上下⽂对象
_instance: null,
version,
get config() {
fig // 全局配置
},
// ⽅法
use(plugin: Plugin, ...options: any[]) {},
mixin(mixin: ComponentOptions) {},
component(name: string, component?: Component): any {},
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
},
unmount() {}
provide(key, value) {}
})
return app
}
}
export function createAppContext(): AppContext {
return {
app: null as any,
config: {
isNativeTag: NO,
performance: false,
globalProperties: {},
optionMergeStrategies: {},
errorHandler: undefined,
warnHandler: undefined,
compilerOptions: {}
},
mixins: [],
components: {},
directives: {},
provides: ate(null),
optionsCache: new WeakMap(),
propsCache: new WeakMap(),
emitsCache: new WeakMap()
}
}
抽象模拟
import { render } from "./renderer";
import { createVNode } from "./vnode";
// createApp
// 在 vue3 ⾥⾯ createApp 是属于 renderer 对象的
// ⽽ renderer 对象需要创建
/
/ 这⾥我们暂时不实现
export const createApp = (rootComponent) => {
const app = {
_component: rootComponent,
mount(rootContainer) {
console.log("基于根组件创建 vnode");
const vnode = createVNode(rootComponent);
console.log("调⽤ render,基于 vnode 进⾏开箱");
render(vnode, rootContainer);
},
};
return app;
};
2、虚拟节点(VNode)
1. VNode 表⽰虚拟节点 Virtual DOM,不是真的 DOM 节点,是对象⽤于描述节点的信息
2. 他只是⽤ javascript 对象来描述真实 DOM,把DOM标签,属性,内容都变成对象的属性
3. 过程就是,把template模板描述成 VNode,然后⼀系列操作之后通过 VNode 形成真实DOM进⾏挂载
4. 虚拟 DOM:对由 Vue 组件树建⽴起来的整个 VNode 树的称呼,由 VNode 组成的
什么作⽤:
1、兼容性强,不受执⾏环境的影响。VNode 因为是 JS 对象,不管 Node 还是浏览器,都可以执⾏,从⽽获得了服务端渲染、原⽣渲染、⼿写渲染函数等能⼒
2、减少操作 DOM。任何页⾯的变化,都只使⽤ VNode 进⾏操作对⽐,只需要在最后⼀步挂载更新DOM,不需要频繁操作DOM,从⽽提⾼页⾯性能
过程:
模板→渲染函数→虚拟DOM树→真实DOM
源码部分
位置 runtime-core/src/vnode.ts ⽂件
export interface VNode<
HostNode = RendererNode,
HostElement = RendererElement,
ExtraProps = { [key: string]: any }
> {
...
// 内部属性
}
VNode本质是个对象,属性按照作⽤分为5类:
1、内部属性
__v_isVNode: true // 标识是否为VNode
[ReactiveFlags.SKIP]: true // 标识VNode不是observable
type: VNodeTypes // VNode 类型
props: (VNodeProps & ExtraProps) | null // 属性信息
key: string | number | null // 特殊 attribute 主要⽤在 Vue 的虚拟 DOM 算法
ref: VNodeNormalizedRef | null // 被⽤来给元素或⼦组件注册引⽤信息。
scopeId: string | null // SFC only
children: VNodeNormalizedChildren // 保存⼦节点
component: ComponentInternalInstance | null // 指向VNode对应的组件实例
dirs: DirectiveBinding[] | null // 保存应⽤在VNode的指令信息
transition: TransitionHooks<HostElement> | null // 存储过渡效果信息
2、DOM 属性
el: HostNode | null // 真实DOM 节点
anchor: HostNode | null // fragment anchor程序锚点
target: HostElement | null // teleport target
targetAnchor: HostNode | null // teleport target anchor
staticCount: number // number of elements contained in a static vnode
3、suspense 属性
suspense: SuspenseBoundary | null
ssContent: VNode | null
ssFallback: VNode | null
4、 optimization 属性(优化)
shapeFlag: number
patchFlag: number
dynamicProps: string[] | null
dynamicChildren: VNode[] | null
5 、应⽤上下⽂属性
appContext: AppContext | null
...
创建 VNode
Vue-Next提供了h函数,实际执⾏的就是createVNode(),由于频繁使⽤封装了⼀层// packages/runtime-core/src/h.ts
// Actual implementation
export function h(type: any, propsOrChildren?: any, children?: any): VNode {
const l = arguments.length
if (l === 2) {
if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
// single vnode without props
if (isVNode(propsOrChildren)) {
return createVNode(type, null, [propsOrChildren])
}
// props without children
return createVNode(type, propsOrChildren)
} else {
// omit props
return createVNode(type, null, propsOrChildren)
}
} else {
if (l > 3) {
children = Array.prototype.slice.call(arguments, 2)
} else if (l === 3 && isVNode(children)) {
children = [children]
}
return createVNode(type, propsOrChildren, children)
}
}
h函数主要逻辑就是根据参数个数和参数类型,执⾏相应处理操作,调⽤ createVNode 函数来创建 VNode 对象开发实例
appponent('componentHello', {
template: "<div>Hello World!<div>"
})
import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, "Hello World!"))
}
编译结果来看,⽣成⼀个render函数,执⾏createElementBlock()函数,是什么东东,⼀探究竟
还是看源码吧
export let currentBlock: VNode[]
// packages/runtime-core/src/vnode.ts
/**
* @private
*/
export function createElementBlock(
type: string | typeof Fragment,
props?: Record<string, any> | null,
children?: any,
patchFlag?: number,
dynamicProps?: string[],
shapeFlag?: number
) {
return setupBlock(
createBaseVNode(
type,
props,
children,
patchFlag,
dynamicProps,
shapeFlag,
true /* isBlock */
)
)
}
function setupBlock(vnode: VNode) {
// save current block children on the block vnode
vnode.dynamicChildren =
isBlockTreeEnabled > 0 ? currentBlock || (EMPTY_ARR as any) : null
// close block
closeBlock()
/
/ a block is always going to be patched, so track it as a child of its
// parent block
if (isBlockTreeEnabled > 0 && currentBlock) {
currentBlock.push(vnode)
}
return vnode
}
function createBaseVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag = 0,
dynamicProps: string[] | null = null,
shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
isBlockNode = false,
needFullChildrenNormalization = false
) {
const vnode = {
__v_isVNode: true,
__v_skip: true,
type,
props,
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
scopeId: currentScopeId,
slotScopeIds: null,
children,
component: null,
suspense: null,
ssContent: null,
ssFallback: null,
dirs: null,
transition: null,
el: null,
anchor: null,
target: null,
targetAnchor: null,
staticCount: 0,
shapeFlag,
patchFlag,
dynamicProps,
dynamicChildren: null,
appContext: null
} as VNode
.
..
return vnode
}
最后看到,还是⽣成 vnode放到 currentBlock中,实际作⽤还是创建是VNode。
现在我们知道了createVNode的作⽤,接下来具体看看代码实现
createVNode代码解读
位置:runtime-core/src/vnode.ts
代码量:89⾏+83⾏,分为两部分createVNode和 createBaseVNode(也叫createElementVNode)
代码部分
function _createVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag: number = 0,
dynamicProps: string[] | null = null,
isBlockNode = false
): VNode {
/* 注意 type 有可能是 string 也有可能是对象
* 如果是对象的话,那么就是⽤户设置的 options
* type 为 string 的时候
* createVNode("div")
* type 为组件对象的时候
*/ createVNode(App)
...
return createBaseVNode(
type,
props,
children,
patchFlag,
dynamicProps,
shapeFlag,
isBlockNode,
true
)
}
createBaseVNode() // 上⾯已经出现过
函数可以接收 6 个参数
参数type
export type VNodeTypes =
| string
| VNode
| Component
| typeof Text
| typeof Static
| typeof Comment
| typeof Fragment
| typeof TeleportImpl
| typeof SuspenseImpl
// packages/runtime-core/src/vnode.ts
export const Text = Symbol(__DEV__ ? 'Text' : undefined)
export const Comment = Symbol(__DEV__ ? 'Comment' : undefined)
export const Static = Symbol(__DEV__ ? 'Static' : undefined)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论