Skip to content

React 性能优化与坑 ​

一、性能优化 ​

减少重复渲染 ​

shouldComponentUpdate ​

jsx
shouldComponentUpdate(nextProps, nextState) {
  if (nextProps.num === this.props.num) {
    return false
  }
  return true
}
shouldComponentUpdate(nextProps, nextState) {
  if (nextProps.num === this.props.num) {
    return false
  }
  return true
}

PureComponent ​

jsx
class App extends React.PureComponent {}
// 自动对比 props 和 state(浅比较)
class App extends React.PureComponent {}
// 自动对比 props 和 state(浅比较)

React.memo(函数组件) ​

jsx
const App = React.memo((props) => {
  return <div>{props.name}</div>
})
const App = React.memo((props) => {
  return <div>{props.name}</div>
})

性能优化总结 ​

组件类型优化方式
只有 propsPureComponent / memo
只有 stateComponent(state 变了直接更新)
同时有 state 和 propsshouldComponentUpdate + componentWillReceiveProps

长列表优化 ​

使用 react-window 虚拟列表。

错误边界 ​

jsx
export default class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null }

  componentDidCatch(error, errorInfo) {
    this.setState({ hasError: true, error })
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || <div>出错了</div>
    }
    return this.props.children
  }
}

// 使用
<ErrorBoundary fallback={<p>出错了</p>}>
  <MyComponent />
</ErrorBoundary>
export default class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null }

  componentDidCatch(error, errorInfo) {
    this.setState({ hasError: true, error })
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || <div>出错了</div>
    }
    return this.props.children
  }
}

// 使用
<ErrorBoundary fallback={<p>出错了</p>}>
  <MyComponent />
</ErrorBoundary>

严格模式 ​

jsx
<React.StrictMode>
  <App />
</React.StrictMode>
<React.StrictMode>
  <App />
</React.StrictMode>

检测内容:

  • 不安全生命周期
  • 旧式字符串 ref
  • 废弃的 findDOMNode
  • 意外副作用
  • 遗留 context API

Fragment ​

<></> 等价于 <React.Fragment></React.Fragment>
<></> 等价于 <React.Fragment></React.Fragment>

路由按需加载 ​

bash
npm i react-loadable
npm i react-loadable
jsx
import Loadable from 'react-loadable'

const SelectComponent = Loadable({
  loader: () => import('@/components/Select'),
  loading: Loading
})
import Loadable from 'react-loadable'

const SelectComponent = Loadable({
  loader: () => import('@/components/Select'),
  loading: Loading
})

首屏加载优化 ​

  • 骨架屏:react-content-loader
  • 服务端渲染:ReactDOMServer

二、常见坑点 ​

state 初始化不要使用 null ​

jsx
// 不要
state = { list: null }

// 使用空数组或空字符串
state = { list: [] }
// 不要
state = { list: null }

// 使用空数组或空字符串
state = { list: [] }

请求数据时判空 ​

jsx
render() {
  if (!this.state.list) return null
  // 或
  if (!arr.length) return null
}
render() {
  if (!this.state.list) return null
  // 或
  if (!arr.length) return null
}

点击事件需要两次的问题 ​

原因:第一次点击时数据尚未更新。可使用 setTimeout 解决:

jsx
setTimeout(() => {
  this.setState({ data: newData })
}, 0)
setTimeout(() => {
  this.setState({ data: newData })
}, 0)

iOS 输入框导致页面上移 ​

jsx
componentDidMount() {
  let timer
  const inputBlur = (e) => {
    if (e.target.tagName.toLowerCase() === 'input') {
      timer = setTimeout(() => window.scrollTo(0, 0), 4)
    }
  }
  const inputFocus = (e) => {
    if (e.target.tagName.toLowerCase() === 'input') {
      clearTimeout(timer)
    }
  }
  parentEl.addEventListener('focusout', inputBlur)
  parentEl.addEventListener('focusin', inputFocus)
}
componentDidMount() {
  let timer
  const inputBlur = (e) => {
    if (e.target.tagName.toLowerCase() === 'input') {
      timer = setTimeout(() => window.scrollTo(0, 0), 4)
    }
  }
  const inputFocus = (e) => {
    if (e.target.tagName.toLowerCase() === 'input') {
      clearTimeout(timer)
    }
  }
  parentEl.addEventListener('focusout', inputBlur)
  parentEl.addEventListener('focusin', inputFocus)
}

内存泄露 ​

报错:Can't perform a React state update on an unmounted component

jsx
class Test extends React.Component {
  _isMounted = false

  componentDidMount() {
    this._isMounted = true
    this._isMounted && this.setState({ ... })
  }

  componentWillUnmount() {
    this._isMounted = false
  }
}
class Test extends React.Component {
  _isMounted = false

  componentDidMount() {
    this._isMounted = true
    this._isMounted && this.setState({ ... })
  }

  componentWillUnmount() {
    this._isMounted = false
  }
}

常见场景:定时器、网络请求、事件监听 — 在组件销毁前都应处理。

链接出现 callback is not defined ​

jsx
window.callback = function() {}
window.callback = function() {}

"不能用作 JSX 组件" 错误 ​

json
// package.json
"resolutions": {
  "@types/react": "17.0.44"
}
// package.json
"resolutions": {
  "@types/react": "17.0.44"
}
bash
pnpm add @types/react@17.0.44
pnpm add @types/react@17.0.44