天天看点

useList 列表hook

useList 列表hook

chart.gif

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

功能

  • 设置查询参数
  • 设置初始列表值
  • 请求列表,叠加数据
  • 请求列表,重置数据

实现

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 如何设置及更新请求参数

const [ query, setQuery ] = useState(initQuery)
  const [ queryCache, setQueryCache ] = useState(initQuery)
  const currentQuery = useRef(initQuery)           

复制

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

  • 分页数更新
// 请求数据
       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 ]  )           

复制

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

  • 参数修改
useList 列表hook

QQ截图20200410191441.png

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

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

复制

// 通过 useEffect

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

复制

  • useRef 与 useState 的区别
// useState
 const [ query, setQuery ] = useState(initQuery)  // 每次更新 返回新的 query, setQuery

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

复制

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

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

        [ query, setQuery ]
    )           

复制

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

问题2 依赖

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

//  使用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 函数也在依赖范围内           

复制

  • 深入理解:React hooks是如何工作的?
  • left-vue-hooks vue的hooks工具包