什么是垂直型网站安徽seo
Vue3源码系列文章目录
目录
Vue3源码系列文章目录
目录
目录
Vue3源码系列文章目录
前言
一、vue3中createApp()实例是如何创建的,实例是什么样子的?
1、以todoMvc为入口,进行断点调试
2、 其调用了工厂函数ensureRenderer()内部的createApp函数, 进入ensureRenderer
3、ensureRenderer()工厂函数内容据说是vue3中最多的,纠结过程容易迷路,此处直接跳转到该工厂函数返回的结果,
4、找到createAppApi()
5、工厂函数创建的app是这个样子
二、vue3中app.mount(), 挂载都做了什么?
1、创建节点vnode
2、创建render函数并执行render函数, 第一步生成vnode传递给patch函数转换成dom,然后将其添加到宿主上
挂载时做了: 将传入的组件数据和状态转换成DOM,并追加到宿主元素上
三、patch状态更新流程
1、从状态更新处打断点
2、单点进入后进入更新状态counter 此处应该是counter = this.counter += 1 会先走get 拿值, 然后走set赋值
3、 单点产看更新,最终走trigger通过!hadkey判断其走trigger Add(添加), 还是走trigger Set(更新)
4、单点进入trigger Set
5、走triggerEffect / triggerEffects
6、走triggerEffect后进入effect.schedule查看其如何更新
7、单点进入, 激活响应式, 此处想看断点应该打在componentUpdateFn
8、最后走patch更新 对比prevTree和nextTree哪里不同然后去更新
四、Vue3 Composition Api 探究
1、Composition Api包括:
2、结合reactive、生命周期钩子、属性和上下文进行体验
五、Vue3 ReractiveApi探究
前言
学习源码的笔记,以及vue3源码学习过程的问题、思路、总结。知识是容易遗忘的,让其留下痕迹,方便以后回顾学习。
一、vue3中createApp()实例是如何创建的,实例是什么样子的?
1、以todoMvc为入口,进行断点调试
2、 其调用了工厂函数ensureRenderer()内部的createApp函数, 进入ensureRenderer
3、ensureRenderer()工厂函数内容据说是vue3中最多的,纠结过程容易迷路,此处直接跳转到该工厂函数返回的结果,
4、找到createAppApi()
5、工厂函数创建的app是这个样子
export function createAppAPI<HostElement>(render: RootRenderFunction<HostElement>,hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {return function createApp(rootComponent, rootProps = null) {if (!isFunction(rootComponent)) {rootComponent = { ...rootComponent }}if (rootProps != null && !isObject(rootProps)) {__DEV__ && warn(`root props passed to app.mount() must be an object.`)rootProps = null}const context = createAppContext()const installedPlugins = new Set()let isMounted = falseconst app: App = (context.app = {_uid: uid++,_component: rootComponent as ConcreteComponent,_props: rootProps,_container: null,_context: context,_instance: null,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[]) {if (installedPlugins.has(plugin)) {__DEV__ && warn(`Plugin has already been applied to target app.`)} else if (plugin && isFunction(plugin.install)) {installedPlugins.add(plugin)plugin.install(app, ...options)} else if (isFunction(plugin)) {installedPlugins.add(plugin)plugin(app, ...options)} else if (__DEV__) {warn(`A plugin must either be a function or an object with an "install" ` +`function.`)}return app},mixin(mixin: ComponentOptions) {if (__FEATURE_OPTIONS_API__) {if (!context.mixins.includes(mixin)) {context.mixins.push(mixin)} else if (__DEV__) {warn('Mixin has already been applied to target app' +(mixin.name ? `: ${mixin.name}` : ''))}} else if (__DEV__) {warn('Mixins are only available in builds supporting Options API')}return app},component(name: string, component?: Component): any {if (__DEV__) {validateComponentName(name, context.config)}if (!component) {return context.components[name]}if (__DEV__ && context.components[name]) {warn(`Component "${name}" has already been registered in target app.`)}context.components[name] = componentreturn app},directive(name: string, directive?: Directive) {if (__DEV__) {validateDirectiveName(name)}if (!directive) {return context.directives[name] as any}if (__DEV__ && context.directives[name]) {warn(`Directive "${name}" has already been registered in target app.`)}context.directives[name] = directivereturn app},mount(rootContainer: HostElement,isHydrate?: boolean,isSVG?: boolean): any {if (!isMounted) {// #5571if (__DEV__ && (rootContainer as any).__vue_app__) {warn(`There is already an app instance mounted on the host container.\n` +` If you want to mount another app on the same host container,` +` you need to unmount the previous app by calling \`app.unmount()\` first.`)}const vnode = createVNode(rootComponent as ConcreteComponent,rootProps)// store app context on the root VNode.// this will be set on the root instance on initial mount.vnode.appContext = context// HMR root reloadif (__DEV__) {context.reload = () => {render(cloneVNode(vnode), rootContainer, isSVG)}}if (isHydrate && hydrate) {hydrate(vnode as VNode<Node, Element>, rootContainer as any)} else {render(vnode, rootContainer, isSVG)}isMounted = trueapp._container = rootContainer// for devtools and telemetry;(rootContainer as any).__vue_app__ = appif (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {app._instance = vnode.componentdevtoolsInitApp(app, version)}return getExposeProxy(vnode.component!) || vnode.component!.proxy} else if (__DEV__) {warn(`App has already been mounted.\n` +`If you want to remount the same app, move your app creation logic ` +`into a factory function and create fresh app instances for each ` +`mount - e.g. \`const createMyApp = () => createApp(App)\``)}},unmount() {if (isMounted) {render(null, app._container)if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {app._instance = nulldevtoolsUnmountApp(app)}delete app._container.__vue_app__} else if (__DEV__) {warn(`Cannot unmount an app that is not mounted.`)}},provide(key, value) {if (__DEV__ && (key as string | symbol) in context.provides) {warn(`App already provides property with key "${String(key)}". ` +`It will be overwritten with the new value.`)}context.provides[key as string | symbol] = valuereturn app}})if (__COMPAT__) {installAppCompatProperties(app, context, render)}return app}
}
二、vue3中app.mount(), 挂载都做了什么?
1、创建节点vnode
2、创建render函数并执行render函数, 第一步生成vnode传递给patch函数转换成dom,然后将其添加到宿主上
-
app.mount('#app')
挂载时做了: 将传入的组件数据和状态转换成DOM,并追加到宿主元素上
三、patch状态更新流程
以下面代码为例子
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head>
<body><div id="app"><h1>vue3 更新流程</h1><p>{{counter}}</p><comp></comp></div><script src="../dist/vue.global.js"></script><script>// vue2: new Vue({}).$mount()// 变化1: 函数创建实例// vue3: createApp({})const app = Vue.createApp({// render() {// return Vue.h('div', {// myprop, title, onClick// })// }data() {return {counter: 1}},mounted() {setInterval(() => {this.counter++}, 1000)}})// 变化2: 实例方法app.component('comp', {template: '<div>comp</div>'})// 变化3: 挂载mountapp.mount('#app') </script>
</body>
</html>
1、从状态更新处打断点
2、单点进入后进入更新状态counter 此处应该是counter = this.counter += 1 会先走get 拿值, 然后走set赋值
此处data是proxy对象, key是counter
3、 单点产看更新,最终走trigger通过!hadkey判断其走trigger Add(添加), 还是走trigger Set(更新)
4、单点进入trigger Set
进入后进入
5、走triggerEffect / triggerEffects
6、走triggerEffect后进入effect.schedule查看其如何更新
7、单点进入, 激活响应式, 此处想看断点应该打在componentUpdateFn
更新走componentUpdateFn所以断点要提前打在componentUpdateFn的elese update上
启动queueFlush()只启动一次就行了, 说是启动一个异步任务
此处可以看到调用栈的promise 异步任务 此时跟初始化非常相似,多了一个上一次执行的结果
可以打到下面nexttree查看 nexttree中的count 加1了
8、最后走patch更新 对比prevTree和nextTree哪里不同然后去更新
四、Vue3 Composition Api 探究
1、Composition Api包括:
- setup
- 生命周期钩子
- getCurrentInstance
- provide/inject
问题1: 执行的时刻? 为什么没有created钩子?
- 从何看起, 首次执行mount挂载, 调用render函数,render内部 调用patch函数进行dom的渲染与更新, patch内部调用processComponent跟组件初始化开始, 从这开始,
- 进入下一步mountComponent
-
进入后首先创建了实例instance,然后进行组件实例初始化setupComponent(instance)
- 进入组件初始化列表 , 如果组件有状态,执行初始化过程, 并返回setup选项的返回值
- 看一下如果组件有状态,是如何处理返回setup的返回值的, 此处可以看到将ctx进行proxy代理, 下面是从组件中拿出创建的setup, 如果有就创建setup上下文将 其存入instance.setupContext
- 查看创建setup上下文, 就可以看到context中为什么有四个属性,且为什么attrs是只读的
export function createSetupContext(instance: ComponentInternalInstance
): SetupContext {// 对外暴露接口const expose: SetupContext['expose'] = exposed => {if (__DEV__ && instance.exposed) {warn(`expose() should be called only once per setup().`)}instance.exposed = exposed || {}}//组件非属性特性let attrs: Dataif (__DEV__) {// We use getters in dev in case libs like test-utils overwrite instance// properties (overwrites should not be done in prod)return Object.freeze({get attrs() {return attrs || (attrs = createAttrsProxy(instance))},get slots() {return shallowReadonly(instance.slots)},get emit() {return (event: string, ...args: any[]) => instance.emit(event, ...args)},expose})} else {//返回的就是setupContextreturn {// 只读的attrsget attrs() {return attrs || (attrs = createAttrsProxy(instance))},slots: instance.slots,emit: instance.emit,expose}}
}
-
继续回到setup, 可以看到setCurrentInstance
- 下面开始调用setup, 此时传入instance.props, 和setupContext, 这就是为什么setup中有两个参数
export function handleSetupResult(instance: ComponentInternalInstance,setupResult: unknown,isSSR: boolean
) {// 首先判断返回的结果是不是函数//如果是函数则作为render函数处理if (isFunction(setupResult)) {// setup returned an inline render functionif (__SSR__ && (instance.type as ComponentOptions).__ssrInlineRender) {// when the function's name is `ssrRender` (compiled by SFC inline mode),// set it as ssrRender instead.instance.ssrRender = setupResult} else {instance.render = setupResult as InternalRenderFunction}} else if (isObject(setupResult)) {if (__DEV__ && isVNode(setupResult)) {warn(`setup() should not return VNodes directly - ` +`return a render function instead.`)}// setup returned bindings.// assuming a render function compiled from template is present.if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {instance.devtoolsRawSetupState = setupResult}// 如果是对象, 转换setupoResult这个对象为响应式对象// 将来组件渲染函数中会首先从setupState中获取值instance.setupState = proxyRefs(setupResult)if (__DEV__) {exposeSetupStateOnRenderContext(instance)}} else if (__DEV__ && setupResult !== undefined) {warn(`setup() should return an object. Received: ${setupResult === null ? 'null' : typeof setupResult}`)}// 最后依然执行组件的安装// 里面主要是处理其他的options apifinishComponentSetup(instance, isSSR)
}
- 查看finishComponentSetup(instance, isSSR), 可得知为什么不需要created()
回答: 执行时刻beforeCreate之类的传统生命周期钩子,实际上setup函数执行的时候,组件实例已经创建了,所以setup中处理beforeCreate和created是没有意义的。
问题2: 传入setup参数中的props和ctx从何而来? 又是什么?
问题3: 如果和data这些数据发生冲突,他们能共存吗,Vue3处理时的行为?
setup优先级更高一些,两者可以共存, 为什么setup优先级更高,如何处理的?