目录
1.虚拟DOM
React通过重复渲染来实现动态更新效果,但是借助Virtual DOM技术,实际上这个过程并不涉及太多的DOM操作,所以渲染效率很高。
渲染React组件时,会对比这一次产生的Virtual DOM和上一次渲染的Virtual DOM,修改真正的DOM树时只需要触及差别中的部分就行。
2.JSX
React JSX 代码可以放在一个独立文件上。
JSX 中不能使用 if else 语句,但可以使用 conditional (三元运算) 表达式。
3.组件
3.1自定义的 React 类名以大写字母开头
可以使用函数定义了一个组件,也可以使用 ES6 class 来定义一个组件。自定义的 React 类名以大写字母开头。
(1)function HelloMessage(props) {return <h1>Hello World!</h1>;}
(2)class Welcome extends React.Component {render() {return <h1>Hello World!</h1>;}}
const element = <HelloMessage />; 为用户自定义的组件。
使用 this.props 对象向组件传递参数。
3.2组件封装
使用JSX使得React的组件(通过JSX)可以把JavaScript、HTML和CSS的功能在一个文件中,实现真正的组件封装。
3.3组件的数据
React组件的数据有2种:对外用prop,内部用state。
propTypes能够在开发阶段发现代码中的问题(console中有提示),但在产品环境不需要部署,可用babel-react-optimize在发布产品代码时自动将propTypes去掉。
必须使用this.setState()修改state的值,它首先改变this.state的值,然后驱动组件经历更新过程。而直接修改this.state的值,虽然值是改变了,但是没有驱动组件进行重新渲染(例如:this.state.count=this.state.count+1;)。
3.4组件的生命周期
组件的生命周期:装载过程(Mount),更新过程(Update),卸载过程(Unmount)
3.4.1装载过程
装载过程依次调用函数顺序
- constructor
getInitialState(用React.createClass方法创造的组件类才会发生作用。ES6中不会产生作用)getDefaultProps(用React.createClass方法创造的组件类才会发生作用。ES6中不会产生作用)- componentWillMount(很少用到)
- render
- componentDidMount
constructor不是必须的,一般用来初始化state或绑定成员函数的this环境。
render是一个纯函数,没有副作用。在render函数中调用this.setState是错误的。
componentWillMount可以在服务器端被调用,也可以在浏览器端被调用。componentDidMount只能在浏览器端被调用。
render函数被调用完之后,componentDidMount函数并不是会被立刻调用,需要等所有组件的render函数都被调用之后才会调用。
在componentDidMount被调用的时候,组件已经被装载到DOM树上了,可以放心获取渲染出来的任何DOM。componentDidMount在React和其他UI库配合使用时就很好用。
(1)并列组件装载过程演示:
//ControlPanel.js文件部分代码
class ControlPanel extends Component {
render() {
console.log('enter ControlPanel render');
return (
<div style={style}>
<Counter caption="First"/>
<Counter caption="Second" initValue={10} />
<Counter caption="Third" initValue={20} />
<button onClick={ () => this.forceUpdate() }>
Click me to re-render!
</button>
</div>
);
}
}
//Counter.js文件部分代码
class Counter extends Component {
constructor(props) {
console.log('enter constructor: ' + props.caption);
super(props);
this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
this.onClickDecrementButton = this.onClickDecrementButton.bind(this);
this.state = {
count: props.initValue
}
}
componentWillReceiveProps(nextProps) {
console.log('enter componentWillReceiveProps ' + this.props.caption)
}
componentWillMount() {
console.log('enter componentWillMount ' + this.props.caption);
}
componentDidMount() {
console.log('enter componentDidMount ' + this.props.caption);
}
onClickIncrementButton() {
this.setState({count: this.state.count + 1});
}
onClickDecrementButton() {
this.setState({count: this.state.count - 1});
}
shouldComponentUpdate(nextProps, nextState) {
return (nextProps.caption !== this.props.caption) ||
(nextState.count !== this.state.count);
}
render() {
console.log('enter render ' + this.props.caption);
const {caption} = this.props;
return (
<div>
<button style={buttonStyle} onClick={this.onClickIncrementButton}>+</button>
<button style={buttonStyle} onClick={this.onClickDecrementButton}>-</button>
<span>{caption} count: {this.state.count}</span>
</div>
);
}
}
刷新网页,在浏览器的console里我们能够看见:
enter constructor: First
enter componentWillMount First
enter render First
enter constructor: Second
enter componentWillMount Second
enter render Second
enter constructor: Third
enter componentWillMount Third
enter render Third
enter componentDidMount First
enter componentDidMount Second
enter componentDidMount Third
当所有三个组件的render函数都被调用之后,三个组件的componentDidMount才连在一起被调用。
render函数被调用完之后,componentDidMount函数并不是会被立刻调用,componentDidMount被调用的时候,render函数返回的东西已经引发了渲染,组件已经被“装载”到了DOM树上。
(2)父子组件装载过程演示:
import React from 'react';
import ReactDOM from 'react-dom';
const buildClass = (name)=>{
return class extends React.Component{
constructor(props) {
super(props);
console.log( name + ' constructor');
}
componentWillMount() {
console.log( name + ' componentWillMount');
}
componentDidMount() {
console.log( name + ' componentDidMount');
}
componentWillUnmount() {
console.log( name + ' componentWillUnmount');
}
componentWillReceiveProps(nextProps) {
console.log( name + ' componentWillReceiveProps(nextProps)');
}
shouldComponentUpdate(nextProps, nextState) {
console.log( name + ' shouldComponentUpdate(nextProps, nextState)');
return true;
}
componentWillUpdate(nextProps, nextState) {
console.log( name + ' componentWillUpdate(nextProps, nextState)');
}
componentDidUpdate(prevProps, prevState) {
console.log( name + ' componetDidUpdate(prevProps, prevState)');
}
}
}
class Child extends buildClass('Child'){
render(){
console.log('Child render')
return (
<div>child</div>
)
}
}
class Parent extends buildClass('Parent'){
render(){
console.log('Parent render')
return (
<Child />
)
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
Parent constructor
Parent componentWillMount
Parent render
Child constructor
Child componentWillMount
Child render
Child componentDidMount
Parent componentDidMount
总结:当执行render子组件的时候,才会进入子组件的生命周期,子组件的周期结束后,再回到上级的周期。
3.4.2更新过程
- componentWillReceiveProps(
nextProps
)- shouldComponentUpdate(
nextProps, nextState
)- componentWillUpdate
- render
- componentDidUpdate
只要是父组件的render函数被调用,不管父组件传给子组件的props有没有改变,都会触发子组件的componentWillReceiveProps函数。
this.setState方法触发的更新过程不会调用componentWillReceiveProps函数(如果调用会造成死循环)。
componentWillReceiveProps(nextProps
)中的nextProps
代表的是这一次渲染传入的props值,this.props代表上一次渲染时的props值。
shouldComponentUpdate返回true时继续更新过程,接下来调用render函数;返回false时立刻停止更新过程,不会引发后续的渲染。
3.4.3卸载过程
React组件的卸载过程只涉及一个函数componentWillUnmount,当React组件要从DOM树上删除掉之前,对应的componentWillUnmount函数会被调用,所以这个函数适合做一些清理性的工作。
componentWillUnmount中的工作往往和componentDidMount有关。
3.5组件间传递数据
1.父组件传递数据给子组件,通过props。如父组件ControlPanel通过caption、initValue传递数据给子组件Counter
class ControlPanel extends Component {
render() {
console.log('enter ControlPanel render');
return (
<div style={style}>
<Counter caption="First"/>
<Counter caption="Second" initValue={10} />
<Counter caption="Third" initValue={20} />
<button onClick={ () => this.forceUpdate() }>
Click me to re-render!
</button>
</div>
);
}
}
2.子组件传递数据给父组件,也是利用props,类似于函数调用。
//子组件的状态改变时,调用onUpdate函数通知父组件
this.setState({count: newValue})
this.props.onUpdate(newValue, previousValue)
//父组件
class ControlPanel extends Component {
...
onCounterUpdate(newValue, previousValue) {
const valueChange = newValue - previousValue;
//TODO
}
render() {
return (
<div style={style}>
<Counter onUpdate={this.onCounterUpdate} caption="First" />
</div>
);
}
}
缺点:prop的参数一致性只能靠开发者来保证。跨级传递prop时违反了低耦合的设计要求。
为了保证重复的数据一致,把数据源放在React组件之外形成全局状态。这就是Flux和Redux中Store的概念。