面试题答案
一键面试新旧生命周期方法对比
- 旧生命周期:
- componentWillMount:在挂载之前调用,此阶段可以进行一些初始化操作,如设置初始状态等。但由于在服务端渲染(SSR)中也会调用,且可能导致重复渲染,React 16.3 开始标记为不安全,后续版本将移除。
- componentWillReceiveProps:在组件接收到新的 props 时调用,可根据新 props 更新 state。但它可能会导致一些意外的状态更新,因为即使 props 没有变化,某些情况下也可能被调用,React 16.3 开始标记为不安全,后续版本将移除。
- componentWillUpdate:在组件即将更新时调用,可以在此进行一些准备工作。同样,在 React 16.3 开始标记为不安全,后续版本将移除。
- 新生命周期:
- getDerivedStateFromProps:静态方法,在组件挂载和更新时都会调用。它接收新的 props 和当前的 state,返回一个对象来更新 state,或者返回 null 表示不需要更新 state。它解决了
componentWillReceiveProps
可能导致的意外状态更新问题,强制将 props 到 state 的推导逻辑分离出来,使状态更新逻辑更清晰。 - getSnapshotBeforeUpdate:在最近一次渲染输出(提交到 DOM 节点)之前调用。它可以返回一个值,这个值会作为
componentDidUpdate
的第三个参数传递。主要用于在 DOM 更新之前捕获一些信息,比如滚动位置等,方便在更新后基于这些信息做一些操作。
- getDerivedStateFromProps:静态方法,在组件挂载和更新时都会调用。它接收新的 props 和当前的 state,返回一个对象来更新 state,或者返回 null 表示不需要更新 state。它解决了
引入新方法的原因
- 解决现有生命周期方法的问题:旧的生命周期方法在 React 的异步渲染模式下存在一些隐患,例如
componentWillMount
、componentWillReceiveProps
和componentWillUpdate
可能会在不恰当的时候被调用,导致意外的副作用和难以调试的问题。新的生命周期方法设计更清晰,能更好地适应 React 的未来架构。 - 适应异步渲染:React 16 引入了 Fiber 架构,支持异步渲染。新的生命周期方法能够更好地与异步渲染协同工作,确保在异步渲染过程中,组件的状态更新和副作用处理更加可靠和可控。
实际项目中利用新方法进行性能优化
- 使用 getDerivedStateFromProps 优化 state 更新:
- 避免不必要的 state 更新:通过仔细分析 props 和 state,在
getDerivedStateFromProps
中只在必要时返回新的 state。例如,一个根据父组件传递的isLoading
prop 来显示加载状态的组件:
class MyComponent extends React.Component { state = { isLoading: false }; static getDerivedStateFromProps(props, state) { if (props.isLoading!== state.isLoading) { return { isLoading: props.isLoading }; } return null; } render() { return ( <div> {this.state.isLoading? <p>Loading...</p> : <p>Loaded</p>} </div> ); } }
- 保持 state 与 props 的一致性:确保 state 是基于 props 推导出来的,这样可以避免 state 与 props 不一致导致的渲染问题,提高渲染的稳定性和可预测性。
- 避免不必要的 state 更新:通过仔细分析 props 和 state,在
- 使用 getSnapshotBeforeUpdate 优化 DOM 操作:
- 保存滚动位置:在列表组件更新时,如果有滚动条,为了保持用户的滚动位置,可以使用
getSnapshotBeforeUpdate
。
class ScrollableList extends React.Component { constructor(props) { super(props); this.listRef = React.createRef(); } getSnapshotBeforeUpdate(prevProps, prevState) { const list = this.listRef.current; return list.scrollTop; } componentDidUpdate(prevProps, prevState, snapshot) { const list = this.listRef.current; list.scrollTop = snapshot; } render() { return ( <div ref={this.listRef}> {/* 列表内容 */} </div> ); } }
- 避免不必要的重排和重绘:通过在 DOM 更新前捕获必要信息,在更新后基于这些信息做操作,可以减少因为多次操作 DOM 导致的重排和重绘,提升性能。
- 保存滚动位置:在列表组件更新时,如果有滚动条,为了保持用户的滚动位置,可以使用