React Hooks

使用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中使用叁征。

疑問

    1. 為什么只能在函數(shù)最外層調(diào)用 Hook纳账,不要在循環(huán)、條件判斷或者子函數(shù)中調(diào)用捺疼?
    1. 為什么 useEffect 第二個參數(shù)是空數(shù)組疏虫,就相當于 ComponentDidMount ,只會執(zhí)行一次啤呼?
    1. 自定義的 Hook 是如何影響使用它的函數(shù)組件的卧秘?
    1. 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)致重新渲染社牲。它也在組件的渲染過程中運行粪薛,而不是之前

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市搏恤,隨后出現(xiàn)的幾起案子违寿,更是在濱河造成了極大的恐慌,老刑警劉巖熟空,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藤巢,死亡現(xiàn)場離奇詭異,居然都是意外死亡息罗,警方通過查閱死者的電腦和手機掂咒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來迈喉,“玉大人绍刮,你說我怎么就攤上這事“っ” “怎么了孩革?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長得运。 經(jīng)常有香客問我膝蜈,道長锅移,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任彬檀,我火速辦了婚禮帆啃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窍帝。我一直安慰自己,他們只是感情好诽偷,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布坤学。 她就那樣靜靜地躺著,像睡著了一般报慕。 火紅的嫁衣襯著肌膚如雪深浮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天眠冈,我揣著相機與錄音飞苇,去河邊找鬼。 笑死蜗顽,一個胖子當著我的面吹牛布卡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播雇盖,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼忿等,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了崔挖?” 一聲冷哼從身側(cè)響起贸街,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎狸相,沒想到半個月后薛匪,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡脓鹃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年逸尖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片将谊。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡岖食,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出碟婆,到底是詐尸還是另有隱情退腥,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布栋齿,位于F島的核電站苗胀,受9級特大地震影響襟诸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜基协,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一歌亲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧澜驮,春花似錦陷揪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至耐量,卻和暖如春飞蚓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背廊蜒。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工趴拧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人山叮。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓著榴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親聘芜。 傳聞我的和親對象是個殘疾皇子兄渺,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容