使用React Hooks封裝一套異步拉取的TableList

Background

React在16.7-alpha版本中新增了React的一些新特性卡辰,如Hook尔崔。

在這里主要想分享一下關(guān)于Hook使用

About Hook

PS: 了解上面三個 api 就可以對Hook進行基本的使用了贮乳。 類似于官方的聲明可以直接去官網(wǎng)查看果港。下面直接進入實戰(zhàn)模式。

Practice

 這里我們先說明我們要做的是什么, 我們在這里最終目的是要做一個由Hook制作的一個異步Table組件费奸,Table使用的是 **Ant-design **里封裝好的組件弥激, 而cli用 **create-react-app **官方自帶吧。因為側(cè)重 hook 所以我們不會涉及到redux之類的愿阐,所以也不必要徒增不必要的代碼量微服。 then let's do it。

Mock Data

第一步缨历,首先我們要構(gòu)造后臺的數(shù)據(jù)以蕴,或者自己找一個可以進行mock的數(shù)據(jù)就可以。這個盡可能模仿后臺辛孵,我這里使用的就用 ant-design-pro 提供的分頁丛肮。https://preview.pro.ant.design/api/rule?currentPage=1&pageSize=10 可以在postman搜索即可 ,瀏覽器打開不正常魄缚。估計是做了額外的處理宝与。但是有個問題就是跨域問題解決不了,我這邊基于easy-mock又做了一個 接口冶匹, 理論上兩個行為是一致的习劫。

主要后臺和前端配合下 前端傳 當(dāng)前頁碼 (currentPage) 和 展示數(shù)量 (pageSize) 即可。 后臺返回需要有 總頁數(shù)嚼隘。 因為我們要做如下這個情況就必須得要有總頁數(shù)诽里。

Like this. ↓

?

image

?

anyway. 數(shù)據(jù)有了就可以下一步了。

Font-end

構(gòu)建目錄

  • create-react-app 構(gòu)建出基本的react項目 這個可以看官方文檔

  • 對package.json進行修改飞蛹,把 react , react-dom 兩個修改成 v16.8.0-alpha.1谤狡, 然后重新安裝

  • 這里還需要把 antd 加上 畢竟我們也要用到他們的Table組件

  • 還有一個請求這里直接用原生的fetch就可以了

貼一下目前的package.json

?

代碼塊

{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "antd": "^3.13.0",
    "react": "v16.8.0-alpha.1",
    "react-dom": "v16.8.0-alpha.1",
    "react-scripts": "2.1.3"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ]
}

?

接下來我們刪除目前不需要用到的頁面,并且把 server-worker 去掉卧檐, 最后簡化到如下即可墓懂。 代碼可在github上預(yù)覽。

接下來第一次嘗試

異步獲取數(shù)據(jù)并且渲染到頁面

首先這里有一個關(guān)鍵的問題 泄隔, **異步 **在一開始拒贱,我們就得明確hook中使用帶有副作用的都得使用 useEffect() 進行定義, 異步也是副作用的一種佛嬉。因此逻澳,我們獲取數(shù)據(jù)的時候應(yīng)該是使用 useEffect() 代碼走一個

yap,我們可以得到我們想要的數(shù)據(jù)了暖呕!

?

image

?

解釋一下這里斜做, useEffect() 這個非常強大, 哈哈哈湾揽。 不愧是在React發(fā)布會上Ryan特別點了一下瓤逼,真好用笼吟。哈哈

這里主要是容納了 componentDidMount 和 componentDidUpdate return 的時候就是 組件 umount的時機, 因此 subscribe在useEffect中霸旗,但return的時候就應(yīng)該把他們給 unsubscribe贷帮。

Ok. just fine.

下一步。

我們已經(jīng)知道如何得到數(shù)據(jù) 诱告, 那么我們把它塞到我們的Table組件中撵枢。試一下

失敗

?

image

?

這里錯誤的原因很簡單,因為數(shù)據(jù)在更新之后并沒有處罰需要 re-render 的條件精居,因此我們加上 useState()

?

image

?

數(shù)據(jù)請求成功锄禽。并且渲染成功! but靴姿,這里有一個問題沃但。為什么我們會有這么多條請求?如果不報錯甚至?xí)霈F(xiàn)死循環(huán)佛吓? 因為 調(diào)用的位置 出現(xiàn)問題了宵晚! 想想我們以前寫的react, 只有一次的觸發(fā)時機辈毯。 那就是 componentWillMount 或者 componentDidMount 那么 坝疼,這個位置在哪里呢搜贤?

useEffect() 就因此而來谆沃。

?

image

?

沒有任何問題! 但是仪芒,這里我多做了一步唁影,就是 給useEffect添加了第二個參數(shù)

useEffect() 的第二個參數(shù)就是專門做數(shù)據(jù)對比,它的入?yún)⑹且粋€變量掂名。只有在變量指向的數(shù)據(jù)進行修改后据沈,進行對比發(fā)現(xiàn)不一致時,才會重新進行渲染饺蔑。

好的锌介,現(xiàn)在才是正題啊猾警!

封裝異步Table組件

定義入?yún)⒎椒?/h5>

展示的columns, 請求后臺api, 傳后臺參數(shù), 后臺返回的數(shù)組的字鍵名稱

function asyncTableList(columns, queryAction, params, cgiKey) {

// do something

}

整套代碼如下

?

代碼塊


/**
 * 異步table
 * 組件使用方法
 * 
 * import renderAsyncTable from './this.file'
 * 
 * const asyncTable = renderAsyncTable(展示的columns, 請求后臺api, 傳后臺參數(shù), 后臺返回的數(shù)組的字鍵名稱)
 * 
 * function App() {
 *   const [query] = useState()
 *   const columns = [...]
 *   const asyncTable = renderAsyncTable(columns, api, query, listName ) 
 *   return (
 *    <div>
 *      {asyncTable}
 *    </div>
 *   )
 * }
 * 
 */
import React, { useEffect, useReducer } from 'react'
import Table from 'antd/lib/table';

function useAsyncTable (columns, queryAction, params, listName) {
  const paginationInitial = {
    current: 1,
    pageSize: 10
  }
  const [state, dispatch] = useReducer(
    (state, action) => {
      const { payload } = action
      switch (action.type) {
        case 'TOGGLE_LOADING':
          return { ...state, loading: !state.loading }
        case 'SET_QUERY':
          return {
            ...state,
            query: payload.params,
            pagination: paginationInitial
          }
        case 'SET_PAGINATION':
          return { ...state, pagination: payload.pagination }
        case 'SET_DATA_SOURCE':
          return { ...state, dataSource: payload.dataSource }
        default:
          return state
      }
    },
    {
      loading: false,
      query: null,
      pagination: paginationInitial,
      dataSource: []
    }
  )
  function handleTableChange (event) {
    if (event) {
      const { current } = event
      dispatch({
        type: 'SET_PAGINATION',
        payload: {
          pagination: {
            ...state.pagination,
            current
          }
        }
      })
    }
  }
  function doQuery () {
    dispatch({
      type: 'TOGGLE_LOADING'
    })
    const { current, pageSize } = state.pagination
    const pagination = {
      current,
      pageSize
    }
    queryAction({
      ...state.query,
      ...pagination
    })
      .catch(err => {
        dispatch({
          type: 'TOGGLE_LOADING'
        })
        return {}
      })
      .then((payload) => {
        if(payload.pagination && payload.list) {
          const { pagination: { total } } = payload
          console.log('total', total)
          dispatch({
            type: 'TOGGLE_LOADING'
          })
          if (!state.pagination.total) {
            dispatch({
              type: 'SET_PAGINATION',
              payload: {
                pagination: { ...state.pagination, total }
              }
            })
          }
          dispatch({
            type: 'SET_DATA_SOURCE',
            payload: {
              dataSource: payload[listName]
            }
          })  
        }
        
      })
  }
  // cDM cDU
  useEffect(
    () => {
      if (params && JSON.stringify(params) !== JSON.stringify(state.query)) {
        dispatch({
          type: 'SET_QUERY',
          payload: {
            params
          }
        })
      } else {
        doQuery()
      }
    },
    [params, state.pagination.current, state.query]
  )
  return (
    <Table
      columns={columns}
      rowKey={record => record.key}
      pagination={state.pagination}
      dataSource={state.dataSource}
      loading={state.loading}
      onChange={handleTableChange}
    />
  )
}
export default useAsyncTable

?

useReducer

這里解釋一下:

在React官網(wǎng)的Hook中也有用到useReducer這種方法孔祸! 怎么說呢? 用hook的方式寫一個redux吧发皿。 用法和redux一樣崔慧。state只能被dispatch的數(shù)據(jù)進行修改。 dispatch用type來區(qū)分修改state里的哪個數(shù)據(jù)穴墅,然后 我這邊用payload 帶數(shù)據(jù)進去惶室。修改 state 內(nèi)部數(shù)據(jù)温自。

每次做diff的之后 只拿 透傳過來的 params 和 存在reducer里的 query做對比,如果有修改就進行修改皇钞。

沒有的話就是調(diào)用請求悼泌。

這里調(diào)用請求可以理解為 :

  1. 只有在 current 頁面不一致的時候調(diào)用
  2. 只有在query更改過后進行修改

而params在這里只是作為一個觸發(fā)修改query的操作,并不會直接進行請求夹界。

調(diào)用頁面

調(diào)用相對簡單券躁。 我們需要傳入的參數(shù)只有4個

  • columns的展示
  • 異步拉取的接口
  • 其他query, 這個query可能需要解析一下掉盅, 因為不是所有的異步拉取只有 pageSize 和 current 兩個參數(shù)也拜。 它們還有可能是 cityId 為 10000 或者 name是小明 也有可能是集合 如 城市A區(qū),名字小白趾痘。這樣慢哈,我們異步在使用異步拉取的接口 就可以不單單的延申為 pageSize 和 current這么單調(diào)。 它也可以combine參數(shù)后得到更好的拓展永票。
  • listName卵贱。 這個主要是為了不同的后臺,有可能它是比較泛的名稱如 lists 或者 beanList 等等侣集,但也有可能是 cities键俱,topics。 因此也用透傳的方式世分。 更加強壯编振。

?

代碼塊

import React, {useState} from 'react';

import renderAsyncTable from './asyncTable'
import getAjax from './utils/ajax'

function App() {
  const [query] = useState()

  const columns = [
    {
      title: '規(guī)則名稱',
      dataIndex: 'name',
    },
    {
      title: '描述',
      dataIndex: 'desc',
    },
    {
      title: '服務(wù)調(diào)用次數(shù)',
      dataIndex: 'callNo',
      render: val => `${val} 萬`,
      // mark to display a total number
      needTotal: true,
    },
    {
      title: '上次調(diào)度時間',
      dataIndex: 'updatedAt',
    },
  ];

  const asyncTable = renderAsyncTable(columns, (query) => getAjax('o2po/data', query), query, 'list' ) 

  return (
    <div style={{width: 600, margin: '30px auto'}}>
      {asyncTable}
    </div>
  )
}
export default App;

?

整套流程就可以了。

?

調(diào)用頁面

?github:
https://github.com/panmouren/asyncTableList

歡迎start臭埋,如果有什么問題歡迎指正踪央。! 指正比start更重要瓢阴。 語雀https://www.yuque.com/zibuyu/fed/ognwv0#Background

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末畅蹂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子荣恐,更是在濱河造成了極大的恐慌液斜,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叠穆,死亡現(xiàn)場離奇詭異少漆,居然都是意外死亡,警方通過查閱死者的電腦和手機痹束,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門检疫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人祷嘶,你說我怎么就攤上這事屎媳《嵋纾” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵烛谊,是天一觀的道長风响。 經(jīng)常有香客問我,道長丹禀,這世上最難降的妖魔是什么状勤? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮双泪,結(jié)果婚禮上持搜,老公的妹妹穿的比我還像新娘。我一直安慰自己焙矛,他們只是感情好葫盼,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著村斟,像睡著了一般贫导。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蟆盹,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天孩灯,我揣著相機與錄音,去河邊找鬼逾滥。 笑死峰档,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的匣距。 我是一名探鬼主播面哥,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼毅待!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起归榕,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤尸红,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后刹泄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體外里,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年特石,在試婚紗的時候發(fā)現(xiàn)自己被綠了盅蝗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡姆蘸,死狀恐怖墩莫,靈堂內(nèi)的尸體忽然破棺而出芙委,到底是詐尸還是另有隱情,我是刑警寧澤狂秦,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布灌侣,位于F島的核電站,受9級特大地震影響裂问,放射性物質(zhì)發(fā)生泄漏侧啼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一堪簿、第九天 我趴在偏房一處隱蔽的房頂上張望痊乾。 院中可真熱鬧,春花似錦椭更、人聲如沸符喝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽协饲。三九已至,卻和暖如春缴川,著一層夾襖步出監(jiān)牢的瞬間茉稠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工把夸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留而线,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓恋日,卻偏偏與公主長得像膀篮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子岂膳,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 31,913評論 2 89
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,093評論 1 32
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5誓竿? 答:HTML5是最新的HTML標(biāo)準(zhǔn)。 注意:講述HT...
    kismetajun閱讀 27,450評論 1 45
  • 一個人穿行在土城谈截,不去理會周圍人側(cè)目的眼光筷屡,不理會旁人:“看那個男法師,長得跟女的一樣簸喂,個子也不高毙死。” 面無表情的...
    筆間流年閱讀 458評論 2 2
  • 柴門聞犬吠,風(fēng)雪夜歸人除呵。原來我最忘不掉的是你再菊。 一題記 因為我十分喜歡狗爪喘,正好...
    李藝然閱讀 415評論 0 2