react系列一,react虚拟dom如何转成真实的dom
react,想必作为前端开发一定不陌生,组件化以及虚拟dom使得react成为最受欢迎额前端框架之一。我们知道react是基于虚拟dom的,但是什么是虚拟dom呢,其实就是一组js对象,那么我们今天就来认识什么是虚拟dom,以及如何转成真实的dom结构,完整的 简易版react 在个人github,实现了diff算法,组件渲染,组件更新,钩子函数。
一.认识虚拟dom
首先我们看如下代码
1 | const title = <h1 className= "title" >Hello, world!</h1>; |
这并不是合法的js代码,它是一种被称为jsx的语法扩展,通过它我们就可以很方便的在js代码中书写html片段。
本质上,jsx是语法糖,上面这段代码会被babel转换成如下代码
我们下载插件 babel-plugin-transform-react-jsx,并且配置.babelrc文件
1 2 3 4 5 6 7 8 | { "presets" : [ "env" ], "plugins" : [ [ "transform-react-jsx" , { "pragma" : "React.createElement" //大部分框架喜欢改成h }] ] } |
于是页面中的jsx就会被babel转成如下的结构
1 2 3 4 5 | const title = React.createElement( 'h1' , { className: 'title' }, 'Hello, world!' ); |
可以看出babel已经把一个dom元素分解成标签名称h1,属性集合对象,以及内部子节点(这里是hello world文本节点),我们首先修改这个方法,为了转成我们需要的结构
1 2 3 4 5 6 7 8 9 10 11 | function createElement( tag, attrs, ...children ) { return { tag, attrs, children } } // 将上文定义的createElement方法放到对象React中 const React = { createElement, } |
函数的参数...children
使用了ES6的rest参数,它的作用是将后面child1,child2等参数合并成一个数组children。
现在我们来试试调用它,一下结构都是babel自动调用React.createElement给我们转成的,当然你也可以自己写方法将真实的dom转为js对象
1 2 3 4 5 6 | const element = ( <div> hello<span>world!</span> </div> ); console.log( element ); |
二.将上列的虚拟dom结构转成真实的dom
1.如果遇到文本节点则直接返回新建的文本节点
1 2 3 4 5 | //处理文本节点 if ( typeof vnode === 'string' ){ const textNode = document.createTextNode( vnode ) return textNode; } |
2.处理普通的元素
1 2 3 4 5 6 7 8 9 10 | //普通的dom const dom = document.createElement( vnode.tag ); if ( vnode.attrs ){ Object.keys( vnode.attrs ).forEach( key => { const value = vnode.attrs[ key ]; setAttribute( dom, key, value ); // 设置属性 } ); } vnode.children.forEach( child => render( child, dom ) ); // 递归渲染子节点 return dom ; // 返回虚拟dom为真正的DOM |
3.遇到普通元素的属性,需要这是属性节点,但是分为两种,一种是普通的属性,比如className,另一种是方法绑定,比如是onClick
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | function setAttribute( dom, name, value ) { // 如果属性名是className,则改回class if ( name === 'className' ) name = 'class' ; // 如果属性名是onXXX,则是一个事件监听方法 if ( /on\w+/.test( name ) ) { name = name.toLowerCase(); dom[ name ] = value || '' ; // 如果属性名是style,则更新style对象 } else if ( name === 'style' ) { if ( !value || typeof value === 'string' ) { dom.style.cssText = value || '' ; } else if ( value && typeof value === 'object' ) { for ( let name in value ) { // 可以通过style={ width: 20 }这种形式来设置样式,可以省略掉单位px dom.style[ name ] = typeof value[ name ] === 'number' ? value[ name ] + 'px' : value[ name ]; } } // 普通属性则直接更新属性 } else { if ( name in dom ) { dom[ name ] = value || '' ; } if ( value ) { dom.setAttribute( name, value ); } else { dom.removeAttribute( name, value ); } } } |
三.查看完整的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | function render ( vnode, container ){ return container.appendChild( _render( vnode ) ); } function _render( vnode ){ if ( typeof vnode === 'number' ) { vnode = String( vnode ); } //处理文本节点 if ( typeof vnode === 'string' ){ const textNode = document.createTextNode( vnode ) return textNode; } //处理组件 if ( typeof vnode.tag === 'function' ) { const component = createComponent( vnode.tag, vnode.attrs ); setComponentProps( component, vnode.attrs ); return component.base; } //普通的dom const dom = document.createElement( vnode.tag ); if ( vnode.attrs ){ Object.keys( vnode.attrs ).forEach( key => { const value = vnode.attrs[ key ]; setAttribute( dom, key, value ); // 设置属性 } ); } vnode.children.forEach( child => render( child, dom ) ); // 递归渲染子节点 return dom ; // 返回虚拟dom为真正的DOM } //实现dom挂载到页面某个元素 const ReactDOM = { render: ( vnode, container ) => { container.innerHTML = '' ; return render( vnode, container ); } } |
现在我们已经实现将虚拟dom转为真实的dom,已经绑定属性,我们现在来像react一样调用这个方法
1 2 3 4 5 6 7 8 9 10 | const element = ( <div> hello<span>world!</span> </div> ); ReactDOM.render( element, document.getElementById( 'main' ) ); |
现在就实现往页面中元素id为main的元素上挂载了该element。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构