vue3.0 組件初始化流程

彩蛋來了 寫在前面,最近打算學習vue3.0 相關知識,本着學習一個東西,最好方法就是模仿寫一個,因此本身動手寫了一個簡化版vue3.0,本身稱做mini-vue3.0 感受對vue3.0 或者 vue2.x核心原理的理解有很大幫助,因此分享出來。mini-vue3.0主要包括:模板編譯、響應式、組件渲染過程等, 倉庫地址mini-vue3.0,歡迎starhtml

組件渲染原理

本文簡單介紹vue3.0 組件的渲染過程,爲了更好說明組件渲染原理,本文會結合一個簡單的例子來講明整個過程。vue

簡單一點渲染過程圖node

component渲染過程圖

複雜一點渲染過程圖react

vue3.0組件初始化流程

設掛載點爲git

<div id="app"></div>
複製代碼

根組件定義爲github

const rootComponent = {
  template: `<div class="parent"> <div :class="data.class">組件渲染內容</div> </div>`,
    setup() {
      // reactive 做用是設置數據響應式
      const data = reactive({
        class: 'demo'
      })
      return {
        data
      }
    }
}
複製代碼

建立App全局上下文

首先建立App全局上下文,建立的上下文以下:app

const appContext = {
  mixins: [], // 存儲全局mixins
  components: {}, // 存儲全局組件
  directives: {}, // 存儲全局指令
}

複製代碼

建立組件VNode

const rootVnode = {
  type: rootComponent, // type的值可爲字符串例如:div 或者 組件options對象
  props: {},
  children: {},
  component: null, // 組件實例
  appContext: appContext // 全局上下文,
}

複製代碼

appContext: 全局上下文中的全局組件、指令,會在render函數執行時候,生成組件VNode時,用於解析全局組件、指令 type: 若是是普通DOM元素,則爲字符串;若是爲組件節點則爲組件定義對象。在咱們例子,爲一個組件定義對象函數

根據VNode渲染根組件

建立組件實例

根據組件VNode 初始化組件實例instance學習

const instance = {
    vnode: rootVnode,// 組件vnode
    parent: null,// 父組件實例,本文例子爲null
    appContext, // 全局上下文
    type: rootVnode.type, // 節點類型
    subTree: null, // 組件內渲染VNode樹
    render: null,
    proxy: null,
    data: {},
    props: {},
    setupState: {},
    // 繼承全局組件和指令
    components: Object.create(appContext.components), 
    directives: Object.create(appContext.directives),
    ctx: { _: instance }
  }
 
複製代碼

ctx: 這個ctx 屬性,是爲了渲染模板的時候,將instance自身做爲執行上下文ui

初始化組件實例相關屬性

// 設置模板渲染render函數
  const Component = instance.type // 組件options
  if (!Component.render && Component.template && compile) {
    // 編譯模板爲render函數
    Component.render = compile(Component.template)
  }
  if (!Component.render) {
    throw Error('請檢查模板是否正確')
  }
  instance.render = Component.render
  // 設置render 函數調用時的渲染上下文
  instance.proxy = new Proxy(instance.ctx, {
    get({ _: instance }, key) {
      const {  setupState } = instance
      // setupState 優先
      if (setupState[key] && hasOwn(setupState, key)) {
        return setupState[key]
      }
    },
    set({ _: instance }, key, val) {
      const { setupState } = instance
      if (setupState[key] && hasOwn(setupState, key)) {
        setupState[key] = val
      }
    },
  })
  const { setup } = Component
  // 調用setup函數
  if (setup) {
    const setupResult = setup()
    // 這裏設置響應式
    instance.setupState = reactive(setupResult)
  }
複製代碼

代碼中compile 爲編譯函數,具體實現原理模板編譯原理

根據本文的例子,獲得render、setupState:

// _c 爲建立VNode
instance.render = function () { return _c('div', 
          {class: "parent"},
          [_c('div', 
          {class: this.data.class},
          [_c('text', {value: '組件渲染內容'})]
        )]
        ) 
      }

instance.setupState = {
  dataProxy: {
    class: 'demo'
  }
}
複製代碼

渲染組件內容

// 渲染組件 effect 爲響應式相關函數,用於依賴收集,而且設置了組件update的更新函數
 // effect 等價於vue2.x 中的 Watcher
  instance.update = effect(function componentEffect() {
    // 渲染組件模板,獲得組件子樹VNode,同時完成依賴收集,instance.proxy 其實代理就是組件自生component
    const subTree = (instance.subTree = instance.render.call(instance.proxy,  instance.proxy))
    // 根據組件子樹VNode ,渲染組件內容
    mountElement(subTree, container, anchor, instance)
    rootVnode.el = subTree.el
    instance.isMounted = true
  })

// 根據VNode 建立具體 Dom元素
// container 父Dom 元素,在咱們例子中爲 id="app" 的元素
const mountElement = (vnode, container, anchor, parentComponent) => {
  const { type, props, children } = vnode
  let el
  // 文本節點特殊處理
  if (type === 'text') {
    el = vnode.el = document.createTextNode(props.value)
  } else {
    el = vnode.el = document.createElement(type)
    // 遞歸渲染掛載子樹
    if (children) {
      mountChildren(vnode.children, el, null, parentComponent)
	}
	// 這裏的 props 表示是Dom元素上的props
    if (props) {
      for (const key in props) {
         el.setAttribute(key, props[key])
      }
    }
  }
  // 插入DOM中
  document.appendChild(container, el, anchor)
}
複製代碼

本文例子中根組件 獲得的subTree以下:

{
	"type": "div",
	"props": {
		"class": "parent"
	},
	"children": [{
		"type": "div",
		"props": {
			"class": "before"
		},
		"children": [{
			"type": "text",
			"props": {
				"value": "組件渲染內容"
			},
			"component": null,
			"appContext": null
		}],
		"component": null,
		"appContext": null
	}],
	"component": null,
	"appContext": null
}
複製代碼
相關文章
相關標籤/搜索