前言
這一章節開始我們要來介紹一個新的 Hook 也就是 useMemo,但是再說明 useMemo 之前我們先來回顧一下 Vue 裡面與 useMemo 類似的東西,也就是 Vue Computed。
Vue Computed
首先讓我們先回憶一下關於 Vue Computed 的用法,我們先來看一下下面這個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const { createApp, ref, computed } = Vue;
const app = createApp({ setup() { const num1 = ref(2); const total = computed(() => num1.value * 2) return { total } } });
app.mount('#app');
|
在上方的例子中我們可以看到我們所熟悉的 Computed (計算屬性),而這個計算屬性會回傳 num1
乘以 2
的值,而這就是我們所熟悉的 Composition API Computed 計算屬性最基本的用法。
忘了 Computed
的用法嗎?沒關係,這邊寫了一個簡單的範例讓你回憶一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const { createApp, ref, computed } = Vue;
const app = createApp({ setup() { const num1 = ref(2); const total = computed(() => { console.log('computed'); return num1.value * 2 }) const add = () => { console.log('methods'); return num1.value * 2 } return { total, add } } });
app.mount('#app');
|
當你打開 Console 的時候,你可以發現 Add
函式總共被呼叫了五次,因此 console.log('methods')
也出現五次,而這是因為我們在畫面上呼叫它了五次,但是 Computed
明明也是呼叫五次,但 console.log('computed')
卻只出現一次,這是因為 Computed
只會在資料改變時才會重新計算並執行,因此當資料沒有任何變化時,就不會再次執行,而這也就是我們所熟悉的 Computed 的特性。
那麼 React 的 useMemo 又是怎樣呢?讓我們接著往下看吧。
useMemo
看到前面的例子後,我們可以知道 Vue 的 Computed 會在資料有變動時才會重新計算,而這個特性就是我們在 React Hook 中的 useMemo
。
首先讓我們將前面 Vue 的範例稍微改成 React Hook 版本,看一下 useMemo
是不是真的與 Vue Computed 類似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| const App = () => { const num = 1; const total = React.useMemo(() => { console.log('useMemo') return num * 2 }); const add = () => { console.log('methods'); return num * 2; } return ( <div> <p>useMemo:{ total }</p> <p>useMemo:{ total }</p> <p>useMemo:{ total }</p> <p>useMemo:{ total }</p> <p>useMemo:{ total }</p> <p>methods:{ add() }</p> <p>methods:{ add() }</p> <p>methods:{ add() }</p> <p>methods:{ add() }</p> <p>methods:{ add() }</p> </div> ) } const root = ReactDOM.createRoot(document.querySelector('#root')); root.render(<App />);
|
以結果來講,確實是一樣的,因此我們可以知道 useMemo
也是在資料有變動時才會重新計算,而這也是我們在 React Hook 中的 useMemo
的特性。
所以我們就到這邊結束囉~
.
..
…
….
…..
沒有啦,其實 useMemo
還有其他特性,所以讓我們繼續往下看吧。
首先先讓我們看一段範例程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| const App = () => { const [ users, setUsers ] = React.useState([]); const getData = async () => { const { data } = await axios.get('https://randomuser.me/api/?results=10'); setUsers(data.results); } React.useEffect(() => { getData() }, []); return ( <div> <ul> { users.map((user) => ( <li key={user.email}> { user.name.first + user.name.last } </li> )) } </ul> </div> ) }
const root = ReactDOM.createRoot(document.querySelector('#root')); root.render(<App />);
|
這個範例程式碼我們會使用到 RandomUser 這個服務,這個服務在練習跟需要一些假的使用者資料時非常好用,完全不用擔心裡面有任何真人個資唷。
那麼 useMemo
除了與 Vue 的 Computed
神似之外,它還有什麼特性呢?首先 useMemo
其實可以傳入兩個參數,分別是 Callback 與 Array,而 useMemo
會去記憶 Callback 計算後的結果,也就是 memoized
的概念,甚至你也可以在第二個陣列中傳入想要監聽的變數,當監聽的變數有變化時就會出發渲染
1 2
| const [ users, setUsers ] = React.useState([]); const newUsers = React.useMemo(() => {}, [ users ])
|
但是如果你沒有傳入第二個陣列的話,則會在每一次觸發渲染時就執行一次 useMemo
,如同前面範例一樣。
接下來讓我們實際撰寫一下會更有感覺,首先前面有提到 RandomUser 這個服務,在我們透過 AJAX 請求資料之後,我們會需要使用 sort
重新排序,所以你有可能這樣撰寫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| const App = () => { const [ users, setUsers ] = React.useState([]);; const [ state, setState ] = React.useState(false);
const getUsers = async() => { const { data } = await axios.get('https://randomuser.me/api/?results=10'); setUsers(data.results); }
React.useEffect(() => { getUsers(); }, []);
const filterUsers = (state) => { const newUser = users.sort((a, b) => { if(state) { return a.dob.age < b.dob.age ? 1 : -1 } return a.dob.age > b.dob.age ? 1 : -1 }); setUsers([...newUser]); }
return ( <div> <button onClick={ () => filterUsers(true) } className="border-4 border-indigo-500">年齡大到小</button> <button onClick={ () => filterUsers(false) }className="border-4 border-indigo-500 ml-4">年齡小到大</button> <hr className="my-4"/> <ul> { users.map((user) =>( <li key={ user.email }> { `Name:${user.name.title}.${user.name.first } ${user.name.last}, Age:${user.dob.age}` } </li> )) } </ul> </div> ) }
const root = ReactDOM.createRoot(document.querySelector('#root')); root.render(<App />);
|
雖然畫面是有正常顯示且排序,但這樣做其實並不是一件好事,因為我們每次點擊按鈕都會觸發 filterUsers
,而 filterUsers
會去重新排序,這樣就會造成效能上的浪費,因為我們只是想要重新排序,而不是重新渲染,所以我們可以使用 useMemo
來優化這個問題
這邊也題外話一下,你可能會注意到 filterUsers
中的 setUsers
我是重新做陣列展開的方式 setUsers([...newUser]);
,這邊會這樣寫的原因是因爲 sort
會直接修改原本的陣列,因此如果你是直接 setUsers(newUser);
的話,你會發現畫面並不會重新渲染,因為我們並沒有修改資料,所以 React 在認知上就不會重新渲染,所以我們必須要做陣列展開的方式,讓 React 認知到我們有修改資料,才會重新渲染。
接著讓我們來看一下 useMemo
這個 Hook,那該怎麼寫,其實很簡單就把它當作在寫 Vue 的 computed
就好,只是我們會特別監聽 state
這個值有變化時才去觸發 useMemo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| const App = () => { const [ users, setUsers ] = React.useState([]);; const [ state, setState ] = React.useState(false);
const getUsers = async() => { const { data } = await axios.get('https://randomuser.me/api/?results=10'); setUsers(data.results); }
React.useEffect(() => { getUsers(); }, []);
const filterUsers = React.useMemo(() => { return users.sort((a, b) => { if(state) { return a.dob.age < b.dob.age ? 1 : -1 } return a.dob.age > b.dob.age ? 1 : -1 }); }, [ state ])
return ( <div> <button onClick={ () => setState(true) } className="border-4 border-indigo-500">年齡大到小</button> <button onClick={ () => setState(false) }className="border-4 border-indigo-500 ml-4">年齡小到大</button> <hr className="my-4"/> <ul> { filterUsers.map((user, index) => ( <li key={ user.email }> { `Name:${user.name.title}.${user.name.first } ${user.name.last}, Age:${user.dob.age}` } </li> )) } </ul> </div> ) }
const root = ReactDOM.createRoot(document.querySelector('#root')); root.render(<App />);
|
以上就是一個簡單粗略的 useMemo
介紹與使用。
後記
本文將會同步更新到我的部落格