记一次hooks陷阱

今天写一个hook,正想发挥hooks这种高级复用方式来缩短我的开发时间,就出现了一个新bug。

我编写的这个hook用于管理数据列表状态。除了导出内部的状态外,还导出一些方法供外部调用。代码简化如下:

function useDataList() {
  const [rows, setRows] = useState([])
  const [pageIndex, setPageIndex] = useState(0)
  async function loadNextPage() {
    const res = await api.searchData(pageIndex+1)
    setRows(res.data.rows)
    setPageIndex(index+1)
  }
  return {
    rows,
    setPageIndex,
    nextPage
  }
}

然后这样使用这个hook:

function App() {
  const {rows, setPageIndex, loadNextPage} = useDataList()
  useEffect(()=>{
    setPageIndex(-1) // 因为loadNextPage中会给pageIndex加一,而初始我们希望请求第0页,因此设为-1
    // 因为setPageIndex()不会立刻改变pageIndex,因此要在下一个事件循环调用loadNextPage()
    setTimeout(()=>{
      loadNextPage()
    })
  }, [])

  return (
    {/* 这里使用rows渲染列表 */}
  )
}

好,现在问题出现了。api.searchData()请求的是第1页,而不是第0页,你知道为什么吗?

 

原因就在于产生了闭包。

useEffect在App第一次渲染的时候执行,以后不再执行。

这时loadNextPage指向的是第一次App()指向时的loadNextPage,

而这个loadNextPage是第一次执行useDataList时导出的,

它内部的pageIndex保存的是第一次执行useDataList()时的pageIndex的值,也就是0。

因此调用loadNextPage()时,请求的页码是pageIndex+1=0+1=1。

我这种情况和网上说的hooks陷阱有点不一样,但是原理是一样的,都是闭包问题。

 

查阅资料发现,可以使用ahooks的useMemorizedFn解决这个问题。

这个API可以保持传入的函数不变,但是每次函数执行时访问的都是最新的state。

于是代码这样改:

function App() {
  const {rows, setPageIndex, loadNextPage} = useDataList()
  const wrapedLoadNextPage = useMemorized(()=>{
    loadNextPage()
  })
  useEffect(()=>{
    setPageIndex(-1)
    setTimeout(()=>{
      wrapedLoadNextPage()
    })
  }, [])
  return (
    {/* 这里使用rows渲染列表 */}
  )
}

 

最后感叹一下,原本以为新技术哪有什么难的,只要迁移以前的知识就行了呗。

结果发现,同一个功能点,用不同的技术实现就是有差别,新的技术产生新的问题,从而导致项目延期。

比如会使用vue开发网页,使用rn开发APP问题应该不大,结果rn的语法和vue不同。

又比如使用uniapp开发过小程序,那使用rn开发APP应该会挺快,结果RN的生态真的简陋,没有uniapp那么齐全方便。

再比如hooks解决了class组件复用上的一些问题,那用起来应该很顺手,结果出了今天的hooks陷阱的问题。

...

下一次学习新技术,要谨慎。

posted @ 2023-02-19 23:08  hdxg  阅读(63)  评论(0)    收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css