React.Component
组件(Component)让你可以把UI分成独立可重用的片段,可以独立思考(译注:它们的关注点是分离的)。React.Component由Recat库顶层API提供。
概述
React.Component是一个抽象基类,直接使用该类几乎没有什么意义。一般情况下,你需要继承该类构造自己的组件。继承该类实现自己的组件时,至少需要定义自己的 render() 方法。
通常情况下,你需要这样来定义一个React Component组件:
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
如果你使用的不是ES6,那么你需要使用模块 create-react-class,这里有更多的参考信息 : Using React without ES6
请注意我们不建议你创建自己的组件基类。在React,代码重用主要通过组合(composition)而不是继承(inheritance)完成。看一下这些一般场景你可以感觉一下如何使用组合(composition)。
组件生命周期(Component Lifeycycle)
每个组件都有几个”生命周期方法”你可以重写用来在处理过程中的特定时机执行一些你的代码逻辑。
以will开头的生命周期方法会在某些事情发生之前被调用,而以did开头的方法会在某些事情发生之后被调用。
组件挂载(Mounting)
下面方法会在组件实例被创建和插入到DOM树中时被调用 :
组件更新(Updating)
一个组件的属性/状态(props/state)发生变化时会引起更新。以下方法在一个组件被渲染时会被调用 :
组件卸载(Unmounting)
当一个组件被从DOM树中移除时,以下方法会被调用:
错误处理(Error Handling)
当组件在它的渲染过程中,生命周期方法执行过程中,或者任意子组件的构造方法执行过程中出现错误,以下方法会被调用:
其他API方法
每个组件还都提供了以下API:
类属性
实例属性
参考文档(Reference)
渲染方法 render()
render()
render()方法是必须要实现的(required)。
当被调用时,他会检查this.props和this.state,并且其返回结果类型如下 :
- React元素(elements) 通常通过JSX创建。这个元素可能是一个代表原生DOM组件的
<div />,或者一个用户自定义的组合组件类型(<MyComponent />). - 字符串和数字(String and numbers) 用来处理成DOM中的文本(text)节点。
- Portals 由 ReactDOM.createPortal创建。
- null 什么都没有处理时返回null
- 布尔变量(Booleans) 什么都没有处理,主要用于支持这种使用模式
return test && <Child />,这里test是一个布尔类型。
函数render()必须是纯粹的,也就是说,他不能修改组件的状态,组件状态不变时多次调用render()必须返回同样结果,而只使用数据进行渲染输出,它也不能直接和浏览器交互(译注:比如不能跟浏览器DOM直接交互)。
如果你想和浏览器直接交互,在componentDidMount()或者其他生命周期方法里面做。保持render()纯粹会让你的组件理解起来更简单。
注意: 如果
shouldComponentUpdate()返回false,render()不会被调用。
片段 (Fragments)
render()方法也可以通过数组的形式返回多个元素,如下所示 :
render() {
return [
<li key="A">First item</li>,
<li key="B">Second item</li>,
<li key="C">Third item</li>,
];
}
注意: 不要忘记给片段(fragment)中的元素设置key属性,否则会提示有关key的警告。
从 React 16.2.0,上面的效果可以等效地通过片段方式来实现,这种方式对于静态元素不需要设置key属性 ,如下例子所示 :
render() {
return (
<React.Fragment>
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
</React.Fragment>
);
}
构造方法 constructor()
constructor(props)
一个React组件的构造方法会在组件挂载前被调用。当实现一个组件的构造方法时,该方法调用其它任何其他语句前必须首先调用super(props),否则this.props在构造方法中会是未定义的(undefined),这可能会导致bug。
在构造方法中要避免任何副作用(side-effect)和订阅(subscription)。对于这些使用情况,使用componentDidMount()。
构造方法是初始化组件状态(state)的合适位置。要在构造方法中初始化组件状态,需要直接将表示状态的对象赋值给this.state而不要尝试在构造方法中使用setState()。
构造方法通常也用于绑定类实例的事件处理器(event handler)。
如果你既不初始化组件状态,也不需要绑定方法,那你就没有必要实现你的React组件的构造方法。
在一些非常情况下,可以基于属性props初始化状态state。这样的做法相当于”分支(forks)”了属性props并且使用属性props的初始值设置了状态state。这里是一个有效的React.Component子类构造方法的例子 :
constructor(props) {
super(props);
this.state = {
color: props.initialColor
};
}
这种模式使用起来需要小心的是状态state并不会跟着属性props的变化而变化。除了保持属性props和状态state同步的手段,经常情况下你更应该使用状态提升方法。
如果你确实将属性props通过上面说的方式”分叉(fork)”到了状态state,那么你就很可能也需要实现方法componentWillReceiveProps(nextProps)来保持属性props和状态state的同步。但是状态提升方法更简单而且不容易出bug。
componentWillMount()
componentWillMount()
componentWillMount()在挂载即将发生时被调用。它会在render()之前被调用,在该方法中同步调用setState()方法不会额外触发一次渲染(译注:也就是不会引起一次额外的render()调用)。
一般情况下,我们建议使用构造方法而不是该方法完成相应的功能。
这个方法内要避免引入任何副作用(side effect)或者订阅(subscription)。对于这些使用情况,使用componentDidMount()。
该方法是可用于服务端渲染(server rendering)的唯一的生命周期方法。
componentDidMount()
componentDidMount()
componentDidMount()在一个组件挂载后立即被调用。需要DOM节点的初始化应该发生在这里。如果你需要从远程节点加载数据,这里是发起网络请求的好地方。
该方法也是创建订阅(subscription)的好地方。如果你在该方法里面创建了订阅,不要忘记在componentWillUnmount()中取消订阅。
在该方法中调用setState()会触发一次额外的渲染,但是它会在浏览器更新屏幕之前发生。这就保证了这种情况下尽管render()会被调用两次,用户不会看到中间状态。使用这种模式一定要小心,因为它经常会导致性能问题。但是,这种方法对于一些情况还是必要的,比如模态对话框或者工具提示消息这种你需要测量一个DOM节点,因为渲染之前你需要这个节点的尺寸和位置信息。
componentWillReceiveProps()
componentWillReceiveProps()
componentWillReceiveProps()在一个已经挂载的组件接收到新的属性props信息时被调用。如果你需要根据属性变化相应地更新组件状态(比如要重置状态),你就可以比较this.props和nextProps然后在该方法中使用this.setState()完成(perform)状态变化(state trabsition)。
需要注意即使属性props没有发生变化,React也会调用该方法,所以如果你仅仅想在变化发生时处理变化,需要确保先比较当前this.props和新的属性值nextProps。在父组件引起你的组件重新渲染时,这种情况是可能发生的。
在挂载过程中React不会使用初始属性props调用componentWillReceiveProps()。React仅仅在组件实例的属性props更新时调用该方法。调用this.setState()一般也不会触发调用componentWillReceiveProps()。
shouldComponentUpdate()
shouldComponentUpdate()
方法shouldComponentUpdate()用来让React知道一个组件的输出是否要受到其状态state或者属性props当前的变化的影响。缺省行为是在每一个状态变化(every state change)发生时重新渲染,并且大多数情况下,你应该依赖这种缺省行为。
shouldComponentUpdate()在新的属性props或者状态state被接收到后,渲染之前被调用。缺省情况下总是返回true。对于初始渲染该方法不会被调用,forceUpdate()被使用时该方法也不会被调用。
该方法返回false并不会阻止当前组件的子组件的重新渲染(当它们的状态state发生变化时)。
目前,shouldComponentUpdate()返回false时,componentWillUpdate(),render()和componentDidUpdate()这三个方法都不会被调用。但是请注意将来React有可能将shouldComponentUpdate()作为提示(hint)而不是严格指令(strict directive)对待,相应地,返回false仍然会导致组件的重新渲染。
如果在做过性能分析之后你确定某个组件很慢,你可以把变成继承自React.PureComponent,该基类使用浅层属性/状态(props和state)比较实现了shouldComponentUpdate()。如果你很有信心想自己手写该方法,你可以比较this.props和nextProps,比较this.state和nextState然后返回false就可以告诉React该更新可以被忽略。
我们不推荐在方法shouldComponentUpdate()中使用深度相等检查或者JSON.stringify(),这些做法效率很低而且损害性能。
componentWillUpdate()
componentWillUpdate(nextProps, nextState)
componentWillUpdate()在新的属性props和状态state接收到,组件渲染前被调用。这个方法可以作为更新发生前做准备的一个机会。对于初始渲染动作,该方法不会被调用。
需要注意该方法中不可以调用this.setState(),而且在该方法返回前,你也不能做任何会触发组件更新的其它事情,比如分发一个Redux action。
如果你想根据props的变化更新state,使用componentWillReceiveProps()。
注意 :
如果shouldComponentUpdate()返回false,componentWillUpdate()不会被调用。`
译注:
在一个更新周期内,一旦shouldComponentUpdate返回true,componentWillUpdate就会被调用。在componentWillUpdate方法内,任何会导致状态变化的this.setState调用都是不允许的,因为该方法componentWillUpdate被严格限制用来只做即将到来的更新的准备工作,而假如你在这里调用了this.setState,该调用会触发另外一个componentWillUpdate,这样就陷入了一个死循环。
该方法比较常见的应用是基于状态变化设置一些变量(不是使用setState这种会导致状态变化的动作),分派一些事件,或者做一些开始动画的准备工作。
componentDidUpdate()
componentDidUpdate(prevProps, prevState)
componentDidUpdate()在组件更新后完成后立即被调用。该方法在初始渲染时不会被调用。
当组件被更新时可以使用该方法作为一个操作DOM的机会。只要你比较了当前属性和之前的属性,这也是一个很好的地方用于网络请求(如果属性没有发生变化,网络请求可能不是必要的)。
注意 :
如果shouldComponentUpdate()返回false,componentDidUpdate()不会被调用。
componentWillUnmount()
componentWillUnmount()
componentWillUnmount()会在一个组件被卸载和销毁前被调用。这个方法用于执行一些必要的清理工作,比如关闭定时器,取消网络请求,或者清除在componentDidMount()创建的任何订阅。
componentDidCatch()
componentDidCatch(error, info)
错误边界(Error Boundary)是这样一种React组件,它们捕捉它们子组件树中任何地方发生的Javascript错误,记录这些错误到日志,然后显示一个fallback UI给用户而不是向用户展示一棵垮掉的组件树。错误边界可以捕捉它下面的整棵组件树中发生在渲染期间,生命周期方法中或者构造方法中的错误。
如果一个组件的类定义了该方法,那么该类的组件就变成了一个错误边界。调用它的setState()方法能让你捕捉到其下属子树中未处理的Javascript错误并展示一个fallback UI。错误边界仅仅设计用于从意料之外的错误之中恢复,所以不要尝试将它用于流程控制。
更多详情,请参考 Error Handling in React 16
注意 :
错误边界仅仅捕捉来自其下属子树的组件中的错误。边界组件自身所发生的错误,它自己并不捕捉。
setState()
setState(updater[, callback])
setState()将组件状态的变化添加到队列,然后告诉React该组件及其子组件需要使用更新了的状态重新渲染。这是在事件发生时或者服务端响应时你更新用户界面要用的基本(primary)方法。
可以把setState()想象成一个请求而不是一个马上更新组件的命令。为了更好的性能,React可能会延迟对其的处理,然后一次性批处理好几个组件。React并不确保状态变化被立即执行。
setState()并不总是立即更新组件。它可能批量更新或者延迟更新到稍微晚些时候执行。这种行为会导致在刚刚调用完setState()时读取this.state读不到最新的状态。对于该问题,相应地使用componentDidUpdate或者一个setState回调函数,这两种方法都保证会在更新执行之后触发,从而可以获取最新的状态数据。如果你想基于之前的状态设置新状态,读一下下面关于参数updater的部分。
setState()总会引起重新渲染,除非shouldComponentUpdate()返回false。如果shouldComponentUpdate()中使用了可变对象,并且条件渲染逻辑在这里无法使用,仅在新状态和旧状态不同时调用setState()方法会避免一些不必要的重新渲染。
setState的第一个参数是一个签名如下的函数 :
(prevState, props) => stateChange
prevState是一个指向之前状态的引用。它不能直接修改。相反地,变化应该被表示成基于输入prevState和props构建的一个新的对象。比如,想象一下我们想增加向状态中的某个值,增量是props.step :
this.setState((prevState, props) => {
return {counter: prevState.counter + props.step};
});
updater函数参数prevState和props接收到的值都被确保是最新值。其输出,也就是返回结果会浅层合并到prevState中去形成更新后的state。
setState()的第二个参数是一个可选的回调函数,一旦setState()请求的变化被应用和组件被重新渲染后它就会被执行。一般情况下我们推荐使用componentDidUpdate()而不是这种方法处理这种逻辑。
作为可选项,你可能想向setState()的第一个参数传递一个对象而不是一个上述的updater函数,比如 :
setState(stateChange[, callback])
这种方法会将这个对象stageChange浅层合并到新的state。比如,调整一个购物车项的数量 :
this.setState({quantity: 2})
这种形式的setState()也是异步的,同一周期(cycle)中的多个调用可能被批处理到一起。比如,如果你想在同一个周期中尝试增加一个项目的数量多次(2次以上),也就是想产生跟下面等价的结果 :
Object.assign(
previousState,
{quantity: state.quantity + 1},
{quantity: state.quantity + 1},
...
)
但实际上同一周期中后续的调用会覆盖前面调用的值,最终数量仅仅增加一次,也就是说,上面的预期的实际结果可能如下所示并非符合预期 :
this.setState((prevState) => {
return {quantity: prevState.quantity + 1};
});
更多详情,可以参考 :
- State and Lifecycle guide
- In depth: When and why are setState() calls batched?
- In depth: Why isn’t this.state updated immediately?
forceUpdate()
component.forceUpdate(callback)
缺省情况下,当你的组件的状态state或者属性props发生变化的时候,组件会重新渲染。如果你的render()还依赖于其他数据,那么你可以通过调用方法forceUpdate()告诉React组件需要重新渲染了。
调用forceUpdate()会引起组件上render()方法的调用,而方法shouldComponentUpdate()会被跳过。对forceUpdate()的调用会触发子组件正常的生命周期方法执行,包括每个子组件的shouldComponentUpdate()。React还是会仅在标记发生变化时才更新DOM。
一般情况下应该尽量避免使用forceUpdate(),而应该仅仅使用从render()读取this.props和this.state的方式。
类属性
defaultProps
defaultProps可以被定义到组件类(译注:是组件类,而非组件实例,注意区分)本身用于设置该类实例的缺省属性。该属性仅用于定义未定义的属性(undefined props),而不应用于明确被设置为null的属性。举例如下 :
class CustomButton extends React.Component {
// ...
}
CustomButton.defaultProps = {
color: 'blue'
};
如果props.color没有提供,下面的代码中CustomButton组件实例的props.color属性值会是缺省值blue:
render() {
return <CustomButton /> ; // props.color 这里会是缺省值 blue
}
如果props.color被明确地设置成了null,那它会保持还是null:
render() {
return <CustomButton color={null} /> ; // props.color 被明确设置成了null,那么它就是 null而不使用缺省值
}
displayName
displayName属性字符串用于调试中的消息。通常情况下你不需要显式地设置它,因为他会从定义组件的函数或者类的名称中演化出来一个。但出于调试目的如果你想显示一个不一样的名称,你可以显式地指定displayName。另外当你想创建一个高阶(higher-order)组件,你可以指定displayName。关于displayName,这里有更多的信息:Wrap the Display Name for Easy Debugging。
实例属性
props
this.props包含了当前组件调用者所定义的属性。这里有有关属性props的介绍:Components and Props。
特别需要值得一提的是,this.props.children是一个特殊的属性,典型地由JSX表达式中多个子标签定义而不是在当前组件标签中定义。(译注:这句翻译可能不够清楚,原文是 In particular, this.props.children is a special prop, typically defined by the child tags in the JSX expression rather than in the tag itself.)
state
state包含了当前组件相关的时刻会变化的那些数据(译注:所以称之为状态)。状态是用户自定义的(user-defined),应该是一个一般(plain) JavaScript对象。
如果某个数据你不会在render()使用它,那它也不应该出现在状态state中。比如,你可以把定时器(timer)的ID直接设置到实例上。
关于状态state,这里有更多信息:State and Lifecycle。
永远不要直接改变state对象,因为随后的setState()调用会替换掉你的变更。将this.state对象看成是一个不可变对象。(译注:但是并不是说该对象的内容不可改变,它的内容必须能被改变,如果要改变它,使用React推荐的方法setState())
本文深入讲解React组件的生命周期方法,包括挂载、更新、卸载等阶段的关键方法及其实现细节,帮助开发者更好地理解和运用React组件。
2997

被折叠的 条评论
为什么被折叠?



