vue 3.0 源码解读
2020-11-11 21:45:28 1 举报
AI智能生成
vue3.0源码分析
作者其他创作
大纲/内容
vue 3.0 源码执行流程图
初始化vue全局函数和变量
@Import
类:EnableAutoConfigurationImportSelector
方法:SpringFactoriesLoader.loadFactoryNames
扫描:META-INF/spring.factories
const emptyAppContext = createAppContext()
执行createAppContext,初始化createApp的appContext(执行上下文)
config
warnHandler: undefined // 警告处理函数
mixins: []
components: {}
provides: Object.create(null) // 提供属性
执行packages/vue/index.ts文件
__DEV__ && initDev()
/ 初始化devexport function initDev() { // 判断运行环境,引入全局变量window或者global const target: any = __BROWSER__ ? window : global // window/global上添加属性version target.__VUE__ = version // 设置dev hook setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__) // 浏览器提示 if (__BROWSER__) { console[console.info ? 'info' : 'log']( `You are running a development build of Vue.\` + `Make sure to use the production build (*.prod.js) when deploying for production.` ) }}
// 设定dev hookexport function setDevtoolsHook(hook: DevtoolsHook) { devtools = hook}
初始化compileToFunction
注册compile函数:registerRuntimeCompiler(compileToFunction)
let compile: CompileFunction | undefined// 注册compile函数export function registerRuntimeCompiler(_compile: any) { compile = _compile}
app = createApp(App)
执行createApp方法
const app = ensureRenderer().createApp(...args)
执行baseCreateRenderer函数:注意这里使用了ts语法的函数重载,分别用于生成SPA和服务端渲染的renderer
执行完ensureRenderer方法后,放回的对象中含有createApp方法,下一步开始执行这个方法,生成app
// 初始化app中context上下文的选项 const context = createAppContext()
// 初始化安装插件集合 const installedPlugins = new Set()
// 初次渲染isMounted设置为false let isMounted = false
// 初始化app中的选项 const app: App = {...}
_component
_props
_container
_context
get/set config
use
mixin
component
directive
unmount
provide
// 在app中执行上下文挂载app实例对象 context.__app = app
最后返回app(return app)
包装app.mount方法,最后返回app实例,即最终的app
app.mount('#app')
执行app中的mount方法,挂载vue实例
如果第一次挂载实例,执行里面的逻辑
执行createVnode方法:// 创建虚拟dom入口,开发环境使用createVNodeWithArgsTransform将createVNode映射到_createVNode,// 生产环境_createVNode// 最后都是调用_createVNode这个方法export const createVNode = (__DEV__ ? createVNodeWithArgsTransform : _createVNode) as typeof _createVNode
执行_createVnode方法**第一次执行这个方法的时候,只传入type一个参数1. 首先会将传入的template(app)编译为ast2. 将ast转为vnode3. 生成对应平台的render哈数
1. 判断是否传入了type// type不存在 if (!type || type === NULL_DYNAMIC_COMPONENT) { if (__DEV__ && !type) { warn(`Invalid vnode type when creating vnode: ${type}.`) } type = Comment }
3. 判断是不是函数组件
4. 初始化传入的type的shapeFlag类型,如果为component组件,值为4
5. 检查该type是否已经响应式处理,如果初次挂载时,响应式处理了,则报错
6. 初始化vnode tree根节点
7. 排除vnode中键为NaN的情况
8. 执行normalizeChildren函数解析子节点children,component初始化时子节点为null
设置树根节点的children和shapeFlag属性
9. 保存vnode
10. 返回当前树根节点vnode
2. 将app执行上下文appContext挂载到vnode根节点属性中
3. dev模式,实现模块热更新加载
4. 判断是ssr还是spa渲染
1. 如果是ssr渲染,则执行ssr render
2. 如果是spa单页面应用,则render单页面
执行render方法路径:packages\untime-core\\src\enderer.ts
判断传入的vnode是否为null
1. vnode为null
unmount已经挂载的节点
2. vnode 不为null
编译template模板为vnode
执行patch(renderer.ts)方法:1. 初始化时,生成vnode;2. 如果vnode已经渲染过,则执行diff算法更新对应的旧vnode;
1. 判断是更新节点还是初次渲染:已经渲染过,并且新的vnode和旧的vnode不一样,则卸载节点
2. PatchFlags为-2(PatchFlags.Bail)时,不做性能优化,optimized为false
判断vnode的shapeFlag,这里以shapeFlag为ShapeFlags.COMPONENT为例
执行processComponent方法,处理组件,生成vnode:
判断是首次渲染还是已经渲染过
1. 初次渲染,即n1为null
缓冲组件,即传入了keep-alive
不缓冲组件
执行mountComponent方法,初次挂载组件
1. 初始化化组件实例:instance(执行ComponentInternalInstance(packages\untime-core\\src\\component.ts))
执行ComponentInternalInstance(packages\untime-core\\src\\component.ts)
1. 初始化实例instance选项
2. 设置instance的执行上下文context属性
dev模式
1. 执行createRenderContext方法,生成instance上下文2. 返回target对象:return target as ComponentRenderContext // 定义组件渲染上下文接口export interface ComponentRenderContext { [key: string]: any // 共有和全局属性 _: ComponentInternalInstance // 组件实例instance}***这里与pro不同之处在于,在instance上挂载了共有和全局配置属性,这些属性只可以配置,不可以枚举***
pro模式
instance的执行上下文为self
3. 设置instance实例的root
4. 设置instance的emit方法
5. dev模式:将instance添加到devtools中
6. 返回当前实例对象:instance
2. dev模式:开启热模块更新功能
3. dev模式:将vnode存储到stack中
4. 如果使用了keep-alive(缓冲组件),则将instance的ctx中的renderer设置为internals
5. 初始化组件:setupComponent(instance)--> runtime-core\\src\\component.ts 这里已经完成了setup函数初始化 --> ast -->ast.codegenNode --> code --> 组件实例选项初始化(data/mixins/computed/lifeCycle等) 此时实例instace已经生成render函数
1. 设置ssr: // 重新设置isInSSRComponentSetup的值 isInSSRComponentSetup = isSSR
执行initProps方法
1. 初始化props和attrs属性const props: Data = {} const attrs: Data = {}
执行def方法
执行normalizePropsOptions方法
1. 如果comp._props存在,直接返回comp._props
2. 初始化normalize、needCastKeys、hasExtends变量
3. 判断comp不是是函数组件并且全局__FEATURE_OPTIONS__为true时__FEATURE_OPTIONS__ && !isFunction(comp),处理组件中的extends和mixins的props
1. 处理comp.extends
2. 处理comp.mixins
4. !raw && !hasExtends:props和混入的属性不存在if (!raw && !hasExtends) { return (comp.__props = EMPTY_ARR) }
5. raw为为数组
遍历数组,将数组中每项最小化后存储到normalized中
6. raw为对象Object
1. 验证raw是否为对象
2. 遍历raw,将每项分别存储到normalized和needCastKeys中
8. 在comp上挂载normalizedEntry属性
9. 返回normalizedEntry对象
2. rawProps存在,例如id/class/value等,格式化
5. 判断是状态组件还是函数组件
1. 状态组件:isStateful=true
1. ssr渲染:实例上直接挂载props属性instance.props = props
2. spa:设置为一层响应对象instance.props = shallowReactive(props)
2. 函数组件:isStateful=false
1. 不存在:instance.props = attrs
2. 存在: instance.props = props
6. 设置实例instance的attrs属性:instance.attrs = attrs
1. instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN:判断是否shapeFlag为slots插槽
1. true: 设置instance的slots
执行normalizeVNodeSlots方法
1. 判断是不是缓冲组件
2. 格式化插槽中的值const normalized = normalizeSlotValue(children)
执行normalizeSlotValue函数
执行normalizeVNode方法将slot生成vnode判断出入的child类型
1. child == null || typeof child === 'boolean'
执行// 如果为空的占位符 return createVNode(Comment)
2. isArray(child)
3. typeof child === 'object'
执行// 这是vnode已经是真正的vnode,这应该是最常见的, // 因为编译模板总是生成所有vnode子数组 return child.el === null ? child : cloneVNode(child)
4. default
3. 设置instance.slots.default instance.slots.default = () => normalized
1. 验证组件的名字是否合法
1. 验证组件名字,执行validateComponentName方法
2. 验证组件中的子组件Component.components,循环遍历
3. 验证component.directives
4. 创建渲染代理属性访问缓存instance.accessCache = {}
6. dev模式:将props属性挂载到ctx上exposePropsOnRenderContext(instance)
7. Component中setup存在,执行该函数
1. 生成setup执行上下文setupContext
执行createSetupContext方法
2. 设置当前实例:// 保存当前实例 currentInstance = instance
3. 初始化停止依赖收集// 初始化时,停止依赖收集 pauseTracking()
4. 执行callWithErrorHandling方法,完成setup函数初始化设置返回setupResult结果
执行callWithErrorHandling方法
执行fn(setup函数)
1. ref api执行路径:reactivity\\src\ef.ts
执行ref(value)方法
执行createRef(value)
1. 判断传入的value是否已经是响应式(value中存在__v_isRef属性为响应式)
2. 判断是否为浅响应式// 是否为一层响应 let value = shallow ? rawValue : convert(rawValue)
不是浅响应,执行convert方法,将其变成深响应对象
3. 初始化响应式对象set/get:r
get value 方法,收集依赖,执行track方法
1. 判断是否允许收集依赖,不允许直接返回if (!shouldTrack || activeEffect === undefined) { return }
2. 判断依赖图谱targetMap中是否存在该target,不存在则将其保存到targetMap
3. targetMap存在该target,获取对应的key值,如果dep不存在,则设置该值为new Set()
4. 开始收集依赖
set value,执行trigger,触发更新
4. 返回r
2. 执行reactive方法路径:\eactivity\\src\eactive.ts
执行reactive(target: object)方法
如果target上存在__v_isReadonly属性,直接返回
创建响应式proxy对象,执行createReactiveObject
1. 判断传入的target是否为object,不是直接返回
2. target为只读属性,直接返回target
3. target已经是响应式proxy对象,返回该响应式对象
4. target是否允许观察,不允许直接返回target
5. 使用new Proxy将target变成响应式对象
6. 在target上设置响应式对象observed
7. 返回observed
3. 执行toRefs方法:toRefs(state)路径:reactivity\\src\ef.ts
遍历state,将里面的属性执行toRef操作
返回结果: res
5. 重新收集依赖: resetTracking()
6. 清空当前实例 currentInstance = null
7. 判断setupResult是否为Promise对象
1. promise对象,执行
2. __FEATURE_SUSPENSE__:true// async setup returned Promise. // bail here and wait for re-entry. instance.asyncDep = setupResult
执行handleSetupResult方法:路径:runtime-core\\src\\component.ts
1. setupResult为函数
2. setupResult为对象
3. setupResult其他情况,则报错
执行finishComponentSetup方法
1. 获取组件:const Component = instance.type as ComponentOptions
2. ssr渲染,instance.render = Component.render
执行compile方法,生成render函数(编译过程单独分支,请看下面的compile)
4. 设置instance.render函数instance.render = (Component.render || NOOP) as InternalRenderFunction
5. // 对于使用'with'块的运行时编译render, // 使用的render代理需要一个不同的'has'处理方式, // 这个代理render更高效,而且只允许全局的白名单失效。
6. 重置ssr:isInSSRComponentSetup = false
2. 初次渲染已完成,节点更新
执行updateComponent方法,更新组件
4. 设置ref
5. 说明:后面定义了一些vnode的操作方法,例如:processText、mountStaticNode等
5. 设置挂载状态为已经挂载:isMounted = true
6. 设置apps属性_container app._container = rootContainer // #app
8. 返回: return vnode.component!.proxy(这里的!含义:如果vnode没有component这个属性,访问proxy也不会报错)
如果重复调用$mount方法,则报错
compile(compileToFunction):编译过程,生成render路径:\\vue\\src\\index.ts
1. template不是字符串,如果template的nodeType存在,则template = template.innerHTML,否自报错
2. 判断compileCache是否有缓冲template,存在直接返回缓冲值
执行baseCompile函数,这里在compile函数options中添加指令转换、节点转换等方法
1. 获取编译compile错误处理函数,const onError = options.onError || defaultOnError // 编译错误处理函数
2. 判断传入的template是不是module,即.vue文件// 是不是模块模式 const isModuleMode = options.mode === 'module' // 判断是不是模块
3. options.prefixIdentifiers ===true,或者isModuleMode= true,则报错
4. 处理编译错误
2. 获取解析模板开始位置startconst start = getCursor(context) // 信息:line/column/offset
执行parseChildren方法,使用while语句循环遍历,直到结束
1. 获取父元素,初始化时为undefinedconst parent = last(ancestors)
// 获取数组中最后一个元素function last<T>(xs: T[]): T | undefined { return xs[xs.length - 1]}
2. 获取命名空间:const ns = parent ? parent.ns : Namespaces.HTML
3. 定义nodes,保存节点 const nodes: TemplateChildNode[] = []
1. 获取字符串模板template,即sourceconst s = context.source
2. 初始化节点node:const s = context.source let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined
3. 判断mode,初始化为TextModes.DATA,mode === TextModes.DATA || mode === TextModes.RCDATA,执行node解析
2. 如果mode === TextModes.DATA && s[0] === '<',则正常解析,表示为开始标签
3. 如果s[1] === '/',则会抛出解析错误
4. 如果/[a-z]/i.test(s[1]),则解析元素节点
执行createCompilerError函数,生成错误error
执行parseText方法,解析文本节点
2. 如果mode === TextModes.CDATA,则endTokens.push(']]>')
3. 获取结束索引位置:let endIndex = context.source.length
5.重新获取开始位置start,const start = getCursor(context)
执行advanced方法,移动行、列,逐步解析字符串
截取context中source字符串 context.source = source.slice(numberOfCharacters)
4. 满足如下条件,直接返回原始文本内容:return rawText mode === TextModes.RAWTEXT || mode === TextModes.CDATA || rawText.indexOf('&') === -1
5. 不满足上述条件,// DATA or RCDATA containing \"&\"\
1. 判断解析完的node是否为文本节点:node.type === NodeTypes.TEXT
获取nodes中最后一项,判断是文本节点,进行相应的合并操作
2. 将node存储到nodes中
执行getBaseTransformPreset函数
7. 执行transform方法,在ast每个节点上生成codegenNode属性
执行transform函数,路径:compiler-core\\src\\transform.ts
1. 设置context上下文中currentNode为node,context.currentNode = node
3. 循环遍历节点,进行指令转换:
分别执行对应的指令转换函数
1. v-once
2. v-if/v-else/v-else-if:createStructuralDirectiveTransform
1. 判断传入name是字符串还是正则: const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n)
2. 返回参数为node/context的函数
节点type为元素:node.type === NodeTypes.ELEMENT
1. 获取节点中的props:const { props } = node
3. 遍历循环props,解析v-if/v-else-if/v-else
1. 前属性类型为指令并且为v-if/v-else/v-else-if:prop.type === NodeTypes.DIRECTIVE && matches(prop.name
1. 转换v-if/v-else-if/v-else
2. 执行transformFor转换v-for
2. 返回exitFns
3. {{ ... }}:transformExpression
4. v-slot:transformSlotOutlet
1. 判断是不是slot插槽:isSlotOutlet(node)
2. 不是slot,直接退出当前函数
5. element:transformElement路径:compiler-core\\src\\transforms\\transformElement.ts
2. 判断标签类型是不是组件:const isComponent = node.tagType === ElementTypes.COMPONENT
1. 组件的情况:执行resolveComponentType方法
1. 获取node中的tag:const { tag } = node
isProp为true
执行createSimpleExpression函数
执行createCallExpression方法
1. SSR渲染:return builtIn
2. 不是SSR渲染:if (!ssr) context.helper(builtIn)
4.使用异步组件,将解析componentcontext.helper(RESOLVE_COMPONENT)
5. 将组件添加到context.components: context.components.add(tag)
2. 不是组件component,值为 `\"${tag}\"`
4. 判断是不是动态组件: const isDynamicComponent = isObject(vnodeTag) && vnodeTag.callee === RESOLVE_DYNAMIC_COMPONENT
5. 初始化一些变量:let vnodeProps: VNodeCall['props'] let vnodeChildren: VNodeCall['children'] let vnodePatchFlag: VNodeCall['patchFlag'] let patchFlag: number = 0 let vnodeDynamicProps: VNodeCall['dynamicProps'] let dynamicPropNames: string[] | undefined let vnodeDirectives: VNodeCall['directives']
6. 判断是否使用块状标记:shouldUseBlock
7. 如果节点上的属性props.length > 0,处理节点属性
8. 如果node的children.length > 0,处理子节点
9. 碎片标记不等于0,处理动态属性,生成碎片标记:patchFlag !== 0设置vnodePatchFlag和vnodeDynamicProps
执行createVNodeCall方法,生成vnode回调环境
6. slotScopes
7. v-text
8. ignoreSideEffect
9. style
10. transition
4. 初始化退出执行函数(节点指令解析完后,会将函数存储到exitFns中,解析完后再执行)
1. node.type为NodeTypes.COMMENT: // 3:注释if (!context.ssr) { //为注释符号注入import, // 这是使用“createVNode”创建注释节点所需要的 context.helper(CREATE_COMMENT)} }
2. node.type为 NodeTypes.INTERPOLATION: // 5:模板{{}}// 不需要遍历,但是需要注入 toString 解析器 if (!context.ssr) { // 将TO_DISPLAY_STRING引入 context.helper(TO_DISPLAY_STRING) }
执行traverseChildren(parent,context)方法
1. 初始化变量i,用于记录节点数量let i = 0
2. 定义nodeRemoved方,节点删除了,对应的i要减1const nodeRemoved = () => { i-- }
1. 获取子节点:const child = parent.children[i]
2. 判断子节点为字符串,continue:if (isString(child)) continue
3. 在context上挂载父节点:contex.parent = parent
4. 设置子节点索引i:context.childIndex = i
5. 设置context中子节点移除方法: context.onNodeRemoved = nodeRemoved
6. 执行exitFns中的函数// 退出转换 let i = exitFns.length while (i--) { // 执行退出函数 exitFns[i]() }
执行createRootCodegen方法,生成根上的codegenNode属性
5. 将context中的属性挂载到root上,即ast, root.helpers = [...context.helpers] root.components = [...context.components] root.directives = [...context.directives] root.imports = [...context.imports] root.hoists = context.hoists root.temps = context.temps root.cached = context.cached
1. 生成code generate上下文context,执行createCodegenContext(ast,options)方法
3. ast解析器,即将ast转为函数可运行时里面用到的函数,例如_createBlock const hasHelpers = ast.helpers.length > 0
4. 判断是否使用块标记:const useWithBlock = !prefixIdentifiers && mode !== 'module'
5. 判断是否生成作用域id:const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
2.ast.helpers.length大于0, 生成解析器导入语句
1. import需要优化处理:optimizeImports为true
3. ssr渲染:从@vue/server-renderer获取解析器ast.ssrHelpers && ast.ssrHelpers.length
执行genImports函数:生成import语句
1. 判断传入的节点是不是字符串:if (isString(node)) { context.push(node) return }
2. node为symbol类型:if (isSymbol(node)) { context.push(context.helper(node)) return }
3. node为其他类型,判断node.type:
1. node.type为:ELEMENT/IF/FOR
2. node.type: NodeTypes.TEXT
3. node.type: NodeTypes.NodeTypes.SIMPLE_EXPRESSION: // 4
4. node.type: NodeTypes.INTERPOLATION: // 5
5. node.type: NodeTypes.TEXT_CALL: // 12
6. node.type: NodeTypes.COMPOUND_EXPRESSION: // 8
7. node.type: NodeTypes.COMMENT: // 3
8. node.type: NodeTypes.VNODE_CALL: // 13
9. node.type:NodeTypes.JS_CALL_EXPRESSION:
10. node.type:NodeTypes.JS_OBJECT_EXPRESSION: // 15
11. node.type:NodeTypes.JS_ARRAY_EXPRESSION:
12. node.type: NodeTypes.JS_FUNCTION_EXPRESSION:
13. node.type:NodeTypes.JS_CONDITIONAL_EXPRESSION:
14. node.type: NodeTypes.JS_CACHE_EXPRESSION:
15. SSR端渲染
1. node.type: NodeTypes.JS_BLOCK_STATEMENT:
2. node.type: NodeTypes.JS_TEMPLATE_LITERAL: // 22
3. node.type: NodeTypes.JS_IF_STATEMENT:
4. node.type: NodeTypes.JS_ASSIGNMENT_EXPRESSION
5. node.type: NodeTypes.JS_SEQUENCE_EXPRESSION:
6. node.type: NodeTypes.JS_RETURN_STATEMENT
16. node.type: NodeTypes.IF_BRANCH: // 10
直接退出
5. 如果需要生成scopeId:if (genScopeId) { push( `const _withId = ${PURE_ANNOTATION}${helper(WITH_SCOPE_ID)}(\"${scopeId}\")` ) newline() }
5. 执行code,生成render函数,并返回render:render = new Function(code)retrurn render
收藏
0 条评论
回复 删除
下一页