1 声明周期
整个生命周期的钩子函数流程如下图:

在这里重点讲一下shouldComponentUpdate,无论是组件的属性发生变化还是状态发生改变都会执行render方法,在render执行之前要先执行shouldComponentUpdate判断一下是否需要更新,并且这个方法我们经常用来优化我们的组件。
通过shouldComponentUpdate进行优化
首先我们想到的是PureComponent 可以帮我们做这件事,shouldComponentUpdate的原理如下:
shouldComponentUpdate(nextProps, nextState){
// 如果组件的状态或者属性的地址没有发生变化的时候,该函数返回`false`以阻止重新渲染
for(let prop in nextProps){
if (nextProps[prop] !== this.props[prop]) {
return true;
}
}
for (let state in nextState) {
if (nextState[state] !== this.state[state]) {
return true;
}
}
return false
}
但是shouldComponentUpdate也存在一些问题,如下图:

有了 Immutable(在第二节有介绍),妈妈再也不用担心的我学习了。
在shouldComponentUpdate中我们可以这样优化:
shouldComponentUpdate(nextProps, nextState){
return !(is(newProps, this.props) && is(nextState, this.state));
}
setState的时候我们可以这样做:
let list = List(this.state.list);
list = list.push(value);
this.setState({
list
});
这样的话,就可以实现既不占用太多的内存,同时还是两个不同的对象。
使用 Chrome 性能分析工具 分析组件性能
- 我们可以通过添加
?react_perf查询字段加载你的应用(例如:http://localhost:3000/?react_perf)。 - 打开 Chrome DevTools Performance 并点击 Record 。
- 执行你想要分析的操作,不要超过20秒,否则 Chrome 可能会挂起。
- 停止记录。
- 在 User Timing 标签下,React事件将会分组列出
2 Immutable
1 Immutable数据结构的特点
- 不可变(Immutable): 一个集合一旦创建,在其他时间是不可更改的。
- 持久的(Persistent): 新的集合可以基于之前的结合创建并产生突变,例如:set。原来的集合在新集合创建之后仍然是可用的。
- 结构共享(Structural Sharing): 新的集合尽可能通过之前集合相同的结构创建,最小程度地减少复制操作来提高性能。
下面的动态图可以帮助我们加深理解:

2 Immutable类库
a) immutable.js
- 内部实现了一套完整的 Persistent Data Structure,还有很多易用的数据类型。像 Collection、List、Map、Set、Record等。
e.g.
let {fromJS, Map} = require('immutable');
let a = {
count: '1'
};
let a1 = Map(a);
let a2 = a1.set('count', '11');
console.log(a);
console.log(a1);
console.log(a2);
/*
{ count: '1' }
Map { "count": "1" }
Map { "count": "11" }
*/
let obj = {name: 'lily', info: {age: 8}};
let obj1 = fromJS(obj);
let obj2 = obj1.setIn(['info', 'age'], 9);
console.log(obj);
console.log(obj1);
console.log(obj2);
/*
{ name: 'lily', info: { age: 8 } }
Map { "name": "lily", "info": Map { "age": 8 } }
Map { "name": "lily", "info": Map { "age": 9 } }
*/
b) seamless-immutable
使用Object.defineProperty扩展了 JavaScript的Array和Object对象来实现,只支持 Array 和 Object 两种数据类型.
let Immutable = require('seamless-immutable');
let c = Immutable({info: {age: 8}});
let c1 = c.merge({info: {age: 9}});
console.log(c.info.age);// 8
console.log(c1.info.age);// 9
3 Immutabled的优势
a) 降低复杂度
let immutable = require('seamless-immutable');
let obj = immutable({ age: 8 });
handle(obj);
console.log(obj.age); // 8
function handle(obj) {
obj.age = 100;
}
b) 节约内存
let { fromJS } = require('immutable');
let a1 = fromJS({
name: 'Lily',
info: {
age: 8
}
});
let a2 = a1.set('name', 'Tom');
// 对没有发生变化的部分进行数据共享
console.log(a1.get('info') === a2.get('info'));// true
c) 方便回溯
3 常用API
Map和List
// Map - 原生object --> Map对象 (只会转换第一层)
// List - 原生array --> List对象 (只会转换第一层)
let m1 = Map({
name: 'Lily', info: {
age: 8,
}
});
let a1 = List([1, 2, 3]);
console.log(m1); // Map { "name": "Lily", "info": [object Object] }
console.log(a1); // List [ 1, 2, 3 ]
fromJS
// fromJS - 原生js --> immutable对象 (深度转换,会将内部嵌套的对象和数组全部转成immutable)
let m2 = fromJS({
name: 'Lily', info: {
age: 8,
}
});
let a2 = fromJS([1, 2, 3]);
console.log(m2); // Map { "name": "Lily", "info": Map { "age": 8 } }
console.log(a2); // List [ 1, 2, 3 ]
immutableData.toJSimmutableData.sizeimmutableData.count
// immutableData.toJS() - immutable对象 --> 原生js (深度转换,会将内部嵌套的Map和List全部转换成原生js)
// immutableData.size - 查看List或者map大小
// immutableData.count() - 查看List或者map大小
let obj = {
name: 'Lily',
info: {
age: 8,
}
};
let m1 = fromJS(obj);
let m2 = fromJS(obj);
console.log(m1.size); // 2
console.log(m1.count()); // 2
r = m1.toJS();
console.log(r); // { name: 'Lily', info: { age: 8 } }
console.log(r == obj); // false
- 增删改查 (所有操作都会返回新的值,不会修改原来值)
// get() getIn()
// set() setIn()
// update() updateIn()
// delete() deleteIn()
let obj = {
name: 'Lily',
info: {
age: 8
}
};
let data = fromJS(obj);
let data1 = data.get('name');
console.log(data1); // Lily
let data2 = data.getIn(['info', 'age']);
console.log(data2); // 8
let data3 = data.set('name', 'Tom');
console.log(data3); // Map { "name": "Tom", "info": Map { "age": 8 } }
let data4 = data.setIn(['info', 'age'], 9);
console.log(data4); // Map { "name": "Lily", "info": Map { "age": 9 } }
let data5 = data.update('name', (value) => value + '123');
console.log(data5); // Map { "name": "Lily123", "info": Map { "age": 8 } }
let data6 = data.updateIn(['info', 'age'], (value) => value + 1);
console.log(data6); // Map { "name": "Lily", "info": Map { "age": 9 } }
let data7 = data.delete('name');
console.log(data7); // Map { "info": Map { "age": 8 } }
let data8 = data.deleteIn(['info', 'age']);
console.log(data8); // Map { "name": "Lily", "info": Map {} }
is()判断两个immutable对象是否相等
let obj = {
name: 'Lily',
info: {
age: 8
}
};
let data1 = fromJS(obj);
let data2 = fromJS(obj);
console.log(is(data1, data2));// true
let data3 = data1.set('name', 'Tom');
console.log(is(data3, data2));// false
immutableData.merge()对象合并
let obj1 = {
a: 1,
b: 2
};
let obj2 = {
b: 9,
c: 3
};
let data1 = fromJS(obj1);
let data2 = fromJS(obj2);
let data3 = data1.merge(data2);
console.log(data3);// Map { "a": 1, "b": 9, "c": 3 }
let data4 = data2.merge(data1);
console.log(data4);// Map { "b": 2, "c": 3, "a": 1 }
3 Reconciliation 一致性比较
当比较不同的两个树,React 首先比较两个根元素。根据根跟的类型不同,它有不同的行为
- 当根元素类型不同时,React 将会销毁原先的树并重写构建新的树
- 当比较两个相同类型的 React DOM 元素时,React 检查它们的属性(attributes),保留相同的底层 DOM 节点,只更新发生改变的属性(attributes)
- 当一个组件更新的时候,组件实例保持不变,以便在渲染中保持state。React会更新组件实例的属性来匹配新的元素,并在元素实例上调用 componentWillReceiveProps() 和 componentWillUpdate()
在这里我们举一个根元素类型不同的栗子, 如下:
import React, {Component} from 'react';
class Counter extends Component {
constructor() {
super();
this.flag = new Date().getTime();
}
componentWillMount() {
console.log(this.flag + ' - componentWillMount');
}
render() {
return (<div>count</div>)
}
componentDidMount() {
console.log(this.flag + ' - componentDidMount');
}
componentWillUnmount() {
console.log(this.flag + ' - componentWillUnmount');
}
}
export default class Reconciliation extends Component {
constructor() {
super();
this.state = {
show: true
};
}
handleChange = () => {
this.setState({
show: !this.state.show
});
}
render() {
return (
<div>
<button onClick={this.handleChange}>change</button>
{
this.state.show
? (<div>
<Counter/>
</div>)
: (<span>
<Counter/>
</span>)
}
</div>
)
}
}
控制台打印的顺序如下:

4 使用 PropTypes 进行类型检查
React 内置了类型检测的功能。要在组件中进行类型检测,你可以赋值 propTypes 属性
class Count extends Component{
// 设置默认属性
static defaultProps = {
name: 'Tom'
}
// 进行类型检测
static propTypes = {
name: PropTypes.string,
age: PropTypes.number,
node: function(props, propName, componentName) {
if (props[propName].length == 0){
return new Error('debugger')
}
}
}
}
.array数组.bool布尔值.func函数.number数字.object对象.string字符串.symbol符号.node任何东西都可以被渲染:numbers, strings, elements,或者是包含这些类型的数组(或者是片段)。.elementReact元素.instanceOf(Message)类的一个实例.oneOf(['News', 'Photos'])枚举值.oneOfType([PropTypes.string,PropTypes.number,PropTypes.instanceOf(Message)])多种类型其中之一.arrayOf(PropTypes.number)某种类型的数组.objectOf(PropTypes.number)某种类型的对象.shape({color: PropTypes.string,fontSize: PropTypes.number})特定形式的对象.func.isRequired可以使用 `isRequired' 链接上述任何一个,以确保在没有提供 prop 的情况下显示警告.any.isRequired任何数据类型的值 function(props, propName, componentName) { return new Error()} 自定义的验证器.arrayOf(function(propValue, key, componentName, location, propFullName) { }
5 Context
在某些场景下,你想在整个组件树中传递数据,但却不想手动地在每一层传递属性。你可以直接在 React 中使用强大的”context” API解决上述问题
- 用法
// 祖先组件
static childContextTypes = {
color: PropTypes.string,
changeColor: PropTypes.func
}
getChildContext(){
return {
color: this.state.color,
changeColor: (color) => {
this.setState({color: color})
}
}
}
// 孙子组件
static contextTypes = {// 孙子组件需要哪些就写哪些,不需要把所有的属性都继承过来
color: PropTypes.string,
changeColor: PropTypes.func
}
this.context // 在组件中可以使用 this.context 就可以获取到上下文的内容了
- e.g.
import React, {Component} from 'react';
import PropTypes from 'prop-types'
class Main extends Component {
render() {
return (
<div>
<Content/>
</div>
)
}
}
class Content extends Component {
static contextTypes = {
color: PropTypes.string,
changeColor: PropTypes.func
}
render() {
return (
<div>
<span style={{color: this.context.color}}>content</span>
<button onClick={() => this.context.changeColor('green')}>变绿</button>
<button onClick={() => this.context.changeColor('red')}>变红</button>
</div>
)
}
}
class Head extends Component {
render() {
return (
<div>
<Title/>
</div>
)
}
}
class Title extends Component {
static contextTypes = {
color: PropTypes.string
}
render() {
return (
<div>
<span style={{color: this.context.color}}>title</span>
</div>
)
}
}
export default class Page extends Component {
static childContextTypes = {
color: PropTypes.string,
changeColor: PropTypes.func
}
constructor(){
super();
this.state = {
color: 'red'
};
}
getChildContext(){
let self = this;
return {
color: this.state.color,
changeColor: (color) => {
this.setState({color: color})
}
}
}
render() {
return (
<div>
<Head/>
<Main/>
</div>
)
}
}
6 片段(React.Fragment)
- React 中一个常见模式是为一个组件返回多个元素。 片段(fragments) 可以让你将子元素列表添加到一个分组中,并且不会在DOM中增加额外节点。
import React, {Component} from 'react';
class Item extends Component {
render() {
return (
<React.Fragment>
{
this.props.list.map((item, index) => (
<li key={index}>{item}</li>
))
}
</React.Fragment>
)
}
}
export default class Fragment extends Component {
constructor() {
super();
this.state = {
list: [1, 2, 3]
};
}
render() {
return (
<div>
<Item list={this.state.list}/>
</div>
)
}
}
生成的DOM结构如下图,React.Fragment并不会生成一个DOM节点

7 插槽(Portals)
Portals 提供了一种很好的方法,将子节点渲染到父组件 DOM 层次结构之外的 DOM 节点。
ReactDOM.createPortal(child, container)
// child - 任何一个可渲染的React元素,比如:一个元素、字符串或者fragment
// container - 一个DOM元素,这个元素就是child将要放置的地方
import React, {Component} from 'react';
import ReactDOM from 'react-dom'
import './portals.css'
class Modal extends Component {
constructor() {
super();
this.modal = document.querySelector('#modal-root');
}
render() {
return ReactDOM.createPortal(this.props.children, this.modal);
}
}
export default class Portals extends Component {
constructor() {
super();
this.state = {
show: false
};
}
handleClick = () => {
this.setState({show: !this.state.show});
};
render() {
return (
<div>
<button onClick={this.handleClick}>show</button>
{
this.state.show && <Modal>
<div id="modal" className="modal">
<div id="modal-content" className="modal-content">
<button onClick={this.handleClick}>X</button>
</div>
</div>
</Modal>
}
</div>
)
}
}
8 Error Boundaries 和 componentDidCatch
Error Boundaries
部分 UI 中的 JavaScript 错误不应该破坏整个应用程序。 为了解决 React 用户的这个问题,React 16引入了一个 “错误边界(Error Boundaries)” 的新概念。
它依赖一个新的生命周期函数即componentDidCatch(error, info) 。
componentDidCatch
这个函数类似于JS中的catch{ }代码块,但是只是用针对于组件的,可以捕获组件内的错误对象。
note
注意:Error Boundary只能捕获他自己树下的错误,不能捕获发生在自己身上的错误。如果一个错误没有被Error Boundary捕获到,这个错误就会冒泡到它的上层离它最近的error boundary,这个跟原生JS中catch {}代码块的工作原理很相似。
9 高阶组件(Higher-Order Components)
高阶组件就是一个函数,用来封装重复的逻辑。传入一个老组件,返回一个新组件。
const NewComponent = higherOrderComponent(OldComponent)
- 举一个例子
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
function ajax(OldComponent) {
class NewComponent extends Component {
constructor() {
super();
this.state = {value: ''};
}
componentDidMount() {
fetch('./data.json').then(res => res.json()).then((userData) => {
this.setState({
value: userData[this.props.keyName]
})
})
}
render() {
return <OldComponent value={this.state.value}/>
}
}
return NewComponent;
}
function local(OldComponent, name, placeHolder) {
class NewComponent extends Component {
constructor() {
super();
this.state = {keyName: ''};
}
componentDidMount() {
this.setState({
keyName: localStorage.getItem(name) || placeHolder
})
}
render() {
return <OldComponent keyName={this.state.keyName}/>
}
}
return NewComponent;
}
class UserName extends Component {
render() {
return (
<div>
用户名 <input type="text" value={this.props.value}/>
<br/>
</div>
)
}
}
UserName = ajax(UserName);
UserName = local(UserName, 'username', '请输入您的用户名');
class Mobile extends Component {
render() {
return (
<div>
手机号 <input type="text" value={this.props.value}/>
<br/>
</div>
)
}
}
Mobile = ajax(Mobile);
Mobile = local(Mobile, 'mobile', '请输入您的手机号码');
class Index extends Component {
render() {
return (
<div>
<UserName/>
<Mobile/>
</div>
)
}
}
ReactDOM.render(<Index/>, document.querySelector('#app'));
本例子中高阶组件的逻辑图如下:

其中,高阶组件将值传递给组件的逻辑如下:
