>[success] # vm._render()
~~~
1.剛才分析了掛載階段,發現想得到這個虛擬dom就需要,渲染函數vm._render()得到一份最新的VNode節點樹
,現在就需要看看這個vm._render 是個什么?
2.通過代碼分析,首先在'core\instance\index.js' ,通過方法renderMixin(Vue)聲明注冊了'_render',
renderMixin 方法在'core\instance\render.js'
~~~
* renderMixin 源碼內容
~~~js
export function renderMixin (Vue: Class<Component>) {
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
/*_render渲染函數,返回一個VNode節點*/
Vue.prototype._render = function (): VNode {
const vm: Component = this
const {
render,
staticRenderFns,
_parentVnode
} = vm.$options
if (vm._isMounted) {
// clone slot nodes on re-renders
/*在重新渲染時會克隆槽位節點 不知道是不是因為Vnode必須必須唯一的原因,網上也沒找到答案,此處存疑。*/
for (const key in vm.$slots) {
vm.$slots[key] = cloneVNodes(vm.$slots[key])
}
}
/*作用域slot*/
vm.$scopedSlots = (_parentVnode && _parentVnode.data.scopedSlots) || emptyObject
if (staticRenderFns && !vm._staticTrees) {
/*用來存放static節點,已經被渲染的并且不存在v-for中的static節點不需要重新渲染,只需要進行淺拷貝*/
vm._staticTrees = []
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode
// render self
/*渲染*/
let vnode
try {
/*調用render函數,返回一個VNode節點*/
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render function`)
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
vnode = vm.$options.renderError
? vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
: vm._vnode
} else {
vnode = vm._vnode
}
}
// return empty vnode in case the render function errored out
/*如果VNode節點沒有創建成功則創建一個空節點*/
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}
~~~
>[info] ## 正式分析
~~~
1.這是一個粗略看源碼做了什么的解析文章,因此我不會去分析源碼內部,進過上面的源碼內容,整個核心
的地方在這里
/*調用render函數,返回一個VNode節點*/
vnode = render.call(vm._renderProxy, vm.$createElement)
2.現在有了新的疑問'$createElement'是哪里來的,在回到'initMixin'方法文件對應位置'vue-src\core\instance\init.js'
,里面有初始化render,的方法initRender(vm)
3.其實這里我不太懂,但是感覺領悟到,并不是所有方法都要掛載到構造函數上,讓構造函數變得無限臃腫
有時候可以將這些方法方法實例上
~~~
~~~js
/*初始化render*/
export function initRender(vm: Component) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null
const parentVnode = vm.$vnode = vm.$options._parentVnode // the placeholder node in parent tree 父樹中的占位符節點
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
//將createElement fn綁定到此實例
//這樣我們就可以在里面得到正確的呈現上下文。
//args順序:tag、data、children、normalizationType、alwaysNormalize
//內部版本由從模板編譯的呈現函數使用
/*將createElement函數綁定到該實例上,該vm存在閉包中,不可修改,vm實例則固定。這樣我們就可以得到正確的上下文渲染*/
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
//標準化總是應用于公共版本,用于
//用戶編寫的渲染函數。
/*常規方法被用于公共版本,被用來作為用戶界面的渲染方法*/
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
}
~~~
>[danger] ##### 在整個代碼分析前 需要理解一段代碼
[render](https://cn.vuejs.org/v2/api/#render)
[官網對render介紹](https://cn.vuejs.org/v2/api/#render)
~~~
1.我們在vue 使用render 時候一般可能會這么寫
new Vue({
render: function (createElement) {
return createElement('h1', this.blogTitle)
}
}).$mount("#app");
2.可以看到在'renderMixin' 中我們取出opition中的render,并且利用call 的形式改變指向和傳參,我這不是
細讀的版本所以'vm._renderProxy' 就理解成當前vue實例指向,這里傳入'vm.$createElement' 做參數,這個方法
也說過是在'initRender' 中聲明的
vnode = render.call(vm._renderProxy, vm.$createElement)
3.這么繞寫個簡單的小demo 給解開,其實option 中render參數,是一個回調函數,真正幫助我們解析內容的是
'createElement '這個函數
~~~
~~~
const opition = {
render: function (createElement) {
return createElement('h1', 'www')
}
}
const createElement = (a, b, c) => {
console.log(a, b, c)
}
opition.render.call(this, createElement)
打印結果:
h1 www undefined
~~~
>[danger] ##### initRender -- createElement
~~~
1.如果使用的是模板方式' vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)',
如果是正常的render 函數'vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)'
2.根據上面寫的小demo 也可以知道真正幫我們解析vnode其實是createElement
3.現在分析createElement這個方法,被定義在'\core\vdom\create-element.js'
4.'Array.isArray(data) || isPrimitive(data)' 用來判讀是否傳了參數data,如果沒傳data這個參數
其他參數都往上提一層
5.關于'alwaysNormalize' 這個 參數在初始化render 時候如果用的是'模板方法'就是false 是render 函數就是true
這里在后續_createElement中對'children' 參數處理有講究
~~~
~~~
export function createElement (
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode {
/*兼容不傳data的情況*/
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
/*如果alwaysNormalize為true,則normalizationType標記為ALWAYS_NORMALIZE*/
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}
/*創建虛擬節點*/
return _createElement(context, tag, data, children, normalizationType)
}
~~~
>[danger] ##### _createElement
[更多詳細內容參考這里](https://ustbhuangyi.github.io/vue-analysis/v2/data-driven/create-element.html#children-%E7%9A%84%E8%A7%84%E8%8C%83%E5%8C%96)
~~~
1.上面可以看到對參數進行整理后,會調用'_createElement'方法這個方法做了什么?
2.關于'normalizationType ' 判讀這里做個說明
2.1.simpleNormalizeChildren 方法調用場景是 render 函數是編譯生成的。
2.2.normalizeChildren 方法的調用場景有 2 種,一個場景是 render 函數是用戶手寫的,當 children 只有
一個節點的時候,Vue.js 從接口層面允許用戶把 children 寫成基礎類型用來創建單個簡單的文本節點,這
種情況會調用 createTextVNode 創建一個文本節點的 VNode;另一個場景是當編譯 slot、v-for 的時候會產
生嵌套數組的情況,會調用 normalizeArrayChildren 方法
~~~
~~~js
/*創建VNode節點*/
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode {
/*
如果data未定義(undefined或者null)或者是data的__ob__已經定義(代表已經被observed,上面綁定了Oberver對象),
https://cn.vuejs.org/v2/guide/render-function.html#約束
那么創建一個空節點
*/
if (isDef(data) && isDef((data: any).__ob__)) {
process.env.NODE_ENV !== 'production' && warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
/*如果tag不存在也是創建一個空節點*/
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
// support single function children as default scoped slot
/*默認默認作用域插槽*/
if (Array.isArray(children) &&
typeof children[0] === 'function') {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
if (typeof tag === 'string') {
let Ctor
/*獲取tag的名字空間*/
ns = config.getTagNamespace(tag)
/*判斷是否是保留的標簽*/
if (config.isReservedTag(tag)) {
// platform built-in elements
/*如果是保留的標簽則創建一個相應節點*/
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
/*從vm實例的option的components中尋找該tag,存在則就是一個組件,創建相應節點,Ctor為組件的構造類*/
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
/*未知的元素,在運行時檢查,因為父組件可能在序列化子組件的時候分配一個名字空間*/
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// direct component options / constructor
/*tag不是字符串的時候則是組件的構造類*/
vnode = createComponent(tag, data, context, children)
}
if (isDef(vnode)) {
/*如果有名字空間,則遞歸所有子節點應用該名字空間*/
if (ns) applyNS(vnode, ns)
return vnode
} else {
/*如果vnode沒有成功創建則創建空節點*/
return createEmptyVNode()
}
}
~~~
- Vue--基礎篇章
- Vue -- 介紹
- Vue -- MVVM
- Vue -- 創建Vue實例
- Vue -- 模板語法
- Vue -- 指令用法
- v-cloak -- 遮蓋
- v-bind -- 標簽屬性動態綁定
- v-on -- 綁定事件
- v-model -- 雙向數據綁定
- v-for -- 只是循環沒那么簡單
- 小知識點 -- 計劃內屬性
- key -- 屬性為什么要加
- 案例說明
- v-if/v-show -- 顯示隱藏
- v-for 和 v-if 同時使用
- v-pre -- 不渲染大大胡語法
- v-once -- 只渲染一次
- Vue -- class和style綁定
- Vue -- filter 過濾器
- Vue--watch/computed/fun
- watch -- 巧妙利用watch思想
- Vue -- 自定義指令
- Vue -- $方法
- Vue--生命周期
- Vue -- 專屬ajax
- Vue -- transition過渡動畫
- 前面章節的案例
- 案例 -- 跑馬燈效果
- 案例 -- 選項卡內容切換
- 案例-- 篩選商品
- 案例 -- 搜索/刪除/更改
- 案例 -- 用computed做多選
- 案例 -- checked 多選
- Vue--組件篇章
- component -- 介紹
- component -- 使用全局組件
- component -- 使用局部組件
- component -- 組件深入
- component -- 組件傳值父傳子
- component -- 組件傳值子傳父
- component -- 子傳父語法糖拆解
- component -- 父組件操作子組件
- component -- is 動態切換組件
- component -- 用v-if/v-show控制子組件
- component -- 組件切換的動畫效果
- component -- slot 插槽
- component -- 插槽2.6
- component -- 組件的生命周期
- component -- 基礎組件全局注冊
- VueRouter--獲取路由參數
- VueRouter -- 介紹路由
- VueRouter -- 安裝
- VueRouter -- 使用
- VueRouter--router-link簡單參數
- VueRouter--router-link樣式問題
- VueRouter--router-view動畫效果
- VueRouter -- 匹配優先級
- vueRouter -- 動態路由
- VueRouter -- 命名路由
- VueRouter -- 命名視圖
- VueRouter--$router 獲取函數
- VueRouter--$route獲取參數
- VueRouter--路由嵌套
- VueRouter -- 導航守衛
- VueRouter -- 寫在最后
- Vue--模塊化方式結構
- webpack--自定義配置
- webpack -- 自定義Vue操作
- VueCli -- 3.0可視化配置
- VueCli -- 3.0 項目目錄
- Vue -- 組件升級篇
- Vue -- 組件種類與組件組成
- Vue -- 組件prop、event、slot 技巧
- Vue -- 組件通信(一)
- Vue -- 組件通信(二)
- Vue -- 組件通信(三)
- Vue -- 組件通信(四)
- Vue -- 組件通信(五)
- Vue -- 組件通信(六)
- Vue -- bus非父子組件通信
- Vue -- 封裝js插件成vue組件
- vue組件分裝 -- 進階篇
- Vue -- 組件封裝splitpane(分割面板)
- UI -- 正式封裝
- Vue -- iview 可編輯表格案例
- Ui -- iview 可以同時編輯多行
- Vue -- 了解遞歸組件
- UI -- 正式使用遞歸菜單
- Vue -- iview Tree組件
- Vue -- 利用通信仿寫一個form驗證
- Vue -- 使用自己的Form
- Vue -- Checkbox 組件
- Vue -- CheckboxGroup.vue
- Vue -- Alert 組件
- Vue -- 手動掛載組件
- Vue -- Alert開始封裝
- Vue -- 動態表單組件
- Vue -- Vuex組件的狀態管理
- Vuex -- 參數使用理解
- Vuex -- state擴展
- Vuex -- getters擴展
- Vuex--mutations擴展
- Vuex -- Action 異步
- Vuex -- plugins插件
- Vuex -- v-model寫法
- Vuex -- 更多
- VueCli -- 技巧總結篇
- CLI -- 路由基礎
- CLI -- 路由升級篇
- CLI --異步axios
- axios -- 封裝axios
- CLI -- 登錄寫法
- CLI -- 權限
- CLI -- 簡單權限
- CLI -- 動態路由加載
- CLI -- 數據性能優化
- ES6 -- 類的概念
- ES6類 -- 基礎
- ES6 -- 繼承
- ES6 -- 工作實戰用類數據管理
- JS -- 適配器模式
- ES7 -- 裝飾器(Decorator)
- 裝飾器 -- 裝飾器修飾類
- 裝飾器--修飾類方法(知識擴展)
- 裝飾器 -- 裝飾器修飾類中的方法
- 裝飾器 -- 執行順序
- Reflect -- es6 自帶版本
- Reflect -- reflect-metadata 版本
- 實戰 -- 驗證篇章(基礎)
- 驗證篇章 -- 搭建和目錄
- 驗證篇章 -- 創建基本模板
- 驗證篇章 -- 使用
- 實戰 -- 更新模型(為了迎合ui升級)
- 實戰 -- 模型與接口對接
- TypeSprict -- 基礎篇章
- TS-- 搭建(一)webpack版本
- TS -- 搭建(二)直接使用
- TS -- 基礎類型
- TS -- 枚舉類型
- TS -- Symbol
- TS -- interface 接口
- TS -- 函數
- TS -- 泛型
- TS -- 類
- TS -- 類型推論和兼容
- TS -- 高級類型(一)
- TS -- 高級類型(二)
- TS -- 關于模塊解析
- TS -- 聲明合并
- TS -- 混入
- Vue -- TS項目模擬
- TS -- vue和以前代碼對比
- TS -- vue簡單案例上手
- Vue -- 簡單弄懂VueRouter過程
- VueRouter -- 實現簡單Router
- Vue-- 原理2.x源碼簡單理解
- 了解 -- 簡單的響應式工作原理
- 準備工作 -- 了解發布訂閱和觀察者模式
- 了解 -- 響應式工作原理(一)
- 了解 -- 響應式工作原理(二)
- 手寫 -- 簡單的vue數據響應(一)
- 手寫 -- 簡單的vue數據響應(二)
- 模板引擎可以做的
- 了解 -- 虛擬DOM
- 虛擬dom -- 使用Snabbdom
- 閱讀 -- Snabbdom
- 分析snabbdom源碼 -- h函數
- 分析snabbdom -- init 方法
- init 方法 -- patch方法分析(一)
- init 方法 -- patch方法分析(二)
- init方法 -- patch方法分析(三)
- 手寫 -- 簡單的虛擬dom渲染
- 函數表達解析 - h 和 create-element
- dom操作 -- patch.js
- Vue -- 完成一個minVue
- minVue -- 打包入口
- Vue -- new實例做了什么
- Vue -- $mount 模板編譯階段
- 模板編譯 -- 分析入口
- 模板編譯 -- 分析模板轉譯
- Vue -- mountComponent 掛載階段
- 掛載階段 -- vm._render()
- 掛載階段 -- vnode
- 備份章節
- Vue -- Nuxt.js
- Vue3 -- 學習
- Vue3.x --基本功能快速預覽
- Vue3.x -- createApp
- Vue3.x -- 生命周期
- Vue3.x -- 組件
- vue3.x -- 異步組件???
- vue3.x -- Teleport???
- vue3.x -- 動畫章節 ??
- vue3.x -- 自定義指令 ???
- 深入響應性原理 ???
- vue3.x -- Option API VS Composition API
- Vue3.x -- 使用set up
- Vue3.x -- 響應性API
- 其他 Api 使用
- 計算屬性和監聽屬性
- 生命周期
- 小的案例(一)
- 小的案例(二)-- 泛型
- Vue2.x => Vue3.x 導讀
- v-for 中的 Ref 數組 -- 非兼容
- 異步組件
- attribute 強制行為 -- 非兼容
- $attrs 包括 class & style -- 非兼容
- $children -- 移除
- 自定義指令 -- 非兼容
- 自定義元素交互 -- 非兼容
- Data選項 -- 非兼容
- emits Option -- 新增
- 事件 API -- 非兼容
- 過濾器 -- 移除
- 片段 -- 新增
- 函數式組件 -- 非兼容
- 全局 API -- 非兼容
- 全局 API Treeshaking -- 非兼容
- 內聯模板 Attribute -- 非兼容
- key attribute -- 非兼容
- 按鍵修飾符 -- 非兼容
- 移除 $listeners 和 v-on.native -- 非兼容
- 在 prop 的默認函數中訪問 this -- ??
- 組件使用 v-model -- 非兼容
- 渲染函數 API -- ??
- Slot 統一 ??
- 過渡的 class 名更改 ???
- Transition Group 根元素 -- ??
- v-if 與 v-for 的優先級對比 -- 非兼容
- v-bind 合并行為 非兼容
- 監聽數組 -- 非兼容