成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

useEffect 實踐案例之一

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

對于 useEffect 的掌握是 React hooks 學習的重中之重。因此我們還需要花一些篇幅繼續圍繞它講解。

上一篇文章中,我們使用兩個案例分析了 useEffect 的理論知識。接下來,我們通過一些具體的實踐案例來學習 useEffect 的運用。

一、需求

現有一個簡單的需求,要實現一個搜索框,輸入內容之后,點擊搜索按鈕,然后得到一個列表。

當列表為空時,顯示暫無數據。

接口請求過程中,需要顯示 Loading 狀態。

Loading 狀態隨便用的一個轉圈圖標來表示,和下面的圖標有點重疊,以后有機會再調整一下 UI。

接口請求成功之后,顯示一個列表。

再次搜索時,顯示 Loading 狀態。

如果接口請求出錯,顯示錯誤頁面。

在實踐中,這是針對一個請求所需要的常規狀態處理,當然很多時候我們在學習的過程中簡化了空數據/Loading/異常等狀態,就導致了許多自學的朋友沒有在工作中友好處理這些狀態的習慣。

二、實現

我們一步一步來實現該需求。

我們假設一個請求需要花費 600ms,在學習階段,我們可以借助 Promise 與 setTimeout 來模擬一個接口請求。

單獨創建一個 api.ts 文件。

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

我計劃設計該函數最終返回一個 Promise 對象。并將一個字符串數組 resolve 出來。該字符串由搜索條件的一個字符與Math.random 產生的隨機數組成。

輸出的列表長這樣。

該 api 函數具體代碼如下:

// ./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('請求異常,請重新嘗試!')
      }
    }, 600)
  })
}

在該函數中,我們使用泛型明確了 Promise 的輸出類型,在后續的使用中就可以利用 TypeScript 的自動類型推導得到具體的返回類型。

接下來我們要創建組件函數。

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

然后我們根據 UI 的情況去分析應該在代碼中設計哪些數據。

首先有一個列表需要展示。

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

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

const [loading, setLoading] = useState(false)

還有一個錯誤信息需要顯示。

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

還有一個稍微有一些特殊的,輸入框中輸入的內容。我們要注意準確分析內容:該內容的展示在已有的 UI 中,是根據鍵盤輸入而展示內容,它不由數據來驅動。

我們在該案例中,僅僅只是記錄輸入的內容,并傳入 searchApi即可。因此我們可以使用 useRef 來存儲該變量。

const str = useRef('')

如果情況有變,有其他的 UI 需要該數據來驅動,那么我們就需要將其調整為使用 useState 來存儲。

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

首先是一個輸入框 input 與按鈕 button。

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

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

代碼中,借助 input 的 onChange 回調來記錄當前輸入的值。

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

點擊按鈕時,修改對應的狀態,并開始發送請求。此時 Loading 應該修改為 true。

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

請求成功之后,Loading 改回 false,list 得到新的數據。如果請求失敗,Loading 依然需要改成 false,并記錄錯誤信息。

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

首先,空數據、錯誤信息、正常列表的顯示情況是互斥的,他們三個只能存在一個。Loading 狀態是每個情況下都有可能發生的,與他們的關系是分別共存的。

因此,當有錯誤信息時,這一塊的內容應該為。

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>
  )
}

案例中出現的 Icon 組件是一個圖標,該組件是我們這個項目自己封裝好的基礎組件。

當是空列表時。

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}>暫無數據</div>
    </div>
  )
}

正常列表有數據時。

<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,此時所有的邏輯已經考慮完畢。

三、優化封裝

我們會發現,列表相關的邏輯實在是有點繁瑣。如果每次遇到一個列表就要處理這么多,豈不是非常消耗時間?

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

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

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

三個數據,error,loading,list 都是差異部分,他們需要作為 props 傳入。

先定義一個類型聲明如下:

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

此時我們看到由于 list 的每一項具體數據內容,可能每一個列表都不一樣,我們無法在這里確認他的類型,因此此處使用泛型來表示。

不知道 list 的每一項具體數據是什么,也就意味著對應的 UI 我們也無法提前得知,只有在使用時才知道,因此還應該補上一個新的 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}>暫無數據</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>
  )
}

封裝好之后,使用起來就非常簡單了,我們只需要把當前上下文中的數據傳入進去即可。

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

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

四、需求改進

在某些場景,初始化時我們并不需要展示空數組,而是需要請求一次接口,然后展示對應的列表,因此,在這種需求的情況下,代碼需要進行一些調整。

首先,Loading 的初始化狀態需要從 false 改為 true,表示一開始就會立即請求數據。

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

然后初始化請求數據的操作,在 useEffect 中完成,傳入空數組作為依賴項,表示只在組件首次渲染完成之后執行一次。

... 

+ 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,這樣需求就完整的被解決,不過此時我們發現,useEffect 的邏輯與 onSure 的邏輯高度重合,他們一個代表初始化邏輯,一個代表更新邏輯。

因此在代碼上做一些簡單的調整。

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()
  }

這樣調整了之后,我們發現一個有趣的事情,當點擊搜索按鈕觸發 onSure 時,我們會執行一次把 loading 修改為 true 的操作。

setLoading(true)

那如果這個時候,我們就可以把 loading 作為 useEffect 的依賴項傳入,onSure 里就可以只保留這一行代碼。

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

function onSure() {
  setLoading(true)
}

這就是我們在本書唯一付費章節「React 哲學」中提到的開關思維。在日常生活中,如果我想要打開電視機,我們只需要關注開關按鈕那一下操作,在這里也是一樣,如果我想要重新請求列表搜索,我只需要關注如何操作 loading 這個開關即可

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

接下來我們將要學習自定義 hook,進一步感受開關思維的魅力。

責任編輯:姜華 來源: 這波能反殺
相關推薦

2023-12-21 09:00:21

函數React 組件useEffect

2022-05-07 15:44:45

eTS 開發鴻蒙

2025-05-12 01:33:00

異步函數Promise

2024-01-02 07:56:13

ReactuseEffect數據驅動 UI

2024-06-12 07:44:28

2023-12-12 09:43:17

桌面開發Net消息機制

2023-12-22 08:46:15

useEffectVueMobx

2019-08-09 10:58:48

2021-06-03 19:55:55

MySQ查詢優化

2011-01-25 10:51:54

系統架構設計師

2019-07-17 15:05:35

應用服務器Tomcat監控

2012-06-25 14:01:10

云計算案例

2022-02-16 15:39:30

ACTS應用XTS子系統鴻蒙

2021-02-04 15:08:37

Vue漸進式框架

2021-05-06 05:39:30

Inotify監聽系統

2021-04-21 21:09:36

緩存系統高可用

2021-11-10 10:48:36

C++函數指針

2011-04-25 11:18:39

Ajax

2021-02-18 22:21:20

ASM服務組件化

2021-10-09 19:05:06

channelGo原理
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产在线www| 国产在线精品一区二区 | 亚洲精品三级 | 国产一区二区三区 | 亚洲免费精品 | 日韩精品免费一区二区在线观看 | 黄色大片在线视频 | 手机看片169 | 中文字幕成人av | 精品国产一区二区在线 | 嫩草视频在线免费观看 | 精品国产一区二区三区性色av | 久久久天天 | 国产一区二区三区亚洲 | 欧美精品一区二区三区在线播放 | 超碰伊人久久 | 少妇无套高潮一二三区 | 免费看91 | 欧美亚洲激情 | 给我免费的视频在线观看 | 91视频网址 | 久久免费资源 | 免费在线一区二区 | www.玖玖玖| 国产在线精品一区二区三区 | 日韩亚洲视频在线 | 国产高清av免费观看 | 国产一区二区三区色淫影院 | 丝袜久久| 日本一区二区视频 | 国产一区二区三区四区 | 人人看人人草 | 香蕉婷婷 | 国产精品日韩欧美一区二区三区 | 中文字幕在线一 | 欧美www在线 | 日韩成人精品一区 | 中文字幕精品一区久久久久 | 美国一级毛片a | 天天躁日日躁性色aⅴ电影 免费在线观看成年人视频 国产欧美精品 | 精品久久久久久久久久久久 |