带着问题阅读Vue3源码

345 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

问题

1、为什么setup 不建议使用this

我们在options api 中 经常会使用

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
updates.
  methods: {
    increment() {
      this.count++// 这里通过this调用data中定义的count
    }
  },
}
</script>
//而在 composition api中
setup(){
   let count = ref(0)
   const increment=()=> {
      count.value++
    }
   return {
      count,
      increment
   }
}
setup  发生在 data computed methods 之前。
setup  中应该避免使用 this

源码:runtime-core文件下-src文件夹下-component.ts文件中setupStatefulComponent方法中,优先调用了 setup函数。而后面再执行 finishComponentSetupapplyOptions 函数中执行了 data,computed,methods

function setupStatefulComponent(

instance: ComponentInternalInstance,

isSSR: boolean

) {
const Component = instance.type as ComponentOptions

if (__DEV__) {
  //篇幅有限省略部分代码
}

// 0. create render proxy property access cache

instance.accessCache = Object.create(null)

// 1. create public instance / render proxy

// also mark it raw so it's never observed

instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))

if (__DEV__) {
exposePropsOnRenderContext(instance)
}

// 2. call setup() 调用setup函数

const { setup } = Component

if (setup) {

const setupContext = (instance.setupContext =

setup.length > 1 ? createSetupContext(instance) : null)


setCurrentInstance(instance)

pauseTracking()

const setupResult = callWithErrorHandling(

setup,

instance,

ErrorCodes.SETUP_FUNCTION,

[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]

)

resetTracking()

unsetCurrentInstance()

// 判断结果是否是一个Promise

if (isPromise(setupResult)) {

setupResult.then(unsetCurrentInstance, unsetCurrentInstance)

if (isSSR) {
   //篇幅有限省略部分代码
} else {

// 调用 handleSetupResult

handleSetupResult(instance, setupResult, isSSR)
}
} else {

// 最后调用 finishComponentSetup

finishComponentSetup(instance, isSSR)
}
}

2、v-for中key的作用

  • 1、主要在Vue中虚拟DOM算法,在新旧nodes对比辨识VNodes
  • 2、如果不使用key,patchUnkeyedChildren,Vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法;
  • 3、如果有key,patchKeyedChildren,基于key的变化重新排列元素顺序,并且会移除/销毁key不存在的元素
  • 4、源码:runtime-core-renderer.ts文件中-在patchChildren方法中进行判断

const patchChildren: PatchChildrenFn = (

n1,

n2,

container,

anchor,

parentComponent,

parentSuspense,

isSVG,

slotScopeIds,

optimized = false

) => {

const c1 = n1 && n1.children

const prevShapeFlag = n1 ? n1.shapeFlag : 0

const c2 = n2.children


const { patchFlag, shapeFlag } = n2

// 当有key 执行patchKeyedChildren

if (patchFlag > 0) {

if (patchFlag & PatchFlags.KEYED_FRAGMENT) {

patchKeyedChildren(

c1 as VNode[],

c2 as VNodeArrayChildren,

container,

anchor,

parentComponent,

parentSuspense,

isSVG,

slotScopeIds,

optimized

)

return

} else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {

// unkeyed 执行patchUnkeyedChildren

patchUnkeyedChildren(

c1 as VNode[],

c2 as VNodeArrayChildren,

container,

anchor,

parentComponent,

parentSuspense,

isSVG,

slotScopeIds,

optimized

)

return

}

}

}

3、defineProperty 与Proxy 的区别

  • 1、 Object.definedProperty 是劫持对象的属性时,如果新增元素:那么Vue2需要再次 调用definedProperty,而 Proxy 劫持的是整个对象,不需要做特殊处理;

  • 2、 修改对象的不同:

使用 defineProperty 时,我们修改原来的 obj 对象就可以触发拦截;

而使用 proxy,就必须修改代理对象,即 Proxy 的实例才可以触发拦截;

  • 3、Proxy 能观察的类型比 defineProperty 更丰富

has:in操作符的捕获器;

deleteProperty:delete 操作符的捕捉器;

等等其他操作;

  • 4、 Proxy 作为新标准将受到浏览器厂商重点持续的性能优化;

  • 5、缺点:Proxy 不兼容IE,也没有 polyfill, defineProperty 能支持到IE9

源码:reactivity文件夹下-src文件夹-reactive.ts文件中createReactiveObject函数中


function createReactiveObject(

target: Target,

isReadonly: boolean,

baseHandlers: ProxyHandler<any>,

collectionHandlers: ProxyHandler<any>,

proxyMap: WeakMap<Target, any>

) {

//篇幅有限省去部分源码

// target already has corresponding Proxy

const existingProxy = proxyMap.get(target)

if (existingProxy) {

return existingProxy

}

// only a whitelist of value types can be observed.

const targetType = getTargetType(target)

if (targetType === TargetType.INVALID) {

return target

}
// Vue3-数据劫持使用的Proxy
const proxy = new Proxy(

target,

targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers

)
proxyMap.set(target, proxy)

return proxy

}



4、虚拟DOM的理解

  • 1、对真实的元素节点进行抽象,抽象成VNode(虚拟节点)

因为对于直接操作DOM来说是有很多的限制的,比如diff、clone等等,但是使用JavaScript编程语言来操作这些,就变得非常的简单,所谓的性能更高。

VNode其本质就是一个JavaScript对象,我们可以使用JavaScript来表达非常多的逻辑,而对于DOM本身来说是非常不方便的;

  • 2、方便实现跨平台,包括你可以将VNode节点渲染成任意你想要的节点

如渲染在canvas、WebGL、SSR、Native(iOS、Android)上;

并且Vue允许你开发属于自己的渲染器(renderer),在其他的平台上渲染;

const vnode = {
   'tag':'div',
   'children':'哈哈'
}
/*
通过渲染器
div: document.createElement
iOS: UIButton
Android: Button
*/

虚拟DOM的渲染过程

虚拟DOM渲染过程.png

5、methods方法中 this 指向问题

<script>
export default {
 data() {
   return {
     count0
  }
},
 // Methods are functions that mutate state and trigger updates.
 methods: {
   increment() {
     this.count++
  }
},
}
</script>

源码: runtime-core文件夹下componentOptions.ts 文件 通过遍历 methods方法,ctx[key] = methodHandler.bind(publicThis)

if(methods){
   for(const key in methods){
       const methodHandler = (methods as MethodOptions)[key]
       if(isFunction(methodHandler)){
           if(__DEV__){
​
          }else{
               ctx[key] = methodHandler.bind(publicThis)
          }
      }else if(__DEV__){
           warn(`Method "${key}" has type "${typeof methodHandler}" in the component definition.` + ``)
      }
  }
}

Vue3源码阅读流程

理解功能模块的作用

  • 1、Compiler模块:编译模板系统;

  • 2、Runtime模块:也可以称之为Renderer模块,真正渲染的模块;

  • 3、Reactivity模块:响应式系统;比如:data中数据, setup中数据

源码主要模块.png

三大系统协调工作

编译系统

将template模块转为render函数

渲染系统

runtime-dom以及runtime-core一系列业务逻辑的处理

响应式系统

主要进行vnode的diff算法

三大系统协调工作.png

Vue.createApp(App).mount('#app')

CreateApp-创建流程

step1-创建render

在runtime-dom/src/index.ts文件中

1、通过ensureRenderer()函数创建 renderer

2、在renderer.ts文件中,通过baseCreateRenderer函数的实现体

3、在patch函数(约2千行) 最终返回render,并且返回一个createApp方法 createApp.png

CreateApp-断点调试流程

可以按照此步骤进行断点调试帮助理解

creatApp入口.png

Mount-挂载流程

step1-调用app对象里面的mount方法

这里其实就是调用apiCreateApp.ts文件中createAppAPI函数

1、返回了一个createApp函数

2、在CreateApp函数中定义了一个app对象

3、调用createApp返回的app对象里面的mount方法

mount-step0.png

step2-执行render函数

通过render函数将根组件挂载到DOM上面

mount-step1.png

step3-挂载Component

mountComponent函数主要做了三步操作

1、createComponentInstance函数初始化组件实例

    const instance: ComponentInternalInstance =

    compatMountInstance ||

    (initialVNode.component = createComponentInstance(

    initialVNode,

    parentComponent,

    parentSuspense

    ))

2、函数组件初始化过程真正给instance 内部状态赋值的方法,初始化组件内容

  setupComponent(instance)

3、监听组件数据的变化,并完成响应式

    setupRenderEffect(

    instance,

    initialVNode,

    container,

    anchor,

    parentSuspense,

    isSVG,

    optimized

    )

mount-step2.png

mount的完成流程

mount-all.png

debug断点调试mount流程

mount-step4.png

Compile 过程

step1-template模板转化成render函数

compile的目的是将template模板转化成render函数

compile编译过程.png

compile编译过程-0.png