模拟实现React的render以及setState方法

这一章的主要目标是模拟实现React的render以及setState更新state后刷新DOM的实现,看到这里你需要一点点ES6的基础,如果这里你还不是很会,推荐一下阮一峰老师的ES6教程写的特别好。

目标

  • 模拟实现React的render方法动态的生成Dom元素

  • 绑定Dom节点上的函数

  • 实现setState操作并且更新数据后运行Render方法重新渲染DOM元素。

效果图

显示效果图

(ps: 每条li之间显示的是因为对数组对象调用toString方法后导致的,这里就不过多解释了)

实现方案

  • 抽离render方法,内部通过innerHtml方法渲染DOM元素(当然了这里只是简单的模拟,innerHTML方法会重新渲染全部的DOM节点,在数据量大结构复杂的元素需要生成的时候会占用极大的性能,React使用的是VirtualDOM配合diff算法实现的反应更快且占用资源更少,后续会在博客中更新)

  • 通过正则表达式解析出自定义特定结构的属性并且在类的内部绑定对应的函数

  • 在调用setState方法时同时触发抽离出来的render方法达到重新渲染视图的目的。

第一步:抽离render方法(用于渲染DOM元素)

(解析):遍历存在state中的dateList数组动态的生成对应的 <li></li> 元素结构动态,然后在判断触发render方法时,该模块是否已经存在Dom结构,如果存在则替换现有DOM结构,反之则插入DOM结构

block
1
2
3
4
5
createDOM(htmlDom){
const fragment = document.createElement('div');
fragment.innerHTML = htmlDom;
return this.bindEvent(fragment.firstElementChild);
}

block
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
render(){
const {dateList}=this.state;
const oldElement=this.$element;
let color='blue';
if (dateList.length >= 5) color = 'red';
if (dateList.length <= 0) color = 'green';
this.$element = this.createDOM(`
<div>
<div style="color:${color}">当前List条数为 ${dateList.length}</div>
<button onClick="this.add">增加一条</button>
<button onClick="this.reduce">减少一条</button>
<ul>
${
dateList.map((item,index)=>`<li style="color:${color}">便签${index+1} 时间是:${item}</li>`)
}
</ul>
</div>
`);
this.$container[oldElement ? 'replaceChild' : 'appendChild'](this.$element, oldElement);
return this.$element;
}

第二步:解析属性并绑定对应函数

(解析):对传入的DOM结构进行递归操作,通过正则表达式解析元素的自定义的属性,通过addEventListener方法绑定对应的函数

block
1
2
3
4
5
6
7
8
bindEvent(el){
el.getAttributeNames().forEach(name => {
const matches=name.match(/^on([a-z]+)$/);
matches && el.addEventListener(matches[1], new Function('return ' + el.getAttribute(name)).bind(this)(),false);
});
Array.from(el.children).forEach(el=>this.bindEvent(el));
return el;
}

第三步:调用setState方法并触发Render方法重新渲染

(解析):使用 Object.assign方法或操作符 (…) 方法将存在state中的数据跟传递过来新的数据合并,同时再调用 render 方法触发页面的DOM结构的重新渲染

block
1
2
3
4
setState(nextState){
this.state={...this.state,...nextState};
this.render();
}

这样基本就能够完成制定的三个目标了。

完整的代码

html部分:

block
1
2

<div id="root"></div>

JavaScript部分:

block
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
function mountNode(counter, root){
(counter.$container = root).appendChild(counter.render());
}
class Counter{
constructor(){
this.state={
dateList:[]
}
this.add=this.add.bind(this);
this.reduce=this.reduce.bind(this);
}
add(){
let newDateList=[...this.state.dateList]
newDateList.push(new Date().toString());
this.setState({dateList:newDateList});
}
reduce(){
let newDateList=[...this.state.dateList]
newDateList.pop();
this.setState({dateList:newDateList});
}
bindEvent(el){
el.getAttributeNames().forEach(name => {
const matches=name.match(/^on([a-z]+)$/);
matches && el.addEventListener(matches[1], new Function('return ' + el.getAttribute(name)).bind(this)(),false);
});
Array.from(el.children).forEach(el=>this.bindEvent(el));
return el;
}
setState(nextState){
this.state={...this.state,...nextState};
this.render();
}
createDOM(htmlDom){
const fragment = document.createElement('div');
fragment.innerHTML = htmlDom;
return this.bindEvent(fragment.firstElementChild);
}
render(){
const {dateList}=this.state;
const oldElement=this.$element;
let color='blue';
if (dateList.length >= 5) color = 'red';
if (dateList.length <= 0) color = 'green';
this.$element = this.createDOM(`
<div>
<div style="color:${color}">当前List条数为 ${dateList.length}</div>
<button onClick="this.add">增加一条</button>
<button onClick="this.reduce">减少一条</button>
<ul>
${
dateList.map((item,index)=>`<li style="color:${color}">便签${index+1} 时间是:${item}</li>`)
}
</ul>
</div>
`);
this.$container[oldElement ? 'replaceChild' : 'appendChild'](this.$element, oldElement);
return this.$element;
}
}
mountNode(new Counter(),document.getElementById('root'));

代码抽离&&优化

看到这里可能,有一定抽象能力的客官可能会质疑了,《走近React的第一天》说好的代码抽离呢,你这也没有抽离啊!不就是简单的整合功能实现了一下功能吗,代码复用性还是不够啊?别别别,客官别急,咱们第三章会细细道来。示例源码