日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

React hooks之useCallback的使用與性能分析

作者:番茄炒蛋加雞腿 更新時間: 2023-07-05 編程語言

使用useCallback優化代碼

useCallback是對傳過來的回調函數優化,返回的是一個函數;useMemo返回值可以是任何,函數,對象等都可以。
簡單來說就是返回一個函數,只有在依賴項發生變化的時候才會更新(返回一個新的函數)。

1.原理分析

useCallback是React Hooks中的一個函數,用于優化函數組件的性能。它的作用是返回一個memoized(記憶化的)函數,這個函數只有在依賴項發生變化時才會重新計算,否則會直接返回上一次計算的結果。
useCallback是對傳過來的回調函數優化,返回的是一個函數;useMemo返回值可以是任何,函數,對象等都可以。
簡單來說就是返回一個函數,只有在依賴項發生變化的時候才會更新(返回一個新的函數)。

2.案例分析:

父組件定義一個請求函數fetchData,和一個狀態query,將query當作fetchData的參數,將該函數傳遞進子組件,當父組件query發生變化時,讓子組件調用該函數發起請求。

class Parent extends Component {
  state = {
    query: 'react'
  };
  fetchData = () => {    
    const url = 'https://hn.algolia.com/api/v1/search?query=' + this.state.query;    // ... Fetch data and do something ...  };  render() {
    return <Child fetchData={this.fetchData} />;
  }
}

class Child extends Component {
  state = {
    data: null
  };
  componentDidMount() {
    this.props.fetchData();
  }
  componentDidUpdate(prevProps) {
    // ?? This condition will never be true
    if (this.props.fetchData !== prevProps.fetchData) {
      this.props.fetchData();
    }
  }
  render() {
    // ...
  }
}

在本代碼中,fetchData是一個class方法!(或者你也可以說是class屬性)它不會因為狀態的改變而不同,所以this.props.fetchData和 prevProps.fetchData始終相等,因此不會重新請求。

2.1舊思維–優化該案例:

子組件使用:

componentDidUpdate(prevProps) {
    this.props.fetchData();
}

這樣可以發起請求,但是會在每次渲染后都去請求。
或者改變父組件:

render() {
    return <Child fetchData={this.fetchData.bind(this, this.state.query)} />;
}

但這樣一來,this.props.fetchData !== prevProps.fetchData 表達式永遠是true,即使query并未改變。這會導致我們總是去請求。(bind() 方法會創建一個新的函數對象)
唯一現實可行的辦法是把query本身傳入 Child 組件。 Child 雖然實際并沒有直接使用這個query的值,但能在它改變的時候觸發一次重新請求:

class Parent extends Component {
  state = {
    query: 'react'
  };
  fetchData = () => {
    const url = 'https://hn.algolia.com/api/v1/search?query=' + this.state.query;
    // ... Fetch data and do something ...
  };
  render() {
    return <Child fetchData={this.fetchData} query={this.state.query} />;  }
}

class Child extends Component {
  state = {
    data: null
  };
  componentDidMount() {
    this.props.fetchData();
  }
  componentDidUpdate(prevProps) {
    if (this.props.query !== prevProps.query) {      this.props.fetchData();    }  }
  render() {
    // ...
  }
}

在class組件中,函數屬性本身并不是數據流的一部分。使用useCallback,函數完全可以參與到數據流中。我們可以說如果一個函數的輸入改變了,這個函數就改變了。如果沒有,函數也不會改變。

2.2開始使用hooks:

場景一: 使用函數組件
但父組件不使用useCallback處理函數

import React, { useCallback, useState, useEffect } from 'react';
import './App.css';

function App() {
  const [query, setQuery] = useState(1);
  const [queryOther, setQueryOther] = useState(1);
  const fecthData = () => {
    console.log('新的fetch');
    return query;
  }
  const add = () => {
    console.log('點擊add');
    setQuery(query + 1);
  }
  const addOther = () => {
    console.log('點擊addOther');
    setQueryOther(queryOther + 1);
  }
  return (
    <>
      <Child fecthData={fecthData} />
      <button onClick={add}>+1</button>
      <button onClick={addOther}>other+1</button>
      <div>{ query }</div>
    </>
  );
}

function Child({ fecthData }: { fecthData: any }) {
  console.log('子組件相關內容');
  useEffect(() => {
    const querN = fecthData();
    console.log('子組件調用該函數獲取到相關內容', querN);
  }, [fecthData])
  return <div>
    123
  </div>
}

export default App;

初始化的時候:
在這里插入圖片描述
點擊按鈕:

在這里插入圖片描述
但是從圖里面可以看到,點擊addOther時,并沒有使得query發生變化,但是子組件仍然調用了該函數發起請求。可以看到這種方法需求可以使得子組件在父組件的狀態query發生變化時,成功發起了請求,但是還是存在副作用。
問題的原因在于狀態queryOther的改變,使得父組件重新渲染,重新生成了fecthData函數,并返回了該函數新的地址,導致子組件刷新。

場景二:父組件使用useCallback處理函數

import React, { useCallback, useState, useEffect } from 'react';
import './App.css';

function App() {
  const [query, setQuery] = useState(1);
  const [queryOther, setQueryOther] = useState(1);
  const fecthData = useCallback(() => {
    console.log('新的fetch');
    return query;
  }, [query])
  const add = () => {
    console.log('點擊add');
    setQuery(query + 1);
  }
  const addOther = () => {
    console.log('點擊addOther');
    setQueryOther(queryOther + 1);
  }
  return (
    <>
      <Child fecthData={fecthData} />
      <button onClick={add}>+1</button>
      <button onClick={addOther}>other+1</button>
      <div>{ query }</div>
    </>
  );
}

function Child({ fecthData }: { fecthData: any }) {
  console.log('子組件相關內容');
  useEffect(() => {
    const querN = fecthData();
    console.log('子組件調用該函數獲取到相關內容', querN);
  }, [fecthData])
  return <div>
    123
  </div>
}

export default App;

初始狀態
在這里插入圖片描述
點擊按鈕:
在這里插入圖片描述

可以看到只有點擊+1按鈕改變query才會使得子組件發起請求,點擊other+1已經沒有處罰上文副作用。

原因分析:
使用了useCallback,useCallback的工作原理是什么?useCallBack的本質工作不是在依賴不變的情況下阻止函數創建,而是在依賴不變的情況下不返回新的函數地址而返回舊的函數地址。不論是否使用useCallBack都無法阻止組件render時函數的重新創建。在本例子中點擊按鈕other+1并沒有使得query發生變化,所以并沒有返回新的fetchData函數地址,又因為在子組件中使用useEffect對fetchData監聽時,所以子組件不會發起請求。但是,點擊按鈕other+1時,子組件雖然沒發起請求,但是還是刷新了,這是什么原因呢?這是因為子組件直接在父組件中掛載,沒有做過任何優化,當父組件重新渲染時,會導致子組件也跟著渲染。所以單純的使用useCallback可以監聽到相應變化,使得子組件做出變化,但是并不能優化性能。所以當我們不用監聽某個狀態使得函數發生改變時,不要輕易使用useCallback,因為使用 useCallBack后每次執行到這里內部比對是否變化,還有存一下之前的函數,消耗更大了。

場景三:優化上述問題,搭配React.memo使用

import React, { useCallback, useState, useEffect } from 'react';
import './App.css';

function App() {
  const [query, setQuery] = useState(1);
  const [queryOther, setQueryOther] = useState(1);
  const fecthData = useCallback(() => {
    console.log('新的fetch');
    return query;
  }, [query])
  const add = () => {
    console.log('點擊add');
    setQuery(query + 1);
  }
  const addOther = () => {
    console.log('點擊addOther');
    setQueryOther(queryOther + 1);
  }
  return (
    <>
      <Child fecthData={fecthData} />
      <button onClick={add}>+1</button>
      <button onClick={addOther}>other+1</button>
      <div>{ query }</div>  ,,m

const Child = React.memo(({ fecthData }: { fecthData: any }) => {
  console.log('子組件相關內容');
  useEffect(() => {
    const querN = fecthData();
    console.log('子組件調用該函數獲取到相關內容', querN);
  }, [fecthData])
  return <div>
    123
  </div>
})


export default App;

初始狀態:
在這里插入圖片描述
點擊按鈕:
在這里插入圖片描述
一切問題都解決了。點擊other+1按鈕,沒有使得子組件發起請求,也沒有使得子組件因為這個無關變量的變化,導致重新渲染。

原因分析:

  • 使用useCallback使得無關變量變化時,阻止了新創建的fetchData的新地址返回,傳給子組件的還是原本的函數地址(useCallBack的本質工作不是在依賴不變的情況下阻止函數創建,而是在依賴不變的情況下不返回新的函數地址而返回舊的函數地址)
  • React.memo 這個方法,此方法內會對 props 做一個淺層比較,如果如果 props 沒有發生改變(useCallback的存在使得props沒變化),則不會重新渲染此組件。

場景四:單純使用React.memo會發生什么

import React, { useCallback, useState, useEffect } from 'react';
import './App.css';

function App() {
  const [query, setQuery] = useState(1);
  const [queryOther, setQueryOther] = useState(1);
  const fecthData = () => {
    console.log('新的fetch');
    return query;
  }
  const add = () => {
    console.log('點擊add');
    setQuery(query + 1);
  }
  const addOther = () => {
    console.log('點擊addOther');
    setQueryOther(queryOther + 1);
  }
  return (
    <>
      <Child fecthData={fecthData} />
      <button onClick={add}>+1</button>
      <button onClick={addOther}>other+1</button>
      <div>{ query }</div>
    </>
  );
}

const Child = React.memo(({ fecthData }: { fecthData: any }) => {
  console.log('子組件相關內容');
  useEffect(() => {
    const querN = fecthData();
    console.log('子組件調用該函數獲取到相關內容', querN);
  }, [fecthData])
  return <div>
    123
  </div>
})


export default App;

初始狀態:
在這里插入圖片描述
點擊按鈕
在這里插入圖片描述
React.memo檢測的是props中數據的棧地址是否改變。而父組件重新構建的時候,會重新構建父組件中的所有函數(舊函數銷毀,新函數創建,等于更新了函數地址),新的函數地址傳入到子組件中被props檢測到棧地址更新。也就引發了子組件的重新渲染。所以,在上面的代碼示例里面,子組件是要被重新渲染的。上文中的fetchData因為失去了useCallback的保護使得子組件的props發生了變化,從而React.memo也失去了作用,而且因為fetchData因為失去了useCallback的保護,使得點擊other+1按鈕改變無關的變量時,子組件也調用了請求函數。

3.useCallback使用總結:

  • 可以使用useCallback可以監聽到相應狀態變化,使得父/子組件做出響應。
  • 但是濫用useCallback會影響性能,需搭配React.memo進行使用,否則適得其反。

原文鏈接:https://blog.csdn.net/qq_44742090/article/details/131525420

  • 上一篇:沒有了
  • 下一篇:沒有了
欄目分類
最近更新