likes
comments
collection
share

react性能优化之异常render次数

作者站长头像
站长
· 阅读数 12

react性能优化之异常render次数

react工程的性能优化部分:

  • 除了前端通用的,比如减少dom节点,懒加载,按需加载之外。
  • react有特别之处就是:可能会有莫名的页面假死(不能操作)。大多情况都是过多的意外render,导致过多的js执行,阻塞了ui的渲染

以下4种常见的情况,容易触发过多异常render,损害性能

使用pure组件(React.PureComponent / React.Memo)

不使用pure组件(React.PureComponent / React.Memo),容易造成过多异常的render

举例

class App extends React.Component {
  constructor() {
    super()
    this.state = {
      aa: 123,
      bb: 222,
      obj: {a: 1}
    }
  }
  render () {
    console.log('render App')
    return (
      <Provider store={store}>
        <div className="App" onClick={() => { // 此处点击触发,A B 子组件都会触发render
          this.setState({
            aa: 123
          })
        }}>
          <A aa={this.state.aa} bb={this.state.obj}/>
          <B bb={this.state.bb}/>
        </div>
      </Provider>
    );
  }
}

// 子组件A 
class A extends React.Component {
  render () {
    console.log('render A')
    console.log(this.props)
    return (
      <div>
        {this.props.aa}
      </div>
    );
  }
}

// 子组件B
const UserContainer = (props) => {
  console.log('render 222')
  console.log(props)
  return (
    <div>
      <h2>{props.bb}</h2>
    </div>
  )
}

问题点:

  • 当我们点击 className="App" 内容区,触发了 this.setState 后,A 和 B 组件都异常重新render了!!

我们理想的目标是:

  • A和B都不触发渲染因为 this.state.bb 根本没动, this.state.aa的值也没变
    • 此处如果不是onClick事件,是onScoll 或 onResize 这种不做防抖的话,很容易触发N次,然后会触发N次render,页面容易直接卡死

解决办法:用 React.PureComponent 或 React.Memo

  • 会自动对props和state的值做一个浅比较如果值的前后没改变,则不会触发render(另外插一嘴vue没有这个问题,可以理解为,vue自动已经处理了)

    • 另外使用了context的话,还是会穿透 并 触发render。是react的正常render。所以context不能滥用(官方推荐实用场景:当前认证的用户、主题或首选语言)

    • 如果想要控制某些props,即使被改变,也不触发render。做法是:类组件用shouldComponentUpdate。函数组件用React.Memo的第二个参数控制。可以自行了解

避免使用匿名函数

function App() {
  const [aa, setAa] = useState(123)
  return (
      <div className="App">
        // 子组件A
        <A onClick={() => {
          console.log('我可能会发请求调接口, 然后处理很多逻辑')
        }} />
        <div onClick={() => setAa(aa+1)}>点击触发render</div>
      </div>
  );
}
export default React.memo(App);

问题点:

  • 当前组件每次render的时候,匿名函数都会被重新创建,会导致A组件被触发render(特别是当A组件放在循环内,更是会触发多次)

我们想要的应该是:

  • A组件不会被触发render

解决办法

  • 不用匿名函数,用 函数的引用 + useCallback 即可 (把函数引用缓存起来,否则每次render都会重新声明 函数引用)
function App() {
  const [aa, setAa] = useState(123)
  const fn = useCallback(() => {
      console.log('发请求调接口, 然后处理很多逻辑')
  }, [])
  return (
      <div className="App">
        // 子组件A
        <A onClick={fn} />
        <div onClick={() => setAa(aa+1)}>点击触发render</div>
      </div>
  );
}
export default React.memo(App);

避免使用内联对象

原因是:每次render时,对象的引用都会改变。一但引用发生改变,会导致以下A、B、C 3个子组件都触发不必要的render

function App() {
  const [aa, setAa] = useState(123)
  console.log('render App')
  
  const style = { margin: 0 }
  const propsObj = { someProp: 'someValue', a: {} }
  return (
      <div>
        <A style={{ margin: 0 }} />
        <B style={style} />
        <C {...propsObj} />
        <div onClick={() => setAa(aa+1)}>点击我,触发App组件的render</div>
      </div>
  );
}
export default React.memo(App);

改进写法

  1. 静态的对象,可以放组件外面
  2. 或者对象统一由useState声明(class组件是放state内),useState内部会保存当前对象状态,并做浅比较
const style = { margin: 0 } // 静态的对象,可以放App组件外面
function App() {
  const [aa, setAa] = useState(123)
  console.log('render App')
  
  const [bb, setBb] = useState({
    someProp: 'someValue', a: {}
  })
  return (
      <div>
        <A style={style} {...bb} />
        <div onClick={() => setAa(aa+1)}>点击我,触发App组件的render</div>
      </div>
  );
}
export default React.memo(App);

不要再react控制范围之外,多次setState

比如在setTimeout内,执行N次setState,就会触发N次render。理想的情况是,只异步render 一次

详细可以看我另一篇:juejin.cn/post/706215…


码字不易,点赞鼓励!!