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()
)