一、useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
返回一个 memoized 值。
把“创建”函数和依赖项数组作为参数传入 useMemo
,它仅会在某个依赖项改变时才重新计算memoized
值。这种优化有助于避免在每次渲染时都进行高开销的计算。
记住,传入useMemo
的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect
的适用范畴,而不是 useMemo
。
如果没有提供依赖项数组,useMemo
在每次渲染时都会计算新的值。
以下举例说明:
- 父组件
function App() {
const [name, setName] = useState('名称')
const [content,setContent] = useState('内容')
return (
<>
<button onClick={() => setName(new Date().getTime())}>name</button>
<button onClick={() => setContent(new Date().getTime())}>content</button>
<Button name={name}>{content}</Button>
</>
)
}
- 子组件
function Button({ name, children }) {
function changeName(name) {
console.log('11')
return name + '改变name的方法'
}
const otherName = changeName(name)
return (
<>
<div>{otherName}</div>
<div>{children}</div>
</>
)
}
熟悉react
的同学可以很显然的看出,当我们点击父组件的按钮的时候,子组件的name
和children
都会发生变化。
不管我们是改变name
或者content
的值,我们发现 changeName
的方法都会被调用。
是不是意味着 我们本来只想修改content
的值,但是由于name
并没有发生变化,所以无需执行对应的changeName
方法。但是发现他却执行了。 这是不是就意味着性能的损耗,做了无用功。
- 优化之后的子组件
function Button({ name, children }) {
function changeName(name) {
console.log('11')
return name + '改变name的方法'
}
const otherName = useMemo(()=>changeName(name),[name])
return (
<>
<div>{otherName}</div>
<div>{children}</div>
</>
)
}
export default Button
这个时候我们点击 改变content
值的按钮,发现changeName
并没有被调用。
但是点击改变name
值按钮的时候,changeName
被调用了。
所以我们可以使用useMemo
方法 避免无用方法的调用,当然一般我们changName
里面可能会使用useState
来改变state
的值,那是不是就避免了组件的二次渲染。达到了优化性能的目的。
二、React.memo
import React, { useState } from 'react'
const Child = props => {
console.log("render child")
return (
<div>
props count: { props.count }
</div>
)
}
const Wrapper = () => {
const [count, setCount] = useState(0)
const [id, setId] = useState(0)
return (
<div>
<h2>React Memo</h2>
<button onClick={() => setId(id + 1)}>render parent</button>
<button onClick={() => setCount(count + 1)}>change count</button>
<Child count={count}></Child>
</div>
)
}
export default Wrapper
上述例子中,我们每次点击 render parent
按钮,Wrapper
组件就会重新渲染,导致Child
组件也会重新渲染打印"render child"
,但实际上,Child
的props
并未改变,所以不需要render
。
在class component
中,我们可以使用 PureComponent
或 shouldComponentUpdate
来控制子组件是否需要渲染.
但在FC
中没这些玩意儿该怎么办?
解决方式是用React.memo
将子组件 child
包裹起来:
import React, { useState } from 'react'
const Child = props => {
console.log("render child")
return (
<div>
props count: { props.count }
</div>
)
}
const Memo = React.memo(Child)
const Wrapper = () => {
const [count, setCount] = useState(0)
const [id, setId] = useState(0)
return (
<div>
<h2>React Memo</h2>
<button onClick={() => setId(id + 1)}>render parent</button>
<button onClick={() => setCount(count + 1)}>change count</button>
<Memo count={count}></Memo>
</div>
)
}
这样,只要在我们修改了count
的情况下,子组件才会渲染
不仅如此,React.memo
还提供了第二参数,一个函数,用来自定义判断前后是否相等,类似
shouldComponentUpdate
,只不过返回值相反。
我们把上面的例子改为如果新的count
值比老的count的差值值大于等于
2,
Child`才能渲染:
import React, { useState } from 'react'
const Child = props => {
console.log("render child")
return (
<div>
props count: { props.count }
</div>
)
}
const Memo = React.memo(Child, (prev, next) => {
return (next.count - prev.count) < 2
})
const Wrapper = () => {
const [count, setCount] = useState(0)
const [id, setId] = useState(0)
return (
<div>
<h2>React Memo</h2>
<button onClick={() => setId(id + 1)}>render parent</button>
<button onClick={() => setCount(count + 1)}>change count</button>
<Memo count={count}></Memo>
</div>
)
}
现在就是点两次change count
按钮,才会触发render child
。
参考:
- https://zh-hans.reactjs.org/docs/hooks-reference.html#usememo
- https://segmentfault.com/a/1190000018697490
- https://zhuanlan.zhihu.com/p/339438975