早期的react设计了许多的生命周期钩子。它们严格定义了组件的生命周期,一般说,生命周期可能会经历如下三个过程:
挂载过程(Mount),也就是把组件第一次在DOM树中渲染的过程;
执行过程如右:constructor(初始化数据,比如设置state和prop的初始值)->componentWillMount(即将废弃)->render(可以认为是个纯函数)->componentDidAmount(ajax请求的主要发生地点,设置state)
constructor:并不是每个组件都需要定义自己的构造函数。在后面的章节我们可以看到,无状态的React组件往往就不需要定义构造函数,一个React组件需要构造函数,往往是为了下面的目的:初始化state,因为组件生命周期中任何函数都可能要访问state,那么整个生命周期中第一个被调用的构造函数自然是初始化state最理想的地方;·绑定成员函数的this环境。
render:render函数无疑是React组件中最重要的函数,一个React组件可以忽略其他所有函数都不实现,但是一定要实现render函数,因为所有React组件的父类React.Component类对除render之外的生命周期函数都有默认实现。
componentDidAmount:需要注意的是,render函数被调用完之后,componentDidMount函数并不是会被立刻调用,componentDidMount被调用的时候,render函数返回的东西已经引发了渲染,组件已经被“装载”到了DOM树上。因为render函数本身并不往DOM树上渲染或者装载内容,它只是返回一个JSX表示的对象,然后由React库来根据返回对象决定如何渲染。而React库肯定是要把所有组件返回的结果综合起来,才能知道该如何产生对应的DOM修改。所以,只有React库调用三个Counter组件的render函数之后,才有可能完成装载,这时候才会依次调用各个组件的componentDidMount函数作为装载过程的收尾。
更新过程(Update),当组件被重新渲染的过程。
当组件被装载到DOM树上之后,用户在网页上可以看到组件的第一印象,但是要提供更好的交互体验,就要让该组件可以随着用户操作改变展现的内容,当props或者state被修改的时候,就会引发组件的更新过程。
组件要更新,有三个触发条件:
1.父组件发生变化
执行过程如右:componentWillReceiveProps(即将废弃)->shouldComponentUpdate(根据true or false决定是否更新)->componentWillUpdate(即将feiqis)->render->componentDidUpdate
2.自身状态变化:通常是state的变化
shouldComponentUpdate->componentWillUpdate->render->componentDidUpdate
3.调用forceUpdate:
shouldComponentUpdate->render->componentDidUpdate
shouldComponentUpdate:除了render函数,shouldComponentUpdate可能是React组件生命周期中最重要的一个函数了。说render函数重要,是因为render函数决定了该渲染什么,而说shouldComponentUpdate函数重要,是因为它决定了一个组件什么时候不需要渲染。render和shouldComponentUpdate函数,也是React生命周期函数中唯二两个要求有返回结果的函数。render函数的返回结果将用于构造DOM对象,而shouldComponentUpdate函数返回一个布尔值,告诉React库这个组件在这次更新过程中是否要继续。在更新过程中,React库首先调用shouldComponentUpdate函数,如果这个函数返回true,那就会继续更新过程,接下来调用render函数;反之,如果得到一个false,那就立刻停止更新过程,也就不会引发后续的渲染了。
说shouldComponentUpdate重要,就是因为只要使用恰当,他就能够大大提高React组件的性能,虽然React的渲染性能已经很不错了,但是,不管渲染有多快,如果发现没必要重新渲染,那就干脆不用渲染好了,速度会更快。我们知道render函数应该是一个纯函数,这个纯函数的逻辑输入就是组件的props和state。所以,shouldComponentUpdate的参数就是接下来的props和state值。如果要定义shouldComponentUpdate,那就根据这两个参数,外加this.props和this.state来判断出是返回true还是返回false。
卸载过程(Unmount),组件从DOM中删除的过程。
React组件的卸载过程只涉及一个函数componentWillUnmount,当React组件要从DOM树上删除掉之前,对应的componentWillUnmount函数会被调用,所以这个函数适合做一些清理性的工作。比如定时器,自定义时间等。
组件通信一般是通过prop来实现的,
接下来实现一个计算器功能,有3个独立的计数器,然后以竖式的形式累加:
// ClickCounter.js
import React, { Component } from 'react'
import PropTypes from 'prop-types';
const styles = {
counter: {
width: 250,
display: 'flex'
},
showbox: {
width: 80,
textAlign: 'center'
},
label:{
width:120,
}
}
class ClickCounter extends Component {
handleIncrease = () => {
this.props.handleIncrease();
}
handleDecrease = () => {
this.props.handleDecrease();
}
render() {
return (
<div style={styles.counter}>
<div style={styles.label}>{this.props.label}</div>
<button onClick={this.handleDecrease}>-</button>
<div style={styles.showbox}>{this.props.count}</div>
<button onClick={this.handleIncrease}>+</button>
</div>
)
}
}
ClickCounter.propTypes = {
count: PropTypes.number.isRequired,
handleIncrease:PropTypes.func.isRequired,
handleDecrease:PropTypes.func.isRequired
}
export default ClickCounter;
import React, { Component } from 'react';
import ClickCounter from './ClickCounter'
import AllCount from './AllCount';
const styles={
app:{
width:250,
margin:'100px auto'
}
}
class App extends Component {
constructor(props) {
super(props)
this.state = {
firstCount: 0,
secoundCount: 0,
thirdCount: 0
}
}
handleIncrease=(type)=>{
this.setState((preState)=>{
let newState={}
newState[type]=preState[type]+1;
return newState;
});
}
handleDecrease=(type)=>{
this.setState((preState)=>{
let newState={}
newState[type]=preState[type]-1;
return newState;
});
}
getSum=()=>{
return this.state.firstCount+this.state.secoundCount+this.state.thirdCount
}
render() {
return (
<div style={styles.app}>
<ClickCounter
label={'first count'}
count={this.state.firstCount}
handleIncrease={()=>{
this.handleIncrease('firstCount')
}}
handleDecrease={()=>{
this.handleDecrease('firstCount')
}}/>
<ClickCounter
label={'secound count'}
count={this.state.secoundCount}
handleIncrease={()=>{
this.handleIncrease('secoundCount')
}}
handleDecrease={()=>{
this.handleDecrease('secoundCount')
}}/>
<ClickCounter
label={'third count'}
count={this.state.thirdCount}
handleIncrease={()=>{
this.handleIncrease('thirdCount')
}}
handleDecrease={()=>{
this.handleDecrease('thirdCount')
}}/>
<hr />
<AllCount count={this.getSum()} />
</div>
);
}
}
export default App;
实现效果如下
react的状态怎么放?
三个状态通常是放在顶层组件的,react官方文档《Thinking in React》说:
if you can’t find a component where it makes sense to own the state, create a new component solely for holding the state and add it somewhere in the hierarchy above the common owner component.
•确定每个组件是否依赖于状态?
•找到共同的父级组件(所有需要状态子组件的共同祖先)。
•常见的组件所有者或另一个更高层次结构的组件。注:如果你找不到一个有值得拥有状态的组件,可以创建一个调试用的新组件,让它拥有所有状态,并把它加到常见所有者组件的上层。
长久以来,笔者根据这个守则进行开发。饱受状态过多的困扰。
父组件向子组件传值,本质是做了两件事情:初始化实例并调用。
// 子组件响应prop
class Child {
constructor(name){
this.$div=document.querySelector('#div');
this.update(name);
}
update(name){
this.name=name;
this.$div.innerHTML=name;
}
}
// 父组件调用
class Parent {
constructor(){
this.child=new Child('djtao');
this.child.update('dangjingtao');
}
}
子组件向父组件传值,是通过回调函数的形式来实现的
// 子组件响应prop
class Child {
constructor(callback){
callback();
}
}
// 父组件调用
class Parent {
constructor(){
this.child=new Child(()=>{
console.log('设置父组件state!')
});
}
}
两个不相关的组件A和B怎么通信?
首先写一个EventEmitter:
class EventEmitter{
constructor(){
this.eventMap={}
}
// 订阅
sub(name,callback){
this.eventMap[name]=this.eventMap[name]||[];
this.eventMap[name].push(callback);
}
// 发布
pub(name,...data){
this.eventMap[name]=this.eventMap[name]||[];
this.eventMap[name].forEach(callback=>callback(...data));
}
}
当props被更新时
// 订阅update
class A extends EventEmitter {
construtor(){
super();
this.pub('update');
}
}
class B{
constructor(){
this.a=new B();
}
// 当子组件的名为update的消息被设置时
this.child.sub('update',()=>{
console.log('子组件更新了!');
})
}
任意两个组件之间通信,可以使用这种方式存放一些全局性的信息。
从计数器的案例来看,先代和子代通信,只需要不断层层传递props即可。不需要任何新的知识。但这显然不是最佳实践。
如果可以跨代通信,就不必浪费那么多无谓的代码。context就是这样一个虫洞。
import React, { Component } from 'react'
import PropTypes from 'prop-types';
class Child extends Component{
// 子组件读取context数据
static contextTypes={
text:propTypes.string
}
constructor(){
}
render(){
return <>{this.context.text}</>
}
}
class Ancestor extends Component{
// 先代组件声明context上的text
static childContextTypes={
text:propTypes.string
}
// 在context上放入数据
getChildContext(){
return {
text:'dangjingtao'
}
}
}
这种也不是最好的方案。
在前面的探索中,我们已经开始尝试把数据源放在React组件之外形成全局状态。如图所示,让各个组件保持和全局状态的一致,这样更容易控制。
而这个全局状态的最佳实践,就是flux和redux的store。
除了state,利用prop在组件之间传递信息也会遇到问题。设想一下,在一个应用中,包含三级或者三级以上的组件结构,顶层的祖父级组件想要传递一个数据给最低层的子组件,用prop的方式,就只能通过父组件中转。
中间那一层父组件根本用不上这个prop,但是依然需要支持这个prop,扮演好搬运工的角色,只因为子组件用得上,这明显违反了低耦合的设计要求。在flux和redux中我们会探讨如何解决这样的困局。
一些指导性原则:
•组件尽可能通过props通信。不用context
•组件属性需要有默认值,做好类型检查
•组件属性尽可能使用简单值。避免使用对象。
•不要给组件限制使用环境:你应该允许它在任意场景下复用。
还记得模板方法模式吗?
假如两个组件A,B有共同的一套方法:
就可以把他们抽象为一个父类:
// 抽象类
class Parent extends Component {
constructor(){
}
getData=()=>{
//...
}
}
class A extends Parent{
// 继承父类的getData
}
class B extends Parent{
// 继承父类的getData
}
高阶组件本质是一个函数传入一个组件,返回一个新有两种,一种是前文提过的装饰器模式:
function Hoc(Com){
return class Wrap extends Component{
render(){
return <Com></Com>
}
}
}
另一种是继承于传入的组件,可用于改写render:
function Hoc(Com){
return class Wrap extends Com{
render(){
// return ...
}
}
}
掌握完本章的内容,就可以用react进行开发了。