淘宝客做销量的网站有哪些,wordpress 锚文本插件,seo最新,中文电影网页模板免费下载关注若川视野, 回复pdf 领取资料#xff0c;回复1#xff0c;可加群长期交流学习刘崇桢#xff0c;微医云服务团队前端工程师#xff0c;左手抱娃、右手持家的非典型码农。9 月初 Vue.js 3.0 正式发布#xff0c;代号 One Piece。大秘… 关注若川视野, 回复pdf 领取资料回复1可加群长期交流学习刘崇桢微医云服务团队前端工程师左手抱娃、右手持家的非典型码农。9 月初 Vue.js 3.0 正式发布代号 One Piece。大秘宝都摆到眼巴前了再不扒拉扒拉就说不过去了。那我们就从初始化开始。目标弄清楚 createApp(App).mount(#app) 到底做了什么弄清楚 Vue3.0 的初始化渲染是怎么样的过程能收获到什么了解 Vue3.0 的初始化过程介绍一个阅读 Vue3.0 源码的入口和方向先跑起来将 vue-next 代码克隆到本地打开 package.json 将 scripts dev 末尾加上 --sourcemap。然后 yarn devvue 目录下的 dist 打包出了一份 vue.global.js 和相应的 sourcemap 文件。这样方便我们一步一步调试代码查看程序在 call Stack 中的每一步调用。查看 vue 官方给出的 demo发现 vue 的使用分为 classic 和 composition我们先用 classic 方式实现一个最简单的 demo。const app {data () {return {counter: 1}}
}
Vue.createApp(app).mount(#app)
ok页面跑起来了。我们就在这段代码打个断点然后一步一步的调试观察createApp(App).mount(#app)到底做了什么了解Vue3.0的初始化过程。在这之前简单了解一下整体的背景我们这次主要涉及到 runtime 运行时的代码。runtime-dom我们先跟着代码进入createApp(App).mount(#app);这个 createApp() 来自 runtime-dom我们通过这个图可以看到他大致做的事情return 了一个注册了 mount 方法 app。这样我们的 demo 至少能跑起来不报错。createApp 调用了 ensureRenderer 方法他确保你能得到一个 renderer 渲染器。renderer 是通过调用创建渲染器的 createRenderer 来生成的这个 createRenderer 来自于 runtime-core后面我们会看到。而这个 rendererOptions 是什么呢const rendererOptions extend({ patchProp, forcePatchProp }, nodeOps);export const nodeOps: OmitRendererOptionsNode, Element, patchProp {insert: (child, parent, anchor) {parent.insertBefore(child, anchor || null);},remove,createElement,createText,// ...
};
是不是就是一些 DOM API 的高阶封装这个在 vue 的生态中叫平台特性。vue 源码中的平台特性就是针对 web 平台的。如果开发者想要在别的平台上运行 vue比如 mpvue、weex就不需要 fork 源码库改源码了直接把 nodeOps 中的方法按着平台的特性逐一实现就可以了。这也是 createRenderer 等跨平台的代码放到 runtime-core 中的原因。当然 runtime-dom 远远不只图中这些东西我们先大致过一下初始化过程以对 vue3.0 有一个大致的了解。runtime-core紧接着进入 runtime-core创建渲染器我们注意 baseCreateRenderer 这个 fn2000 多行的代码量里面的东西都是渲染的核心代码从平台特性 options 取出相关 API实现了 patch、处理节点、处理组件、更新组件、安装组件实例等等方法最终返回了一个对象。这里我们看到了【2】中渲染器调用的 createApp 方法他是通过 createAppAPI 创建的。代码进入 createAppAPI。这里我们又看见了熟悉的 Vue2.x 中的 API挂载在 app 上面。至此Vue.createApp(app).mount(#app)创建 app 实例的流程终于在【7】中 return app 告一段落我们拿到了【2】中的 app 实例。大致瞄一眼 app 我们可以在 apiCreateApp.ts 中找到其实现初次渲染 .mount(#app)上面的介绍中其实有两处 .mount 的实现一处是在 runtime-dom【2】中的 mount我们叫他 dom-mount。一处是【7】中的 mount我们叫他 core-mount。dom-mount的实现const { mount } app; // 先暂存core-mount
app.mount (containerOrSelector: Element | string): any {const container normalizeContainer(containerOrSelector); // #app dom 节点if (!container) return;const component app._component;if (!isFunction(component) !component.render !component.template) {component.template container.innerHTML; // 平台特性的逻辑}// clear content before mountingcontainer.innerHTML ;const proxy mount(container); // 执行core-mountcontainer.removeAttribute(v-cloak);return proxy;
};
dom-mount 并不是重写 core-mount而是提取了平台特性的逻辑。比如上面如果 component 不是 function又没有 render、template就读取 dom 节点内部的 html 作为渲染模板。然后再执行 core-mountmount(container)。代码很简单就两步创建根组件的 vnode渲染这个 vnode创建根组件的vnode创建 vnode是一个初始化 vnode 的过程这个阶段中下面的这些属性被初始化为具体的值还有很多属性没有罗列都是初始值。当 vnode 描述不同的事物时他的属性值也各不相同这些在 vnode 初始化阶段确定的属性在渲染组件时能带来非常重要的效率提升。type标识 VNode 的种类html 标签的描述type 属性就是一个字符串即标签的名字组件的描述type 属性就是引用组件类或函数本身文本节点的描述type 属性就是 nullpatchFlag标识组件变化的地方shapeFlagVNode 的标识标明 VNode 属于哪一类demo 中的shapeFlag 是 4STATEFUL_COMPONENT有状态的组件。在packages/shared/src/shapeFlags.ts中定义了这些通过将十进制数字 1 左移不同的位数得来的枚举值。export const enum ShapeFlags {ELEMENT 1, // 1 - html/svg 标签FUNCTIONAL_COMPONENT 1 1, // 2 - 函数式组件STATEFUL_COMPONENT 1 2, // 4 - 有状态组件TEXT_CHILDREN 1 3, // 8ARRAY_CHILDREN 1 4, // 16SLOTS_CHILDREN 1 5, // 32TELEPORT 1 6, // 64SUSPENSE 1 7, // 128COMPONENT_SHOULD_KEEP_ALIVE 1 8, // 256 - 需要被 keepAlive 的有状态组件COMPONENT_KEPT_ALIVE 1 9, // 512 - 已经被 keepAlive 的有状态组件COMPONENT ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT // 组件
}
为什么为 VNode 标识这些枚举值呢在 Vue2.x 的 patch 过程中代码通过 createElm 区分 VNode 是 html 还是组件或者 text 文本。所以 Vue2.x 的 patch 是一个试错过程在这个阶段是有很大的性能损耗的。Vue3.0 把对 VNode 的判断放到了创建的时候这样在 patch 的时候就能避免消耗性能的判断。最终我们看一下 vnode 的结构export interface VNodeHostNode RendererNode,HostElement RendererElement,ExtraProps { [key: string]: any }{/*** internal*/__v_isVNode: true // 一个始终为 true 的值有了它我们就可以判断一个对象是否是 VNode 对象/*** internal 内部属性*/[ReactiveFlags.SKIP]: truetype: VNodeTypesprops: (VNodeProps ExtraProps) | nullkey: string | number | nullref: VNodeNormalizedRef | nullscopeId: string | null // SFC onlychildren: VNodeNormalizedChildrencomponent: ComponentInternalInstance | nulldirs: DirectiveBinding[] | nulltransition: TransitionHooksHostElement | null// DOM 相关el: HostNode | nullanchor: HostNode | null // fragment anchortarget: HostElement | null // teleport targettargetAnchor: HostNode | null // teleport target anchorstaticCount: number // number of elements contained in a static vnode// suspense 支持 suspense 的属性suspense: SuspenseBoundary | nullssContent: VNode | nullssFallback: VNode | null// optimization only 优化模式中使用的属性shapeFlag: numberpatchFlag: numberdynamicProps: string[] | nulldynamicChildren: VNode[] | null// application root node onlyappContext: AppContext | null
}
渲染这个vnodeok书接上回我们拿到 根组件的 VNode接下来执行到 render 函数。render 的核心逻辑就是 patch 函数。patch 函数patch 有两种含义: 1整个虚拟 dom 映射到真实 dom 的过程2patch 函数。我们这里讲的是函数。patch 就是 render 渲染组件的关键逻辑【5】中 baseCreateRenderer 2000 行左右的代码主要是为了 patch 服务的。// patching not same type, unmount old tree
if (n1 !isSameVNodeType(n1, n2)) {anchor getNextHostNode(n1)unmount(n1, parentComponent, parentSuspense, true)n1 null
}
// 对于前后节点类型不同的vue 是直接卸载之前的然后重新渲染新的不会考虑可能的子节点复用。
...const { type, ref, shapeFlag } n2
switch (type) { // 根据节点类型 type 分发到不同的 processcase Text:processText(n1, n2, container, anchor)breakcase Comment:processCommentNode(n1, n2, container, anchor)breakcase Static:...case Fragment: ...default: // 根据不同的节点标识 shapeFlag 分发到不同的 processif (shapeFlag ShapeFlags.ELEMENT) { processElement(...) } else if (shapeFlag ShapeFlags.COMPONENT) {processComponent(...)...
patch 根据节点 VNode4.1 创建的根组件的 vnode 的 type 和 shapeFlags 执行不同的 process。typeText 文本typeComment 注释typeStatic 静态标签typeFragment 片段VNode 的类型是 Fragment就只需要把该 VNode 的子节点渲染到页面。有了他就没有只能有一个根节点的限制也可以做到组件平级递归shapeFlagsShapeFlags.ELEMENT 原生节点html/svg 标签shapeFlagsShapeFlags.COMPONENT 组件节点shapeFlagsShapeFlags.TELEPORT 传送节点将组件渲染的内容传送到制定的 dom 节点中shapeFlagsShapeFlags.SUSPENSE 挂起节点异步渲染Vue3 新增组件 - Fragment、Teleport、Suspense可见此链接 (https://www.yuque.com/hugsun/vue3/component)我们的 demo 中的根组件 VNode 的 shapeFlag 是 40100ShapeFlags.COMPONENT0110按位与后结果为非零代码会进入 processCompoent。processXXXprocessXXX 是对挂载mount和更新update补丁的统一操作入口。processXXX 会根据节点是否是初次渲染进行不同的操作。如果没有老的 VNode就挂载组件mount。首次挂载递归创建真实节点。如果有老的 VNode就更新组件update。更新补丁的的渲染系统的介绍放到下下篇来介绍。挂载创建组件内部实例内部实例也会暴露一些实例属性给其他更高级的库或工具使用。组件实例属性很多很重要也能帮助理解可以在 packages/runtime-core/src/component.ts 查看实例的接口声明 ComponentInternalInstance。很壮观啊啪的一下 100 多行属性的定义主要包括基本属性、响应式 state 相关、suspense 相关、生命周期钩子等等安装组件实例初始化 props 和 slots安装有状态的组件这里会初始化组件的响应式【15】setupStatefulComponent调用了 setup(props, setupContext)。如果没有 setup 时会调用 applyOptions应用 vue2.x 的 options API最终对 data() 的响应式处理也是使用 vue3.0 的 reactive。上面讲过安装组件实例触发响应式初始化就发生在这里具体怎么触发的这块又是一个千层套路放到下一篇中。【16】主要是根据 template 拿到组件的 render 渲染函数和应用 vue2.x 的 options API。我们看一下 template 模板编译后生成的 render 函数。我们大致看下生成的 render 函数有几点需要注意这里的 render 函数执行后的返回是组件的 VNode_createVNode 函数用于创建 VNode_createVNode函数的入参type、patchFlags、dynamicProps等function _createVNode(type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, // type标识 VNode 的种类props: (Data VNodeProps) | null null,children: unknown null,patchFlag: number 0, // 标记节点动态变化的地方dynamicProps: string[] | null null, // 动态 propsisBlockNode false
): VNode { ... }
createVNode 在创建根节点的时候就出现过用于创建虚拟 DOM。这个是内部使用的 API面向用户的 API 还是h函数。export function h(type: any, propsOrChildren?: any, children?: any): VNode { ... }
h 的实现也是调用 createVNode但是没有 patchFlag、dynamicProps、isBlockNode 这三个参数。也就是 h 是没有 optimization 的应该是因为这三个参数让用户自己算容易出错。看来这个 patchFlags 有点意思标识组件变化的地方用于 patch 的 diff 算法优化。export const enum PatchFlags {TEXT 1, // 动态文字内容CLASS 1 1, // [2]动态 class 绑定STYLE 1 2, // [4]动态样式PROPS 1 3, // [8]动态 props不是 class 和 style 的动态 propsFULL_PROPS 1 4, // [16]有动态的 key也就是说 props 对象的 key 不是确定的。key 变化时进行一次 full diffHYDRATE_EVENTS 1 5, // [32]STABLE_FRAGMENT 1 6, // [64]children 顺序确定的 fragmentKEYED_FRAGMENT 1 7, // [128]children 中有带有 key 的节点的 fragmentUNKEYED_FRAGMENT 1 8, // [256]没有 key 的 children 的 fragmentNEED_PATCH 1 9, // [512]DYNAMIC_SLOTS 1 10, // [1024]动态的插槽// SPECIAL FLAGS -------------------------------------------------------------// 以下是特殊的 flag负值HOISTED -1, // 表示他是静态节点他的内容永远不会改变BAIL -2, // 用来表示一个节点的 diff 应该结束
}
之所以使用位运算是因为用 | 来进行复合TEXT | PROPS得到0000 1001即十进制 9。标识他既有动态文字内容也有动态 props。用 进行 checkpatchFlag TEXT0000 1001 0000 0001得到0000 0001只要结果大于 0就说明属性命中。方便扩展、计算更快...patchFlag 被赋值到 VNode 的属性中他在后面更新节点时会被用到。为了配合代码的正常流转先放一放代码继续 F10。如果你去调试代码会发现这真的是千层套路啊一直 shift F11 跳出代码到怀疑人生才终于回到 mountComponent...总结一下 setupComponent 安装组件实例主要做了什么事情initProps、initSlots、响应式初始化、得到模板的 render 函数等等。回顾前文跳出到【13】setup 安装组件实例后下一步是 setupRenderEffect 激活渲染函数的副作用激活渲染函数的副作用 setupRenderEffect实现基于【21】effect 副作用意味着响应式数据变化后引起的变更。effect 源自 reactive传入一个 fn 得到一个 reactiveEffect。effect 的入参 componentEffect 是一个命名函数会立即执行。componentEffect 执行过程中触发响应式数据的 getter 拦截会在全局数据响应关系仓库记录当前componentEffect。在响应式对象发生改变时派发更新执行componentEffect。回到componentEffectfunction componentEffect() {if (!instance.isMounted) {let vnodeHook: VNodeHook | null | undefinedconst { el, props } initialVNodeconst { bm, m, parent } instance// beforeMount hook 生命周期钩子函数if (bm) {invokeArrayFns(bm)}...// subTree 根节点的 subTree通过 renderComponentRoot 根据 render 生成的 vnode//大家回忆一下 render 是什么是不是根组件的 template 编译后得到的好多_createVNode 的渲染器函数const subTree (instance.subTree renderComponentRoot(instance))...// 更新patch(null, subTree, container, ...)...if (m) { // parent 的 mounted 执行之前先执行 subTree 的 patchqueuePostRenderEffect(m, parentSuspense)}...instance.isMounted true // 标志实例已挂载} else { ... }
}
执行前面编译后得到的渲染函数 render生成subTree: vnode最后执行 patch上文中渲染根节点的 vnode 时执行过 patch这里就进入了一个大循环根据组件的 children 的 type 和 shapeFlagbaseCreateRenderer 会继续进行各种 processXXX 处理直至基于 平台特性 的 DOM 操作 挂载到各自的父节点中。这个顺序是深度遍历的过程子节点的 patch 完成之后再进行父节点的 mounted。patch 循环 subTree 一览// subTree 的 模板 template
div idapph1composition-api/h1p clickadd :attr-keycounter{{counter}}/pp :class{counter: counter % 2}{{doubleCounter}}/p
/div// patchFlag: 64
// STABLE_FRAGMENT 1 6, // 64 表示children 顺序确定的 fragment
// shapeFlag: 16
// ARRAY_CHILDREN 1 4, // 16
观察上面这个模板Vue2.x 中的模板只能有一个根元素Vue3.0 的这个 demo 中有三个根元素这得益于新增的 fragment 组件。vnode 标识出来 patchFlag64表示 children 顺序确定的 fragmentvnode 标识出来 shapeFlag:16表示当前节点是一个孩子数组。vnode 标识出来 dynamicChildren标识动态变化的孩子节点。显然是两个 p 标签可以想象这个数组的元素也是当前呈现的 vnode只不过具体属性值不同罢了等等还有 4 吗我不知道...当然还有processxxx 中一般都会判断是挂载还是更新更新的时候就会用到 patchFlag比如 patchElement... 下次一定等等还有 5 吗我不知道...当然还有第五层我就已经裂开了啊...ああげない あ不给你哦 ????????????
いいらない い不要了啦 ????????????
ううごけない う动不了了 ????????????
ええらべない え不会选嘛 ????????????
おおせない お按不到耶 [裂开][裂开][裂开]
刚看源码不久只能靠 F11 、参考其他文档凭自己的理解写出这样的文章肯定有很多理解不对的地方希望得到批判指正。附录Vue3初始化.drawio (https://www.yuque.com/office/yuque/0/2020/drawio/441847/1605880555730-4e18923f-c087-4082-af06-ec51986ba658.drawio?fromhttps%3A%2F%2Fwww.yuque.com%2Fdocs%2Fshare%2F64bd5cdc-3086-4154-a447-04032d161830%3F%23)推荐阅读我在阿里招前端我该怎么帮你现在还可以加模拟面试群如何拿下阿里巴巴 P6 的前端 Offer如何准备阿里P6/P7前端面试--项目经历准备篇大厂面试官常问的亮点该如何做出如何从初级到专家(P4-P7)打破成长瓶颈和有效突破若川知乎问答2年前端经验做的项目没什么技术含量怎么办若川知乎高赞有哪些必看的 JS库末尾你好我是若川江湖人称菜如若川历时一年只写了一个学习源码整体架构系列~(点击蓝字了解我)关注若川视野回复pdf 领取优质前端书籍pdf回复1可加群长期交流学习我的博客地址https://lxchuan12.gitee.io 欢迎收藏觉得文章不错可以点个在看呀^_^另外欢迎留言交流小提醒若川视野公众号面试、源码等文章合集在菜单栏中间【源码精选】按钮欢迎点击阅读也可以星标我的公众号便于查找