? 组件是构成React视图的一个基本单元。有些组件会有自己本地的状态(state), 当它们的值由于用户的操作而发生改变时,组件就会重新渲染。在一个React应用中,一个组件可能会被频繁地进行渲染。这些渲染虽然有一小部分是必须的,不过大多数都是无用的,它们的存在会大大降低我们应用的性能。
-
React.memo
在讲React.memo之前先举个🌰
// index.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import Child from ./child
function App() {
const [title, setTitle] = useState("这是一个 title")
return (
<div className="App">
<h1>{ title }</h1>
<button onClick={() => setTitle("title 改变了")}>改标题</button>
<Child name="Child"></Child>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement)
// child.js
import React from "react";
function Child(props) {
console.log(props.name)
return <h1>{props.name}</h1>
}
export default Child
当我们点击Button时,虽然只改变了title,传递给子组件Child的props未发生改变,但在我们每次点击Button时,控制台都会打印一次Child。 => 子组件重新渲染了。
当 Child 组件是一个非常大的组件,渲染一次会消耗很多的性能,那么我们就应该尽量减少这个组件的渲染,否则就容易产生性能问题,所以子组件如果在 props 没有变化的情况下,就算父组件重新渲染了,子组件也不应该渲染。
此时我问可以通过React.memo来实现这一需求
// child.js
import React from "react";
function Child(props) {
console.log(props.name)
return <h1>{props.name}</h1>
}
export default React.memo(Child)
此时,当传递给Child的props不变时,Child组件便不会重新渲染。
注:默认情况下其只会对 props 的复杂对象做浅层对比(浅层对比就是只会对比前后两次 props 对象引用是否相同,不会对比对象里面的内容是否相同),如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);
-
React.useCallback
同样,先举个🌰
// index.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import Child from "./child";
function App() {
const [title, setTitle] = useState("这是一个 title");
const [subtitle, setSubtitle] = useState("这是一个副title");
const callback = () => {
setTitle("标题改变了");
};
return (
<div className="App">
<h1>{title}</h1>
<h2>{subtitle}</h2>
<button onClick={() => setSubtitle("副标题改变了")}>改副标题</button>
<Child onClick={callback} name="Child" />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
import React from "react";
function Child(props) {
console.log(props);
return (
<>
<button onClick={props.onClick}>改标题</button>
<h1>{props.name}</h1>
</>
);
}
export default React.memo(Child);
当点击"改副标题"按钮后,副标题改变为"副标题改变了",且控制台会输出"Child",说明子组件重新渲染。
使用了React.memo,为什么还会重新渲染?
在函数式组件里每次重新渲染,函数组件都会重头开始重新执行,那么这两次创建的 callback 函数肯定发生了改变,所以导致了子组件重新渲染。=> 在函数没有改变的时候,重新渲染的时候保持两个函数的引用一致
useCallback使用方法
const callback = () => {
doSomething(a, b);
}
const memoizedCallback = useCallback(callback, [a, b])
把函数以及依赖项作为参数传入 useCallback
,它将返回该回调函数的 memoized 版本,这个 memoizedCallback 只有在依赖项有变化的时候才会更新, 和useEffect类似。
// index.js
import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
import Child from "./child";
function App() {
const [title, setTitle] = useState("这是一个 title");
const [subtitle, setSubtitle] = useState("这是一个副标题");
const callback = () => {
setTitle("标题改变了");
};
// 通过 useCallback 进行记忆 callback,并将记忆的 callback 传递给 Child
const memoizedCallback = useCallback(callback, [])
return (
<div className="App">
<h1>{title}</h1>
<h2>{subtitle}</h2>
<button onClick={() => setSubtitle("副标题改变了")}>改副标题</button>
<Child onClick={memoizedCallback} name="Child" />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
-
unstable_batchedUpdates批量处理
用于手动批量更新state,可以指定多个setState合并为一个更新请求
如果这些setState写在React的事件处理程序(event handler)里,比如onClick,那么他们会被同时执行;
如果这些setState写在promises,setTimeout或者非React的js原生的事件处理程序(比如document.addEventListener)里,那么他们不会被同时执行。但是可以用ReactDOM.unstable_batchedUpdates来强制同时执行。
import React, {useState} from "react";
import { unstable_batchedUpdates } from "react-dom";
const Unstable: React.FC = () => {
const [titleOne, setTitleOne] = useState<string>('titleOne');
const [titleTwo, setTitleTwo] = useState<string>("titleTwo");
const [titleThree, setTitlethree] = useState<string>("titleThree");
//同时执行
// const onClick = () => {
// setTitleOne('titleOne变了');
// setTitleTwo('titleTwo变了');
// setTitlethree('titleThree变了');
// }
//一个
// const onClick = () => {
// setTimeout(() => {
// setTitleOne('titleOne变了');
// setTitleTwo('titleTwo变了');
// setTitlethree('titleThree变了');
// }, 0);
// }
const onClick = () => {
setTimeout(() => {
unstable_batchedUpdates(() => {
setTitleOne('titleOne变了');
setTitleTwo('titleTwo变了');
setTitlethree('titleThree变了');
})
}, 0);
}
console.log("render");
return (
<>
<h1>{titleOne}</h1>
<h3>{titleTwo}</h3>
<h3>{titleThree}</h3>
<button onClick={onClick}>点击</button>
</>
)
}
export default Unstable;
此时,点击按钮后只会打印一次"render".