- 通过
create-react-app easy_react
脚手架创建一个项目 - 清理
src
目录,这里我们只留下一个index.js
即可 - 修改
index.js
如下
// index.js import React from 'react'; import ReactDOM from 'react-dom'; const style = { color: 'red', background: 'yellow', fontSize:'20px' } ReactDOM.render( <h1 id={'gu_yan'} className={'gu-yan'} style={style}> Guyan </h1>, document.getElementById('root')); 复制代码
启动项目
- 执行
yarn start
页面效果如下
分析阶段
- 将
index.js
的代码拷贝到babel
中进行转换,效果如下 - 分析上图
- 比较代码前后的变化,我们发现变化如下
/** * <h1 id={'gu_yan'} className={'gu-yan'} style={style}> * Guyan * </h1>, document.getElementById('root')); */ ‖ ‖ ‖ ↓ React.createElement("h1", { id: 'gu_yan', className: 'gu-yan', style: style }, "Guyan") 复制代码
- 将
index.js
文件,修改如下,发现页面无变化,控制台打印结果如下图
// index.js import React from 'react'; import ReactDOM from 'react-dom'; const style = { color: 'red', background: 'yellow', fontSize: '20px' }; const element = React.createElement("h1", { id: 'gu_yan', className: 'gu-yan', style: style }, "Guyan") console.log(element) ReactDOM.render(element, document.getElementById('root')); 复制代码
- 如上图可知
React.createElement()
执行返回的结果是一个对象,这个对象我们就称之为虚拟DOM
,到这里我们就可以总结出虚拟DOM
的由来了- 【1】在
webpack
打包的时候会调用babel-loader
,将JSX
语法转义为React.createElement(...)
的形式 - 【2】
React.createElement
执行返回一个对象,这对象就是虚拟DOM
- 【1】在
- 解析
React.createElement
的执行过程- 【1】收集属性对象,处理特殊的属性比如
children
,key
,ref
;本文只举例说明children
- 【1-1】创建一个
props
对象 - 【1-2】将传进来的第二个参数对象中的每一个属性都挂在
props
上,值为第二个参数对象中相对于的值 - 【1-3】给
props
挂一个children
属性,值为传进来的第三个参数- 注:
children
这个值是一个字符串或者是一个数组。如果执行React.createElement
的时候传入的参数大于3,那么children
的值就是一个数组,其值为除前两个之外的所有属性
- 注:
- 【1-1】创建一个
- 【2】将传入的
type
和收集的属性对象作为参数传入到ReactElement
中执行(reactElement(type,props)
) - 【3】
ReactElement
执行创建一个newElement
对象 - 【4】给
newElement
挂一个$$typeof
属性,这里我们统一将其值赋为Symbol(react.element)
- 【5】给
newElement
挂一个type
属性,值为传进来的type
- 【6】给
newElement
挂一个props
属性,值为传进来的props
- 【7】返回
newElement
(虚拟DOM
)
- 【1】收集属性对象,处理特殊的属性比如
- 解析
ReactDom.render
的执行过程- 【1】判断传入的虚拟
DOM
的类型- 【1.1】普通文本类型,则第一个参数可能是
string
或者number
,则直接创建一个真实文本节点 - 【1.2】普通
html
标签组件,比如<div></div>
,第一个参数对象的type
属性是一个string
类型,创建一个真实的DOM
节点并将第一个参数的props
属性中的一些普通属性挂载到这个真实的DOM
节点上,并对一些特殊的属性比如children
的进行处理,详细见下文的实现阶段 - 【1.3】函数组件(
function Component
),第一个参数对象的type
属性是一个function
类型,type
执行传入第一个参数的props
- 【1.4】类组件(
class Component
),第一个参数对象的type
属性上有一个isReactComponent
属性,new type(props)
创建实例,并让实例的render
方法执行
- 【1.1】普通文本类型,则第一个参数可能是
- 【2】将创建的真实节点插入到父节点中
- 【1】判断传入的虚拟
实现阶段
react.js
// _react.js class Component { static isReactComponent = true; constructor(props){ this.props = props; } } function ReactElement(type,props){ const virtual_dom = {}; virtual_dom.$$typeof = Symbol.for('react.element'); virtual_dom.type = type; virtual_dom.props = props; return virtual_dom; } function createElement(type,config,children){ const props = {}; for (const propName in config){ if (config.hasOwnProperty(propName)){ props[propName] = config[propName]; } } const childrenLength = arguments.length - 2; if (childrenLength === 1){ props.children = children; }else if (childrenLength > 2){ props.children = Array.from(arguments).slice(2); } return ReactElement(type,props); } export default {createElement,Component} 复制代码
react-dom.js
// _react_dom.js function render(virtual_dom,parent_node){ if (typeof virtual_dom ==='string' || typeof virtual_dom === 'number'){ /** *处理直接渲染文本节点的情况,比如ReactDOM.render('guYan', document.getElementById('root')); */ return parent_node.appendChild(document.createTextNode(virtual_dom)); } if (virtual_dom.type.isReactComponent){ /** *处理直接渲染Class Component的情况 */ virtual_dom = new virtual_dom.type(virtual_dom.props).render(); render(virtual_dom,parent_node); } if (typeof virtual_dom.type === 'function'){ /** *处理直接渲染function Component的情况 */ virtual_dom = virtual_dom.type(virtual_dom.props); render(virtual_dom,parent_node); } let {type , props} = virtual_dom; let real_dom = document.createElement(type); // 根据type创建真实节点 for (const propName in props){ // 对props进行处理 if(props.hasOwnProperty(propName)){ const prop = props[propName]; if(propName === 'className'){ real_dom.className = prop; }else if (propName === 'style'){ let cssText = Object.keys(prop).map(attr=>(`${attr.replace(/([A-Z])/g,function(){ return `-${arguments[1].toLowerCase()}` })}:${prop[attr]}`)).join(';'); real_dom.style.cssText = cssText; }else if (propName === 'children'){ /** *children可能是字符串或者是数组,如果是字符串转化成数组,然后递归调用render,需要注意的是 * 此时传入的父节点是我们创建的real_dom */ let childArr = Array.isArray(prop) ? prop : [prop]; childArr.forEach(child=>render(child,real_dom)); }else { real_dom.setAttribute(propName,prop); } } } return parent_node.appendChild(real_dom); } 复制代码
写在最后
- 日常推荐使用函数组件
- 1.使用简单
- 2.节约内存
- 3.提高性能
- 本文中的仅仅在表面层说明原理。并未深度剖析,如何错误还望指教。谢谢!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript RIA开发实战
(英)Dennis Odell / 张立浩 / 清华大学出版社 / 2010 / 48.00元
本书介绍如何采用最合理的方式为RIA编写可靠的、易于维护的HTML、CSS和JavaScript代码,以及如何使用Ajax技术在后台实现浏览器与Web服务器的动态通信。本书将介绍您在构建Web应用程序时可能遇到的性能限制,以及如何以最佳的方式克服这些限制。此外,本书提供的提示可以使用户界面响应更加灵敏。 本书也将介绍如何通过添加使用自定义字体的印刷标题、多媒体回放组件、自定义窗体控件和动态绘......一起来看看 《JavaScript RIA开发实战》 这本书的介绍吧!