码农之家

react – 08.useMemo / React.memo

1.React.memo:

1.React.memo的作用:

对组件进行缓存,React由于虚拟dom的关系,当父组件重新调用的时候,子组件就会被重新调用,这样就多出了无意义的性能开销,实际上状态没有变化的子组件是不需要渲染的,就比如这样:

import { useState } from "react"
const B = () => {
  console.log("孙组件渲染")
  return (
    <div>孙组件</div>
  )
}
const A = () => {
  console.log("子组件渲染")
  const [count1, setCount1] = useState(0)
  const handleAdd1 = () => {
    setCount1(count1 + 1)
  }
  return (
    <div>
      <div>子组件 -- {count1}</div>
      <button onClick={handleAdd1}>click</button>
      <B></B>
    </div>
  )
}
const App = () => {
  const [count, setCount] = useState(0)
  const handleAdd = () => {
    setCount(count + 1)
  }
  console.log("根组件渲染")
  return (
    <div>
      <div>根组件 -- {count}</div>
      <button onClick={handleAdd}>click</button>
      <A></A>
    </div>
  )
}
export default App

当根组件触发渲染的时候,子组件也会跟随渲染,子组件渲染了,孙组件也会跟随渲染,父组件触发渲染,默认所有的子组件都会跟着渲染

在React的class时代,我们一般通过pureComponents对数据进行一次浅比较,以判断是否需要重新渲染,但是React引入Hooks特征后,我们可以使用React.memo来解决这个问题,达到提高性能优化的目的

2.React.memo语法:

const Com = React.memo(functional)

const Com = React.memo(() => {
  return (<></>)
})

React.memo是一个高阶组件,它接收另一个组件作为参数,并且会返回一个包装过的新组件,包装过的新组件就会具有缓存功能,包装过后,只有组件的props发生变化的时候,才会触发组件的重新渲染,否则总是返回缓存中的结果(除非有必要,才会被重新渲染)

其中,React.memo()是一个高阶组件,可以使用这个来包裹一个已有的函数部分

3.例子:

举个例子,我们在父组件声明一个子组件,在子组件声明一个孙组件,当我在父组件中调用子组件的时候,我们不希望孙组件也调用,我们可以这样做:

import React, { useState } from "react"
const B = React.memo(() => {
  console.log("孙组件渲染")
  return (
    <div>孙组件</div>
  )
})
const A = () => {
  console.log("子组件渲染")
  const [count1,setCount1] = useState(0)
  const handleAdd1 = () => {
    setCount1(count1 + 1)
  }
  return (
    <div>
      <div>子组件 -- {count1}</div>
      <button onClick={handleAdd1}>click</button>
      <B></B>
    </div>
  )
}
const App = () => {
  const [count,setCount] = useState(0)
  const handleAdd = () => {
    setCount(count + 1)
  }
  console.log("根组件渲染")
  return (
    <div>
      <div>根组件 -- {count}</div>
      <button onClick={handleAdd}>click</button>
      <A></A>
    </div>
  )
}
export default App

但是当缓存组件的值发生变化或者符合条件的时候,我们才希望被调用,这个时候我们可以通过传参的形式去实现:

import React, { useState } from "react"
const B = React.memo((props:any) => {
  console.log("孙组件渲染")
  return (
    <div>
      {props.data && <div>孙组件</div>}
    </div>
  )
})
const A = () => {
  console.log("子组件渲染")
  const [count1, setCount1] = useState(1)
  const flag = count1 % 4 === 0
  const handleAdd1 = () => {
    setCount1(count1 + 1)
  }
  return (
    <div>
      <div>子组件 -- {count1}</div>
      <button onClick={handleAdd1}>click2</button>
      <B data={flag}></B>
    </div>
  )
}
const App = () => {
  const [count, setCount] = useState(0)
  const handleAdd = () => {
    setCount(count + 1)
  }
  console.log("根组件渲染")
  return (
    <div>
      <div>根组件 -- {count}</div>
      <button onClick={handleAdd}>click1</button>
      <A></A>
    </div>
  )
}
export default App

2.useMemo

1.useMemo的作用:

在传递创建函数和依赖项时,创建函数需要返回一个值,如果此时依赖项发生改变,就需要调用useMemo函数了,该函数会返回一个新值

useMemo能够对state的值进行缓存

2.useMemo语法:

const memoValue = useMemo(callback,array)

const memoValue = useMemo(() => {
  return () => {
    
  }
}, [value]) // 表示监控value变化

callback:这是一个函数,用于处理逻辑,包含return函数

array:array会导致重新执行的数组依赖,array内state改变时,才会重新执行callback

使用array需要注意以下几点:

1.如果不传数组,每次更新都会重新计算

2.如果传空数组,只会计算一次

3.依赖对应的值,对应的值发生变化的时候,重新执行callback

  • 这个就有点像useEffect的使用一样

3.例子:

import { useState } from 'react'
const App = () => {
  const [user, setUser] = useState<any>({
    userId: "123456",
    userName: "xiaoming",
    userAge: 22,
    userSex: 1
  })
  const [article, setArticle] = useState<any>([])
  const filterSex = () => {
    console.log("计算性别数据")
    return user.userSex == 1 ? 'male' : 'female'
  }
  const handleArticle = () => {
    console.log("查询文章")
    setArticle(
      new Array(5).fill({
        title: "React 从入门到放弃"
      })
    )
  }
  return (
    <div>
      <div>用户基本信息:</div>
      <div>
        <p>用户Id:{user.userId}</p>
        <p>用户名:{user.userName}</p>
        <p>用户年龄:{user.userAge}</p>
        <p>用户性别:{filterSex()}</p>
      </div>
      <button onClick={handleArticle}>获取文章</button>
      <div>
        {article.map((item: any, index: number) => {
          return <p key={index}>{item.title}</p>
        })}
      </div>
    </div>
  )
}
export default App

优化前的代码,当我们点击获取文章的时候,整个组件页面都会被重新渲染

我们发现用户性别字段是一个三元表达式,其实在真实的项目开发中,类似的数据计算有很多,每次组件重复渲染都会导致数据重新计算,这样会造成大量的资源浪费,我们一般使用useMemo进行状态变量数据缓存,以达到性能优化的目的:

import { useState, useMemo } from 'react'
const App = () => {
  const [user, setUser] = useState<any>({
    userId: "123456",
    userName: "xiaoming",
    userAge: 22,
    userSex: 1
  })
  const [article, setArticle] = useState<any>([])
  const filterSex = useMemo(() => {
    console.log("计算性别数据")
    return user.userSex == 1 ? 'male' : 'female'
  }, [user])
  const handleArticle = () => {
    console.log("查询文章")
    setArticle(
      new Array(5).fill({
        title: "React 从入门到放弃"
      })
    )
  }
  return (
    <div>
      <div>用户基本信息:</div>
      <div>
        <p>用户Id:{user.userId}</p>
        <p>用户名:{user.userName}</p>
        <p>用户年龄:{user.userAge}</p>
        <p>用户性别:{filterSex}</p>
      </div>
      <button onClick={handleArticle}>获取文章</button>
      <div>
        {article.map((item: any, index: number) => {
          return <p key={index}>{item.title}</p>
        })}
      </div>
    </div>
  )
}
export default App

我们定义一个filterSex变量来接收useMemo返回的缓存变量即可解决重复计算的问题,只有当user触发更新的时候,才会重新触发filterSex内部计算,这样就达到了缓存性能优化的目的了

import React, { useState, useMemo, useReducer } from 'react'
const UserImgs = React.memo((props: any) => {
console.log("获取头像")
console.log(props.loading)
return (
<>
{
props.data?.userImg ?
<div style={{
width: '100px',
height: '100px',
overflow: 'hidden',
borderRadius: '5px'
}}>
<img src={props.data?.userImg}
style={{
width: '100px',
height: '100px',
display: 'block'
}}
/>
</div>
:
<div className="defaultIcon" style={{
width: '100px',
height: '100px',
overflow: 'hidden',
borderRadius: '5px',
background: '#eee',
display:'flex',
alignItems:'center',
justifyContent:'center'
}}>{!props.loading ? '' : 'loading...'}</div>
}
</>
)
})
const UserInfo = React.memo((props: any) => {
const filterSex = useMemo(() => {
return props.data.userSex == 1 ? 'male' : props.data.userSex == 0 ? 'female' : ''
}, [props.data.userSex])
const copyInfo = useMemo(() => {
return props.data
}, [props.data.userImg])
return (
<>
<h1>用户基本信息:</h1>
<UserImgs data={copyInfo} loading={props.loading}></UserImgs>
<div>
<p>用户Id:{props.data.userId}</p>
<p>用户名:{props.data.userName}</p>
<p>用户年龄:{props.data.userAge}</p>
<p>用户性别:{filterSex}</p>
</div>
</>
)
})
const App = () => {
const [user, setUser] = useState<any>({})
const [article, setArticle] = useState<any>([])
const [loading, dispatch] = useReducer((state: boolean, action: any) => {
if (action?.state) {
return true
}
return state
}, false)
const handleArticle = () => {
console.log("查询文章")
setArticle(
new Array(5).fill({
title: "React 从入门到放弃"
})
)
}
const handleUserInfo = () => {
console.log("更新用户信息")
dispatch({
state: true
})
setTimeout(() => {
setUser({
userId: "123456",
userName: "xiaoming",
userAge: 22,
userSex: 1,
userImg: "https://img0.baidu.com/it/u=1993557595,4075530522&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500"
})
dispatch({
state: false
})
}, 500)
}
return (
<div>
<UserInfo data={user} loading={loading}></UserInfo>
<button onClick={handleUserInfo}>刷新用户基本信息</button>
<button onClick={handleArticle}>获取文章</button>
<div>
{article.map((item: any, index: number) => {
return <p key={index}>{item.title}</p>
})}
</div>
</div>
)
}
export default App

4.使用useMemo注意事项:

在实际工作中使用useMemo,需要注意以下两点

1.useMemo应该用于缓存函数组件中计算资源消耗较大的场景,因为useMemo本身就占用一定的缓存,在非必要的场景下使用反而不利于性能的优化

2.在处理量很小的情况下使用useMemo,可能会额外的使用开销

5.总结:

React.memo进行组件数据缓存,当props发生变化的时候,才会重新渲染组件,默认只执行一次

useMemo进行组件方法数据缓存,当useMemo返回值发生变化的时候,才会重新执行计算渲染,默认只执行一次

原文链接:https://juejin.cn/post/7235078247239385149 作者:千度麒麟