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 ] )
复制
这里会在请求完成后更新查询参数,主要为了统一分页数,为什么要在请求完成后更新分页数呢?请求新的分页数据前,分页数都是需要自增的, 设想如果我们在请求前更新分页数,而此时请求失败。用户再次请求数据时,将跳过前一次失败的数据。
- 参数修改
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) // 始终返回同一对象
复制
除了返回值不同外,主要的区别有两点
- useState 每次更新都返回新的值, useRef 始终指向同一对象。
- 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工具包