手写几个自定义React Hook

原创
04/01 10:52
阅读数 122

        React的Hook规则其实很简单的,只要保证use打头即可。但是如何理解它的挂载,闭包行为, 异步带来的问题,以及useRef容易滥用,手动管理依赖等等,还真的是有点费力,一般人真架不住它的水这么深!

     举个例子,你很高兴的以为使用useCallback,或useMemo缓存了某个值或函数,然后传递给子组件的props中去,就以为避免了一部分子组件的更新,但可能子组件还需要用  memo(子组件)  这么包裹一下。于是你就memo(子组件)一下,那么现在子组件一定会少了更新吗? 文档上又写了
一句,见下图,这不让人为难了吗?  到底避免不避免子组件更新,还是依赖代码测试的实际结果,否则你任何优化可能只是一厢情愿,甚至有反作用!
                    

      继续,当你的useCallback 中,有一个异步请求,请求5秒后完成,再setXXX, 或dispatch更新组件,那么这5秒的时间中,组件可能已经reRender多次了,根据react的函数性,useCallback的函数操作的state, 已经不是当前页面的state了!我刚刚代码测试了!

      我说了2遍代码测试,是的,即使我这么自信的老鸟,仅通过文档无法确切Hook的具体行为的,在用Hook时,颤颤巍巍,处处 log,如履薄冰。一方面怕被人说写法不地道,为什么不优化,一方面又怕自己玩着花样写,自己给自己埋雷,无法控制它的行为!

      虽说如此,我还是写了一些Hook的,简单记录一下这些Hook:

一、生命周期Hook

    虽然useEffect万能的可以模拟很多生命周期,但我无法忍受代码中有太多的useEffect,我还必须找它的参数,才能知道它的行为,这不很怪异吗?我参考网上代码,实现了create,mount,update,unMount的生命周期钩子,以及相关概念的验证,不多解释了,直接看代码上的注释吧!

    尤其注意的时,mount事件和第一次update事件,由于都是使用useEffect来模拟的,所以它们出现的时机依赖于你在组件内调用的顺序!父子组件的生命周期的执行顺序是最有意义的事情,希望每个人都要深刻理解。

import { useEffect, useRef } from "react";

// useRef, 保证created早于 mounted  因为mounted使用useEffect,所以它是在update之后的事件
export const useCreated = (fn) => {
    const init = useRef(true)
    init.current && (fn() || (init.current = false))
}

export const useMount = (fn) => useEffect(fn, [])
export const useUnMount = (fn) => useEffect(() => fn, [])
export const useUpdate = (fn) => useEffect(fn)


/** 用下面两个组件,测试父子组件的生命周期.  
 * 父组件<Todo>             
 * 子组件<Foo>         
 * 
 * 加载时:父--子--父。
 *      todo created
            foo created
            foo mounted
            foo Update      由于 使用useEffect,这是加载即第一次
        todo mounted
        todo Update         由于 使用useEffect,这是加载即第一次
 *
    更新时:先子后父
 *      foo Update   
        todo Update

    卸载时: 先父后子
        todo UnMount
        foo UnMount
 */

二、useMediaQuery

      由于最近的任务是自适应页面,看到了其它库有这个函数,比如ahook,  @chakra-ui 。ahook库中,名字叫useResponsive,底层使用window.resize来实现的,滑天下之大稽。 公司项目是使用@chakra-ui框架,它的useMeidaQuery用的是标准window.matchMedia,但它的使用需要手写media query表达式,所以我写了一个更简单的Hook, 直接传入指定的断点,返回相应的变量状态即可!    

 // 第一个参数是断点, 3个断点则有4个区间
 // 第二个参数是:onChange函数。 每次区间跳动时,触发一次
 // 组件内 使用方法如下:
  let matches = useMediaQuery([500, 1000, 1500], () => {
    console.log("change media query:", matches)
  })


// JSX绑定值:
      <div>500以内: {matches[0]?"是":"否"}</div>
      <div>1000以内: {matches[1]?"是":"否"}</div>
      <div>1500以内: {matches[2]?"是":"否"}</div>
      <div>1500以上: {matches[3]?"是":"否"}</div>


         效果是:         

useMediaQuery 的源码如下:


/**
 * 输入一组有序断点,返回一组状态值
 * @example  3个断点 生成4个区域
 * let matches = useMediaQuery([500, 1000, 1500], () => {
      console.log("change media query:", matches)
  })
 * @param {Array<Number>} breakpoints  数字数组,  由小到大。
 * @param {Function} onChange  onChange回调。
 */
export default function useMediaQuery(breakpoints, onChange) {

    let [matches, setMatches] = useState([])
    useEffect(() => {
        // 生成 query 表达式
        let start = 1, querys = []
        breakpoints.forEach(bp => {
            querys.push(`(min-width:${start}px) and (max-width:${bp-1}px)`);
            start = bp;
        })
        querys.push(`(min-width:${start}px)`)
        let mqlList = querys.map(q => window.matchMedia(q));
        //添加所有监听, 通过Idx追踪位置
        mqlList.forEach((mql, idx) => {
            matches[idx] = mql.matches
            mql.addEventListener("change", mql.fn = function (ev) {
                matches[idx] = ev.matches
                if (ev.matches) {
                    setTimeout(() => {
                        setMatches([...matches])
                        onChange()
                    }, 0);
                }
            })
        })
        setMatches([...matches]) //更新一下
        return () => {
            mqlList.map(mql => mql.removeEventListener("change", mql.fn))
            mqlList = null
        }
    }, [])
    return matches
}

      最后,在使用useMediaQuery时,很多人是用它配合 CSS IN JS 这样的框架,去定义不同宽度时的样式的。 个人以为这是不合理的用法,因为每次窗口变化,引起状态变化,必然带来一次reRender,带来运行时的消耗。如果只是控制样式,还是要直接写到css 文件中去,才是性能最高的,浏览器一次加载css,执行css的行为。 只有当需求是:不同宽度时,应用的逻辑不一样了,比如大窗口弹窗,小窗口toast一下, 这时才有必要使用useMediaQuery 这样的 Hook。 总之不要以为用上Hook就比用CSS牛逼这样的想法。

     在编写动画上也是这样,能写css动画,理论上就要比js动画性能更好,其道理大致一样!

 

 

 

 

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部
返回顶部
顶部