几种常用的自定义hooks

自 React 16.8 之后,推荐使用函数式组件

Hooks 相关知识点参考:React Hooks完全上手指南

以下记录一些项目中经常用到的自定义hooks

useAsync:异步接口请求

import { useState, useCallback } from 'react'

export default const useAsync = (asyncFunc) => {
  // 设置三个异步逻辑相关的 state
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)
  // 定义一个 callback 用于执行异步逻辑
  const execute = useCallback(() => {
    // 请求开始时,设置 loading 为 true,清除已有数据和 error 状态
    setLoading(true)
    setData(null)
    setError(null)
    return asyncFunc().then(res => {
      // 请求成功,将数据写进 state,设置 loading 为 false
      setData(res)
      setLoading(false)
    }).catch(err => {
      // 请求失败,设置 loading 为 false,并设置错误状态
      setError(error)
      setLoading(false)
    })
  }, [asyncFunc])

  return { execute, loading, data, error }
}

在组件中使用示例

import React from 'react'
import useAsync from './useAsync'

export default function UserList(props) {
  // 通过 useAsync 这个函数,只需要提供异步逻辑的实现
  const {
    execute: fetchUsers,
    data: users,
    loading,
    error,
  } = useAsync(async () => {
    const res = await fetch('http://xxx/xxx')
    const json = await res.json
    return json.data
  })

  return (
    // 根据状态渲染 UI...
  )
}

useUpdate:更新时执行

import { useRef, useEffect } from 'react'

export default function useUpdate(
  callback = () => {},
  dependences,
  initialData = false
) {
  const isInitialMount = useRef(true)
  useEffect(() => {
    // 第一次,也就是mount阶段 不执行onChange,只有后续更新的时候才调用
    // 因为在页面中,一般mount阶段会写请求数据之类的操作
    if (isInitialMount.current && !initialData) {
      isInitialMount.current = false
    } else {
      callback()
    }
  }, dependences)
}

使用示例

useUpdate(() => {
  console.log(count)
}, [count])

useForceUpdate:强制更新

import { useReducer, useLayoutEffect, useRef } from 'react'

export default function useForceUpdate() {
  // 函数组件没有forceUpdate,用这种方法代替
  const [ignored, forceUpdate] = useReducer(x => x + 1, 0)
  const resolveRef = useRef(null)

  useLayoutEffect(() => {
    resolveRef.current && resolveRef.current()
  }, [ignored])

  const promise = () => {
    return new Promise((resolve, reject) => {
      forceUpdate()
      resolveRef.current = resolve
    })
  }

  return { forceUpdate: promise }
}

使用示例

// 函数组件没有forceUpdate,用这种方法代替
const { forceUpdate } = useForceUpdate()
useEffect(() => {
  forceUpdate().then(() => {
    // 得等到上一次渲染完成后,才能拿到最新的宽度和高度
    chart?.forceFit()
  })
}, [count])

useEvent:封装事件处理函数

主要有两个特点

  • 在组件多次render时保持引用一致。
  • 函数内始终能获取到最新的propsstate
import { useLayoutEffect, useCallback, useRef } from 'react'

export default function useEvent(handler) {
  const handlerRef = useRef(null)
  // 视图渲染完成后更新 handlerRef.current 指向
  // 事件回调触发的时机是在视图完成渲染之后,这样可以稳定获取到最新的state与props
  useLayoutEffect(() => {
    handlerRef.current = handler
  })
  // 用useCallback包裹,使得render时返回的函数引用一致
  return useCallback((...args) => {
    const fn = handlerRef.current
    return fn(...args)
  }, [])
}

使用示例

import { useState } from 'react'

function Demo() {
  const [text, setText] = useState('')

  // 不管render多少次,onInputChange都是不变的,做节流、防抖等
  const onInputChange = useEvent((value) => {
    setText(value)
  });

  return <input onChange={onInputChange} />
}

usePrevious:记录上一次的值

import { useEffect, useRef } from 'react'

export default function usePrevious(value) {
  const ref = useRef()

  useEffect(() => {
    ref.current = value
  }, [value])

  return ref.current
}

使用示例

const [count, setCount] = useState(1)
const preCount = usePrevious(count)

useScroll:监听滚动位置

import { useState, useEffect } from 'react'
// 获取横向,纵向滚动条位置
export default const getPosition = () => {
  return {
    x: document.body.scrollLeft,
    y: document.body.scrollTop,
  }
}
export default const useScroll = () => {
  // 定一个 position 这个 state 保存滚动条位置
  const [position, setPosition] = useState(getPosition())
  useEffect(() => {
    const handler = () => {
      setPosition(getPosition())
    }
    // 监听 scroll 事件,更新滚动条位置
    document.addEventListener('scroll', handler)
    return () => {
      // 组件销毁时,取消事件监听
      document.removeEventListener('scroll', handler)
    }
  }, [])
  return position
}

返回顶部功能示例

import React, { useCallback } from 'react'
import useScroll from './useScroll'

export default function ScrollTop (props) {
  const { y } = useScroll()
  const goTop = useCallback(() => {
    document.body.scrollTop = 0
  }, [])

  const style = {
    position: 'fixed',
    right: '10px',
    bottom: '10px',
  }
  // 当滚动条位置纵向超过300时,显示返回顶部按钮。否则不渲染任何UI
  if (y <= 300) return null
  return (
    <button onClick={goTop} style={style}>
      Back to Top
    </button>
  )
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357

推荐阅读更多精彩内容