使用React Hooks有什么優(yōu)勢烘贴?
什么是hooks
hook 是一些可以讓你在函數(shù)組件里面鉤入react state 以及生命周期的特定的函數(shù)。
- Hooks 本質(zhì)是把面向生命周期編程變成了面向業(yè)務(wù)邏輯編程眨层;
- Hooks 使用上是一個邏輯狀態(tài)盒子庙楚,輸入輸出表示的是一種聯(lián)系;
- Hooks 是 React 的未來趴樱,但還是無法完全替代原始的 Class馒闷。
- 每個 Hook 都為Function Component提供使用 React 狀態(tài)和生命周期特性的通道。Hooks 不能在Class Component中使用叁征。
疑問
- 為什么只能在函數(shù)最外層調(diào)用 Hook纳账,不要在循環(huán)、條件判斷或者子函數(shù)中調(diào)用捺疼?
- 為什么 useEffect 第二個參數(shù)是空數(shù)組疏虫,就相當于 ComponentDidMount ,只會執(zhí)行一次啤呼?
- 自定義的 Hook 是如何影響使用它的函數(shù)組件的卧秘?
- Capture Value 特性是如何產(chǎn)生的?
class 組件
主要問題
- 在hooks出來之前官扣,常見的代碼重用方式是HOCs和render props翅敌,這兩種方式帶來的問題是:你需要解構(gòu)自己的組件,非常的笨重惕蹄,同時會帶來很深的組件嵌套
- 復(fù)雜的組件邏輯:復(fù)雜的業(yè)務(wù)邏輯里面存在各種生命周期蚯涮,導(dǎo)致代碼拆分比較困難,很難復(fù)用
- 難理解的class 組件
hooks 的到來:
帶組件狀態(tài)的邏輯很難重用:
class 組件復(fù)用主要通過引入render props或higher-order components這樣的設(shè)計模式卖陵。如react-redux提供的connect方法遭顶。這種方案不夠直觀,而且需要改變組件的層級結(jié)構(gòu)泪蔫,極端情況下會有多個wrapper嵌套調(diào)用的情況棒旗。
Hooks可以在不改變組件層級關(guān)系的前提下,方便的重用帶狀態(tài)的邏輯鸥滨。也可以自己定義狀態(tài)組件
復(fù)雜組件難于理解:
大量的業(yè)務(wù)邏輯需要放在componentDidMount和componentDidUpdate等生命周期函數(shù)中嗦哆,而且往往一個生命周期函數(shù)中會包含多個不相關(guān)的業(yè)務(wù)邏輯谤祖,如日志記錄和數(shù)據(jù)請求會同時放在componentDidMount中。另一方面老速,相關(guān)的業(yè)務(wù)邏輯也有可能會放在不同的生命周期函數(shù)中粥喜,如組件掛載的時候訂閱事件,卸載的時候取消訂閱橘券,就需要同時在componentDidMount和componentWillUnmount中寫相關(guān)邏輯额湘。
Hooks可以封裝相關(guān)聯(lián)的業(yè)務(wù)邏輯,讓代碼結(jié)構(gòu)更加清晰旁舰。
難于理解的 Class 組件:
JS 中的this關(guān)鍵字讓不少人吃過苦頭锋华,它的取值與其它面向?qū)ο笳Z言都不一樣,是在運行時決定的
Hooks可以在不引入 Class 的前提下箭窜,使用 React 的各種特性毯焕。
class RandomUserModal extends React.Component {
constructor(props) {
super(props);
this.state = {
user: {},
loading: false,
};
this.fetchData = this.fetchData.bind(this);
}
componentDidMount() {
if (this.props.visible) {
this.fetchData();
}
}
componentDidUpdate(prevProps) {
if (!prevProps.visible && this.props.visible) {
this.fetchData();
}
}
// 獲取數(shù)據(jù)
fetchData() {
// 打開loading
this.setState({ loading: true });
fetch('https://randomuser.me/api/')
.then(res => res.json())
.then(json => this.setState({
user: json.results[0],
loading: false,
}));
}
render() {
const user = this.state.user;
return (
<ReactModal
isOpen={this.props.visible}
>
<button onClick={this.props.handleCloseModal}>Close Modal</button>
{this.state.loading ?
<div>loading...</div>
:
<ul>
<li>Name: {`${(user.name || {}).first} ${(user.name || {}).last}`}</li>
<li>Gender: {user.gender}</li>
<li>Phone: {user.phone}</li>
</ul>
}
</ReactModal>
)
}
}
該 Modal 的展示與否由父組件控制,因此會傳入?yún)?shù) visible 和 handleCloseModal(用于 Modal 關(guān)閉自己)磺樱。 實現(xiàn)在 Modal 打開的時候才進行數(shù)據(jù)獲取纳猫,我們需要同時在 componentDidMount 和 componentDidUpdate 兩個生命周期里實現(xiàn)數(shù)據(jù)獲取的邏輯
我們需要將其按照 React 組件生命周期進行拆解。這種拆解除了代碼冗余竹捉,還很難復(fù)用芜辕。
Hooks 寫法:
function RandomUserModal(props) {
const [user, setUser] = React.useState({});
const [loading, setLoading] = React.useState(false);
React.useEffect(() => {
if (!props.visible) return;
setLoading(true);
fetch('https://randomuser.me/api/').then(res => res.json()).then(json => {
setUser(json.results[0]);
setLoading(false);
});
}, [props.visible]);
return (
// View 部分幾乎與上面相同
);
}
優(yōu)勢是代碼精簡, 可以通過 自定義 的hook 將 重要的邏輯抽離出去
// 自定義 Hook
function useFetchUser(visible) {
const [user, setUser] = React.useState({});
const [loading, setLoading] = React.useState(false);
React.useEffect(() => {
if (!visible) return;
setLoading(true);
fetch('https://randomuser.me/api/').then(res => res.json()).then(json => {
setUser(json.results[0]);
setLoading(false);
});
}, [visible]);
return { user, loading };
}
function RandomUserModal(props) {
const { user, loading } = useFetchUser(props.visible);
return (
// 與上面相同
);
}
useState
useState 是一個hook,它的入?yún)⑹莝tate 的初始值块差,返回一個數(shù)組侵续,包含當前state 和 用于更改 state 的函數(shù)
- class 組件有一個大的state 對象,通過this.setState 一次改變整個state對象
- 函數(shù)組件根本沒有狀態(tài)憨闰,但useState hook允許我們在需要時添加很小的狀態(tài)塊
React有能力在調(diào)用每個組件之前做一些設(shè)置状蜗,這就是它設(shè)置這個狀態(tài)的時候。
其中做的一件事設(shè)置 Hooks 數(shù)組鹉动。 它開始是空的, 每次調(diào)用一個hook時诗舰,React 都會向該數(shù)組添加該 hook。
假如有這樣一個函數(shù)
function AudioPlayer() {
const [volume, setVolume] = useState(80);
const [position, setPosition] = useState(0);
const [isPlaying, setPlaying] = useState(false);
.....
}
因為它調(diào)用useState 3次训裆,React 會在第一次渲染時將這三個 hook 放入 Hooks 數(shù)組中。
下次渲染時蜀铲,同樣的3個hooks以相同的順序被調(diào)用边琉,所以React可以查看它的數(shù)組,并發(fā)現(xiàn)已經(jīng)在位置0有一個useState hook 记劝,所以React不會創(chuàng)建一個新狀態(tài)变姨,而是返回現(xiàn)有狀態(tài)。
1厌丑、React 創(chuàng)建組件時定欧,它還沒有調(diào)用函數(shù)渔呵。React 創(chuàng)建元數(shù)據(jù)對象和Hooks的空數(shù)組。假設(shè)這個對象有一個名為nextHook的屬性砍鸠,它被放到索引為0的位置上扩氢,運行的第一個hook將占用位置0。
2爷辱、React 調(diào)用你的組件(
這意味著它知道存儲hooks的元數(shù)據(jù)對象
)录豺。3、調(diào)用useState饭弓,React創(chuàng)建一個新的狀態(tài)双饥,將它放在hooks數(shù)組的第0位,返回
[volume弟断,setVolume]
對咏花,并將volume 設(shè)置為其初始值80,它還將nextHook索引遞增1阀趴。4昏翰、再次調(diào)用useState,React查看數(shù)組的第1位舍咖,看到它是空的矩父,并創(chuàng)建一個新的狀態(tài)。 然后它將nextHook索引遞增為2排霉,并返回
[position窍株,setPosition]
。5攻柠、第三次調(diào)用useState球订。 React看到位置2為空,同樣創(chuàng)建新狀態(tài)瑰钮,將nextHook遞增到3冒滩,并返回
[isPlaying,setPlaying]
浪谴。
useEffect
你可以把 useEffect Hook 看做 componentDidMount开睡,componentDidUpdate 和 componentWillUnmount 這三個函數(shù)的組合。
默認情況下苟耻,它在第一次渲染之后和每次更新之后都會執(zhí)行篇恒。
useEffect做了什么
引用官方文檔的例子:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
通過使用這個 Hook,你可以告訴 React 組件需要在渲染后執(zhí)行某些操作凶杖。React 會保存你傳遞的函數(shù)(我們將它稱之為 “effect”)胁艰,并且在執(zhí)行 DOM 更新之后調(diào)用它。
- 在渲染的時候被創(chuàng)建,在瀏覽器繪制之后運行腾么。
- 如果給出了銷毀指令奈梳,它們將在下一次繪制前被銷毀。
- 它們會按照定義的順序被運行解虱。
渲染函數(shù)只是創(chuàng)建了 fiber 節(jié)點攘须,但是并沒有繪制任何內(nèi)容。
通常來說饭寺,應(yīng)該是 fiber 保存包含了 effect 節(jié)點的隊列阻课。每個 effect 節(jié)點都是一個不同的類型,并能在適當?shù)臓顟B(tài)下被定位到:
hook effect 將會被保存在 fiber 一個稱為 updateQueue 的屬性上艰匙,每個 effect 節(jié)點都有如下的結(jié)構(gòu)(詳見源碼):
type Effect = {
tag: HookEffectTag, // 它控制了 effect 節(jié)點的行為
create: () => mixed, // 繪制之后運行的回調(diào)函數(shù)
destroy: (() => mixed) | null,
inputs: Array<mixed>, // 一個集合限煞,該集合中的值將會決定一個 effect 節(jié)點是否應(yīng)該被銷毀或者重新創(chuàng)建。
next: Effect, //它指向下一個定義在函數(shù)組件中的 effect 節(jié)點
};
export type HookEffectTag = number;
export const NoEffect = /* */ 0b00000000;
export const UnmountSnapshot = /* */ 0b00000010;
export const UnmountMutation = /* */ 0b00000100;
export const MountMutation = /* */ 0b00001000;
export const UnmountLayout = /* */ 0b00010000;
export const MountLayout = /* */ 0b00100000;
export const MountPassive = /* */ 0b01000000;
export const UnmountPassive = /* */ 0b10000000;
// 這個 tag 屬性值是由二進制的值組合而成
React 提供了一些特殊的 effect hook:比如 useMutationEffect() 和 useLayoutEffect()员凝。這兩個 effect hook 內(nèi)部都使用了 useEffect()署驻,實際上這就意味著它們創(chuàng)建了 effect hook,但是卻使用了不同的 tag 屬性值健霹。
react 又是如何檢查處罰的呢旺上?
do {
if ((effect.tag & unmountTag) !== NoHookEffect) {
// Unmount
const destroy = effect.destroy;
effect.destroy = null;
if (destroy !== null) {
destroy();
}
}
if ((effect.tag & mountTag) !== NoHookEffect) {
// Mount
const create = effect.create;
let destroy = create();
if (typeof destroy !== 'function') {
if (__DEV__) {
if (destroy !== null && destroy !== undefined) {
warningWithoutStack(
false,
'useEffect function must return a cleanup function or ' +
'nothing.%s%s',
typeof destroy.then === 'function'
? ' Promises and useEffect(async () => ...) are not ' +
'supported, but you can call an async function inside an ' +
'effect.'
: '',
getStackByFiberInDevAndProd(finishedWork),
);
}
}
destroy = null;
}
effect.destroy = destroy;
}
effect = effect.next;
}
React 組件中有兩種常見的副作用:
需要清理的副作用
不需要清理的副作用。
無需清除的effect:
只想在 React 更新 DOM 之后運行一些額外的代碼糖埋。比如發(fā)送網(wǎng)絡(luò)請求宣吱,手動變更 DOM,記錄日志瞳别,這些都是常見的無需清除的操作征候。
需要清除的effect:
例如訂閱外部數(shù)據(jù)源。這種情況下祟敛,清除工作是非常重要的疤坝,可以防止引起內(nèi)存泄露!
使用class
class FriendStatus extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
render() {
if (this.state.isOnline === null) {
return 'Loading...';
}
return this.state.isOnline ? 'Online' : 'Offline';
}
}
你通常會在 componentDidMount 中設(shè)置訂閱馆铁,并在 componentWillUnmount 中清除它跑揉。
使用生命周期函數(shù)迫使我們拆分這些邏輯代碼,即使這兩部分代碼都作用于相同的副作用
使用 Hook 的示例:
function FriendStatus(props) {
// ...
useEffect(() => {
// ...
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
這是 effect 可選的清除機制埠巨。每個 effect 都可以返回一個清除函數(shù)历谍。如此可以將添加和移除訂閱的邏輯放在一起。它們都屬于 effect 的一部分
多個effect:
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// ...
}
Hook 允許我們按照代碼的用途分離他們辣垒, 而不是像生命周期函數(shù)那樣扮饶。React 將按照 effect 聲明的順序依次調(diào)用組件中的每一個 effect。
它會在調(diào)用一個新的 effect 之前對前一個 effect 進行清理
// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // 運行第一個 effect
// Update with { friend: { id: 200 } } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 清除上一個 effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // 運行下一個 effect
// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 清除上一個 effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // 運行下一個 effect
// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 清除最后一個 effect
effect優(yōu)化
每次渲染后都執(zhí)行清理或者執(zhí)行 effect 可能會導(dǎo)致性能問題
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
}, [props.friend.id]); // 僅在 props.friend.id 發(fā)生變化時乍构,重新訂閱
只在最頂層使用 Hook
不要在循環(huán),條件或嵌套函數(shù)中調(diào)用 Hook, 確备缯冢總是在你的 React 函數(shù)的最頂層調(diào)用他們岂丘。遵守這條規(guī)則,你就能確保 Hook 在每一次渲染中都按照同樣的順序被調(diào)用
useContext
接收一個 context 對象(React.createContext 的返回值)并返回該 context 的當前值眠饮。當前的 context 值由上層組件中距離當前組件最近的 <MyContext.Provider> 的 value prop 決定
當組件上層最近的 <MyContext.Provider> 更新時奥帘,該 Hook 會觸發(fā)重渲染,并使用最新傳遞給 MyContext provider 的 context value 值
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
useReducer:
useState的替代方案仪召。接受類型為(state寨蹋,action)=> newState的reducer,并返回與dispatch方法配對的當前狀態(tài)
const [state, dispatch] = useReducer(reducer, initialArg, init);
有兩種不同初始化 useReducer state 的方式
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter({initialState}) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
初始值也可以延遲初始化, useReducer(reducer, initialCount, init), init 是一個函數(shù), 初始值將設(shè)置為init(initialArg)
如果從Reducer Hook返回與當前狀態(tài)相同的值扔茅,則React將退出而不渲染子項或觸發(fā)效果已旧。
useCallback
useCallback將返回一個回調(diào)的memoized(一種優(yōu)化手段,遇到計算開銷很大的函數(shù)時,會緩存其計算結(jié)果,下次同樣的輸入就可以直接返回緩存的結(jié)果)版本,該版本僅在其中一個依賴項發(fā)生更改時才會更改召娜。
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useCallback(fn, deps) 相當于 useMemo(() => fn, deps)运褪。
useRef
const refContainer = useRef(initialValue);
useRef 返回一個可變的 ref 對象,其 .current 屬性被初始化為傳入的參數(shù)(initialValue)玖瘸。返回的 ref 對象在組件的整個生命周期內(nèi)保持不變秸讹。
當 ref 對象內(nèi)容發(fā)生變化時,useRef 并不會通知你雅倒。變更 .current 屬性不會引發(fā)組件重新渲染
然而璃诀,useRef()
比ref
屬性更有用。它可以很方便地保存任何可變值蔑匣,其類似于在 class 中使用實例字段的方式
useRef這個hooks函數(shù)劣欢,除了傳統(tǒng)的用法之外,它還可以“跨渲染周期”保存數(shù)據(jù)殖演。
在一個組件中有什么東西可以跨渲染周期氧秘,也就是在組件被多次渲染之后依舊不變的屬性?第一個想到的應(yīng)該是state趴久。沒錯丸相,一個組件的state可以在多次渲染之后依舊不變。但是彼棍,state的問題在于一旦修改了它就會造成組件的重新渲染灭忠。
import React, { useState, useEffect, useMemo, useRef } from 'react';
export default function App(props){
const [count, setCount] = useState(0);
const doubleCount = useMemo(() => {
return 2 * count;
}, [count]);
const timerID = useRef();
useEffect(() => {
timerID.current = setInterval(()=>{
setCount(count => count + 1);
}, 1000);
}, []);
useEffect(()=>{
if(count > 10){
clearInterval(timerID.current);
}
});
// 用ref對象的current屬性來存儲定時器的ID
return (
<>
<button ref={couterRef} onClick={() => {setCount(count + 1)}}>Count: {count}, double: {doubleCount}</button>
</>
);
}
為什么只能在函數(shù)最外層調(diào)用 Hook,不要在循環(huán)座硕、條件判斷或者子函數(shù)中調(diào)用?
function ExampleWithManyStates() {
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
}
// useState無論調(diào)用多少次弛作,相互之間是獨立的
react是根據(jù)useState出現(xiàn)的順序來定的
//第一次渲染
useState(42); //將age初始化為42
useState('banana'); //將fruit初始化為banana
useState([{ text: 'Learn Hooks' }]); //...
//第二次渲染
useState(42); //讀取狀態(tài)變量age的值(這時候傳的參數(shù)42直接被忽略)
useState('banana'); //讀取狀態(tài)變量fruit的值(這時候傳的參數(shù)banana直接被忽略)
useState([{ text: 'Learn Hooks' }]); //...
如果放在循環(huán)或者判斷里面
let showFruit = true;
function ExampleWithManyStates() {
const [age, setAge] = useState(42);
if(showFruit) {
const [fruit, setFruit] = useState('banana');
showFruit = false;
}
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
}
//第一次渲染
useState(42); //將age初始化為42
useState('banana'); //將fruit初始化為banana
useState([{ text: 'Learn Hooks' }]); //...
//第二次渲染
useState(42); //讀取狀態(tài)變量age的值(這時候傳的參數(shù)42直接被忽略)
// useState('banana');
useState([{ text: 'Learn Hooks' }]); //讀取到的卻是狀態(tài)變量fruit的值,導(dǎo)致報錯
這樣一來不能確保hooks 的執(zhí)行順序一致华匾。
useMemo
我們都知道 react 一個組件的更新映琳,然后下面的字組件都會更新,有一次被問到class 組件中,如果讓不需要更新的組件不更新萨西,當時只想起來了 shouldComponentUpdate有鹿,他是在重新渲染的過程中觸發(fā)的,
PureComponent 就是自動為我們加了shouldComponentUpdate 的方法谎脯,如果組件的 props 和 state 都沒發(fā)生改變葱跋, render 方法就不會觸發(fā)
1、 上面提到了 PureComponent來優(yōu)化 class 組件源梭,
2娱俺、 React.memo() (16.6正式發(fā)布的)用戶函數(shù)組件和PureComponent 很相似
hooks引入了useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
記住,傳入 useMemo 的函數(shù)會在渲染期間執(zhí)行
不要再這個函數(shù)執(zhí)行的內(nèi)部執(zhí)行與渲染無關(guān)的操作废麻,諸如副作用這類的操作屬于 useEffect 的適用范疇荠卷。
不適用useMemo 之前:
import React , {useState,useMemo} from 'react';
function Home(){
const [name, setName] = useState('名稱');
const [content,setContent] = useState('內(nèi)容')
return (
<>
<button onClick={() => setName(new Date().getTime())}>name</button>
<button onClick={() => setContent(new Date().getTime())}>content</button>
<ChildComponent name={name}>{content}</ChildComponent>
</>
)
}
function ChildComponent({name,children}){
function changeName(name){
console.log('觸發(fā)了changeName')
return name+',改變了name'
}
const _changeName = changeName(name)
return (
<>
<div>{_changeName}</div>
<div>{children}</div>
</>
)
}
// 點擊修改內(nèi)容,changeName也會觸發(fā)脑溢,每次都會執(zhí)行僵朗。如果我們想要name 變化的時候 changeName 才觸發(fā)。
使用useMemo 優(yōu)化:
// 父組件不變
function ChildComponent({name,children}){
function changeName(name){
console.log('觸發(fā)了changeName')
return name+',改變了name'
}
// const _changeName = changeName(name)
const _changeName = useMemo(()=>changeName(name),[name])
return (
<>
<div>{_changeName}</div>
<div>{children}</div>
</>
)
}
// name 變化的時候屑彻,changeName 才會觸發(fā)
useMemo僅在其依賴項數(shù)組中的元素發(fā)生更改時重新計算值(如果沒有依賴項 - 即數(shù)組為空验庙,則會在每次調(diào)用/呈現(xiàn)時重新計算)。調(diào)用該函數(shù)不會導(dǎo)致重新渲染社牲。它也在組件的渲染過程中運行粪薛,而不是之前