抽离代码&&模拟React的生命周期

这一章的目标是抽离《走近React的第二天》中可复用的代码部分同时模拟React的部分基础生命周期,这里你需要对React的生命周期有一点基础的了解,明白每个周期的作用,这样才能方便你去学习这一章的内容。

目标

  • 抽离可复用代码

  • 模拟React部分生命周期

React生命周期

React提供的生命周期在React实际开发过程中是十分重要的,网上已经有很多关于React的生命周期的介绍,就不过多的赘述了,这里分享几个我觉得不错的介绍详解React生命周期React 生命周期

显示效果图

(图一:React生命周期)

显示效果图

(图二:效果图)

实现方案

  • 首先在父组件Component中抽离出Render、setState、bindEvent、createDOM等可复用方法,使得子组件可以通过extends继承的方式拿到Component中抽离出的方法。

  • 在渲染的时候通过判断目前是否已完成渲染(this.$element)以及调用_createDOM来进行生命周期的判定。

  • componentWillMount   该生命周期应该是在组件将要渲染之前触发,所以前置条件是(this.$element)为undefined且在appendChild方法调用之前触发。

  • componentDidMount   该生命周期应该是在组件第一次完成渲染之后触发,所以前置条件是(this.$element)为当前最近的DOM节点的且在appendChild方法调用之后再触发。

  • shouldComponentUpdate   该生命周期应该是一个比较特殊的生命周期,该生命周期应该是在组件已经完成渲染且State中数据修改之后且该生命周期返回值为true的时候才会触发,在组件中判定该生命周期返回值为true才会继续完成渲染。

  • componentWillUpdate   该生命周期应该是在组件已经完成渲染且State中数据修改之后触发,所以前置条件是(this.$element)已有DOM节点的且在replaceChild方法调用之前再触发

  • componentDidUpdate   该生命周期应该是在组件已经完成渲染且State中数据修改之后触发,所以前置条件是(this.$element)已有DOM节点的且在replaceChild方法调用之后再触发

父组件Component

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
class Component{
constructor(props={}){
this.props=props;
}
setState(nextState){
this.state={...this.state,...nextState};
this._render();
}
_createDOM(htmlDom){
const fragment = document.createElement('div');
fragment.innerHTML = htmlDom;
return this._bindEvent(fragment.firstElementChild);
}
_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;
}
_render(){
const oldElement = this.$element;
if (oldElement) {
if(this.shouldComponentUpdate&&!this.shouldComponentUpdate.bind(this)()){
return ;
}
this.componentWillUpdate&&this.componentWillUpdate.bind(this)();
this.$element = this._createDOM(this.render());
this.$container.replaceChild(this.$element, oldElement);
this.componentDidUpdate && this.componentDidUpdate.bind(this)();
} else {
this.componentWillMount && this.componentWillMount.bind(this)();
this.$element = this._createDOM(this.render());
this.$container.appendChild(this.$element);
this.componentDidMount && this.componentDidMount.bind(this)();
}
return this.$element;
}
}

子组件Counter

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
class  Counter extends Component{
constructor(props){
super(props);
this.state={
dateList:[]
}
this.add=this.add.bind(this);
this.reduce=this.reduce.bind(this);
}
componentWillMount(){
console.log('componentWillMount', this.state);
}
componentDidMount(){
console.log('componentDidMount', this.state);
}
componentDidUpdate(){
console.log('componentDidUpdate', this.state);
}
shouldComponentUpdate(){
return true;
}
componentWillUpdate(){
console.log('componentWillUpdate', this.state);
}
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});
}
render(){
const {dateList}=this.state;
const oldElement=this.$element;
let color='blue';
if (dateList.length >= 5) color = 'red';
if (dateList.length <= 0) color = 'green';
console.log('render')
return (
`
<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>
`
)
}
}

现在回头再看现在的Counter组件,是不是跟使用通过继承React的Component组件的写法已基本无异了,当然这里仅仅只是基础的模拟实现帮助理解学习。示例源码