自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

useEffect 實(shí)踐案例之一

開發(fā) 前端
案例中的樣式使用了CSS Module,因此 ClassName 的語法會(huì)與前面介紹的有所不同,我們把 S.input 當(dāng)成一個(gè)字符串來看待即可。

對(duì)于 useEffect 的掌握是 React hooks 學(xué)習(xí)的重中之重。因此我們還需要花一些篇幅繼續(xù)圍繞它講解。

上一篇文章中,我們使用兩個(gè)案例分析了 useEffect 的理論知識(shí)。接下來,我們通過一些具體的實(shí)踐案例來學(xué)習(xí) useEffect 的運(yùn)用。

一、需求

現(xiàn)有一個(gè)簡(jiǎn)單的需求,要實(shí)現(xiàn)一個(gè)搜索框,輸入內(nèi)容之后,點(diǎn)擊搜索按鈕,然后得到一個(gè)列表。

當(dāng)列表為空時(shí),顯示暫無數(shù)據(jù)。

接口請(qǐng)求過程中,需要顯示 Loading 狀態(tài)。

Loading 狀態(tài)隨便用的一個(gè)轉(zhuǎn)圈圖標(biāo)來表示,和下面的圖標(biāo)有點(diǎn)重疊,以后有機(jī)會(huì)再調(diào)整一下 UI。

接口請(qǐng)求成功之后,顯示一個(gè)列表。

再次搜索時(shí),顯示 Loading 狀態(tài)。

如果接口請(qǐng)求出錯(cuò),顯示錯(cuò)誤頁面。

在實(shí)踐中,這是針對(duì)一個(gè)請(qǐng)求所需要的常規(guī)狀態(tài)處理,當(dāng)然很多時(shí)候我們?cè)趯W(xué)習(xí)的過程中簡(jiǎn)化了空數(shù)據(jù)/Loading/異常等狀態(tài),就導(dǎo)致了許多自學(xué)的朋友沒有在工作中友好處理這些狀態(tài)的習(xí)慣。

二、實(shí)現(xiàn)

我們一步一步來實(shí)現(xiàn)該需求。

我們假設(shè)一個(gè)請(qǐng)求需要花費(fèi) 600ms,在學(xué)習(xí)階段,我們可以借助 Promise 與 setTimeout 來模擬一個(gè)接口請(qǐng)求。

單獨(dú)創(chuàng)建一個(gè) api.ts 文件。

在該文件中,我們聲明一個(gè)名為 searchApi 的函數(shù),該函數(shù)接收一個(gè)字符串作為參數(shù)。

我計(jì)劃設(shè)計(jì)該函數(shù)最終返回一個(gè) Promise 對(duì)象。并將一個(gè)字符串?dāng)?shù)組 resolve 出來。該字符串由搜索條件的一個(gè)字符與Math.random 產(chǎn)生的隨機(jī)數(shù)組成。

輸出的列表長(zhǎng)這樣。

該 api 函數(shù)具體代碼如下:

// ./api.ts
export function searchApi(param: string) {
  return new Promise<string[]>((resolve, reject) => {
    const p = param.split('')
    const arr: string[] = []
    for(var i = 0; i < 10; i++) {
      const pindex = i % p.length
      arr.push(`${p[pindex] || '^ ^'} - ${Math.random()}`)
    }
    setTimeout(() => {
      if (Math.random() * 10 > 1) {
        resolve(arr)
      } else {
        reject('請(qǐng)求異常,請(qǐng)重新嘗試!')
      }
    }, 600)
  })
}

在該函數(shù)中,我們使用泛型明確了 Promise 的輸出類型,在后續(xù)的使用中就可以利用 TypeScript 的自動(dòng)類型推導(dǎo)得到具體的返回類型。

接下來我們要?jiǎng)?chuàng)建組件函數(shù)。

// index.tsx
export default function DemoOneNormal() {
  // ...
}

然后我們根據(jù) UI 的情況去分析應(yīng)該在代碼中設(shè)計(jì)哪些數(shù)據(jù)。

首先有一個(gè)列表需要展示。

const [list, setList] = useState<string[]>([])

然后有一個(gè) Loading 的顯示與隱藏需要控制。

const [loading, setLoading] = useState(false)

還有一個(gè)錯(cuò)誤信息需要顯示。

const [error, setError] = useState('')

還有一個(gè)稍微有一些特殊的,輸入框中輸入的內(nèi)容。我們要注意準(zhǔn)確分析內(nèi)容:該內(nèi)容的展示在已有的 UI 中,是根據(jù)鍵盤輸入而展示內(nèi)容,它不由數(shù)據(jù)來驅(qū)動(dòng)。

我們?cè)谠摪咐?,僅僅只是記錄輸入的內(nèi)容,并傳入 searchApi即可。因此我們可以使用 useRef 來存儲(chǔ)該變量。

const str = useRef('')

如果情況有變,有其他的 UI 需要該數(shù)據(jù)來驅(qū)動(dòng),那么我們就需要將其調(diào)整為使用 useState 來存儲(chǔ)。

接下來思考 JSX 代碼的編寫。

首先是一個(gè)輸入框 input 與按鈕 button。

<input 
  className={s.input} 
  placeholder="請(qǐng)輸入您要搜索的內(nèi)容" 
  notallow={(e) => str.current = e.target.value} 
/>
<Button 
  className={s.button} 
  onClick={onSure}
>
  搜索
</Button>

案例中的樣式使用了 css module,因此 className 的語法會(huì)與前面介紹的有所不同,我們把 s.input 當(dāng)成一個(gè)字符串來看待即可。

代碼中,借助 input 的 onChange 回調(diào)來記錄當(dāng)前輸入的值。

// const str = useRef('')
notallow={(e) => str.current = e.target.value}

點(diǎn)擊按鈕時(shí),修改對(duì)應(yīng)的狀態(tài),并開始發(fā)送請(qǐng)求。此時(shí) Loading 應(yīng)該修改為 true。

function onSure() {
  setLoading(true)
  searchApi(str.current).then(res => {
    setList(res)
    setLoading(false)
    setError('')
  }).catch(err => {
    setLoading(false)
    setError(err)
  })
}

請(qǐng)求成功之后,Loading 改回 false,list 得到新的數(shù)據(jù)。如果請(qǐng)求失敗,Loading 依然需要改成 false,并記錄錯(cuò)誤信息。

接下來我們要思考列表的 UI 代碼。

首先,空數(shù)據(jù)、錯(cuò)誤信息、正常列表的顯示情況是互斥的,他們?nèi)齻€(gè)只能存在一個(gè)。Loading 狀態(tài)是每個(gè)情況下都有可能發(fā)生的,與他們的關(guān)系是分別共存的。

因此,當(dāng)有錯(cuò)誤信息時(shí),這一塊的內(nèi)容應(yīng)該為。

if (error) {
  return (
    <div className={s.wrapper}>
      {loading && (
        <div className={s.loading_wrapper}>
          <Icon spin type='loading' style={{ fontSize: 40 }} />
        </div>
      )}
      <Icon type='event' color='red' style={{ fontSize: 32 }} />
      <div className={s.error}>{error}</div>
    </div>
  )
}

案例中出現(xiàn)的 Icon 組件是一個(gè)圖標(biāo),該組件是我們這個(gè)項(xiàng)目自己封裝好的基礎(chǔ)組件。

當(dāng)是空列表時(shí)。

if (list.length === 0) {
  return (
    <div className={s.wrapper}>
      {loading && (
        <div className={s.loading_wrapper}>
          <Icon spin type='loading' color='#2860Fa' style={{ fontSize: 38 }} />
        </div>
      )}
      <Icon type='event' color='#ccc' style={{ fontSize: 32 }} />
      <div className={s.nodata}>暫無數(shù)據(jù)</div>
    </div>
  )
}

正常列表有數(shù)據(jù)時(shí)。

<div className={s.list}>
  {loading && (
    <div className={s.loading_wrapper}>
      <Icon spin type='loading' color='#2860Fa' style={{ fontSize: 38 }} />
    </div>
  )}

  {list.map(item => (
    <div key={item} className={s.item}>{item}</div>
  ))}
</div>

OK,此時(shí)所有的邏輯已經(jīng)考慮完畢。

三、優(yōu)化封裝

我們會(huì)發(fā)現(xiàn),列表相關(guān)的邏輯實(shí)在是有點(diǎn)繁瑣。如果每次遇到一個(gè)列表就要處理這么多,豈不是非常消耗時(shí)間?

因此我們這里考慮將這些邏輯統(tǒng)一封裝到 List 組件里,下次要使用直接拿出來用就可以了。

// ./List/index.tsx
export default function List(props) {}

在封裝時(shí),我們首先要考慮哪些屬性需要作為 props 傳入該 List 組件。關(guān)于封裝的思考,和其他的邏輯封裝是一樣的,我們需要先考慮在不同的場(chǎng)景之下,他們的共性與差異分別是什么,差異的部分作為參數(shù)傳入。

三個(gè)數(shù)據(jù),error,loading,list 都是差異部分,他們需要作為 props 傳入。

先定義一個(gè)類型聲明如下:

interface ListProps<T> {
  loading?: boolean,
  error?: string,
  list?: T[]
}

此時(shí)我們看到由于 list 的每一項(xiàng)具體數(shù)據(jù)內(nèi)容,可能每一個(gè)列表都不一樣,我們無法在這里確認(rèn)他的類型,因此此處使用泛型來表示。

不知道 list 的每一項(xiàng)具體數(shù)據(jù)是什么,也就意味著對(duì)應(yīng)的 UI 我們也無法提前得知,只有在使用時(shí)才知道,因此還應(yīng)該補(bǔ)上一個(gè)新的 props 屬性。

interface ListProps<T> {
  loading?: boolean,
  error?: string,
  list?: T[],
+ renderItem: (item: T) => ReactNode
}

然后我們只需要把差異部分與共同部分在組件邏輯中組合起來即可,List 組件完整代碼如下:

import Icon from 'components/Icon'
import { ReactNode } from 'react'
import s from './index.module.scss'

interface ListProps<T> {
  loading?: boolean,
  error?: string,
  list?: T[],
  renderItem: (item: T) => ReactNode
}

export default function List<T>(props: ListProps<T>) {
  const {list = [], loading, error, renderItem} = props

  if (error) {
    return (
      <div className={s.wrapper}>
        {loading && (
          <div className={s.loading_wrapper}>
            <Icon spin type='loading' style={{ fontSize: 40 }} />
          </div>
        )}
        <Icon type='event' color='red' style={{ fontSize: 32 }} />
        <div className={s.error}>{error}</div>
      </div>
    )
  }

  if (list.length === 0) {
    return (
      <div className={s.wrapper}>
        {loading && (
          <div className={s.loading_wrapper}>
            <Icon spin type='loading' color='#2860Fa' style={{ fontSize: 38 }} />
          </div>
        )}
        <Icon type='event' color='#ccc' style={{ fontSize: 32 }} />
        <div className={s.nodata}>暫無數(shù)據(jù)</div>
      </div>
    )
  }

  return (
    <div className={s.list}>
      {loading && (
        <div className={s.loading_wrapper}>
          <Icon spin type='loading' color='#2860Fa' style={{ fontSize: 38 }} />
        </div>
      )}

      {list.map(renderItem)}
    </div>
  )
}

封裝好之后,使用起來就非常簡(jiǎn)單了,我們只需要把當(dāng)前上下文中的數(shù)據(jù)傳入進(jìn)去即可。

<List 
  list={list} 
  loading={loading}  
  error={error}
  renderItem={(item) => (
    <div key={item} className={s.item}>{item}</div>
  )}
/>

該案例組件文件路徑:src/pages/demos/effect/search/Normal.tsx

四、需求改進(jìn)

在某些場(chǎng)景,初始化時(shí)我們并不需要展示空數(shù)組,而是需要請(qǐng)求一次接口,然后展示對(duì)應(yīng)的列表,因此,在這種需求的情況下,代碼需要進(jìn)行一些調(diào)整。

首先,Loading 的初始化狀態(tài)需要從 false 改為 true,表示一開始就會(huì)立即請(qǐng)求數(shù)據(jù)。

- const [loading, setLoading] = useState(false)
+ const [loading, setLoading] = useState(true)

然后初始化請(qǐng)求數(shù)據(jù)的操作,在 useEffect 中完成,傳入空數(shù)組作為依賴項(xiàng),表示只在組件首次渲染完成之后執(zhí)行一次。

... 

+ useEffect(() => {
+   searchApi(str.current).then(res => {
+     setList(res)
+     setLoading(false)
+     setError('')
+   }).catch(err => {
+     setLoading(false)
+     setError(err)
+   })
+ }, [])

function onSure() {
  setLoading(true)
  searchApi(str.current).then(res => {
    setList(res)
    setLoading(false)
    setError('')
  }).catch(err => {
    setLoading(false)
    setError(err)
  })
}

...

OK,這樣需求就完整的被解決,不過此時(shí)我們發(fā)現(xiàn),useEffect 的邏輯與 onSure 的邏輯高度重合,他們一個(gè)代表初始化邏輯,一個(gè)代表更新邏輯。

因此在代碼上做一些簡(jiǎn)單的調(diào)整。

function getList() {
    searchApi(str.current).then(res => {
      setList(res)
      setLoading(false)
      setError('')
    }).catch(err => {
      setLoading(false)
      setError(err)
    })
  }

  useEffect(() => {
    getList()
  }, [])

  function onSure() {
    setLoading(true)
    getList()
  }

這樣調(diào)整了之后,我們發(fā)現(xiàn)一個(gè)有趣的事情,當(dāng)點(diǎn)擊搜索按鈕觸發(fā) onSure 時(shí),我們會(huì)執(zhí)行一次把 loading 修改為 true 的操作。

setLoading(true)

那如果這個(gè)時(shí)候,我們就可以把 loading 作為 useEffect 的依賴項(xiàng)傳入,onSure 里就可以只保留這一行代碼。

useEffect(() => {
  loading && getList()
}, [loading])

function onSure() {
  setLoading(true)
}

這就是我們?cè)诒緯ㄒ桓顿M(fèi)章節(jié)「React 哲學(xué)」中提到的開關(guān)思維。在日常生活中,如果我想要打開電視機(jī),我們只需要關(guān)注開關(guān)按鈕那一下操作,在這里也是一樣,如果我想要重新請(qǐng)求列表搜索,我只需要關(guān)注如何操作 loading 這個(gè)開關(guān)即可

該案例組件文件路徑:src/pages/demos/effect/search/Normal2.tsx。

接下來我們將要學(xué)習(xí)自定義 hook,進(jìn)一步感受開關(guān)思維的魅力。

責(zé)任編輯:姜華 來源: 這波能反殺
相關(guān)推薦

2023-12-21 09:00:21

函數(shù)React 組件useEffect

2022-05-07 15:44:45

eTS 開發(fā)鴻蒙

2024-01-02 07:56:13

ReactuseEffect數(shù)據(jù)驅(qū)動(dòng) UI

2024-06-12 07:44:28

2023-12-12 09:43:17

桌面開發(fā)Net消息機(jī)制

2023-12-22 08:46:15

useEffectVueMobx

2019-08-09 10:58:48

2021-06-03 19:55:55

MySQ查詢優(yōu)化

2011-01-25 10:51:54

系統(tǒng)架構(gòu)設(shè)計(jì)師

2019-07-17 15:05:35

應(yīng)用服務(wù)器Tomcat監(jiān)控

2022-02-16 15:39:30

ACTS應(yīng)用XTS子系統(tǒng)鴻蒙

2012-06-25 14:01:10

云計(jì)算案例

2021-05-06 05:39:30

Inotify監(jiān)聽系統(tǒng)

2021-02-04 15:08:37

Vue漸進(jìn)式框架

2011-04-25 11:18:39

Ajax

2021-11-30 07:02:10

虛擬化Linux 中斷

2021-02-18 22:21:20

ASM服務(wù)組件化

2021-11-10 10:48:36

C++函數(shù)指針

2021-04-21 21:09:36

緩存系統(tǒng)高可用

2022-02-28 14:54:48

openHarmon鴻蒙操作系統(tǒng)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)