7.1 渲染器与响应系统的结合
渲染器就是将vnode渲染到容器中,容器就是某个html节点,响应式实现一个简单的渲染器,这引入vue3中的effect和ref,这两个之前分析过,也可以用之前实现effect和ref
<div id="app"></div>
<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
const { effect, ref } = VueReactivity
function renderer(domString, container) {
container.innerHTML = domString
}
const count = ref(1)
effect(() => {
renderer(`<h1>${count.value}</h1>`, document.getElementById('app'))
})
count.value++
effect 执行后,第一次渲染,然后track收集副作用函数,当count.vlue改变,导致trigger触发执行 副作用函数,第二次渲染
搞清几个概念
1.renderer是渲染器,包含render渲染器,hydrate
2.render是渲染器的行为,渲染,是动词,
3.Vnode是虚拟dom,即js描述html标签
4.container是容器,渲染函数渲染的节点为容器
5.mount是挂载,表示渲染器成功将Vnode,渲染到容器上了,完成一次渲染
渲染器的抽象实现
function createRenderer() {
function patch(n1, n2, container) {
}
function render(vnode, container) {
if (vnode) {
// 新 vnode 存在,将其与旧 vnode 一起传递给 patch 函数进行打补丁
patch(container._vnode, vnode, container)
} else {
if (container._vnode) {
// 旧 vnode 存在,且新 vnode 不存在,说明是卸载(unmount)操作
// 只需要将 container 内的 DOM 清空即可
container.innerHTML = ''
}
}
// 把 vnode 存储到 container._vnode 下,即后续渲染中的旧 vnode
container._vnode = vnode
}
return {
render
}
}
const renderer = createRenderer()
// 首次渲染
renderer.render(vnode1, document.querySelector('#app'))
// 第二次渲染
renderer.render(vnode2, document.querySelector('#app'))
// 第三次渲染
renderer.render(null, document.querySelector('#app'))
当每一次渲染,会比较前一次和新的Vnode直接的差异,当新的Vnode为空,清空容器中的HTML,path函数承担,比较Vnode,还要挂载Vnode,是整个渲染器的核心
7.3 自定义渲染器
这里要解决的问题是,要将操作Dom的逻辑变成参数,提取出来,因为要设计一个多平台的渲染器,操作Dom的逻辑是浏览器专属的.创建dom,为标签插入字符,挂载到Dom上这些逻辑,都由用户传入,
function createRenderer(options) {
const {
createElement,
insert,
setElementText
} = options
// 挂载
function mountElement(vnode, container) { // 将Vnode渲染到容器中
const el = createElement(vnode.type) //根据Vnode创建一个Dom原生
if (typeof vnode.children === 'string') { //如果是文本标签
setElementText(el, vnode.children) // 将元素
}
insert(el, container) // 执行挂载
}
// n1是旧Vnode n2是新的Vnode,
function patch(n1, n2, container) {
if (!n1) { // 当旧Vnode不存在,表示是第一渲染,直接调用渲染函数
mountElement(n2, container)
} else {
//
}
}
function render(vnode, container) {
if (vnode) {
// 新 vnode 存在,将其与旧 vnode 一起传递给 patch 函数进行打补丁
patch(container._vnode, vnode, container)
} else {
if (container._vnode) {
// 旧 vnode 存在,且新 vnode 不存在,说明是卸载(unmount)操作
// 只需要将 container 内的 DOM 清空即可
container.innerHTML = ''
}
}
// 把 vnode 存储到 container._vnode 下,即后续渲染中的旧 vnode
container._vnode = vnode
}
return {
render
}
}
const vnode = {
type: 'h1',
children: 'hello'
}
// renderer.render(vnode, document.querySelector('#app'))
const renderer2 = createRenderer({
createElement(tag) {
console.log(`创建元素 ${tag}`)
return { tag }
},
setElementText(el, text) {
console.log(`设置 ${JSON.stringify(el)} 的文本内容:${text}`)
el.text = text
},
insert(el, parent, anchor = null) {
console.log(`将 ${JSON.stringify(el)} 添加到 ${JSON.stringify(parent)} 下`)
parent.children = el
}
})
const container = { type: 'root' }
renderer2.render(vnode, container)
当然这里的逻辑是第一次渲染的,path函数没有考虑多次渲染,
问题总结:
(二十三)渲染器有哪些基本概念?虚拟DOM的结构是?为什么造成DOM的方法都被当option选项传入?
(1)
搞清几个概念
1.renderer是渲染器,包含render渲染器,hydrate
2.render是渲染器的行为,渲染,是动词,
3.Vnode是虚拟dom,即js描述html标签
4.container是容器,渲染函数渲染的节点为容器
5.mount是挂载,表示渲染器成功将Vnode,渲染到容器上了,完成一次渲染
6.patch(n1,n2,container)是渲染器的核心,他是处理新旧Vnode的和DOM的核心
n1表示旧vnode,n2表示新vnode,container表示:容器
(2) vnode是个对象,有非常重要的3个属性,type表示节点类型,props表示节点的属性,children表示子节点的类型,渲染器围绕着这type和children不同变化来处理的
例如:当type为string类型,表示是一个普通标签,children为string表示是标签的内容
let vnode={type:String,props:,children:}
(3)因为需要多平台渲染,为了设计一个通用的渲染器