岸边的风:个人主页
个人专栏 :《 VUE 》 《 javaScript 》
⛺️ 生活的理想,就是为了理想的生活 !
目录
从一个例子开始
const HelloVueApp = {
data() {
return {
message: 'Hello Vue!'
}
}
}
Vue.createApp(HelloVueApp).mount('#hello-vue')
亲自试一试
那么 createApp
里面都干了什么呢?我们接着往下看
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
if (__DEV__) {
injectNativeTagCheck(app)
}
const { mount } = app
app.mount = (containerOrSelector: Element | string): any => {
const container = normalizeContainer(containerOrSelector)
if (!container) return
const component = app._component
if (!isFunction(component) && !component.render && !component.template) {
component.template = container.innerHTML
}
// clear content before mounting
container.innerHTML = ''
const proxy = mount(container)
container.removeAttribute('v-cloak')
return proxy
}
return app
}) as CreateAppFunction
我们可以看到重点在于 ensureRenderer
,
const rendererOptions = {
patchProp, // 处理 props 属性
...nodeOps // 处理 DOM 节点操作
}
// lazy create the renderer - this makes core renderer logic tree-shakable
// in case the user only imports reactivity utilities from Vue.
let renderer: Renderer | HydrationRenderer
let enabledHydration = false
function ensureRenderer() {
return renderer || (renderer = createRenderer(rendererOptions))
}
调用 createRenderer
export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions) {
return baseCreateRenderer(options)
}
调用 baseCreateRenderer
, baseCreateRenderer
这个函数简直可以用庞大来形容,vnode
diff
patch
均在这个方法中实现,回头我们再来细看实现,现在我们只需要关心他最后返回的什么
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
const {
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
createComment: hostCreateComment,
setText: hostSetText,
setElementText: hostSetElementText,
parentNode: hostParentNode,
nextSibling: hostNextSibling,
setScopeId: hostSetScopeId = NOOP,
cloneNode: hostCloneNode,
insertStaticContent: hostInsertStaticContent
} = options
// ....此处省略两千行,我们先不管
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
从源码中我们看到 baseCreateRenderer
最终返回 render
hydrate
createApp
3个函数, 但在 createApp
这个函数中我们本质上只需要返回 createApp
这个函数就好,这里返回了3个,说明其它两个会在别处用到,具体哪里能用到,后面我们再回头看
接着将生成的 render
传给 createAppAPI
这个真正的 createApp
方法,hydrate
为可选参数,ssr
的场景下会用到,这边我们也先跳过
看了 baseCreateRenderer
这个函数,再看 createAppAPI
就会觉得太轻松了。。。毕竟不是一个量级的
createAppAPI
首先判断
export function createAppAPI(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction {
return function createApp(rootComponent, rootProps = null) {
if (rootProps != null && !isObject(rootProps)) {
__DEV__ && warn(`root props passed to app.mount() must be an object.`)
rootProps = null
}
// 创建默认APP配置
const context = createAppContext()
const installedPlugins = new Set()
let isMounted = false
const app: App = {
_component: rootComponent as Component,
_props: rootProps,
_container: null,
_context: context,
get config() {
return context.config
},
set config(v) {
if (__DEV__) {
warn(
`app.config cannot be replaced. Modify individual options instead.`
)
}
},
// 都是一些眼熟的方法
use() {},
mixin() {},
component() {},
directive() {},
// mount 我们拎出来讲
mount() {},
unmount() {},
// ...
}
return app
}
}
createAppContext
实现
export function createAppContext(): AppContext {
return {
config: {
isNativeTag: NO,
devtools: true,
performance: false,
globalProperties: {},
optionMergeStrategies: {},
isCustomElement: NO,
errorHandler: undefined,
warnHandler: undefined
},
mixins: [],
components: {},
directives: {},
provides: Object.create(null)
}
}
到这里,整个createApp
流程就结束了,在整个环节中,我们故意忽略了很多细节,不过不重要 ,这个篇幅我们只需要createApp
在主流程做了什么了就好
你可能会有很多疑问,比如:
模板是怎么编绎的?
生命周期是怎么挂载的?
组件是怎么注册的?
响应式怎么做到的?
我们先记着,后面有篇幅单独拎出来
看到这个函数你可能会有些许困惑,为什么叫h
呢?代表着什么呢?
h
其实代表的是 hyperscript 。它是 HTML 的一部分,表示的是超文本标记语言,当我们正在处理一个脚本的时候,在虚拟 DOM 节点中去使用它进行替换已成为一种惯例。这个定义同时也被运用到其他的框架文档中
Hyperscript 它本身表示的是 "生成描述 HTML 结构的脚本"
好了,了解了什么是 h
,现在我们来看官方对他的一个定义
定义: 返回一个“虚拟节点” ,通常缩写为 VNode: 一个普通对象,其中包含向 Vue 描述它应该在页面上呈现哪种节点的信息,包括对任何子节点的描述。用于手动编写render
// type only
h('div')
// type + props
h('div', {})
// type + omit props + children
// Omit props does NOT support named slots
h('div', []) // array
h('div', 'foo') // text
h('div', h('br')) // vnode
h(Component, () => {}) // default slot
// type + props + children
h('div', {}, []) // array
h('div', {}, 'foo') // text
h('div', {}, h('br')) // vnode
h(Component, {}, () => {}) // default slot
h(Component, {}, {}) // named slots
// named slots without props requires explicit `null` to avoid ambiguity
h(Component, null, {})
const App = {
render() {
return Vue.h('h1', {}, 'Hello Vue3js.cn')
}
}
Vue.createApp(App).mount('#app')
亲自试一试
h
接收三个参数
export function h(type: any, propsOrChildren?: any, children?: any): VNode {
if (arguments.length === 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 (isVNode(children)) {
children = [children]
}
return createVNode(type, propsOrChildren, children)
}
}
_createVNode
做的事情也很简单
function _createVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
// 更新标志
patchFlag: number = 0,
// 自定义属性
dynamicProps: string[] | null = null,
// 是否是动态节点,(v-if v-for)
isBlockNode = false
): VNode {
// type必传参数
if (!type || type === NULL_DYNAMIC_COMPONENT) {
if (__DEV__ && !type) {
warn(`Invalid vnode type when creating vnode: ${type}.`)
}
type = Comment
}
// Class 类型的type标准化
// class component normalization.
if (isFunction(type) && '__vccOpts' in type) {
type = type.__vccOpts
}
// class & style normalization.
if (props) {
// props 如果是响应式,clone 一个副本
if (isProxy(props) || InternalObjectKey in props) {
props = extend({}, props)
}
let { class: klass, style } = props
// 标准化class, 支持 string , array, object 三种形式
if (klass && !isString(klass)) {
props.class = normalizeClass(klass)
}
// 标准化style, 支持 array ,object 两种形式
if (isObject(style)) {
// reactive state objects need to be cloned since they are likely to be
// mutated
if (isProxy(style) && !isArray(style)) {
style = extend({}, style)
}
props.style = normalizeStyle(style)
}
}
// encode the vnode type information into a bitmap
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
if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {
type = toRaw(type)
warn(
`Vue received a Component which was made a reactive object. This can ` +
`lead to unnecessary performance overhead, and should be avoided by ` +
`marking the component with \`markRaw\` or using \`shallowRef\` ` +
`instead of \`ref\`.`,
`\nComponent that was made reactive: `,
type
)
}
// 构造 VNode 模型
const vnode: VNode = {
__v_isVNode: true,
__v_skip: true,
type,
props,
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
scopeId: currentScopeId,
children: null,
component: null,
suspense: null,
dirs: null,
transition: null,
el: null,
anchor: null,
target: null,
targetAnchor: null,
staticCount: 0,
shapeFlag,
patchFlag,
dynamicProps,
dynamicChildren: null,
appContext: null
}
normalizeChildren(vnode, children)
// presence of a patch flag indicates this node needs patching on updates.
// component nodes also should always be patched, because even if the
// component doesn't need to update, it needs to persist the instance on to
// the next vnode so that it can be properly unmounted later.
// patchFlag 标志存在表示节点需要更新,组件节点一直存在 patchFlag,因为即使不需要更新,它需要将实例持久化到下一个 vnode,以便以后可以正确卸载它
if (
shouldTrack > 0 &&
!isBlockNode &&
currentBlock &&
// the EVENTS flag is only for hydration and if it is the only flag, the
// vnode should not be considered dynamic due to handler caching.
patchFlag !== PatchFlags.HYDRATE_EVENTS &&
(patchFlag > 0 ||
shapeFlag & ShapeFlags.SUSPENSE ||
shapeFlag & ShapeFlags.TELEPORT ||
shapeFlag & ShapeFlags.STATEFUL_COMPONENT ||
shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT)
) {
// 压入 VNode 栈
currentBlock.push(vnode)
}
return vnode
}
到这里,h
函数已经全部看完了,我们现在知道 h
叫法的由来,其函数内部逻辑只做参数检查,真正的主角是 _createVNode
_createVNode
做的事情有
props
class
VNode
打上编码标记VNode
有的同学可能会有疑问️,VNode
最后是怎么转换成真实的 DOM
呢?