1.虚拟DOM(Virtual Document Object Model)
- DOM的本质是什么:用JS表示的UI元素
- DOM和虚拟DOM的区别:
- DOM是由浏览器中的JS提供功能,所以我们只能人为的使用 浏览器提供的固定的API来操作DOM对象;
- 虚拟DOM:并不是由浏览器提供的,而是我们程序员手动模拟实现的,类似于浏览器中的DOM,但是有着本质的区别;
- 为什么要实现虚拟DOM:
- 什么是React中的虚拟DOM:程序员使用js对象模拟出来的DOM。
- 虚拟DOM的目的:通过对比新旧的两棵模拟DOM树,然后通过diff算法找出这两棵树的差异,只对有更新的DOM进行按需更新,没有改变的DOM则不做改动。
只更新改变的DOM
与整棵DOM树重建
相比,前者使用虚拟DOM刷新UI界面就更加高效了。
2. DIff算法
Web 界面由 DOM 树来构成,当其中某一部分发生变化时,其实就是对应的某个 DOM 节点发生了变化。在 React 中,构建 UI 界面的思路是由当前状态决定界面。前后两个状态就对应两套UI界面,然后由 React 的Diff 算法来比较两个界面的区别。
2.1 不同节点类型的比较
为了在树之间进行比较,我们首先要能够比较两个节点,在 React 中即比较两个虚拟 DOM 节点,当两个节点不同时,应该如何处理。这分为两种情况:
(1)节点类型不同
(2)节点类型相同,但是属性不同。
当在树中的同一位置前后输出了不同类型的节点,React 直接删除前面的节点,然后创建并插入新的节点。假设我们在树的同一位置前后两次输出不同类型的节点。
renderA: <div />
renderB: <span />
=> [removeNode <div />], [insertNode <span />]
当一个节点从 div 变成 span 时,简单的直接删除 div 节点,并插入一个新的 span 节点。这符合我们对真实 DOM 操作的理解。
需要注意的是,删除节点意味着彻底销毁该节点,而不会在后续的比较中再去看是否有另外一个节点等同于该删除的节点
。如果该删除的节点之下有子节点,那么这些子节点也会被完全删除,它们也不会用于后面的比较。这也是算法复杂能够降低到 O(n)的原因。
上面提到的是对虚拟 DOM 节点
的操作,而同样的逻辑也被用在 React 组件
的比较,例如:
renderA: <Header />
renderB: <Content />
=> [removeNode <Header />], [insertNode <Content />]
当 React 在同一个位置遇到不同的组件时,也是简单的销毁第一个组件,而把新创建的组件加上去。这正是应用了第一个假设,不同的组件一般会产生不一样的 DOM 结构,与其浪费时间去比较它们基本上不会等价的 DOM 结构,还不如完全创建一个新的组件加上去。
由这一 React 对不同类型的节点的处理逻辑我们很容易得到推论,那就是 React 的 DOM Diff 算法实际上只会对树进行逐层比较,如下所述。
2.2逐层进行节点比较
提到树,相信大多数同学立刻想到的是二叉树,遍历,最短路径等复杂的数据结构算法。而在 React 中,树的算法其实非常简单,那就是两棵树只会对同一层次的节点进行比较。如下图所示:
React 只会对相同颜色方框内的 DOM 节点进行比较,即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。
例如,考虑有下面的 DOM 结构转换:
A 节点被整个移动到 D 节点下,直观的考虑 DOM Diff 操作应该是:
A.parent.remove(A);
D.append(A);
但因为 React 只会简单的考虑同层节点的位置变换,对于不同层的节点,只有简单的创建和删除。当根节点发现子节点中 A 不见了,就会直接销毁 A;而当 D 发现自己多了一个子节点 A,则会创建一个新的 A 作为子节点。因此对于这种结构的转变的实际操作是:
A.destroy();
A = new A();
A.append(new B());
A.append(new C());
D.append(A);
可以看到,以 A 为根节点的树被整个重新创建。
2.3 相同类型节点的比较
第二种节点的比较是相同类型的节点,算法就相对简单而容易理解。React 会对属性进行重设从而实现节点的转换
。例如:
renderA: <div id="before" />
renderB: <div id="after" />
=> [replaceAttribute id "after"]
虚拟 DOM 的 style 属性稍有不同,其值并不是一个简单字符串而必须为一个对象,因此转换过程如下:
renderA: <div style={
{
color: 'red'}} />
renderB: <div style={
{
fontWeight: 'bold'}} />
=> [removeStyle color], [addStyle font-weight 'bold']
2.4 列表节点的比较
相信很多使用 React 的同学大多遇到过这样的警告:
这是 React 在遇到列表时却又找不到 key 时提示的警告。虽然无视这条警告大部分界面也会正确工作,但这通常意味着潜在的性能问题。因为 React 觉得自己可能无法高效的去更新这个列表。
列表节点的操作通常包括添加、删除和排序。例如下图,我们需要往 B 和 C 直接插入节点 F,在 jQuery 中我们可能会直接使用 $(B).after(F) 来实现。而在 React 中,我们只会告诉 React 新的界面应该是 A-B-F-C-D-E,由 Diff 算法完成更新界面。
这时如果每个节点都没有唯一的标识,React 无法识别每一个节点,那么更新过程会很低效,即,将 C 更新成 F,D 更新成 C,E 更新成 D,最后再插入一个 E 节点。效果如下图所示:
可以看到,React 会逐个对节点进行更新,转换到目标节点。而最后插入新的节点 E,涉及到的 DOM 操作非常多。而如果给每个节点唯一的标识(key),那么 React 能够找到正确的位置去插入新的节点,入下图所示:
3. JSX语法
JSX语法的特点:
- 如要要使用 JSX 语法,必须先运行
cnpm i babel-preset-react -D
,然后再.babelrc
中添加 语法配置; - JSX语法的本质:以HTML代码写页面,但内部还是会以React.createElement()对HTML进行编译再渲染到页面上;
- 如果要在 JSX 语法内部,书写 JS 代码了,那么,所有的JS代码,必须写到
{ }
内部; - 当 编译引擎,在编译JSX代码的时候,如果遇到了
<
那么就把它当作 HTML代码去编译,如果遇到了{}
就把 花括号内部的代码当作 普通JS代码去编译; - 在
{ }
内部,可以写任何符合JS规范的代码; - 在JSX中,如果要为元素添加
class
属性了,那么,必须写成className
,因为class
在ES6中是一个关键字;和class
类似,label标签的for
属性需要替换为htmlFor
. - 在JSX创建DOM的时候,所有的节点,必须有唯一的根元素进行包裹;
- 如果要写注释了,注释必须放到
{ }
内部. - 在JSX中不 能使用
if
else
,语句,但可以使用三元表达式来代替。 - 在JSX中不能使用普通
for
循环,可以使用Array.map()
代替 - 内联样式需要用
style={ {key:value}}
的形式去写。第一个花括号是告知编译器把它当作js语法编译,内部花括号告知编译器这是js对象。 - 标签首字母:若为小写字母开头,则将该标签转化为HTML的同名元素,若HTML中无该标签对应的同名元素,则报错;若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
JSX使用示例:
1.JS表达式:表达式必须写在花括号{}
中。
ReactDOM.render(
<div>
<h1>{
1+1}</h1>
<h1>{
i == 1 ? 'True!' : 'False'}</h1>
</div>
,
document.getElementById('app')
);
2.React推荐使用内联样式。我们可以使用 camelCase 语法来设置内联样式. React 会在指定元素数字后自动添加 px 。
var myStyle = {
fontSize: 100,
color: '#FF0000'
};
ReactDOM.render(
<h1 style = {
myStyle}>React学习</h1>,
document.getElementById('app')
);
或
ReactDOM.render(
<h1 style = {
{
color:'#FF0000',fontSize:24}}>菜鸟教程</h1>,
document.getElementById('example')
);
内联样式第一层花括号是告知编译器这是JSX语法,第二层花括号是对象形式包含多个CSS属性。
3.注释:注释需要写在花括号中
ReactDOM.render(
<div>
<h1>React</h1>
{
/*注释...*/}
</div>,
document.getElementById('app')
);
4.数组:JSX允许在模板中插入数组,数组会自动展开所有成员
var arr = [
<h1>React</h1>,
<h2>学的不仅是技术,更是梦想!</h2>,
];
ReactDOM.render(
<div>{
arr}</div>,
document.getElementById('app')
);