本文详细描述
react组件类(component class)的API细节。
Overview
react通过类class来定义组件component,同时,该类需要继承extend React.Component。
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
在 React.Component 的子类subclass中必须要有render属性,其他所有的属性都是可选的。
The Component Lifecycle
每个组件都有一些生命周期方法lifecycle methods,可以通过重写override来在特定时间被执行。
- 常用的声明周期:
- 较少使用的生命周期:
挂载阶段 Mounting
当一个组件实例component instance被创建并被插入到DOM中时,以下方法会按照顺序被调用:
contructor()static getDerivedStateFromProps()render()componentDidMount()
更新阶段 Updating
组件的更新可能是setState或props改变导致,以下方法会在组件被重新渲染 re-render时,依照顺序依次调用:
static getDerivedStateFromProps()shouldComponentUpdate()render()getSnapshotBeforeUpdate()componentDidUpdate()
销毁阶段 Unmounting
componentWillUnmount()
错误处理 Error Handling
组件渲染执行render /生命周期方法执行lifecycle methods/构造器contructor执行 中有出错时,下列方法会被调用:
static getDerivedStateFromError()componentDidCatch()
Other APIs
每个组件实例都支持以下APIs:
setState()forceUpdate()
Class Properties
static defaultPropsstatic displayName
Instance Properties
propsstate
Reference
常用的生命周期
以下生命周期会覆盖绝大部分在创建react组件中会碰到的情况。
render()
被调用时,它会检查this.props和this.state,并返回以下类型之一:
React elements: 通常由JSX创建,用来通知react渲染Dom节点或自定义组件。Arrays & fragments: 使你可以从render返回多个elements。Portals:允许将子元素渲染成一个不同的DOM子树subtree。String & numbers: 会被渲染成DOM文本节点Boolean & null: 不渲染,常被用来作为条件渲染conditional render。比如return text && <Com/>。
render()函数应该是一个pure function纯函数,这意味着它每次运行得到的结果应该是一样的。
render()并不直接与浏览器交互,如果有这种需求,请在生命周期函数中实现。
contructor
如果你不想要初始化state或者并不需要绑定方法,可以不用定义组件中的构造器contructor
constructor()构造器会在组件被挂载之前被调用,构造器中必须执行super(props),否则构造器中的的this不能指定为实例并且拥有this.props,导致报错。
通常,构造器常被用来:
- 通过给
this.state赋值一个对象来初始化本地state。 - 给实例绑定 事件处理器
event handler。
构造器是唯一可以直接赋值state的地方,其他情况下,都必须通过this.setState来修改state。
componentDidMount()
componentDidMount()会在组件被挂载(mounted被插入Dom树中)后立即被执行,至此才算是完成了初始化initialization。
适合在这里做网络请求 订阅subscribtion等操作(别忘了在componentWillUnmount里取消订阅unsubscribtion)
可以在componentDidMount()中去调用setState,它会触发另外一次额外的render,但是在屏幕上我们只会看到一次(异步渲染,我们其实看不到中间状态的展示)。小心这种方式,可能会导致性能问题,最好是在初始化state的时完成。
换句话说,有些时候你又非用不可,比如渲染一个模态框/提示框时,需要提前知道Dom的尺寸这类的场景。
componentDidUpdate(prevProps,prevState,snapShot)
componentDidUpate()会在更新结束后立即执行,首次渲染(initial render)并不会执行。
这是一个操作Dom的好地方,也是一个发起网络请求的好地方(比如props发生了改变就发起网络请求的使用场景)
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
你可以在这里立即调用setState但是必须被包裹在 条件语句里,否则就会进入渲染的死循环。就像componentDidMount,这样也会导致重新渲染(虽然用户看不到中间?)而影响性能。
不要尝试用props给state赋值,遇到这种场景,直接用props。
如果组件中定义了getSnapshotBeforeUpdate(),该方法返回的值会作为componentDidUpdate的第三个参数。
如果
shouldComponentUpdate()返回false,后面的render和didUpdate都不会执行
componentWillUnmount()
这个方法会在组件卸载和销毁前被调用,在这个方法里执行必须的清理工作,比如 取消网络请求 取消定时器 取消订阅等。
Rarely Used Lifecycle Methods
下列组件的生命周期方法不常被用到,有时候还是挺管用的:
shouldComponentUpdate(nextProps, nextState)
使用shouldComponentUpdate()可以让state或props的改变不影响组件的展示。默认情况下,每次state/props的改变,都应该重新渲染。
当state/props改变时,这个方法就会被调用,但是首次渲染或使用forceUpdate并不会触发此方法。
这个方法只被用来做 性能优化performance optimization,不要依赖它来阻止渲染,可以使用内嵌的 纯组件PureComponent而不是去手写shouldComponentUpdate()。纯组件会对props和state进行 浅比较shallow comparison确保不会漏过一些关键的update。
如果你对自己很自信,也可以手动去控制react更新(注意即使返回false,当子组件状态更新时,子组件还是会重新渲染)
static getDerivedStateFromProps(props, state)
getDerivedStateFromProps()是在render()之前被调用的,初始挂载和后续的更新都会触发。它会返回一个对象作为最终版本的state,返回null就不更新了。
这个方法常被用来处理 状态依赖于props的改变 的场景。
派生state会导致冗余的代码,并且让你的组件难以理解,我们可以尝试一下方法作为代替:
- 如果你有带副作用的操作(比如数据获取或动画),改用
componentDidUpdate() - 如果你想要在
props发生改变后,重新计算数据,使用缓存memoization。 - 如果你想要在
props发生改变后,重设state,考虑使用受控组件 component fully controlled或带key的非受控组件
这个方法每次渲染都会执行
getSnapshotBeforeUpdate(prevProps, prevState)
getSnapshotBeforeUpdate()是最接近渲染结果的地方。方法返回的结果将作为componentDidUpdate()的第三个参数。
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// Are we adding new items to the list?
// Capture the scroll position so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// If we have a snapshot value, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
// (snapshot here is the value returned from getSnapshotBeforeUpdate)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
getSnapshotBeforeUpdate()是 渲染阶段生命周期render phrase lifecycles和 提交阶段生命周期commit phrase lifecycles的中间地带。
Error Boundaries
Error Boundaries是react捕获JS异常的指称(包括 渲染/生命周期/构造器里的异常)。
类组件可以通过添加static getDerivedStateFromError或componentDidCatch来捕获这些异常,并且提供一个兜底的UI。
static getDerivedStateFromError(error)
它在后代组件有异常时被触发,通过可以通过更新状态来处理异常。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
getDerivedStateFromError是渲染阶段的生命周期函数,不要在其中进行带副作用的操作。交给componentDidCatch来干这个。
componentDidCatch(error, info)
这个方法在 后代组件 descendant components报错时触发,接收的参数有:
- error 错误对象
- 错误组件栈信息
componentDidCatch是在 提交阶段commit phrase的生命周期函数,可以进行副作用操作,一般用来打印日志。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, info) {
// Example "componentStack":
// in ComponentThatThrows (created by App)
// in ErrorBoundary (created by App)
// in div (created by App)
// in App
logComponentStackToMyService(info.componentStack);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
Legacy Lifecycle Methods
以下的生命周期方法虽然还能用,但是即将被废弃:
componentWillMount()compoenntWillReceiveProps()componentWillUpdate()
Other APIs
不同于生命周期方法是react调用的,下述的方法都是你自己在组件中调用:
setState(updater, [callback])
setState将组件状态的改变队列化,并且通知react用更新后的数据重新渲染视图。
将setState视为一次更新请求更为贴切,为了获取更好的体验,react可能会在一轮中更新好几次组件,所以并不能保证setState执行后,更新是立即开始的。
第一个参数是一个更新器函数updater:
(state, props) => stateChange
state是一个组件当前状态的引用,不应该直接修改它,我们应该构建一个新的对象去替换它。
updator接收的state和props都是最新的,更新器的返回值会被shallowly merge给state。
第二个参数是一个在组件渲染完成以后的回调,当然这个逻辑可以放在componentDidUpdate里。
你也可以通过选择给setState传递一个对象这种形式来更新state,它会直接shallowly merge给state。这种形式的setState也是异步asynchronous的。如果想要基于最新的值来操作state,最好使用更新器的形式。
forceUpdate(callback)
如果组件依赖一些非props/state的数据,同时你也想实现更新,就可以调用forceUpdate()。
调用forceUpdate()会触发render但是不会触发当前组件的shouldComponentUpdate,子组件的生命周期函数都是完整触发的。
Class Properties
static defaultProps
默认属性defaultProps支持undefined类型的props,在props传递的是null时就不生效了。
class CustomButton extends React.Component {
// ...
}
CustomButton.defaultProps = {
color: 'blue'
};
render() {
return <CustomButton color={null} /> ; // props.color will remain null
}
displayName
通常displayName字符串常被用来在高阶组件high-order component debug信息。
Instance Properties
props
this.props是由组件调用者定义的属性。
特别地,this.props.children是一个特殊的属性,通常以子标签child tag的形式定义在由JSX表达式里。
state
state包含着组件中特定的那些可能会改变的数据,它是一个由用户定义的对象。
如果有一些值并不是被用来渲染或作为数据流使用(比如定时器ID),可以放在组件实例上而不是state里。