From a164be54805df622b0bcce20c9b896431339717d Mon Sep 17 00:00:00 2001 From: egu0 Date: Thu, 9 May 2024 00:33:41 +0800 Subject: [PATCH] =?UTF-8?q?12.2.=E5=88=9B=E5=BB=BA=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E7=BB=84=E4=BB=B6=E7=9A=84=E8=99=9A=E6=8B=9F=20dom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++++- component.html | 18 ++++++++------- src/init.js | 18 ++++++++------- src/lifecycle.js | 4 ++-- src/utils/index.js | 10 +++++++++ src/vnode/index.js | 56 ++++++++++++++++++++++++++++++++++++++++------ src/vnode/patch.js | 38 ++++++++++++++++++++++--------- 7 files changed, 115 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 476758a..46538de 100644 --- a/README.md +++ b/README.md @@ -511,7 +511,7 @@ watch 使用的四种方式 -## 12.组件 +## 12.自定义组件 ### 1.component 和 extend 两个静态方法 @@ -521,3 +521,9 @@ watch 使用的四种方式 +### 2.创建自定义组件的虚拟 dom + + + + + diff --git a/component.html b/component.html index 5fde650..8e1a1c7 100644 --- a/component.html +++ b/component.html @@ -10,6 +10,9 @@
+ + {{msg}} +
@@ -27,17 +30,16 @@ msg: 'monica', } }, - //在 options.components 字段局部注册组件 - // components: { - // 'my-button': { - // template: `` - // } - // } + + //在 options.components 字段局部注册组件。优先级比同名全局组件高 + components: { + 'my-button': { + template: `` + } + } }) console.log('vm', vm) - console.log('Vue.options', Vue.options) - \ No newline at end of file diff --git a/src/init.js b/src/init.js index 6d05851..07581f1 100644 --- a/src/init.js +++ b/src/init.js @@ -6,7 +6,7 @@ import { mergeOptions } from "./utils/index" export function initMixin(Vue) { Vue.prototype._init = function (options) { let vm = this // 原型链中的 this 是 Vue 实例 - vm.$options = mergeOptions(Vue.options, options) + vm.$options = mergeOptions(vm.constructor.options, options) callHook(vm, 'beforeCreated') @@ -26,15 +26,17 @@ export function initMixin(Vue) { Vue.prototype.$mount = function (el) { let vm = this - if (vm.$options.template) { - if (vm.$options.render) { } else { } - } else { - let el_ele = document.querySelector(el) - vm.$el = el_ele - let template = el_ele.outerHTML + let options = vm.$options + if (!options.render) { + let template = options.template + if (el) { + el = document.querySelector(el) + vm.$el = el + template = el.outerHTML + } let render = compileToFunction(template) vm.$options.render = render } - mountComponent(vm, el) + mountComponent(vm) } } diff --git a/src/lifecycle.js b/src/lifecycle.js index 9d26837..fdd38f6 100644 --- a/src/lifecycle.js +++ b/src/lifecycle.js @@ -1,14 +1,14 @@ import Watcher from "./observe/watcher" import { patch } from "./vnode/patch" -export function mountComponent(vm, el) { +export function mountComponent(vm) { callHook(vm, 'beforeMounted') // vm._render() : 将 render 函数转为虚拟 dom // vm._update() : 将虚拟 dom 转为真实 dom 并替换原 dom let updateComponent = () => { - vm._update(vm._render(el)) + vm._update(vm._render()) } new Watcher(vm, updateComponent, () => { diff --git a/src/utils/index.js b/src/utils/index.js index 4c4d466..9c15f33 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -6,6 +6,16 @@ collection.data = function (oldData, newData) { // collection.watch = function () { } // collection.methods = function () { } +collection.components = function (oldData, newData) { + const obj = Object.create(oldData) + if (newData) { + for (let key in newData) { + obj[key] = newData[key] + } + } + return obj +} + //生命周期钩子 export const HOOKS = [ "beforeCreate", diff --git a/src/vnode/index.js b/src/vnode/index.js index 57fc888..a5a2a39 100644 --- a/src/vnode/index.js +++ b/src/vnode/index.js @@ -1,6 +1,6 @@ export function renderMixin(Vue) { Vue.prototype._c = function () {//标签 - return createElement(...arguments) + return createElement(this, ...arguments) } Vue.prototype._v = function () {//文本 return createText(...arguments) @@ -16,7 +16,7 @@ export function renderMixin(Vue) { * 根据 render 函数生成虚拟 dom * @param {*} el */ - Vue.prototype._render = function (el) { + Vue.prototype._render = function () { let vm = this let render = vm.$options.render let vnode = render.call(this) @@ -24,20 +24,62 @@ export function renderMixin(Vue) { } } -function createElement(tagName, attrs = {}, ...children) { - return createNode(tagName, attrs, attrs ? attrs.key : undefined, children, undefined) +function createElement(vm, tagName, attrs = {}, ...children) { + attrs = attrs || {} + if (isReserved(tagName)) { + return createVNode(vm, tagName, attrs, attrs ? attrs.key : undefined, children, undefined, undefined) + } else { + const Ctor = vm.$options['components'][tagName] + return createComponent(vm, tagName, attrs, children, Ctor) + } +} + +function createComponent(vm, tagName, attrs, children, Ctor) { + if (typeof Ctor == 'object') { + Ctor = vm.constructor.extend(Ctor) + } + + attrs.hook = { + init(vnode) { + let instance = new vnode.compOptions.Ctor({}) + vnode.componentInstance = instance + instance.$mount() + } + } + return createVNode('vm', 'vue-comp_' + tagName, attrs, undefined, undefined, undefined, { Ctor, children }) +} + +//https://zhuanlan.zhihu.com/p/89214182 +let TagList = [ + 'html', 'head', 'title', 'base', 'link', 'meta', 'style', 'script', 'noscript', + 'template', 'body', 'section', 'nav', 'article', 'aside', 'h1', 'h2', 'h3', 'h4', + 'h5', 'h6', 'header', 'footer', 'p', 'hr', 'pre', 'blockquote', 'ol', 'ul', 'li', + 'dl', 'dt', 'dd', 'figure', 'figcaption', 'div', 'a', 'em', 'strong', 'small', 's', + 'cite', 'q', 'dfn', 'abbr', 'data', 'form', 'fieldset', 'legend', 'label', 'input', + 'button', 'select', 'datalist', 'optgroup', 'option', 'textarea', 'keygen', 'output', + 'progress', 'meter', 'details', 'summary', 'menuitem', 'menu', 'img', 'iframe', + 'embed', 'object', 'param', 'video', 'audio', 'source', 'track', 'canvas', 'map', + 'area', 'svg', 'math', 'table', 'caption', 'colgroup', 'col', 'tbody', 'thead', 'tfoot', + 'tr', 'td', 'th', 'time', 'code', 'var', 'samp', 'hbd', 'sub', 'i', 'b', 'u', 'mark', + 'ruby', 'rt', 'rp', 'bdi', 'bdo', 'span', 'br', 'wbr', 'ins', 'del', +] + +function isReserved(tagName) { + return TagList.includes(tagName) } -function createNode(tagName, data, key, children, text) { +function createVNode(vm, tagName, data, key, children, text, compOptions) { return { + vm, tag: tagName, data, key, children, - text + text, + compOptions } } function createText(text) { - return createNode(undefined, undefined, undefined, undefined, text) + return createVNode(undefined, undefined, undefined, undefined, undefined, text, undefined) } \ No newline at end of file diff --git a/src/vnode/patch.js b/src/vnode/patch.js index eb0eb52..88c610d 100644 --- a/src/vnode/patch.js +++ b/src/vnode/patch.js @@ -4,8 +4,13 @@ * @param {*} vnode 虚拟 dom */ export function patch(oldNode, vnode) { + if (!oldNode) { + return createElm(vnode) + } + //首次渲染时,oldNode 是真实的 DOM 元素,拥有属性 nodeType if (oldNode.nodeType === 1) { + debugger // 根据虚拟 dom 生成 dom let newNode = createElm(vnode) @@ -18,8 +23,6 @@ export function patch(oldNode, vnode) { } else { //此时 oldNode 时旧的虚拟 dom let oldVNode = oldNode, newVNode = vnode - // console.log('old vnode', oldVNode) - // console.log('new vnode', newVNode) //更新标签类型 //
{{name}}
@@ -164,21 +167,34 @@ function updateElAttr(vnode, oldProps = {}) { } export function createElm(vnode) { - let { tag, data, key, children, text } = vnode + let { vm, tag, data, key, children, text } = vnode if (typeof tag === 'string') { - //标签 - vnode.el = document.createElement(tag) - //为 vnode.el 添加属性 - updateElAttr(vnode) - //为 vnode.el 添加子元素 - children.forEach(child => { - vnode.el.appendChild(createElm(child)) - }) + if (createComponent(vnode)) { + //创建组件的真实 dom + return vnode.componentInstance.$el + } else { + //标签 + vnode.el = document.createElement(tag) + //为 vnode.el 添加属性 + updateElAttr(vnode) + //为 vnode.el 添加子元素 + children && children.forEach(child => { + vnode.el.appendChild(createElm(child)) + }) + } } else { //文本 vnode.el = document.createTextNode(text) } return vnode.el +} + +function createComponent(vnode) { + if (vnode.data && vnode.data.hook && vnode.data.hook.init) { + //调用 init 方法创建子组件实例 + vnode.data.hook.init(vnode) + } + return vnode.componentInstance ? true : false } \ No newline at end of file -- Gitee