挂载阶段 (使用 class 的方式)
constructor
- 组件挂载之前,会调用它的构造函数,通常在这个阶段会处理两种情况:
- 给 this.state 赋值对象初始化内容的 state
- 为
事件处理函数
绑定实例
constructor(props) {
super(props)
this.state = {count : 0}
this.handleClick = this.handleClick.bind(this)
}
static getDerivedStateFromProps(props, state)
会在调用 render 方法之前调用,并在初始化挂载及后续更新时会被调用,会返回一个对象来更新 state, 如果返回 null 则不更新任何内容。
getDerivedStateFromProps 存在只有一个目的:
让组件在 props 变化时更新 sate
// After
class ExampleComponent extends React.Component {
// 在构造函数中初始化 state,
// 或者使用属性初始化器。
state = {
isScrollingDown: false,
lastRow: null,
};
static getDerivedStateFromProps(props, state) {
if (props.currentRow !== state.lastRow) {
return {
isScrollingDown: props.currentRow > state.lastRow,
lastRow: props.currentRow,
};
}
// 返回 null 表示无需更新 state。
return null;
}
}
-
为什么不直接将上一个 props 作为参数传递给 getDerivedStateFromProps ?
- prevProps 参数在第一个调用 getDerivedStateFromProps(实例化之后)时为 null,需要在每次访问 prevProps 时添加 if-not-null 检测
- 在未来的版本中,不传递上一个 props 给这个方法是为了释放内存。(如果 React 无需传递上一个 props 给生命周期,那么它就无需保存上一个 props 对象在内存中)
简单理解为,如果在开发中不需要使用 props 传递参数并赋值给 state,那么优化考虑 react 并没有为我们在内部实现,而是开放一个选项,让我们则需而选
。
-
它能实现什么作用 ?
- state 的值在任何时候都取于 props。
- 当然我们也可以使用简单的方式,在使用时,在子组件中直接用 props.xxx
我想到的使用场景,经常在一些业务中会使用的,如果需要在 子组件中,第一次渲染的值,来自于父组件的props,而后面更新无需用到,可以考虑用这种方式。
// 父组件
class App extends Component {
state = {
value: '默认值'
}
render() {
return (
<div>
<Input1 value={this.state.value}/>
</div>
)
}
}
// 子组件
class Input1 extends Component {
constructor(props) {
super(props)
this.state = {
value: '',
refresh: false
}
}
static getDerivedStateFromProps(props, state) {
if (state.value === '' && !state.refresh) {
return {
value: props.value,
refresh: true
}
}
return null
}
onChange = (value) => {
this.setState({
value: value.target.value
})
}
render() {
return (
<input value={this.state.value} onChange={this.onChange}></input>
)
}
}
- 弊端 (派生状态会导致代码冗余),并使组件难以维护,替代方案: -> 来自 react 官网
- 如果你需要执行副作用(例如,数据提取或动画)以响应 props 中的更改,请改用 componentDitUpdate
- 如果只想在 prop 更改时重新计算某些数据,请使用 memoization helper 代替
- 如果你想在 prop 更改时重置某些state,考虑使用完全受控或使用 key 使组件完全不受控替代。
- 具体可参考 https://zh-hans.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
render()
- render 是 class 组件中唯一必须实现的方法
- 当 render 被调用时,会检查 props 和 state 的变化并返回一下类型之一:
- React 元素,通常通过 jsx 创建。
- 数组或 fragments。
- Portals。 可以渲染子节点到不同的 DOM 子树中。
- 字符串或数值类型。在 DOM 中会被渲染为文本节点
- 布尔类型或者 null,什么都不渲染。
注意
如果 shouldComponentUpdate 返回 false,则不会调用 render
-
componentDidMount
组件挂载后(插入 DOM树中)立即调用。依赖于 DOM 节点初始化放在这里,如果要网络请求数据,也放在这里。
可以在这里方法里,添加订阅,但不要忘记在 componentWillUnmount 里取消订阅
注意
可以在 componentDidMount 里直接使用 setState ,但会额外渲染,此渲染会发生在浏览器更新屏幕之前。因此保证了即使在 render() 渲染两次的情况下,用户不会看到中间状态。 慎用改方式,因为它会导致性能问题。
更新阶段
static getDerivedStateFromProps()
- 同 挂载阶段一样
shouldComponentUpdate()
shouldComponentUpdate(nextProps, nextState) 优化组件性能
根据 shouldComponentUpdate 的返回值,判断 react 组件更新是否受当前 state 或 props 的更改影响。默认是 state 每次改变都会让组件重新渲染。
当 props 或 state 发生变化时,shouldComponentUpdate 会在渲染之前被调用,默认值返回 true,首次渲染或使用 forceUpdate 时不会调用。
如果是浅比较,应该使用 PureComponent ,而不是手动编写 shouldComponentUpdate,因为可能会产生 bug.
shouldComponentUpdate 默认返回是 true,如果返回 false 告知 react 可以跳过更新,
注意
:返回false 并不会阻止子组件在 state 更改时重新渲染。(也就是说,父组件使用 shouldComponentUpdate,不会影响子组件 state 更新时,重新渲染子组件)如果 返回 false,则不会调用 render 和 componentDidUdpate()。
render()
- 同挂载时
getSnapshotBeforeUpdate()
- getSnapshotBeforeUpdate(prevProps, prevState)
- 最近一次渲染输出(提交到DOM节点)之前调用,它能在组件发生更改之前从 DOM 中获取一些信息(例如:滚动位置)。此生命周期返回的任何值将作为参数传递给 componentDidUpdate
- 它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。
- 应返回 snapshot 的值(或 null)。
componentDidUpdate()
componentDidUpdate(prevProps, prevState, snapshot)
componentDidUpdate 会在更新后被立即调用,首次渲染不会执行此方法。
当组件更新后,可以在此生命周期中对 DOM 进行操作,如果对前后更新的 props 进行了比较,也可以选择进行网络请求。
注意 componentDidUpdate() 中直接调用 setState(), ·它必须被包裹在一个条件语句中·
否则可能会导致死循环,还会额外导致组件重新渲染,会影响组件性能。
注意 shouldComponentUpdate() 返回值是 false,则不会调用componentDidUpdate()
卸载阶段
componentWillUnmount()
- 会在组件卸载及销毁之前直接调用,一般此处执行必要的清理操作,例如 清除 timer,清除订阅等。
- componentWillUnmount 中不应该调用 setState(), 因为该组件不会重新渲染,组件实例卸载后,将永远不会再挂载。
componentWillUnmount() { // 清除自定义事件 window.removeEventListener('xxx', this.handleClick) }
错误处理
- 当渲染过程,生命周期或子组件的构造函数抛出错误时,会调用的方法
static getDerivedStateFromError()
- 会在后代组件抛出错误后被调用,它将抛出的错误作为参数,并返回一个值以更新 state
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染可以显降级 UI
return { hasError: true };
}
render() {
if (this.state.hasError) {
// 你可以渲染任何自定义的降级 UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
注意
:注意 getDerivedStateFromError() 会在渲染阶段调用,因此不允许出现副作用。 如遇此类情况,请改用 componentDidCatch()。
componentDidCatch()
-
此生命周期在后代抛出错误后被调用。接受两个参数:
- error -> 抛出的错误
- info -> 带有 componentStack key 的对象。
componentDidCatch() 会在“提交”阶段被调用,因此允许执行副作用。 它应该用于记录错误之类的情况
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染可以显示降级 UI
return { hasError: true };
}
componentDidCatch(error, info) {
// "组件堆栈" 例子:
// 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) {
// 你可以渲染任何自定义的降级 UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
其他 API
forceUpdate()
component.forceUpdate(callback)
默认情况下,当组件的 state 或 props 发生变化时,组件将重新渲染,如果 render 方法依赖于其他数据,则可以调用 forceUpdate() 强制让组件重新渲染。
如果调用 forceUpdate 将导致组件直接调用 render 方法,会跳过该组件的 shouldComponentUpdate 方法,当子组件会正常触发 生命周期方法。
建议应该避免使用 forceUpdate,尽量在 render 中使用 props 和 state
Class 属性
- defaultProps 可以为 Class 组件添加默认 props。这一般用于 props 未赋值,但又不能为 null 的情况。
class CustomButton extends React.Component {
// ...
}
CustomButton.defaultProps = {
color: 'blue'
}
Fiber
- Fiber 是 React 16 对 React 核心算法的一次重写
- Fiber 会使同步的渲染过程变成异步的
- Fiber 会将一个大的更新任务拆解为许多的小任务
Fiber 架构的重要特性就是可以被打断的异步渲染模式,根据 “能否被打断” 这一标准 React16 的生命周期被划分为了 render 和 commit 两个阶段
render 阶段在执行过程中允许被打断 而 commit 阶段则总是同步执行的。
为什么 React16 之后废除了以下几个生命周期
- componentWillMount
- componentWillUpdate
- componentWillReceiveProps
因为这些生命周期是 render 阶段,在执行过程中是允许被打断的,而被打断后“再次会重新执行这些生命周期”,所以会出现重复,如果开发者滥用,会出现很多 BUG, 所以废除了。
React 16 改造生命周期的主要动机
- 是为了配合 Fiber 架构带来的异步渲染机制
- 针对生命周期中长期被滥用的部分推出了具有强制性的最佳实践
- 确保了 Fiber 机制下数据和视图的安全性
- 同时也确保了生命周期方法的行为更加纯粹、可控、可预测
每天进步一点点 💪