React组件和生命周期简介

时间:2021-09-30 20:12:06
 
 

React 简介----React 是 Facebook 出品的一套颠覆式的前端开发类库。为什么说它是颠覆式的呢?

内存维护虚拟 DOM

对于传统的 DOM 维护,我们的步骤可能是:
1.初始化DOM结构
2.从服务器获取新数据
3.使用新数据更新局部DOM
4.绑定各种事件

首先,我们操作 DOM 是最昂贵的开销,对于需要反复更新 DOM 的网页,无疑是噩梦。而 React 引入了一个全新的概念:虚拟 DOM。虚拟 DOM 是驻留在内存里的一种特殊的对象类型数据,我们可以理解为这是真实 DOM 在内存里的映射。除了结构上的映射外,这个虚拟的 DOM 还包括了渲染真实所需要的数据以及绑定的事件。

最小差异化局部更新真实 DOM

虚拟 DOM 在创建时,首先是使用 JSX 的语法生成一个真实 DOM 树的映射,其次是从服务器端拉取远程数据,接着注入到这个虚拟DOM 树中,同时绑定事件。好了,有了虚拟 DOM、数据、事件,万事俱备。接下来,调用 render() 方法一次性渲染出真实的 DOM,然后全量插入到网页中。虚拟 DOM 静静地躺在内存里,等待数据更新。新数据来临,调用 setState() 方法更新数据到虚拟 DOM 中(此过程会进行差异化对比),然后自动调用 render() 再局部更新渲染出真实的 DOM 。

1.一个虚拟DOM,对应一个真实DOM
2. 一旦数据更新,重新生成虚拟DOM,并对真实DOM进行最小差异化局部更新, 就这么简单。却带来性能上的较大提升。

JSX语法

React采用jsx语法, jsx是 JavaScript 和 HTML 的结合体, JSX 的目标是在 JavaScript 中更加方便的创建 HTML 节点。JSX 经过解释器解释,最终呈现标准的 JavaScript 语法,例如在 React中,下面的 JSX 代码:

return (
<div className="commentBox">
Hello, world! I am a CommentBox.
</div>
);

将被解释为如下代码:

return (
React.createElement(
'div',
{className: "commentBox"},
"Hello, world! I am a CommentBox."
)
);

可以看到,解释器会分析 JSX 的语义并将其转化为创建元素的方法,而并非将 HTML 部分当作简单的字符串原样输出,因此不用担心 JSX会引发 XSS。另一方面,由于 HTML 部分不是被当作字符串处理,JSX 中 HTML 元素的部分写法与标准 HTML 有些许出入。

另外需要注意的是:由于一条 JSX 语句只能够创建一个虚拟 HTML 节点,因此 JSX 语句中至多拥有一个根 HTML 节点。下面的 JSX 就无法完成解释:

return (
<h1>Main Title</h1>
<h2>Sub Title</h2>
)

组件

组件是 React 最基本的渲染单位,React 中全是模块化、可组装的组件。组件的嵌套和拼装组成了整个页面。JSX 语法可以让组件的构建更加方便,每个组件拥有自己的属性(props)和状态(state),通过属性赋值和状态修改,React 就可以实现整个页面的呈现和交互。

React.createElement与document.createElement的区别

我们都知道,JavaScript 通过 DOM 实现与 HTML 的交互,然而我们在 React 中构造一个元素却调用了React.createElement方法,表面上看同样是生成一个 DOM 对象,那么为什么不使用document.createElement方法呢?原因就在于React.createElement方法并没有真正生成一个 DOM 对象,而是生成了一个虚拟的 DOM 对象,这也是 React 的核心思想所在。React 中的所有操作都是对虚拟 DOM 而不是对真实 DOM 的操作。所以 React 的状态修改不会实时体现在页面上,而是在整个组件渲染时,React 会比较组件的状态改变,仅将发生改变DOM 进行重绘,虽然看起来这个过程十分复杂,但实践证明这一机制确实能够提高页面渲染效率。这正是 React 高效的秘诀之一。

React组件参数说明

当通过调用 React.createClass() 来创建组件的时候,你应该提供一个包含render方法的对象,并且也可以包含其它的在这里描述的生命周期方法。

render

函数原型ReactComponent render(),
render()方法是必须的。当调用的时候,会检测this.props和this.state,返回一个单子级组件。该子级组件可以是虚拟的本地DOM组件(比如 <div /> 或者 React.DOM.div()),也可以是自定义的复合组件。你也可以返回null或者false来表明不需要渲染任何东西。实际上,React渲染一个<noscript>标签来处理当前的差异检查逻辑。当返回null或者false的时候this.getDOMNode()将返回null。render()函数应该是纯粹的,也就是说该函数不修改组件state,每次调用都返回相同的结果,不读写DOM信息,也不和浏览器交互(例如通过使用setTimeout)。如果需要和浏览器交互,在componentDidMount()中或者其它生命周期方法中做这件事。保持render()纯粹,可以使服务器端渲染更加切实可行,也使组件更容易被理解。

propTypes

函数原型 object propTypes 
propTypes是React提供的一种验证机制,该对象中定义了一系列的验证方法,可对props进行验证。组件初始化时,如果传递的props属性和propTypes不匹配,则会打印一个console.warn日志。

React.createClass({
propTypes: {
// 验证布尔值
optionalBool: React.PropTypes.bool,
// 验证是一个函数
optionalFunc: React.PropTypes.func,
// 验证是数字
optionalNumber: React.PropTypes.number,
// 自定义验证器,验证失败需要返回一个 Error 对象。不要直接
// 使用 `console.warn` 或抛异常,因为这样 `oneOfType` 会失效。
customProp: function(props, propName, componentName) {
//自定义的验证方法 ……
} ,
// 其它验证 ……
},
/* 其它specification... */
});
propTypes使用示例: var App = React.createClass({
propTypes: {
site: React.PropTypes.shape({
domain: React.PropTypes.string.isRequired,
name: React.PropTypes.string
}).isRequired
},
render: function() {
return (
<p>站点信息-{this.props.site.name}:{this.props.site.domain}</p>
);
}
});
var site = {
name: 4, // 不合法的类型
domain: 'itbilu.com'
}
ReactDOM.render( <App site={site} />, document.getElementById('example')); // 运行后会抛出以下错误
// Warning: Failed propType: Invalid prop `site.name` of type `number` supplied to `App`, expected `string`.

mixins

函数原型 array mixins
mixin 数组允许使用混合来在多个组件之间共享行为。如一个组件需要定期更新,这时我们可以setInterval()方法很容易的做到,但当不需要它时,我们要取消定时器以节省内存。下面我们使用 React 提供的生命周期方法来通知组件创建或销毁,并结合mixin,实现组件的定时清理:

var SetIntervalMixin = {
componentWillMount: function() {
this.intervals = [];
},
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() {
this.intervals.map(clearInterval);
}
});
var TickTock = React.createClass({
mixins: [SetIntervalMixin], // 引用 mixin
getInitialState: function() {
return {seconds: 0};
},
componentDidMount: function() {
this.setInterval(this.tick, 1000); // 调用 mixin 的方法
},
tick: function() {
this.setState({seconds: this.state.seconds + 1});
},
render: function() {
return (
<p>React 已经运行了 {this.state.seconds} 秒。</p>
);
}
});
React.render( <TickTock />, document.getElementById('example'));

statics

函数原型 object statics

statics对象允许你定义静态的方法,这些静态的方法可以在组件类上调用。例如:

var MyComponent = React.createClass({
statics: {
customMethod: function(foo) {
return foo === 'bar';
}
},
render: function() {
}
}); MyComponent.customMethod('bar'); // true

在这块定义的方法都是静态的,意味着你可以在任何组件实例创建之前调用它们,这些方法不能获取组件的props和state。如果你想在静态方法中检查props的值,在调用处把props作为参数传入到静态方法。

displayName

函数原型 string displayName 
displayName 描述插件的显示名称。JSX自动设置该值。

var App = React.createClass({

 displayName: 'App',

 render: function () {

   return (
<h1>itbilu.com</h1>
)
} });

React组件生命周期

所谓生命周期,就是一个对象从开始生成到最后消亡所经历的状态某个确定的时间点执行的方法,理解生命周期,是合理开发的关键。通过反复试验,得到了组件的生命周期在不同状态下的执行顺序:

1.当首次装载组件时: 按顺序执行 getDefaultProps、getInitialState、componentWillMount、render 和 componentDidMount;
2.当重新装载组件时, 此时按顺序执行 getInitialState、componentWillMount、render 和componentDidMount,并不执     行getDefaultProps
3.当再次渲染组件时,组件接受到新的Props,此时按顺序执行componentWillReceiveProps、shouldComponentUpdate、
 componentWillUpdate、render 和componentDidUpdate。

4.当再次渲染组件时,组件接受到新的state,此时按顺序执行shouldComponentUpdate、componentWillUpdate、render 和componentDidUpdate。

4.当组件卸载时: 执行componentWillUnmount

React组件和生命周期简介

如图,可以把组件生命周期大致分为三个阶段:

第一阶段:是组件第一次绘制阶段,如图中的上面虚线框内,在这里完成了组件的加载和初始化;
第二阶段:是组件在运行和交互阶段,如图中左下角虚线框,这个阶段组件可以处理用户交互,或者接收事件更新界面;
第三阶段:是组件卸载消亡的阶段,如图中右下角的虚线框中,这里做一些组件的清理工作。
下图对每种情况做了更清晰明了的说明

生命周期回调函数

下面来详细介绍生命周期中的各回调函数。

getDefaultProps
其原型如下:
object getDefaultProps();
在组件类创建的时候调用一次,然后返回值被缓存下来。如果父组件没有指定 props 中的某个键,则此处返回的对象中的相应属性将会合并到this.props(使用in检测属性).该方法在任何实例创建之前调用,因此不能依赖于this.props。另外,getDefaultProps() 
返回的任何复杂对象将会在实例间共享,而不是每个实例拥有一份拷贝。

getInitialState
其原型如下:
object getInitialState()
在组件挂载之前调用一次。返回值将会作为 this.state 的初始值。

componentWillMount

然后,准备加载组件,会调用 componentWillMount(),其原型如下:
void componentWillMount()
这个函数调用时机是在组件创建,并初始化了状态之后,在第一次绘制 render() 之前。可以在这里做一些业务初始化操作,也可以设置组件状态。这个函数在整个生命周期中只被调用一次。

componentDidMount
在组件第一次绘制之后,会调用 componentDidMount(),函数原型如下:
 void componentDidMount()
通知组件已经加载完成。这个函数调用的时候,真实DOM已经构建完成,你可以在这个函数开始获取其中的元素或者子组件了。需要注意的是,React框架是先调用子组件的 componentDidMount(),然后调用父组件的componentDidMount函数。从这个函数开始,HTML就可以和JS交互了,例如设置计时setTimeout或者setInterval,或者发起网络请求。这个函数也是只被调用一次。这个函数之后,就进入了稳定运行状态,等待事件触发。

componentWillReceiveProps

如果组件收到新的属性(props),就会调用 componentWillReceiveProps(),其原型如下:

void componentWillReceiveProps(object nextProps)

输入参数 nextProps 是即将被设置的属性,旧的属性还是可以通过 this.props 来获取。在这个回调函数里面,你可以根据属性的变化,通过调用 this.setState() 来更新你的组件状态,这里调用更新状态是安全的,并不会触发额外的 render() 调用。如下:

componentWillReceiveProps:function(nextProps) { 
 this.setState({
    likesIncreasing: nextProps.likeCount >this.props.likeCount
 });
}

shouldComponentUpdate

当组件接收到新的属性和状态改变的话,都会触发调用 shouldComponentUpdate(...),函数原型如下:

boolean shouldComponentUpdate(object nextProps, object nextState)

输入参数nextProps和上面的componentWillReceiveProps函数一样,nextState表示组件即将更新的状态值。这个函数的返回值决定是否需要更新组件,如果true表示需要更新,继续走后面的更新流程。否者,则不更新,直接进入等待状态。默认情况下,这个函数永远返回true用来保证数据变化的时候UI能够同步更新。在大型项目中,你可以自己重载这个函数,通过检查变化前后属性和状态,来决定UI是否需要更新,能有效提高应用性能。

componentWillUpdate

如果组件状态或者属性改变,并且上面的shouldComponentUpdate返回为true,就会开始准更新组件,并调用componentWillUpdate(),其函数原型如下:

void componentWillUpdate(object nextProps, object nextState)

输入参数与shouldComponentUpdate一样,在这个回调中,可以做一些在更新界面之前要做的事情。需要特别注意的是,在这个函数里面,你就不能使用this.setState来修改状态。这个函数调用之后,就会把nextProps和nextState分别设置到this.props和this.state中。紧接着这个函数,就会调用render()来更新界面了。

componentDidUpdate
调用了render()更新完成界面之后,会调用componentDidUpdate()来得到通知,其函数原型如下:

void componentDidUpdate(object prevProps, object prevState)

因为到这里已经完成了属性和状态的更新了,此函数的输入参数变成了prevProps和prevState。

componentWillUnmount

当组件要被从界面上移除的时候,就会调用componentWillUnmount(),其函数原型如下:
void componentWillUnmount()
在这个函数中,可以做一些组件相关的清理工作,例如取消计时器、网络请求等。

总结:

1.react组件的方法和属性

  1. 组件的参数
    • 渲染组件            :render
    • 调试输出            :displayName
    • 跨组件共享        :mixins
    • 验证对象            :propTypes
    • 静态方法对象    :statics
  2. 组件生命周期
    • 创建期                :getDefaultProps
    • 创建期                :getInitialState
    • 创建期                :componentWillMount
    • 创建期                :componentDidMount
    • 存在期                :componentWillReceiveProps
    • 存在期                :shouldComponentUpdate
    • 存在期                :componentWillUpdate
    • 存在期                :componentDidUpdate
    • 销毁&清理期        :componentWillUnmount

2.编程建议

1. 不建议在 getDefaultPropsgetInitialStateshouldComponentUpdatecomponentWillUpdaterender,componentWillUnmount 
中调用setState,特别注意:不能在shouldComponentUpdate  componentWillUpdate中调用setState,因为更新 state 会导致组件更新进而调用shouldComponentUpdatecomponentWillUpdate方法,在shouldComponentUpdatecomponentWillUpdate中更新了state,又会导致组件更新而调用shouldComponentUpdatecomponentWillUpdate方法,简而言之,会导致循环调用。

2.除了需要操作DOM的初始化操作放在componentDidMount中,其余的初始化操作应全部丢到componentWillMount中进行,特别是涉及修改state 的操作。因为这些方法如果丢到componentDidMount中,会导致组件加载完成后立刻检测到state变更,触发组件再次更新,影响页面性能。

3.不要在getDefaultProps中进行任何针对实例的操作。该方法在任何实例创建前调用,在该方法中访问不到任何实例。

4.一定要在componentWillUnmount中做好清理工作,否则你会面临一大堆不可预测且难以调试的 bug。