ReactP4_JSX=>虚拟DOM=>真实DOM_阶段性案例
这边是react学习笔记,期间加入了大量自己的理解,用于加强印象,若有错误之处还请多多指出
关于JSX
jsx 从本质上来说是 React.createElement(component, props, …children) 函数的语法糖
而所有的jsx都会通过类似于babel这样的js编译器被转换成React.createElement的函数调用
关于babel的转换可以在babel的官方网站查找
babel转换
关于babel转换的问题,这里进行一个简单的案例示范:
第一步:进入babel官方网站,点击顶部导航栏中的try it out选项,进入转换页面
第二步:在左侧进行转换配置
第三步:在左侧文本框输入内容即可在右侧完成转换
从此处可以正式babel转换后得到的代码都是不同参数下对React.createElement的函数调用
React.createElement源码
React_v16.13.1代码的下载地址https://github.com/facebook/react/archive/v16.13.1.zip
源代码入口文件的存储的位置在解压文件react-16.13.1 => packages => react => src => index.js中
随后在入口文件index.js中找到export关键字,即导出的函数中找到createElement方法
根据结尾导出提示可知,该方法是从src的React文件中被引用的
在React.js文件中找到creatElement方法
此处首先看到的是一个判断方法
该处主要对开发环境进行判断,如果是需要开发环境,则在内部会进行大量的数据验证判断(具体内容不介绍),从而尽可能多的给出警告提示,如果不是开发环境,则直接执行createElement函数。
createElement函数:
接下来是重头戏,代码太占位置,所以不贴了,具体内容自行查看
此处可知,createElement需要传递三个参数:
-
参数一:type——当前ReactElement的类型。如果是标签元素,那么就使用字符串表示 “div”,如果是组件元素,那么就直接使用组件的名称
-
参数二:config——所有jsx中的属性都在config中以对象的属性和值的形式存储
-
参数三:children——存放在标签中的内容,以children数组的方式进行存储。
其中,多个元素React内部有对它们进行处理,处理的源码在下方
对传入的参数利用argument,从第三个元素开始遍历提取,放入一个新的数组,数组中的就是后面所有的参数了。
虚拟DOM
通过 React.createElement 最终创建出来一个 ReactElement对象,原因是React利用ReactElement对象组成了一个JavaScript的对象树,JavaScript的对象树就是虚拟DOM(Virtual DOM)
对于虚拟DOM的介绍,我看的是虚拟DOM解析及其在框架里的应用
采用虚拟DOM的主要原因:
- 很难跟踪状态发生的改变:原有的开发模式,很难跟踪到状态发生的改变,不方便针对应用程序进行调试
- 操作真实DOM性能较低:传统的开发模式会进行频繁的DOM操作,而这一的做法性能非常的低
导致真实DOM操作性能低的原因
- document.createElement本身创建出来的就是一个非常复杂的对象
- DOM操作会引起浏览器的回流和重绘(很吃性能的两个事件),所以在开发中应该避免频繁的DOM操作
通过ReactDOM.render让 虚拟DOM 和 真实DOM同步起来,这个过程中叫做协调(Reconciliation)
个人观点:如果把渲染网页比作是考场上答题,那么虚拟DOM就是我们在草稿纸上画一个草图,真实DOM就是根据草图上的内容在答题纸上写下最后的答案。直接改答题卡是一件很麻烦的事情,但是在草稿上涂涂改改的是很随意的一件事儿。同样的,对虚拟DOM的操作不论从性能还是效率上来说,都是远大于对真实DOM直接进行操作的。可以说,虚拟DOM的存在,是一种对浏览器渲染方式的优化选择。
查看ReactElement的树结构
render() {
var elementObj = (
<div>
<div className="header">
<h2 title="标题">标题内容</h2>
</div>
<div className="content">
<h2>页面的内容</h2>
<button>提交</button>
<a href="http://www.baidu.com">百度一下</a>
</div>
<div className="footer">
<p>尾部内容</p>
</div>
</div>
)
console.log(elementObj);
return elementObj;
}
我们可以将之前的jsx返回结果进行打印
关于第一个子元素header继续展开的结果
而ReactElement最终形成的树结构就是Virtual DOM(随手画图板画的)
阶段性案例
完成一个简单的类购物车列表,效果如下图:
其中:
- 点击按钮“+”,所选行的购买数量+1;
- 购买数量为1时,按钮“-”不可被点击;
- 点击“移除”按钮,当前行被删除;
- 序列号和总价格能够实时更新;
- 如果所有项被移除完,则显示“内容为空”字样;
结果代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table {
border: 1px solid #eee;
border-collapse: collapse;
}
th, td {
border: 1px solid #eee;
padding: 10px 16px;
text-align: center;
}
th {
background-color: #ccc;
}
.count {
margin: 0 5px;
}
</style>
</head>
<body>
<div id="app"></div>
<script src="../react/react.development.js"></script>
<script src="../react/react-dom.development.js"></script>
<script src="../react/babel.min.js"></script>
<script src="./format-utils.js"></script>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
books: [
{
id: 1,
name: '《算法导论》',
date: '2006-9',
price: 85.00,
count: 1
},
{
id: 2,
name: '《UNIX编程艺术》',
date: '2006-2',
price: 59.00,
count: 1
},
{
id: 3,
name: '《编程珠玑》',
date: '2008-10',
price: 39.00,
count: 1
},
{
id: 4,
name: '《代码大全》',
date: '2006-3',
price: 128.00,
count: 1
},
]
}
}
renderBooks() {
return (
<div>
<table>
<thead>
<tr>
<th></th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{
this.state.books.map((item, index) => {
return (
<tr>
<td>{index+1}</td>
<td>{item.name}</td>
<td>{item.date}</td>
<td>{formatPrice(item.price)}</td>
<td>
<button disabled={item.count <= 1} onClick={e => this.changeBookCount(index, -1)}>-</button>
<span className="count">{item.count}</span>
<button onClick={e => this.changeBookCount(index, 1)}>+</button>
</td>
<td><button onClick={e => this.removeBook(index)}>移除</button></td>
</tr>
)
})
}
</tbody>
</table>
<h2>总价: {this.getTotalPrice()}</h2>
</div>
)
}
renderEmptyTip() {
return <h2>内容为空~</h2>
}
render() {
return this.state.books.length ? this.renderBooks(): this.renderEmptyTip();
}
changeBookCount(index, count) {
const newBooks = [...this.state.books];
newBooks[index].count += count;
this.setState({
books: newBooks
})
}
removeBook(index) {
// React中设计原则: state中的数据的不可变性;
this.setState({
books: this.state.books.filter((item, indey) => index != indey)
})
}
getTotalPrice() {
const totalPrice = this.state.books.reduce((preValue, item) => {
return preValue + item.count * item.price;
}, 0);
return formatPrice(totalPrice);
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
</body>
</html>
感谢coderwhy(王红元老师)的课程
最后,爱生活,爱猪猪