react hooks笔记(1)

主要内容是关于学习react hooks中遇到的一些问题。

闭包问题

问题

在使用react hooks的时候遇到以下场景:

function App() {
  const [count, setCount] = useState(1)
  useEffect(() => {
    setInterval(() => {
      console.log(count);
    }, 1000)
  }, [])
  return (
    <div>
      count: {count}
      <br />
      <button
        onClick={() => {
          setCount((val) => val + 1);
        }}
      >
        增加 1
      </button>
    </div>
  )
}

会发现,我们页面上展示的count会因为点击按钮而增加,但是useEffect中打印出来的count始终是1,为什么呢?

  • 因为我们的react hooks的定义用的是链表的数据结构,大致定义如下:

type Hook = {
  memorizedState: any,
  baseState: any,
  baseUpdate: Update,
  next: Hook | null
}

那么我们分析上面的情况,

  • 首次执行时,我们的第一个hooks就是useState,它的next指向第二个hooks,也就是useEffect

  • 当我们点击按钮,count+1,此时会触发组件的rerender,此时又会触发useState,但是初始值baseState还是1。

  • 因为useEffect第二个参数为空数组,依赖项为空也就是只有第一次渲染时执行,所以interval还是第一次渲染时的interval,因为useEffect中的callback没有被调用。

解决方法

添加依赖项 + 清除定时器

我们要解决以上的问题,就要让useEffect中的callback在每次rerender后,接着useState去执行,所以要在第二个参数中添加count为依赖项。

并且,因为过程中会多次设置定时器,所以每次要判断是否存在定时器,并且进行清除,这里用的是useRef去缓存timer

function App() {
  const [count, setCount] = useState(1)
  const timer = useRef<number | null>(null);
  useEffect(() => {
    if (timer.current) {
      clearInterval(timer.current);
    }

    timer.current = setInterval(() => {
      console.log(count);
    }, 1000)
  }, [count])
  return (
    <div>
      count: {count}
      <br />
      <button
        onClick={() => {
          setCount((val) => val + 1);
        }}
      >
        增加 1
      </button>
    </div>
  )
}

useRef

上一步骤用到的是useRef去缓存定时器,但其实我们可以直接用useRef去缓存count

function App() {
  const [count, setCount] = useState(1)
  const lastCount = useRef(count)
  useEffect(() => {
    setInterval(() => {
      console.log(lastCount.current);
    }, 1000)
  }, [])
  return (
    <div>
      count: {count}
      <br />
      <button
        onClick={() => {
          setCount((val) => val + 1)
          lastCount.current += 1
        }}
      >
        增加 1
      </button>
    </div>
  )
}

useLatest

可以看出方法2比较简便,所以我们可以基于方法2封装一个自定义hooks,用于获取最新的值。

import { useRef } from 'react';

function useLatest<T>(value: T) {
  const ref = useRef(value);
  ref.current = value;

  return ref;
}

export default useLatest;

useEvent

react官方也有相似的处理方法,现处于RFC阶段,它能在保持事件处理函数不变的情况下处理函数中可变的propsstate

useMemorizedFn

上面的思路可以做个总结,就是不管我们的状态怎么变,我们只要拿到最新的状态就好了,对此我们可以封装如下的hooks,对传入的function进行持久化处理。

  • 使用useRef来保证它的引用地址不变,值是最新的。

  • 使用useMemo来缓存fn,不用每次都创建一个新的fn

function useMemorizedFn(fn) {
  const fnRef = useRef(fn);

  fnRef.current = useMemo(() => fn, [fn]);

  const memorizedFn = useRef();
  if (!memorizedFn.current) {
    memorizedFn.current = function (this, ...args) {
      return fnRef.current.apply(this, args);
    }
  }
  return memorizedFn.current
}

手写实现useState、useEffect

useState

第一版

这样写实现了useState的基本功能,其中render表示组件的渲染,但是rerender之后还是初始值,因为会重新调用useState

function useState(initvalue) {
  let state = initValue;
  function setState(newValue) {
    state = newState;
    render()
  }
  return [state, setState]
}

第二版

useState之外定义了state,虽然解决了初始值的问题,但是这样只能有一个state

let _state;
function useState(initValue) {
  _state = _state || initValue;
  function setState(newValue) {
    _state = newState;
    render()
  }
  return [_state, useState]
}

第三版

这里用了数组来代替链表,用指针代替nextcursor++来避免hooks之间的相互影响。

let memorizedState = []; // hooks的数组,用以模拟链表
let cursor = 0;
function useState(initValue) {
  memorizedState[cursor] = memorizedState[cursor] || initValue;
  const currentCursor = cursor;
  function setState(newValue) {
    memorizedState[currentCursor] = newValue;
    render()
  }
  return [memorizedState[cursor++], setState]
}

useEffect

useEffect的实现如下,主要是遍历依赖数组,判断依赖有无变化,有变化就执行callback

function useEffect(callback, depArray) {
  const hasNoDeps = !depArray;
  const deps = memorizedState[cursor];
  const hasChangedDeps = deps ? !depArray.every((el, i) => el === deps[i]) : true;

  if (hasNoDeps || hasChangedDeps) {
    callback();
    memorizedState[cursor] = depArray;
  }
  cursor++;
}

  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值