TA的每日心情 | 无聊 昨天 09:05 |
---|
签到天数: 1050 天 连续签到: 1 天 [LV.10]测试总司令
|
先来看一个单测。
- test('receive component instance as 2nd arg', () => {
- transformVNodeArgs((args, instance) => {
- if (instance) {
- return ['h1', null, instance.type.name]
- } else {
- return args
- }
- })
- const App = {
- // this will be the name of the component in the h1
- name: 'Root Component',
- render() {
- return h('p') // this will be overwritten by the transform
- }
- }
- const root = nodeOps.createElement('div')
- createApp(App).mount(root)
- })
复制代码 我们先从最熟悉的createApp(App).mount(root)这句入手,分两步看起。第一步 createApp(App)创建App实例,第二步mount(root)挂载。
1. createApp
packages\runtime-dom\src\index.ts
- const createApp = ((...args) => {
- const app = ensureRenderer().createApp(...args);
- {
- injectNativeTagCheck(app);
- }
- const { mount } = app;
- app.mount = (containerOrSelector) => {
- // 调用解构生成的mount方法...
- };
- return app;
- });
复制代码 该方法返回app实例,并在其上定义mount方法,即第二步的mount方法。
此app实例是由ensureRenderer返回render方法调用后的实例,并调用其上的createApp方法生成的。
- function ensureRenderer() {
- return renderer || (renderer = createRenderer(rendererOptions))
- }
复制代码 即 ensureRenderer --> createRenderer --> baseCreateRenderer -->
其中,rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps) ,作为操作DOM的方法。baseCreateRenderer定义了一系列操作的闭包方法,供渲染使用(packages\runtime-core\src\renderer.ts)。
接下来就是createAppAPI(render, hydrate)(packages\runtime-core\src\apiCreateApp.ts)
- export function createAppAPI<HostElement>( render: RootRenderFunction, hydrate?: RootHydrateFunction): CreateAppFunction<HostElement> {
- return function createApp(rootComponent, rootProps = null) {
- //...
- //app实例上下文context对象
- //config: { isNativeTag: NO, devtools: true, performance: false, globalProperties: {}, optionMergeStrategies: {}, isCustomElement: NO, warnHandler: undefined },
- // mixins: [], components: {}, directives: {}, provides: Object.create(null) }
- const context = createAppContext()
- const installedPlugins = new Set()
- let isMounted = false
- const app: App = { nder: RootRenderFunction, hydrate?: RootHydrateFunction): CreateAppFunction<HostElement> {
- _component: rootComponent as Component,
- _props: rootProps,
- _container: null,
- _context: context,
- version,
- get config() {
- return context.config
- },
- set config(v) {
- if (__DEV__) {
- warn( `app.config cannot be replaced. Modify individual options instead.`)
- }
- },
- use(plugin: Plugin, ...options: any[]) {},
- mixin(mixin: ComponentOptions) {},
- component(name: string, component?: PublicAPIComponent): any {},
- directive(name: string, directive?: Directive) {},
- mount(rootContainer: HostElement, isHydrate?: boolean): any {}
- unmount() {}
- provide(key, value) {}
- }
- return app
- }
- }
复制代码 mount方法就是在第二步中的主要逻辑。
- function injectNativeTagCheck(app: App) {
- // Inject `isNativeTag`
- // this is used for component name validation (dev only)
- Object.defineProperty(app.config, 'isNativeTag', {
- value: (tag: string) => isHTMLTag(tag) || isSVGTag(tag),
- writable: false
- })
- }
复制代码 注入验证组件name的isNativeTag。至此,第一步告一段落。
2. mount
挂载:
- const { mount } = app
- app.mount = (containerOrSelector: Element | string): any => {
- const container = normalizeContainer(containerOrSelector) // document.querySelector(container)或者container
- if (!container) return const component = app._component // App根组件
- 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
- }
复制代码 mount的过程:
- function mount(rootContainer: HostElement, isHydrate?: boolean): any {
- if (!isMounted) {
- const vnode = createVNode(rootComponent as Component, rootProps)
- // store app context on the root VNode.
- // this will be set on the root instance on initial mount.
- vnode.appContext = context // createApp时创建的app上下文
- // HMR root reload
- if (__DEV__) {
- context.reload = () => { // 什么时候触发???
- render(cloneVNode(vnode), rootContainer)
- }
- }
- if (isHydrate && hydrate) {
- hydrate(vnode as VNode<Node, Element>, rootContainer as any)
- } else {
- render(vnode, rootContainer)
- }
-
- isMounted = true
- app._container = rootContainer
-
- return vnode.component!.proxy
- }
- }
复制代码 isMounted为false,基于根组件创建vnode,绑定上下文,执行render函数完成页面渲染,isMounted置为true,app实例的_container绑定根DOM元素root。
接下来就是老太太裹脚布般的render过程:
- const render: RootRenderFunction = (vnode, container) => {
- if (vnode == null) {
- if (container._vnode) {
- unmount(container._vnode, null, null, true) // 卸载
- }
- } else {
- patch(container._vnode || null, vnode, container)
- }
- flushPostFlushCbs() // check递归次数
- container._vnode = vnode
- }
复制代码 patch(null, vnode, container)打补丁。
- const patch: PatchFn = (
- n1,
- n2,
- container,
- anchor = null,
- parentComponent = null,
- parentSuspense = null,
- isSVG = false,
- optimized = false ) => {
- // patching & not same type, unmount old tree
- if (n1 && !isSameVNodeType(n1, n2)) {
- anchor = getNextHostNode(n1)
- unmount(n1, parentComponent, parentSuspense, true)
- n1 = null
- }
- if (n2.patchFlag === PatchFlags.BAIL) {
- optimized = false
- n2.dynamicChildren = null
- }
- const { type, ref, shapeFlag } = n2
- switch (type) {
- case Text:
- processText(n1, n2, container, anchor)
- break
- case Comment:
- processCommentNode(n1, n2, container, anchor)
- break
- case Static:
- if (n1 == null) {
- mountStaticNode(n2, container, anchor, isSVG)
- } else if (__DEV__) {
- patchStaticNode(n1, n2, container, isSVG)
- }
- break
- case Fragment:
- processFragment(/*参数还是那些参数*/)
- break
- default:
- if (shapeFlag & ShapeFlags.ELEMENT) {
- processElement(/*参数还是那些参数*/)
- } else if (shapeFlag & ShapeFlags.COMPONENT) {
- processComponent(/*参数还是那些参数*/)
- } else if (shapeFlag & ShapeFlags.TELEPORT) {
- ;(type as typeof TeleportImpl).process( /*参数还是那些参数,*/ internals )
- } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
- ;(type as typeof SuspenseImpl).process( /*参数还是那些参数,*/ internals )
- } else if (__DEV__) {
- warn('Invalid VNode type:', type, `(${typeof type})`)
- }
- }
- // set ref
- if (ref != null && parentComponent) {
- setRef(ref, n1 && n1.ref, parentComponent, parentSuspense, n2)
- }
- }
复制代码 在这个[url=]单元测试[/url]的情况下,type是App对象,即Object类型。ref为undefined,shapeFlag为4,所以来到了processComponent方法。
processComponent
--> mountComponent
--> instance = createComponentInstance(vnode, parent = null, suspense = null)
--> instance.ctx = createRenderContext(instance)
--> setupComponent(instance)
--> initProps(instance, props = null, isStateful = 4, isSSR = false)
--> initSlots(instance, children = null)
--> setupStatefulComponent(instance, isSSR)
-->setupRenderEffect( instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized )
normalizePropsOptions函数可以看做类似扁平化,返回的是[normalized, needCastKeys],是对props、extends、mixins中的props做递归,浅拷贝得到的props和驼峰命名的key的集合。
initProps初始化给instance实例的props和attrs,并对props最外层数据做响应式。
initSlot初始化给instance实例的slots为响应的vnode
setupStatefulComponent先对组件名、子组件名以及指令进行预判,给instance添加给accessCache、proxy属性,执行setup方法,然后给instance添加render函数(与vue2相同,去组件的render函数或者编译template生成),最后是一些兼容2.x的操作。
最后是setupRenderEffect方法,根据instance.isMounted属性判断是首次渲染还是更新,执行patch --> processElement --> mountElement,呈现视图。
|
|