900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > 渲染器 render

渲染器 render

时间:2021-03-15 12:09:55

相关推荐

渲染器 render

最近在看《vue.js 设计与实现》,看到了虚拟 DOM 这里,做了个笔记

文章目录

1、VNode虚拟DOM节点2、render工作原理3、代码实现4、render拓展

1、VNode虚拟DOM节点

虚拟DOM节点如下:

const vnode = {tag: 'h2',props: {class: 'active',data: 'text',onClick: () => alert('Hello Render!')},children: 'Click Me!'}

tag:用来描述标签名称,所以tag: 'h2'描述的就是一个<h2>标签。props:是一个对象,用来描述<h2>标签的类名、属性、事件等内容。可以看到,我给h2绑定一个active类名,一个text自定义属性,一个click点击事件。children:用来描述标签的子节点,在上面的代码中,children是一个字符串值,意思是h2标签有一个文本子节点:<h2>Click Me!</h2>

2、render工作原理

第一步:创建元素,把vnode.tag作为标签名称来创建DOM元素。第二步:为元素添加属性和事件,遍历vnode.props对象。如果key是class,说明它是一个类名,将其直接绑定给tag元素;如果key以on字符串开头,说明它是一个事件,把字符on截取掉后再调用toLowerCase函数将事件名称小写化,最终得到合法的事件名称,例如onClick会变成click,最后调用addEventListener绑定事件处理函数。第三步:处理children,如果children是一个数组,就递归地调用render继续渲染。注意,此时我们要把刚刚创建的元素作为挂载节点(父节点);如果children是字符串,则使用createTextNode函数创建一个文本节点,并将其添加到新的创建的元素内。

3、代码实现

自己手写一个简陋的render渲染器,上代码:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>.active {color: red;}</style></head><body><!-- 根节点 --><div id="app"></div><script>// 获取根节点const app = document.getElementById("app");// 要进行的挂载的虚拟 dom 节点const vnode = {// 节点类型tag: 'h2',// 节点上面绑定的类型、属性、方法props: {class: 'active', // 类名data: 'text', // 自定义属性onClick: () => alert('Hello Render!') // 方法},// 子节点children: 'Click Me!'}// 封装的渲染器 render 函数// vnode 是虚拟 dom 节点// container 是要挂载节点的元素const render = (vnode, container) => {// 根据 vnode.tag 创建对应的 dom 节点const el = document.createElement(vnode.tag);// 遍历 vnode.props,给节点绑定类名、属性、事件等for (const key in vnode.props) {if (key === 'class') {// 如果是类名,则给元素添加类名el.className += vnode.props[key]} else if (/^on/.test(key)) {// 如果属性是以 on 开头的,那么就绑定对应的事件el.addEventListener(key.substr(2).toLowerCase(), // 改变事件类型:例如 onClick 变为 clickvnode.props[key] // 绑定对应的事件)} else {// 如果是自定义属性,给元素绑定对应的属性el.setAttribute(key, vnode.props[key])}}// 处理子节点if (typeof vnode.children === 'string') {// 如果子节点是 string 类型,那么就是本文节点el.appendChild(document.createTextNode(vnode.children));} else if (Array.isArray(vnode.children)) {// 如果子节点是 array 类型,那么继续渲染子节点vnode.children.forEach(child => render(child, el));}// 将渲染的 vnode 挂载在根节点上app.appendChild(el);}// 调用 render 函数render(vnode, app)</script></body></html>

看一下页面效果:

4、render拓展

在上面进行VNode渲染的过程,我只是讨论了当VNode是一个虚拟DOM节点的情况,那么接下来我将会讨论组件component的渲染,而component组件应该有两种情况

component是一个函数;component是一个对象;

首先直接来看代码的实现:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>.active1 {color: red;}.active2 {color: blue;}.active3 {color: orange;}</style></head><body><!-- 根节点 --><div id="app"></div><script>// 获取根节点const app = document.getElementById("app");// 要进行绑定的 component 组件 —— 返回函数const component1 = function () {return {// 节点类型tag: 'h2',// 节点上面绑定的类型、属性、方法props: {class: 'active2', // 类名data: 'component1', // 自定义属性onClick: () => alert('Hello Component1!') // 方法},// 子节点children: 'Function Component!'}}// 要进行绑定的 component 组件 —— 返回对象const component2 = {// 该组件对象的 render 属性render() {return {// 节点类型tag: 'h2',// 节点上面绑定的类型、属性、方法props: {class: 'active3', // 类名data: 'component2', // 自定义属性onClick: () => alert('Hello Component2!') // 方法},// 子节点children: 'Object Component!'}}}// 要进行的挂载的虚拟 dom 节点(标签元素)const vnode1 = {// 节点类型tag: 'h2',// 节点上面绑定的类型、属性、方法props: {class: 'active1', // 类名data: 'vnode', // 自定义属性onClick: () => alert('Hello Render!') // 方法},// 子节点children: 'Click Me!'}// 要进行的挂载的虚拟 dom 节点(组件1)const vnode2 = {tag: component1}// 要进行的挂载的虚拟 dom 节点(组件2)const vnode3 = {tag: component2}// 挂载标签元素的方法const momentElement = (vnode, container) => {// 根据 vnode.tag 创建对应的 dom 节点const el = document.createElement(vnode.tag)// 遍历 vnode.props,给节点绑定类名、属性、事件等for (const key in vnode.props) {if (key === 'class') {// 如果是类名,则给元素添加类名el.className += vnode.props[key]} else if (/^on/.test(key)) {// 如果属性是以 on 开头的,那么就绑定对应的事件el.addEventListener(key.substr(2).toLowerCase(), // 改变事件类型:例如 onClick 变为 clickvnode.props[key] // 绑定对应的事件)} else {// 如果是自定义属性,给元素绑定对应的属性el.setAttribute(key, vnode.props[key])}}// 处理子节点if (typeof vnode.children === 'string') {// 如果子节点是 string 类型,那么就是本文节点el.appendChild(document.createTextNode(vnode.children))} else if (Array.isArray(vnode.children)) {// 如果子节点是 array 类型,那么继续渲染子节点vnode.children.forEach(child => render(child, el))}// 将渲染的 vnode 挂载在根节点上app.appendChild(el);}// 挂载组件的函数const momentComponent = (vnode, container) => {// 如果组件返回的是函数:调用组件函数,获取组件要渲染的内容(虚拟 DOM)// 如果组件返回的是对象:vnode.tag 是组件对象,调用它的 render 函数得到组件要渲染的内容(虚拟 DOM)const componentTree = typeof vnode.tag === 'function' ? vnode.tag() : vnode.tag.render()// 递归地调用 render 渲染 componentTreerender(componentTree, container)}// 封装的渲染器 render 函数// vnode 是虚拟 dom 节点// container 是要挂载节点的元素const render = (vnode, container) => {if (typeof vnode.tag === 'string') {// 如果 vnode.tag 是 string 类型,则表示该 vnode 是标签元素,调用挂载标签元素的函数momentElement(vnode, container)} else if (typeof vnode.tag === 'object' || typeof vnode.tag === 'function') {// 如果 vnode.tag 是 object 类型,则表示该 vnode 是组件,调用挂载组件的函数momentComponent(vnode, container)}}// 调用 render 函数render(vnode1, app)render(vnode2, app)render(vnode3, app)</script></body></html>

看一下页面效果:

事项说明:

组件就是一组DOM元素的封装代码中我的注释写的都挺清楚了,一定要注意component组件的类型分为Object和Function两种类型,这两种不同情况的返回值都是不一样的,一定要注意返回值,不要搞混。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。