前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >useList 列表hook

useList 列表hook

作者头像
copy_left
发布2020-04-16 19:13:53
1.2K0
发布2020-04-16 19:13:53
举报
文章被收录于专栏:方球

chart.gif

列表是我们日常开发中经常会碰到的一类展示形式, 只是以不同的 UI 显示在用户面前,例如: 菜单,表格等,其中一些操作, 加载,重置,等是基本相同的,所以我们希望抽离这部分公共逻辑, 这里记录使用hook封装时碰到的一些问题

功能
  • 设置查询参数
  • 设置初始列表值
  • 请求列表,叠加数据
  • 请求列表,重置数据
实现
代码语言:javascript
复制
import React, {
    useState,
    useCallback,
    useRef
} from 'react';

async function asyncVessel(promise) {
    return promise.then(res => [res, null]).catch(err => [null, err])
}

/**
 * 列表请求hook 
 * @param { Function } requery 请求函数 
 * @param { Object } initQuery 初始查询参数 
 * @param { Array } initList 初始列表
 * @returns { Array }
 *  - list 当前列表
 *  - util
 *    - reset 重置设置爱
 *      isEnd 是否已获取所有数据
 *      loadList 请求列表,叠加
 *      query 查询数据 state
 *      currentQuery 查询数据 ref
 *      reloadList 请求类表, 重置
 *      updateQuery 更新查询数据
 * 
 * 
 * @example
 * 
 * const [ list, util ] = useList( load, { sort: 1, type: 2, pageNo: 0, pageSize: 10 } )
 * 
 * 模板
 * 
 * <div>
 *  
 *  <SearchBar onSearch={ util.reloadList } ></SearchBar>
 * 
 *  <List>
 *     { list.map(item =>  <List-item :key={item.id} > { ... } </List-item> ) }
 *  </List>
 * </div>
 * 
 */

function useList(requery, initQuery, initList=[]){

    
    const [ list, setList ] = useState(initList)
    const [ isEnd, setEnd ] = useState(false)
    const [ query, setQuery ] = useState(initQuery)
    const [ queryCache, setQueryCache ] = useState(initQuery)
    const currentQuery = useRef(initQuery)

    const updateQuery = useCallback(
        data => {
            currentQuery.current = { ...currentQuery.current, ...data }
            setQuery( prev => ({...prev, ...data}) )
        },

        [ query, setQuery ]
    )
    
    // 请求列表,叠加数据
    const loadList = useCallback( async () => {

        if(isEnd) return

        currentQuery.current = { ...currentQuery.current, pageNo: currentQuery.current.pageNo + 1 }

        const [ res, err ] = await asyncVessel(requery(currentQuery.current))

        if(err) return

        updateQuery(currentQuery.current)
        
        setEnd( !res.result || !res.result.length)
        setList( prev => [ ...prev, ...res.result ]  )
        
    }, [ isEnd, setEnd, setList, requery ])
    
    
    // 请求列表, 重载数据
    const reloadList = useCallback( async () => {
        
        setEnd(false)

        updateQuery({ pageNo: 1 })
        setList([])

        const [ res, err ] = await asyncVessel(requery(currentQuery.current))

        if(err) return

        setList( [...res.result] )
        
    }, [ isEnd, setEnd, setList, requery ])

    
    // 重置设置
    const reset = useCallback(
        () => {
            setEnd(false)
            updateQuery(queryCache)
        },
        [ setEnd, updateQuery , queryCache]
    )

    const util =  {
        reset,
        isEnd,
        loadList,
        query,
        currentQuery,
        reloadList,
        updateQuery
    }
    
    return [ list, util ]

}

export default useList
问题1 如何设置及更新请求参数
代码语言:javascript
复制
  const [ query, setQuery ] = useState(initQuery)
  const [ queryCache, setQueryCache ] = useState(initQuery)
  const currentQuery = useRef(initQuery)

可以看到这里设置了三类 query, quryCache 用户重置参数, query 用于更新视图, currentQuery 用于获取最新参数. 这样设置的原因需要结合请求及参数的更新来看

  • 分页数更新
代码语言:javascript
复制
       // 请求数据
       const [ res, err ] = await asyncVessel(requery(currentQuery.current))

        if(err) return

        updateQuery(currentQuery.current)  // 更新 query
        
        setEnd( !res.result || !res.result.length)
        setList( prev => [ ...prev, ...res.result ]  )

这里会在请求完成后更新查询参数,主要为了统一分页数,为什么要在请求完成后更新分页数呢?请求新的分页数据前,分页数都是需要自增的, 设想如果我们在请求前更新分页数,而此时请求失败。用户再次请求数据时,将跳过前一次失败的数据。

  • 参数修改

QQ截图20200410191441.png

通常参数的修改也是用户交互的一部分, 简单的通过表单或开关修改, 这是我们需要将参数与组件绑定在一起,但这时就会遇到一个问题。 修改参数后如何更新列表

代码语言:javascript
复制
   // setQuery 更新数据是一个异步的过程,通过设置参数后直接调用load,并不可靠
  const [  query,  setQuery ] = useState({....}) 
  loadList(query)  // 这样只能拿到旧的query值
代码语言:javascript
复制
// 通过 useEffect

useEffect(
 () => {
     loadList( query )
 },
[ query.sort ]
)
//  这样可以获取到 query 最新的值,但丢失了主动触发请求的能力。 
// 并不是每次查询数据的更新都需要列表数据。
// 如果使用中间变量做缓存,那内置query state 就没有多大意义了。
  • useRef 与 useState 的区别
代码语言:javascript
复制
// useState
 const [ query, setQuery ] = useState(initQuery)  // 每次更新 返回新的 query, setQuery

// useRef
const currentQuery = useRef(initQuery)  // 始终返回同一对象

除了返回值不同外,主要的区别有两点

  1. useState 每次更新都返回新的值, useRef 始终指向同一对象。
  2. useState 的值更新将触发视图更新, useRef 不会触发关联视图的更新。
代码语言:javascript
复制
// 封装参数更新函数
 const updateQuery = useCallback(
        data => {
            currentQuery.current = { ...currentQuery.current, ...data }
            setQuery( prev => ({...prev, ...data}) )
        },

        [ query, setQuery ]
    )

正式因为前面的特点,所以独立封装了参数的更新函数,同时更新currentQuery, query 保证取值及视图展示。

问题2 依赖

react hook 与 vue hook 明显的区别之一,react 需要我们手动关联并处理依赖,保证取值的正确及效率.

代码语言:javascript
复制
   //  使用useCallback 只在关联依赖更新时,更新函数。
    const reloadList = useCallback( async () => {
        
        setEnd(false)

        updateQuery({ pageNo: 1 })
        setList([])

        const [ res, err ] = await asyncVessel(requery(currentQuery.current))

        if(err) return

        setList( [...res.result] )
        
    }, [ isEnd, setEnd, setList, requery ])  // setState 函数也在依赖范围内
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 功能
  • 实现
  • 问题1 如何设置及更新请求参数
  • 问题2 依赖
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档