欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React?useEffect異步操作常見問題小結

 更新時間:2022年06月29日 10:57:41   作者:汪鴻的好朋友  
本文主要介紹了React?useEffect異步操作常見問題小結,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

useEffect 和異步任務搭配使用的時候會遇到的一些坑總結。

三個常見的問題:

1、如何在組件加載的時候發(fā)起異步任務

2、如何在組件交互的時候發(fā)起異步任務

3、其他陷阱

一、react hooks發(fā)異步請求

1、使用useEffect發(fā)起異步任務,第二個參數使用空數組可以實現組件加載的時候執(zhí)行方法體,返回值函數在組件卸載時執(zhí)行一次,用來清理一些東西。

2、使用 AbortController 或者某些庫自帶的信號量 ( axios.CancelToken) 來控制中止請求,更加優(yōu)雅地退出

3、當需要在其他地方(例如點擊處理函數中)設定定時器,在useEffect返回值中清理時,使用局部變量或者useRef來記錄這個timer,不要使用useState.

4、組件中出現setTimeout等閉包時,盡量在閉包內部引用ref而不是state,否則容易出現讀取到舊的值

5、useState返回的更新狀態(tài)方法是異步的,要在下次重繪的時候才能獲取新的值。不要試圖在更改狀態(tài)之后立馬獲取狀態(tài)。

二、如何在組件加載的時候發(fā)起異步任務

這類需求非常常見,典型的例子是在列表組件加載的時候發(fā)送請求到后端,獲取列表展現。

import React, { useState, useEffect } from 'react';
  const SOME_API = '/api/get/value';
  export const MyComponent: React.FC<{}> = => {
  const [loading, setLoading] = useState(true);
  const [value, setValue] = useState(0);
  useEffect( => {
    (async => {
      const res = await fetch(SOME_API);
      const data = await res.json;
      setValue(data.value);
      setLoading(false);
    });
  }, []);
  return (
    <>
      {
        loading ? (
        <h2>Loading...</h2>
        ) : (
        <h2>value is {value}</h2>
        )
      }
    </>
  );
} 

如上是一個基礎的帶 Loading 功能的組件,會發(fā)送異步請求到后端獲取一個值并顯示到頁面上。如果以示例的標準來說已經足夠,但要實際運用到項目中,還不得不考慮幾個問題

三、如果在響應回來之前組件被銷毀了會怎樣?

這時候React會報一個??警告信息:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subions and asynchronous tasks in a useEffect cleanup http://function.in Notification 

就是說一個組件卸載之后不應該再修改他的狀態(tài)。雖然不影響運行,但是這種問題不應該存在。那么如何解決?

問題的核心在于,在組件卸載后依然調用了 setValue(data.value)和 setLoading(false)來更改狀態(tài)。因此一個簡單的辦法是標記一下組件有沒有被卸載,可以利用 useEffect的返回值。

// 省略組件其他內容,只列出 diff
useEffect( => {
  let isUnmounted = false;
  (async => {
    const res = await fetch(SOME_API);
    const data = await res.json;
    if (!isUnmounted) {
      setValue(data.value);
      setLoading(false);
    }
  });
  return => {
  isUnmounted = true;
  }
}, []); 

這樣可以順利避免這個 Warning。

有沒有更加優(yōu)雅的解法?

上述做法是在收到響應時進行判斷,即無論如何需要等響應完成,略顯被動。一個更加主動的方式是探知到卸載時直接中斷請求,自然也不必再等待響應了。這種主動方案需要用到 AbortController。

AbortController 是一個瀏覽器的實驗接口,它可以返回一個信號量(singal) ,從而中止發(fā)送的請求。這個接口的兼容性不錯,除了 IE 之外全都兼容(如 Chrome, Edge, FF 和絕大部分移動瀏覽器,包括 Safari)。

useEffect( => {
  let isUnmounted = false;
  const abortController = new AbortController; // 創(chuàng)建
  (async => {
    const res = await fetch(SOME_API, {
    singal: abortController.singal, // 當做信號量傳入
    });
    const data = await res.json;
    if (!isUnmounted) {
      setValue(data.value);
      setLoading(false);
    }
  });
  return => {
    isUnmounted = true;
    abortController.abort; // 在組件卸載時中斷
  }
}, []); 

singal 的實現依賴于實際發(fā)送請求使用的方法,如上述例子的 fetch方法接受 singal屬性。如果使用的是 axios,它的內部已經包含了 axios.CancelToken,可以直接使用,例子在這里。

import React, { Component } from 'react';
import axios from 'axios';
?
class Example extends Component {
  signal = axios.CancelToken.source();
?
  state = {
    isLoading: false,
    user: {},
  }
  
  componentDidMount() {
    this.onLoadUser();
  }
  
  componentWillUnmount() {
    this.signal.cancel('Api is being canceled');
  }
  
  onLoadUser = async () => {
    try {
      this.setState({ isLoading: true });
      const response = await axios.get('https://randomuser.me/api/', {
        cancelToken: this.signal.token,
      })
      this.setState({ user: response.data, isLoading: true });
    } catch (err) {
      if (axios.isCancel(err)) {
        console.log('Error: ', err.message); // => prints: Api is being canceled
      } else {
        this.setState({ isLoading: false });
      }
    }
   } 
    render() {
      return (
        <div>
          <pre>{JSON.stringify(this.state.user, null, 2)}</pre>
        </div>
      )
    }
 
} 

四、如何在組件交互時發(fā)起異步任務

另一種常見的需求是要在組件交互(比如點擊某個按鈕)時發(fā)送請求或者開啟計時器,待收到響應后修改數據進而影響頁面。這里和上面一節(jié)(組件加載時)最大的差異在于 React Hooks 只能在組件級別編寫,不能在方法(dealClick)或者控制邏輯(if, for 等)內部編寫,所以不能在點擊的響應函數中再去調用 useEffect。但我們依然要利用 useEffect 的返回函數來做清理工作。

以計時器為例,假設我們想做一個組件,點擊按鈕后開啟一個計時器(5s),計時器結束后修改狀態(tài)。但如果在計時未到就銷毀組件時,我們想停止這個計時器,避免內存泄露。用代碼實現的話,會發(fā)現開啟計時器和清理計時器會在不同的地方,因此就必須記錄這個 timer??慈缦碌睦樱?/p>

import React, { useState, useEffect } from 'react';
 
export const MyComponent: React.FC<{}> = () => {
    const [value, setValue] = useState(0);
 
    let timer: number;
 
    useEffect(() => {
        // timer 需要在點擊時建立,因此這里只做清理使用
        return () => {
            console.log('in useEffect return', timer); // <- 正確的值
            window.clearTimeout(timer);
        }
    }, []);
 
    function dealClick() {
        timer = window.setTimeout(() => {
            setValue(100);
        }, 5000);
    }
 
    return (
        <>
            Value is {value}
            <button onClick={dealClick}>Click Me!</button>
        </>
    );
} 

既然要記錄 timer,自然是用一個內部變量來存儲即可(暫不考慮連續(xù)點擊按鈕導致多個 timer 出現,假設只點一次。因為實際情況下點了按鈕還會觸發(fā)其他狀態(tài)變化,繼而界面變化,也就點不到了)。

這里需要注意的是,如果把 timer 升級為狀態(tài)(state) ,則代碼反而會出現問題??紤]如下代碼:

import React, { useState, useEffect } from 'react';
 
export const MyComponent: React.FC<{}> = () => {
    const [value, setValue] = useState(0);
    const [timer, setTimer] = useState(0); // 把 timer 升級為狀態(tài)
 
    useEffect(() => {
        // timer 需要在點擊時建立,因此這里只做清理使用
        return () => {
            console.log('in useEffect return', timer); // <- 0
            window.clearTimeout(timer);
        }
    }, []);
 
    function dealClick() {
        let tmp = window.setTimeout(() => {
            setValue(100);
        }, 5000);
        setTimer(tmp);
    }
 
    return (
        <>
             Value is {value}
            <button onClick={dealClick}>Click Me!</button>
        </>
    );
} 

有關語義上 timer 到底算不算作組件的狀態(tài)我們先拋開不談,僅就代碼層面來看。利用 useState 來記住 timer 狀態(tài),利用 setTimer 去更改狀態(tài),看似合理。但實際運行下來,在 useEffect 返回的清理函數中,得到的 timer 卻是初始值,即 0。

為什么兩種寫法會有差異呢?

其核心在于寫入的變量和讀取的變量是否是同一個變量。

第一種寫法: 代碼是把 timer 作為組件內的局部變量使用。在初次渲染組件時,useEffect 返回的閉包函數中指向了這個局部變量 timer。在 dealClick 中設置計時器時返回值依舊寫給了這個局部變量(即讀和寫都是同一個變量),因此在后續(xù)卸載時,雖然組件重新運行導致出現一個_新的_局部變量 timer,但這不影響閉包內_老的_ timer,所以結果是正確的。

第二種寫法: timer 是一個 useState 的返回值,并不是一個簡單的變量。從 React Hooks 的源碼來看,它返回的是 [hook.memorizedState, dispatch],對應我們接的值和變更方法。當調用 setTimer 和 setValue 時,分別觸發(fā)兩次重繪,使得 hook.memorizedState 指向了 newState(注意:不是修改,而是重新指向)。但 useEffect 返回閉包中的 timer 依然指向舊的狀態(tài),從而得不到新的值。(即讀的是舊值,但寫的是新值,不是同一個)

如果覺得閱讀 Hooks 源碼有困難,可以從另一個角度去理解:雖然 React 在 16.8 推出了 Hooks,但實際上只是加強了函數式組件的寫法,使之擁有狀態(tài),用來作為類組件的一種替代,但 React 狀態(tài)的內部機制沒有變化。在 React 中 setState 內部是通過 merge 操作將新狀態(tài)和老狀態(tài)合并后,重新返回一個_新的_狀態(tài)對象。不論 Hooks 寫法如何,這條原理沒有變化?,F在閉包內指向了舊的狀態(tài)對象,而 setTimer 和 setValue 重新生成并指向了新的狀態(tài)對象,并不影響閉包,導致了閉包讀不到新的狀態(tài)。

我們注意到 React 還提供給我們一個 useRef, 它的定義是:

useRef 返回一個可變的 ref 對象,其 current 屬性被初始化為傳入的參數(initialValue)。返回的 ref 對象在組件的整個生命周期內保持不變。

ref 對象可以確保在整個生命周期中值不變,且同步更新,是因為 ref 的返回值始終只有一個實例,所有讀寫都指向它自己。所以也可以用來解決這里的問題。

import React, { useState, useEffect, useRef } from 'react';
 
export const MyComponent: React.FC<{}> = () => {
    const [value, setValue] = useState(0);
    const timer = useRef(0);
 
    useEffect(() => {
        // timer 需要在點擊時建立,因此這里只做清理使用
        return () => {
            window.clearTimeout(timer.current);
        }
    }, []);
 
    function dealClick() {
        timer.current = window.setTimeout(() => {
            setValue(100);
        }, 5000);
    }
 
    return (
        <>
              Value is {value}
            <button onClick={dealClick}>Click Me!</button>
        </>
    );
} 

事實上我們后面會看到,useRef 和異步任務配合更加安全穩(wěn)妥。

五、其他陷阱

修改狀態(tài)是異步的

//錯誤例子
import React, { useState } from 'react';
 
export const MyComponent: React.FC<{}> = () => {
    const [value, setValue] = useState(0);
 
    function dealClick() {
        setValue(100);
        console.log(value); // <- 0
    }
 
    return (
             Value is {value}, AnotherValue is {anotherValue}
    );
} 

useState 返回的修改函數是異步的,調用后并不會直接生效,因此立馬讀取 value 獲取到的是舊值(0)。

React 這樣設計的目的是為了性能考慮,爭取把所有狀態(tài)改變后只重繪一次就能解決更新問題,而不是改一次重繪一次,也是很容易理解的。

在 timeout 中讀不到其他狀態(tài)的新值

//錯誤例子
import React, { useState, useEffect } from 'react';
 
export const MyComponent: React.FC<{}> = () => {
    const [value, setValue] = useState(0);
    const [anotherValue, setAnotherValue] = useState(0);
 
    useEffect(() => {
        window.setTimeout(() => {
            console.log('setAnotherValue', value) // <- 0
            setAnotherValue(value);
        }, 1000);
        setValue(100);
    }, []);
 
    return (
             Value is {value}, AnotherValue is {anotherValue}
    );
} 

這個問題和上面使用 useState 去記錄 timer 類似,在生成 timeout 閉包時,value 的值是 0。雖然之后通過 setValue 修改了狀態(tài),但 React 內部已經指向了新的變量,而舊的變量仍被閉包引用,所以閉包拿到的依然是舊的初始值,也就是 0。

要修正這個問題,也依然是使用 useRef,如下:

import React, { useState, useEffect, useRef } from 'react';
 
export const MyComponent: React.FC<{}> = () => {
    const [value, setValue] = useState(0);
    const [anotherValue, setAnotherValue] = useState(0);
    const valueRef = useRef(value);
    valueRef.current = value;
 
    useEffect(() => {
        window.setTimeout(() => {
            console.log('setAnotherValue', valueRef.current) // <- 100
            setAnotherValue(valueRef.current);
        }, 1000);
        setValue(100);
    }, []);
 
    return (
             Value is {value}, AnotherValue is {anotherValue}
    );
} 

還是 timeout 的問題

假設我們要實現一個按鈕,默認顯示 false。當點擊后更改為 true,但兩秒后變回 false( true 和 false 可以互換)??紤]如下代碼:

import React, { useState } from 'react';
 
export const MyComponent: React.FC<{}> = () => {
    const [flag, setFlag] = useState(false);
 
    function dealClick() {
        setFlag(!flag);
 
        setTimeout(() => {
            setFlag(!flag);
        }, 2000);
    }
 
    return (
        <button onClick={dealClick}>{flag ? "true" : "false"}</button>
    );
} 

我們會發(fā)現點擊時能夠正常切換,但是兩秒后并不會變回來。究其原因,依然在于 useState 的更新是重新指向新值,但 timeout 的閉包依然指向了舊值。所以在例子中,flag 一直是 false,雖然后續(xù) setFlag(!flag),但依然沒有影響到 timeout 里面的 flag。

解決方法有二。

第一個還是利用 useRef

import React, { useState, useRef } from 'react';
 
export const MyComponent: React.FC<{}> = () => {
    const [flag, setFlag] = useState(false);
    const flagRef = useRef(flag);
    flagRef.current = flag;
 
    function dealClick() {
        setFlag(!flagRef.current);
 
        setTimeout(() => {
            setFlag(!flagRef.current);
        }, 2000);
    }
 
    return (
        <button onClick={dealClick}>{flag ? "true" : "false"}</button>
    );
} 

第二個是利用 setFlag 可以接收函數作為參數,并利用閉包和參數來實現(函數式更新)

import React, { useState } from 'react';
 
export const MyComponent: React.FC<{}> = () => {
    const [flag, setFlag] = useState(false);
 
    function dealClick() {
        setFlag(!flag);
 
        setTimeout(() => {
            setFlag(flag => !flag);
        }, 2000);
    }
 
    return (
        <button onClick={dealClick}>{flag ? "true" : "false"}</button>
    );
} 

當 setFlag 參數為函數類型時,這個函數的意義是告訴 React 如何從當前狀態(tài)產生出新的狀態(tài)(類似于 redux 的 reducer,不過是只針對一個狀態(tài)的子 reducer)。既然是當前狀態(tài),因此返回值取反,就能夠實現效果。

總結

在 Hook 中出現異步任務尤其是 timeout 的時候,我們要格外注意。useState 只能保證多次重繪之間的狀態(tài)值是一樣的,但不保證它們就是同一個對象,因此出現閉包引用的時候,盡量使用 useRef 而不是直接使用 state 本身,否則就容易踩坑。反之如果的確碰到了設置了新值但讀取到舊值的情況,也可以往這個方向想想,可能就是這個原因所致。

到此這篇關于React useEffect異步操作常見問題小結的文章就介紹到這了,更多相關React useEffect異步 內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Component與PureComponent對比解析

    Component與PureComponent對比解析

    這篇文章主要為大家介紹了Component與PureComponent解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-03-03
  • react中使用heatmap.js實現熱力圖的繪制

    react中使用heatmap.js實現熱力圖的繪制

    heatmap.js?是一個用于生成熱力圖的?JavaScript?庫,React?是一個流行的?JavaScript?庫,用于構建用戶界面,本小編給大家介紹了在React?應用程序中使用heatmap.js實現熱力圖的繪制的示例,文中通過代碼示例介紹的非常詳細,需要的朋友可以參考下
    2023-12-12
  • 在react-router4中進行代碼拆分的方法(基于webpack)

    在react-router4中進行代碼拆分的方法(基于webpack)

    這篇文章主要介紹了在react-router4中進行代碼拆分的方法(基于webpack),小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-03-03
  • React之關于Promise的用法

    React之關于Promise的用法

    這篇文章主要介紹了React之關于Promise的用法及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • React項目中使用zustand狀態(tài)管理的實現

    React項目中使用zustand狀態(tài)管理的實現

    zustand是一個用于狀態(tài)管理的小巧而強大的庫,本文主要介紹了React項目中使用zustand狀態(tài)管理的實現,具有一定的參考價值,感興趣的可以了解一下
    2023-10-10
  • 使用React.forwardRef傳遞泛型參數

    使用React.forwardRef傳遞泛型參數

    這篇文章主要介紹了使用React.forwardRef傳遞泛型參數方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • React+Typescript項目環(huán)境搭建并使用redux環(huán)境的詳細過程

    React+Typescript項目環(huán)境搭建并使用redux環(huán)境的詳細過程

    這篇文章主要介紹了React+Typescript項目環(huán)境搭建并使用redux環(huán)境的詳細過程,本文通過圖文實例相結合給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-09-09
  • React獲取Java后臺文件流并下載Excel文件流程解析

    React獲取Java后臺文件流并下載Excel文件流程解析

    這篇文章主要介紹了React獲取Java后臺文件流下載Excel文件,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-06-06
  • React實現頁面狀態(tài)緩存(keep-alive)的示例代碼

    React實現頁面狀態(tài)緩存(keep-alive)的示例代碼

    因為?react、vue都是單頁面應用,路由跳轉時,就會銷毀上一個頁面的組件,但是有些項目不想被銷毀,想保存狀態(tài),本文給大家介紹了React實現頁面狀態(tài)緩存(keep-alive)的代碼示例,需要的朋友可以參考下
    2024-01-01
  • react.js 獲取真實的DOM節(jié)點實例(必看)

    react.js 獲取真實的DOM節(jié)點實例(必看)

    下面小編就為大家?guī)硪黄猺eact.js 獲取真實的DOM節(jié)點實例(必看)。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-04-04

最新評論