Demo1 Codesandbox
Demo2 Codesandbox
我們知道影響 React 重新渲染的兩大關鍵 props / state ,當virtual-DOM發現props或state改變時,就會渲染使用這些數據對應的畫面UI。因此在優化效能的路上,就是需要減少一些不必要但卻很昂貴的渲染。
昨天我們談到了useMemo,可以在某個對應的state沒變時,不重新渲染某個數值及其畫面。那props也有個對應的工具存在,也就是說當props沒改變時,我們就不去重新渲染畫面。這個東西便是React.memo
。
React.memo
是一個HOC (Higher Order Component),什麼是HOC呢?
把一個元件丟進HOC他會幫你做些事情,再還你加工過的元件;就好比高階函數 (Higher Order Function) !
React 官網
React.memo
這個HOC就是幫你比較元件當中的 props 有沒有改變。
用法如下
function FancyButton(props) {
... 元件
}
function areEqual(prevProps, nextProps) {
... 根據props回傳 true / false 的函式
}
export default React.memo(FancyButton, areEqual);
/*
或是const MyComponent = React.memo(function MyComponent(props) {
...
});
*/
高階函數滿足以下至少一個條件
1.是指將某個函數作為參數(arguments)給另一個函數
2.某個函數返回(return)一個函數。
【wiki】higher-order function
實際舉個常見的例子
// 1 (arguments) 匿名函式 傳入 setTimeout
setTimeout(() => alert('Hello'), 3000)
// 2 (arguments) 函式 isToyota 傳入 Array.prototype.filter
const cars = [
{ make: 'Ford', model: 'Focus' },
{ make: 'Ford', model: 'Kuga' },
{ make: 'Toyota', model: 'Wish' },
{ make: 'Toyota', model: 'Yaris' },
]
const isToyota = car => car.make === 'Toyota'
console.log(cars.filter(isToyota))
// [
// { make: 'Toyota', model: 'Wish' },
// { make: 'Toyota', model: 'Yaris' },
// ]
// 3 (return) 回傳出一個函式存入 addTen
function add(x) {
return function (y) {
return x + y
}
}
let addTen = add(10)
addTen(7) // 17
React.memo(CustomComponent, areEqual);
,當中有兩個參數
1.需要做效能優化、比較 props 的元件本人。
2.想要比較的 props 有哪些。 如果你沒輸入,這個參數預設為比較整個 props 內所有數值。
App 當中有 2 個 state ,分別是 count1
, count2
,各自也有顆遞增的按鈕。
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
------------------------------
<button onClick={() => setCount1(count1 + 1)}> count1 is {count1} </button>
<button onClick={() => setCount2(count2 + 1)}> count2 is {count2} </button>
把一個 Child 元件放入App,並且提供一個 count1
作為其 props
。
const Child = ({ count }) => {
console.log("====== Child Render ======");
return <div>count1 is {count}</div>;
};
<button onClick={() => setCount1(count1 + 1)}> count1 is {count1} </button>
<Child count={count1} />
<button onClick={() => setCount2(count2 + 1)}> count2 is {count2} </button>
此時我們發現,不管點 count1 或 count2 的 按鈕, 因為App本身有state
改變了,底下的Child都會被重新渲染。
但對於 <Child count={count1} />
而言,它關注的props
只有count1
而已,假如這Child的渲染很複雜,我們不希望它一直被重新渲染,我們可以使用React.memo
。
首先我們透過建立另外一個元件作為比較。
const Child = ({ count }) => {
console.log("====== Child Render ======");
return <div>count1 is {count}</div>;
};
const ChildWithMemo = React.memo(({ count }) => {
console.log("====== Child with Memo Render ======");
return <div>count is {count}</div>;
});
<button onClick={() => setCount1(count1 + 1)}> count1 is {count1} </button>
<Child count={count1} />
<ChildWithMemo count={count1} />
<button onClick={() => setCount2(count2 + 1)}> count2 is {count2} </button>
Demo1 Codesandbox
此時我們便可以發現,當 count2
改變時,包上memo的 <ChildWithMemo count={count1} />
元件,不會再被重新渲染了。
App 當中有 1 個 state
叫 perosn
, person
是個物件,裡面包含了 姓名 / 年齡 / 薪水,這三個屬性。
我們有兩顆按鈕,一個叫加薪、一個叫做變老 QwQ,分別會對 person
更新 年齡 跟 薪水
const [person, setPerson] = useState({
name: "Ken",
age: 20,
salary: 30000
});
const raiseSalary = () =>
setPerson({ ...person, salary: parseInt(person.salary * 1.25) });
const raiseAge = () => setPerson({ ...person, age: person.age + 1 });
<div className="App">
<button onClick={raiseSalary}>加薪</button>
<button onClick={raiseAge}>變老</button>
<div> 姓名 {person.name}</div>
<div> 年齡 {person.age}</div>
<DemoSalary person={person} />
</div>
const DemoSalary = ({ person }) => {
console.log("====== DemoSalary render ======");
return <div> 薪水 {person.salary}</div>;
}
今天我們希望這有個<DemoSalary person={person} />
,只有當薪水改變時,我們才會重新渲染,這時候我們就可以使用到React.memo(元件,比較函式)
的第二個參數啦!當上個props
跟下個props
的薪水是一樣時,我們就回傳true
,否則回傳false
。
const isSalaryEqual = (prevProps, nextProps) =>
prevProps.person.salary === nextProps.person.salary;
接著著再把這個function,放進React.memo
的第二個參數,便大功告成啦!
const DemoSalary = React.memo(({ person }) => {
console.log("====== DemoSalary render ======");
return <div> 薪水 {person.salary}</div>;
}, isSalaryEqual);
Demo2 codesandbox link
危險~! 這邊必須注意的是,如果你僅單純比較某些 props
特定的部分,你必須確保畫面中沒有用到 props
其他部分。否則他們的畫面將不會被更新到。 假如說我 DemoSalary
當中顯示了名字,但你更改姓名時,畫面不會更新你的名字。
pure functional 是指同一個function的input不管執行多少次,output永遠都會一樣。元件而言就是,相同的props進去幾次,畫面永遠都一樣。
因為我們優化了效能,不渲染多餘的畫面時,不能遺漏渲染某些畫面的可能性(可能元件有某些side effect),因為畫面不會重新渲染,所以我們必須保證每次同樣的props都會產生相同的畫面。
React 性能優化那件大事,使用 memo、useCallback、useMemo
React.memo 与 useMemo ai哟 知乎
//Fooish 程式技術// React State
Javascript面面觀:核心篇《高階函數》
前端工程研究:理解函式編程核心概念與如何進行 JavaScript 函式編程
Use React memo Wisely
[譯] 詳解 React 渲染