亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

react中的watch監(jiān)視屬性-useEffect介紹

 更新時(shí)間:2022年09月14日 17:01:11   作者:前端三腳貓  
這篇文章主要介紹了react中的watch監(jiān)視屬性-useEffect使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

react的watch監(jiān)視屬性-useEffect

在vue中可以使用watch屬性,去監(jiān)視一個(gè)值,當(dāng)這個(gè)值進(jìn)行變化的時(shí)候就去執(zhí)行一些操作。在react是沒有這個(gè)屬性的,但是它也一樣可以達(dá)到相同的效果,那么接下來看看它是怎么實(shí)現(xiàn)的呢?

在react中實(shí)現(xiàn)監(jiān)聽效果有一個(gè)比較簡單的方法,就是使用useEffect 這個(gè)hook,在我們剛接觸這個(gè)hook時(shí)經(jīng)常會(huì)被代入到類組件中的生命周期上,其實(shí)它不光有生命周期的功能更是可以實(shí)現(xiàn)監(jiān)聽的效果。

1、首先我們先來看下useEffect 用來當(dāng)作生命周期時(shí)是怎么使用的,代碼如下:

import React, { useEffect, useState } from 'react'
export default function Wtach() {
  const [num, setNum] = useState(0)
  useEffect(() => {
    console.log('我是初始化的hook')
    return () => {
      console.log('我是卸載的hook')
    }
  }, [])
  useEffect(() => {
    console.log('我是更新的hook')
  })
  return (
    <div>
      <p>{`當(dāng)前數(shù)量是${num}`}</p>
      <button onClick={() => setNum(num + 1)}>增加數(shù)量</button>
    </div>
  )
}

上述代碼中,第一個(gè)useEffect 中傳入兩個(gè)參數(shù)第一個(gè)參數(shù)為一個(gè)callback回調(diào)函數(shù), 第二個(gè)參數(shù)傳入一個(gè)空數(shù)組,callback回調(diào)函數(shù)又返回了一個(gè)用于卸載組件時(shí)調(diào)用的函數(shù)。

他們的調(diào)用時(shí)期分別為初始化加載完成頁面調(diào)用useEffect 傳入的第一個(gè)函數(shù)(此時(shí)對(duì)應(yīng)omponentDidMount生命周期鉤子),在卸載的時(shí)候react會(huì)調(diào)用第一個(gè)callback函數(shù)里面返回的函數(shù)(此時(shí)對(duì)應(yīng)componentWillUnmount生命周期鉤子),這樣以來他們就完全實(shí)現(xiàn)了和生命周期一樣的兩個(gè)功能。

打印如下:

再來看第二個(gè)useEffect ,它只傳入了一個(gè)callback回調(diào)參數(shù),這個(gè)傳入的callback就會(huì)在每次頁面更新完成后進(jìn)行調(diào)用(它對(duì)應(yīng)了componentDidUpdate生命周期鉤子)。

效果如下:

2、上面看完了useEffect 實(shí)現(xiàn)生命周期的功能,下面就來看下它是怎么實(shí)現(xiàn)類似watch屬性的。

代碼如下:

import React, { useEffect, useState } from 'react'
export default function Wtach() {
  const [num, setNum] = useState(0)
  useEffect(() => {
    console.log('我是改變后的num', num)
  }, [num])
  return (
    <div>
      <p>{`當(dāng)前數(shù)量是${num}`}</p>
      <button onClick={() => setNum(num + 1)}>增加數(shù)量</button>
    </div>
  )
}

上述代碼useEffect 中除了正常傳入第一個(gè)callback函數(shù),第二個(gè)參數(shù)傳入一個(gè)數(shù)組里面的值是num,此時(shí)傳入的callback函數(shù)的調(diào)用就依賴了第二個(gè)參數(shù)數(shù)組中num的值,每當(dāng)num的值改變后react就會(huì)去調(diào)用傳入的第一個(gè)callback函數(shù),這樣就實(shí)現(xiàn)了類似watch屬性的功能。

效果如下:

最后如果你想要監(jiān)聽多個(gè)值,不需要寫多個(gè)useEffect ,只需要在第二個(gè)數(shù)組參數(shù)中增加新的值即可,如下:

useEffect使用指南

最近在寫一些React的應(yīng)用,用上了最新的Hooks。Hooks好用,但是對(duì)于剛上手Hooks的小伙伴來說,坑也挺多的。所以決定總結(jié)一下Hooks的使用經(jīng)驗(yàn),從useEffect開始。useEffect用于處理組件中的effect,通常用于請求數(shù)據(jù),事件處理,訂閱等相關(guān)操作。這里以數(shù)據(jù)請求為例,來深入介紹useEffect的用法。

最基本的使用

首先,舉一個(gè)簡單的例子:

import React, { useState } from 'react';
?
function App() {
? const [data, setData] = useState({ hits: [] });
?
? return (
? ? <ul>
? ? ? {data.hits.map(item => (
? ? ? ? <li key={item.objectID}>
? ? ? ? ? <a href={item.url}>{item.title}</a>
? ? ? ? </li>
? ? ? ))}
? ? </ul>
? );
}
?
export default App;

App組件顯示了一個(gè)項(xiàng)目列表,狀態(tài)和狀態(tài)更新函數(shù)來自與useState這個(gè)hooks,通過調(diào)用useState,來創(chuàng)建App組件的內(nèi)部狀態(tài)。初始狀態(tài)是一個(gè)object,其中的hits為一個(gè)空數(shù)組,目前還沒有請求后端的接口。

為了獲取后端提供的數(shù)據(jù),接下來將使用axios來發(fā)起請求,同樣也可以使用fetch,這里會(huì)使用useEffect來隔離副作用。

import React, { useState, useEffect } from 'react';
import axios from 'axios';
?
function App() {
? const [data, setData] = useState({ hits: [] });
?
? useEffect(async () => {
? ? const result = await axios(
? ? ? 'http://localhost/api/v1/search?query=redux',
? ? );
?
? ? setData(result.data);
? });
?
? return (
? ? <ul>
? ? ? {data.hits.map(item => (
? ? ? ? <li key={item.objectID}>
? ? ? ? ? <a href={item.url}>{item.title}</a>
? ? ? ? </li>
? ? ? ))}
? ? </ul>
? );
}
?
export default App;

在useEffect中,不僅會(huì)請求后端的數(shù)據(jù),還會(huì)通過調(diào)用setData來更新本地的狀態(tài),這樣會(huì)觸發(fā)view的更新。

但是,運(yùn)行這個(gè)程序的時(shí)候,會(huì)出現(xiàn)無限循環(huán)的情況。useEffect在組件mount時(shí)執(zhí)行,但也會(huì)在組件更新時(shí)執(zhí)行。因?yàn)槲覀冊诿看握埱髷?shù)據(jù)之后都會(huì)設(shè)置本地的狀態(tài),所以組件會(huì)更新,因此useEffect會(huì)再次執(zhí)行,因此出現(xiàn)了無限循環(huán)的情況。我們只想在組件mount時(shí)請求數(shù)據(jù)。我們可以傳遞一個(gè)空數(shù)組作為useEffect的第二個(gè)參數(shù),這樣就能避免在組件更新執(zhí)行useEffect,只會(huì)在組件mount時(shí)執(zhí)行。

import React, { useState, useEffect } from 'react';
import axios from 'axios';
?
function App() {
? const [data, setData] = useState({ hits: [] });
?
? useEffect(async () => {
? ? const result = await axios(
? ? ? 'http://localhost/api/v1/search?query=redux',
? ? );
?
? ? setData(result.data);
? }, []);
?
? return (
? ? <ul>
? ? ? {data.hits.map(item => (
? ? ? ? <li key={item.objectID}>
? ? ? ? ? <a href={item.url}>{item.title}</a>
? ? ? ? </li>
? ? ? ))}
? ? </ul>
? );
}
?
export default App;

useEffect的第二個(gè)參數(shù)可用于定義其依賴的所有變量。如果其中一個(gè)變量發(fā)生變化,則useEffect會(huì)再次運(yùn)行。如果包含變量的數(shù)組為空,則在更新組件時(shí)useEffect不會(huì)再執(zhí)行,因?yàn)樗粫?huì)監(jiān)聽任何變量的變更。

還有最后一個(gè)問題。在代碼中,我們使用async / await從第三方API獲取數(shù)據(jù)。如果你對(duì)async/await熟悉的話,你會(huì)知道,每個(gè)async函數(shù)都會(huì)默認(rèn)返回一個(gè)隱式的promise。但是,useEffect不應(yīng)該返回任何內(nèi)容。這就是為什么會(huì)在控制臺(tái)日志中看到以下警告:

Warning: useEffect function must return a cleanup function or nothing. Promises and useEffect(async () => …) are not supported, but you can call an async function inside an effect

這就是為什么不能直接在useEffect中使用async函數(shù),因此,我們可以不直接調(diào)用async函數(shù),而是像下面這樣:

import React, { useState, useEffect } from 'react';
import axios from 'axios';
?
function App() {
? const [data, setData] = useState({ hits: [] });
?
? useEffect(() => {
? ? const fetchData = async () => {
? ? ? const result = await axios(
? ? ? ? 'http://localhost/api/v1/search?query=redux',
? ? ? );
?
? ? ? setData(result.data);
? ? };
?
? ? fetchData();
? }, []);
?
? return (
? ? <ul>
? ? ? {data.hits.map(item => (
? ? ? ? <li key={item.objectID}>
? ? ? ? ? <a href={item.url}>{item.title}</a>
? ? ? ? </li>
? ? ? ))}
? ? </ul>
? );
}
?
export default App;

響應(yīng)更新

上面的例子中,我們實(shí)現(xiàn)了再組件mount時(shí)請求數(shù)據(jù)。但是很多情況下,我們需要響應(yīng)用戶的輸入,然后再請求。這個(gè)時(shí)候我們會(huì)引入一個(gè)input框,監(jiān)聽query值的變化:

import axios from 'axios';
?
function App() {
? const [data, setData] = useState({ hits: [] });
? const [query, setQuery] = useState('redux');
?
? useEffect(() => {
? ? const fetchData = async () => {
? ? ? const result = await axios(
? ? ? ? 'http://localhost/api/v1/search?query=redux',
? ? ? );
?
? ? ? setData(result.data);
? ? };
?
? ? fetchData();
? }, []);
?
? return (
? ? <Fragment>
? ? ? <input
? ? ? ? type="text"
? ? ? ? value={query}
? ? ? ? onChange={event => setQuery(event.target.value)}
? ? ? />
? ? ? <ul>
? ? ? ? {data.hits.map(item => (
? ? ? ? ? <li key={item.objectID}>
? ? ? ? ? ? <a href={item.url}>{item.title}</a>
? ? ? ? ? </li>
? ? ? ? ))}
? ? ? </ul>
? ? </Fragment>
? );
}
?
export default App;

有個(gè)query值,已經(jīng)更新query的邏輯,還需要將這個(gè)query值傳遞給后臺(tái),這個(gè)操作會(huì)在useEffect中進(jìn)行:

function App() {
? const [data, setData] = useState({ hits: [] });
? const [query, setQuery] = useState('redux');
?
? useEffect(() => {
? ? const fetchData = async () => {
? ? ? const result = await axios(
? ? ? ? `http://localhost/api/v1/search?query=${query}`,
? ? ? );
?
? ? ? setData(result.data);
? ? };
?
? ? fetchData();
? }, []);
?
? return (
? ? ...
? );
}
?
export default App;

前面我們說了,目前的useEffect只會(huì)在組件mount時(shí)執(zhí)行,并且useEffect的第二個(gè)參數(shù)是依賴的變量,一旦這個(gè)依賴的變量變動(dòng),useEffect就會(huì)重新執(zhí)行,所以我們需要添加query為useEffect的依賴:

function App() {
? const [data, setData] = useState({ hits: [] });
? const [query, setQuery] = useState('redux');
?
? useEffect(() => {
? ? const fetchData = async () => {
? ? ? const result = await axios(
? ? ? ? `http://localhost/api/v1/search?query=${query}`,
? ? ? );
?
? ? ? setData(result.data);
? ? };
?
? ? fetchData();
? }, [query]);
?
? return (
? ? ...
? );
}
?
export default App;

一旦更改了query值,就可以重新獲取數(shù)據(jù)。但這會(huì)帶來另一個(gè)問題:query的任何一次變動(dòng)都會(huì)請求后端,這樣會(huì)帶來比較大的訪問壓力。這個(gè)時(shí)候我們需要引入一個(gè)按鈕,點(diǎn)擊這個(gè)按鈕再發(fā)起請求

function App() {
? const [data, setData] = useState({ hits: [] });
? const [query, setQuery] = useState('redux');
? const [search, setSearch] = useState('');
?
? useEffect(() => {
? ? const fetchData = async () => {
? ? ? const result = await axios(
? ? ? ? `http://localhost/api/v1/search?query=${query}`,
? ? ? );
?
? ? ? setData(result.data);
? ? };
?
? ? fetchData();
? }, [query]);
?
? return (
? ? <Fragment>
? ? ? <input
? ? ? ? type="text"
? ? ? ? value={query}
? ? ? ? onChange={event => setQuery(event.target.value)}
? ? ? />
? ? ? <button type="button" onClick={() => setSearch(query)}>
? ? ? ? Search
? ? ? </button>
?
? ? ? <ul>
? ? ? ? {data.hits.map(item => (
? ? ? ? ? <li key={item.objectID}>
? ? ? ? ? ? <a href={item.url}>{item.title}</a>
? ? ? ? ? </li>
? ? ? ? ))}
? ? ? </ul>
? ? </Fragment>
? );
}

可以看到上面我們添加了一個(gè)新的按鈕,然后創(chuàng)建新的組件state:search。每次點(diǎn)擊按鈕時(shí),會(huì)把search的值設(shè)置為query,這個(gè)時(shí)候我們需要修改useEffect中的依賴項(xiàng)為search,這樣每次點(diǎn)擊按鈕,search值變更,useEffect就會(huì)重新執(zhí)行,避免不必要的變更:

function App() {
? const [data, setData] = useState({ hits: [] });
? const [query, setQuery] = useState('redux');
? const [search, setSearch] = useState('redux');
?
? useEffect(() => {
? ? const fetchData = async () => {
? ? ? const result = await axios(
? ? ? ? `http://localhost/api/v1/search?query=${search}`,
? ? ? );
?
? ? ? setData(result.data);
? ? };
?
? ? fetchData();
? }, [search]);
?
? return (
? ? ...
? );
}
?
export default App;

此外,search state的初始狀態(tài)設(shè)置為與query state 相同的狀態(tài),因?yàn)榻M件首先會(huì)在mount時(shí)獲取數(shù)據(jù)。所以簡單點(diǎn),直接將的要請求的后端URL設(shè)置為search state的初始值。

function App() {
? const [data, setData] = useState({ hits: [] });
? const [query, setQuery] = useState('redux');
? const [url, setUrl] = useState(
? ? 'http://localhost/api/v1/search?query=redux',
? );
?
? useEffect(() => {
? ? const fetchData = async () => {
? ? ? const result = await axios(url);
?
? ? ? setData(result.data);
? ? };
?
? ? fetchData();
? }, [url]);
?
? return (
? ? <Fragment>
? ? ? <input
? ? ? ? type="text"
? ? ? ? value={query}
? ? ? ? onChange={event => setQuery(event.target.value)}
? ? ? />
? ? ? <button
? ? ? ? type="button"
? ? ? ? onClick={() =>
? ? ? ? ? setUrl(`http://localhost/api/v1/search?query=${query}`)
? ? ? ? }
? ? ? >
? ? ? ? Search
? ? ? </button>
? ? <ul>
? ? ? ? {data.hits.map(item => (
? ? ? ? ? <li key={item.objectID}>
? ? ? ? ? ? <a href={item.url}>{item.title}</a>
? ? ? ? ? </li>
? ? ? ? ))}
? ? ? </ul>
? ? </Fragment>
? );
}

如何處理Loading和Error

良好的用戶體驗(yàn)是需要在請求后端數(shù)據(jù),數(shù)據(jù)還沒有返回時(shí)展現(xiàn)loading的狀態(tài),因此,我們還需要添加一個(gè)loading的state

import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';
?
function App() {
? const [data, setData] = useState({ hits: [] });
? const [query, setQuery] = useState('redux');
? const [url, setUrl] = useState(
? ? 'http://hn.algolia.com/api/v1/search?query=redux',
? );
? const [isLoading, setIsLoading] = useState(false);
?
? useEffect(() => {
? ? const fetchData = async () => {
? ? ? setIsLoading(true);
?
? ? ? const result = await axios(url);
?
? ? ? setData(result.data);
? ? ? setIsLoading(false);
? ? };
?
? ? fetchData();
? }, [url]);
? return (
? ? <Fragment>
? ? ? <input
? ? ? ? type="text"
? ? ? ? value={query}
? ? ? ? onChange={event => setQuery(event.target.value)}
? ? ? />
? ? ? <button
? ? ? ? type="button"
? ? ? ? onClick={() =>
? ? ? ? ? setUrl(`http://localhost/api/v1/search?query=${query}`)
? ? ? ? }
? ? ? >
? ? ? ? Search
? ? ? </button>
?
? ? ? {isLoading ? (
? ? ? ? <div>Loading ...</div>
? ? ? ) : (
? ? ? ? <ul>
? ? ? ? ? {data.hits.map(item => (
? ? ? ? ? ? <li key={item.objectID}>
? ? ? ? ? ? ? <a href={item.url}>{item.title}</a>
? ? ? ? ? ? </li>
? ? ? ? ? ))}
? ? ? ? </ul>
? ? ? )}
? ? </Fragment>
? );
}
?
export default App;

在useEffect中,請求數(shù)據(jù)前將loading置為true,在請求完成后,將loading置為false。我們可以看到useEffect的依賴數(shù)據(jù)中并沒有添加loading,這是因?yàn)椋覀儾恍枰賚oading變更時(shí)重新調(diào)用useEffect。請記?。褐挥心硞€(gè)變量更新后,需要重新執(zhí)行useEffect的情況,才需要將該變量添加到useEffect的依賴數(shù)組中。

loading處理完成后,還需要處理錯(cuò)誤,這里的邏輯是一樣的,使用useState來創(chuàng)建一個(gè)新的state,然后在useEffect中特定的位置來更新這個(gè)state。由于我們使用了async/await,可以使用一個(gè)大大的try-catch:

import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';
?
function App() {
? const [data, setData] = useState({ hits: [] });
? const [query, setQuery] = useState('redux');
? const [url, setUrl] = useState(
? ? 'http://localhost/api/v1/search?query=redux',
? );
? const [isLoading, setIsLoading] = useState(false);
? const [isError, setIsError] = useState(false);
?
? useEffect(() => {
? ? const fetchData = async () => {
? ? ? setIsError(false);
? ? ? setIsLoading(true);
?
? ? ? try {
? ? ? ? const result = await axios(url);
?
? ? ? ? setData(result.data);
? ? ? } catch (error) {
? ? ? ? setIsError(true);
? ? ? }
?
? ? ? setIsLoading(false);
? ? };
?
? ? fetchData();
? }, [url]);
? return (
? ? <Fragment>
? ? ? <input
? ? ? ? type="text"
? ? ? ? value={query}
? ? ? ? onChange={event => setQuery(event.target.value)}
? ? ? />
? ? ? <button
? ? ? ? type="button"
? ? ? ? onClick={() =>
? ? ? ? ? setUrl(`http://localhost/api/v1/search?query=${query}`)
? ? ? ? }
? ? ? >
? ? ? ? Search
? ? ? </button>
?
? ? ? {isError && <div>Something went wrong ...</div>}
?
? ? ? {isLoading ? (
? ? ? ? <div>Loading ...</div>
? ? ? ) : (
? ? ? ? <ul>
? ? ? ? ? {data.hits.map(item => (
? ? ? ? ? ? <li key={item.objectID}>
? ? ? ? ? ? ? <a href={item.url}>{item.title}</a>
? ? ? ? ? ? </li>
? ? ? ? ? ))}
? ? ? ? </ul>
? ? ? )}
? ? </Fragment>
? );
}
?
export default App;

每次useEffect執(zhí)行時(shí),將會(huì)重置error;在出現(xiàn)錯(cuò)誤的時(shí)候,將error置為true;在正常請求完成后,將error置為false。

處理表單

通常,我們不僅會(huì)用到上面的輸入框和按鈕,更多的時(shí)候是一張表單,所以也可以在表單中使用useEffect來處理數(shù)據(jù)請求,邏輯是相同的:

function App() {
? ...
?
? return (
? ? <Fragment>
? ? ? <form
? ? ? ? onSubmit={() =>
? ? ? ? ? setUrl(`http://localhost/api/v1/search?query=${query}`)
? ? ? ? }
? ? ? >
? ? ? ? <input
? ? ? ? ? type="text"
? ? ? ? ? value={query}
? ? ? ? ? onChange={event => setQuery(event.target.value)}
? ? ? ? />
? ? ? ? <button type="submit">Search</button>
? ? ? </form>
?
? ? ? {isError && <div>Something went wrong ...</div>}
?
? ? ? ...
? ? </Fragment>
? );
}

上面的例子中,提交表單的時(shí)候,會(huì)觸發(fā)頁面刷新;就像通常的做法那樣,還需要阻止默認(rèn)事件,來阻止頁面的刷新。

function App() {
? ...
?
? const doFetch = () => {
? ? setUrl(`http://localhost/api/v1/search?query=${query}`);
? };
?
? return (
? ? <Fragment>
? ? ? <form onSubmit={event => {
? ? ? ? doFetch();
?
? ? ? ? event.preventDefault();
? ? ? }}>
? ? ? ? <input
? ? ? ? ? type="text"
? ? ? ? ? value={query}
? ? ? ? ? onChange={event => setQuery(event.target.value)}
? ? ? ? />
? ? ? ? <button type="submit">Search</button>
? ? ? </form>
?
? ? ? {isError && <div>Something went wrong ...</div>}
?
? ? ? ...
? ? </Fragment>
? );
}

自定義hooks

我們可以看到上面的組件,添加了一系列hooks和邏輯之后,已經(jīng)變得非常的龐大。而hooks的一個(gè)非常的優(yōu)勢,就是能夠很方便的提取自定義的hooks。這個(gè)時(shí)候,我們就能把上面的一大堆邏輯抽取到一個(gè)單獨(dú)的hooks中,方便復(fù)用和解耦:

function useFetchApi = () => {
? const [data, setData] = useState({ hits: [] });
? const [url, setUrl] = useState(
? ? 'http://localhost/api/v1/search?query=redux',
? );
? const [isLoading, setIsLoading] = useState(false);
? const [isError, setIsError] = useState(false);
?
? useEffect(() => {
? ? const fetchData = async () => {
? ? ? setIsError(false);
? ? ? setIsLoading(true);
?
? ? ? try {
? ? ? ? const result = await axios(url);
?
? ? ? ? setData(result.data);
? ? ? } catch (error) {
? ? ? ? setIsError(true);
? ? ? }
?
? ? ? setIsLoading(false);
? ? };
?
? ? fetchData();
? }, [url]);
?
? const doFetch = () => {
? ? setUrl(`http://localhost/api/v1/search?query=${query}`);
? };
?
? return { data, isLoading, isError, doFetch };
}

在自定義的hooks抽離完成后,引入到組件中

function App() {
? const [query, setQuery] = useState('redux');
? const { data, isLoading, isError, doFetch } = useHackerNewsApi();
?
? return (
? ? <Fragment>
? ? ? ...
? ? </Fragment>
? );
}

然后我們需要在form組件中設(shè)定初始的后端URL

const useHackerNewsApi = () => {
? ...
?
? useEffect(
? ? ...
? );
?
? const doFetch = url => {
? ? setUrl(url);
? };
?
? return { data, isLoading, isError, doFetch };
};
?
function App() {
? const [query, setQuery] = useState('redux');
? const { data, isLoading, isError, doFetch } = useHackerNewsApi();
?
? return (
? ? <Fragment>
? ? ? <form
? ? ? ? onSubmit={event => {
? ? ? ? ? doFetch(
? ? ? ? ? ? `http://localhost/api/v1/search?query=${query}`,
? ? ? ? ? );
?
? ? ? ? ? event.preventDefault();
? ? ? ? }}
? ? ? >
? ? ? ? <input
? ? ? ? ? type="text"
? ? ? ? ? value={query}
? ? ? ? ? onChange={event => setQuery(event.target.value)}
? ? ? ? />
? ? ? ? <button type="submit">Search</button>
? ? ? </form>
?
? ? ? ...
? ? </Fragment>
? );
}

使用useReducer整合邏輯

到目前為止,我們已經(jīng)使用了各種state hooks來管理數(shù)據(jù),包括loading、error、data等狀態(tài)。但是我們可以看到,這三個(gè)有關(guān)聯(lián)的狀態(tài)確是分散的,它們通過分離的useState來創(chuàng)建,為了有關(guān)聯(lián)的狀態(tài)整合到一起,我們需要用到useReducer。

如果你寫過redux,那么將會(huì)對(duì)useReducer非常的熟悉,可以把它理解為一個(gè)輕量額redux。useReducer 返回一個(gè)狀態(tài)對(duì)象和一個(gè)可以改變狀態(tài)對(duì)象的dispatch函數(shù)。跟redux類似的,dispatch函數(shù)接受action作為參數(shù),action包含type和payload屬性。我們看一個(gè)簡單的例子吧:

import React, {
? Fragment,
? useState,
? useEffect,
? useReducer,
} from 'react';
import axios from 'axios';
?
const dataFetchReducer = (state, action) => {
? ...
};
?
const useDataApi = (initialUrl, initialData) => {
? const [url, setUrl] = useState(initialUrl);
?
? const [state, dispatch] = useReducer(dataFetchReducer, {
? ? isLoading: false,
? ? isError: false,
? ? data: initialData,
? });
?
? ...
};

useReducer將reducer函數(shù)和初始狀態(tài)對(duì)象作為參數(shù)。在我們的例子中,data,loading和error狀態(tài)的初始值與useState創(chuàng)建時(shí)一致,但它們已經(jīng)整合到一個(gè)由useReducer創(chuàng)建對(duì)象,而不是多個(gè)useState創(chuàng)建的狀態(tài)。

const dataFetchReducer = (state, action) => {
? ...
};
?
const useDataApi = (initialUrl, initialData) => {
? const [url, setUrl] = useState(initialUrl);
?
? const [state, dispatch] = useReducer(dataFetchReducer, {
? ? isLoading: false,
? ? isError: false,
? ? data: initialData,
? });
?
? useEffect(() => {
? ? const fetchData = async () => {
? ? ? dispatch({ type: 'FETCH_INIT' });
?
? ? ? try {
? ? ? ? const result = await axios(url);
?
? ? ? ? dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
? ? ? } catch (error) {
? ? ? ? dispatch({ type: 'FETCH_FAILURE' });
? ? ? }
? ? };
?
? ? fetchData();
? }, [url]);
?
? ...
};

在獲取數(shù)據(jù)時(shí),可以調(diào)用dispatch函數(shù),將信息發(fā)送給reducer。使用dispatch函數(shù)發(fā)送的參數(shù)為object,具有type屬性和可選payload的屬性。type屬性告訴reducer需要應(yīng)用哪個(gè)狀態(tài)轉(zhuǎn)換,并且reducer可以使用payload來創(chuàng)建新的狀態(tài)。在這里,我們只有三個(gè)狀態(tài)轉(zhuǎn)換:發(fā)起請求,請求成功,請求失敗。

在自定義hooks的末尾,state像以前一樣返回,但是因?yàn)槲覀兡玫降氖且粋€(gè)狀態(tài)對(duì)象,而不是以前那種分離的狀態(tài),所以需要將狀態(tài)對(duì)象解構(gòu)之后再返回。這樣,調(diào)用useDataApi自定義hooks的人仍然可以訪問data,isLoading 和 isError這三個(gè)狀態(tài)。

const useDataApi = (initialUrl, initialData) => {
? const [url, setUrl] = useState(initialUrl);
?
? const [state, dispatch] = useReducer(dataFetchReducer, {
? ? isLoading: false,
? ? isError: false,
? ? data: initialData,
? });
?
? ...
?
? const doFetch = url => {
? ? setUrl(url);
? };
?
? return { ...state, doFetch };
};?

接下來添加reducer函數(shù)的實(shí)現(xiàn)。它需要三種不同的狀態(tài)轉(zhuǎn)換FETCH_INIT,F(xiàn)ETCH_SUCCESS和FETCH_FAILURE。每個(gè)狀態(tài)轉(zhuǎn)換都需要返回一個(gè)新的狀態(tài)對(duì)象。讓我們看看如何使用switch case語句實(shí)現(xiàn)它:

? switch (action.type) {
? ? case 'FETCH_INIT':
? ? ? return {
? ? ? ? ...state,
? ? ? ? isLoading: true,
? ? ? ? isError: false
? ? ? };
? ? case 'FETCH_SUCCESS':
? ? ? return {
? ? ? ? ...state,
? ? ? ? isLoading: false,
? ? ? ? isError: false,
? ? ? ? data: action.payload,
? ? ? };
? ? case 'FETCH_FAILURE':
? ? ? return {
? ? ? ? ...state,
? ? ? ? isLoading: false,
? ? ? ? isError: true,
? ? ? };
? ? default:
? ? ? throw new Error();
? }
};

取消數(shù)據(jù)請求

React中的一種很常見的問題是:如果在組件中發(fā)送一個(gè)請求,在請求還沒有返回的時(shí)候卸載了組件,這個(gè)時(shí)候還會(huì)嘗試設(shè)置這個(gè)狀態(tài),會(huì)報(bào)錯(cuò)。我們需要在hooks中處理這種情況,可以看下是怎樣處理的:

const useDataApi = (initialUrl, initialData) => {
? const [url, setUrl] = useState(initialUrl);
?
? const [state, dispatch] = useReducer(dataFetchReducer, {
? ? isLoading: false,
? ? isError: false,
? ? data: initialData,
? });
?
? useEffect(() => {
? ? let didCancel = false;
?
? ? const fetchData = async () => {
? ? ? dispatch({ type: 'FETCH_INIT' });
?
? ? ? try {
? ? ? ? const result = await axios(url);
?
? ? ? ? if (!didCancel) {
? ? ? ? ? dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
? ? ? ? }
? ? ? } catch (error) {
? ? ? ? if (!didCancel) {
? ? ? ? ? dispatch({ type: 'FETCH_FAILURE' });
? ? ? ? }
? ? ? }
? ? };
? fetchData();
?
? ? return () => {
? ? ? didCancel = true;
? ? };
? }, [url]);
?
? const doFetch = url => {
? ? setUrl(url);
? };
?
? return { ...state, doFetch };
};

我們可以看到這里新增了一個(gè)didCancel變量,如果這個(gè)變量為true,不會(huì)再發(fā)送dispatch,也不會(huì)再執(zhí)行設(shè)置狀態(tài)這個(gè)動(dòng)作。這里我們在useEffe的返回函數(shù)中將didCancel置為true,在卸載組件時(shí)會(huì)自動(dòng)調(diào)用這段邏輯。也就避免了再卸載的組件上設(shè)置狀態(tài)。 

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • react18?hooks自定義移動(dòng)端Popup彈窗組件RcPop

    react18?hooks自定義移動(dòng)端Popup彈窗組件RcPop

    這篇文章主要介紹了react18?hooks自定義移動(dòng)端Popup彈窗組件RcPop,react-popup?基于react18+hook自定義多功能彈框組件,整合了msg/alert/dialog/toast及android/ios彈窗效果,需要的朋友可以參考下
    2023-08-08
  • React路由組件三種傳參方式分析講解

    React路由組件三種傳參方式分析講解

    本文主要介紹了React組件通信之路由傳參(react-router-dom),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • React Draggable插件如何實(shí)現(xiàn)拖拽功能

    React Draggable插件如何實(shí)現(xiàn)拖拽功能

    這篇文章主要介紹了React Draggable插件如何實(shí)現(xiàn)拖拽功能問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • 詳解react-native-fs插件的使用以及遇到的坑

    詳解react-native-fs插件的使用以及遇到的坑

    本篇文章主要介紹了react-native-fs插件的使用以及遇到的坑,詳細(xì)的介紹了react-native-fs安裝使用,具有一定的參考價(jià)值,有興趣的可以了解一下
    2017-09-09
  • React自定義Hook-useForkRef的具體使用

    React自定義Hook-useForkRef的具體使用

    本文主要介紹了React自定義Hook-useForkRef的具體使用,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • react組件中debounce防抖功能失效問題解決辦法

    react組件中debounce防抖功能失效問題解決辦法

    在React組件中,如果使用useState管理searchKey,每次輸入變化都會(huì)導(dǎo)致組件重新渲染,從而生成新的debounce函數(shù),導(dǎo)致防抖功能失效,解決方法是使用useRef定義searchKey為非響應(yīng)式數(shù)據(jù),從而維持debounce函數(shù)的穩(wěn)定,確保防抖功能有效,感興趣的朋友跟隨小編一起看看吧
    2024-10-10
  • Redux saga異步管理與生成器詳解

    Redux saga異步管理與生成器詳解

    這篇文章主要介紹了Redux saga異步管理與生成器,工作中使用了redux-saga這個(gè)redux中間件,如果不明白內(nèi)部原理使用起來會(huì)讓人摸不著頭腦,閱讀源碼后特意對(duì)其原理做下總結(jié)
    2023-02-02
  • React中路由的參數(shù)傳遞路由的配置文件詳解

    React中路由的參數(shù)傳遞路由的配置文件詳解

    路由的配置文件目前我們所有的路由定義都是直接使用Route組件,并且添加屬性來完成的,路由的參數(shù)傳遞有二種方式這,兩種方式在Router6.x中都是提供的hook函數(shù)的API,?類組件需要通過高階組件的方式使用,本文通過示例代碼詳解講解,需要的朋友參考下吧
    2022-11-11
  • 一文搞懂redux在react中的初步用法

    一文搞懂redux在react中的初步用法

    Redux是JavaScript狀態(tài)容器,提供可預(yù)測化的狀態(tài)管理,今天通過本文給大家分享redux在react中使用及配置redux到react項(xiàng)目中的方法,感興趣的朋友跟隨小編一起看看吧
    2021-06-06
  • React實(shí)現(xiàn)隨機(jī)顏色選擇器的示例代碼

    React實(shí)現(xiàn)隨機(jī)顏色選擇器的示例代碼

    顏色選擇器是一個(gè)用于選擇和調(diào)整顏色的工具,它可以讓用戶選擇他們喜歡的顏色,本文主要介紹了React實(shí)現(xiàn)隨機(jī)顏色選擇器的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-12-12

最新評(píng)論