自定义React Hooks

更新日期: 2022-07-30阅读: 624标签: Hooks

1.为什么使用Hooks

副作用问题:诸如数据获取、订阅、定时执行任务、手动修改reactdom这些行为都可以称为副作用;而Hooks的出现可以使用useEffect来处理这些副作用

复杂的状态管理:之前通常使用redux、mobx这些第三方状态管理器来管理复杂的状态,而Hooks可以使用useReducer、useContext配合实现复杂的状态管理;

开发效率和质量问题:函数式组件比类组件简洁,效率高,性能也好。

2.useState

const [state, setState] = useState(initState)

2.1每次渲染都是独立的闭包

  • 每次渲染都有自己的props和state
  • 每次渲染都有自己的事件处理函数
  • 组件函数每次渲染都会被调用,每一次调用中number的值都是常量,并且并赋予了当前渲染中的状态值
  • 在单次渲染中,props和state始终不变
function Counter1() {
    let [info, setInfo] = useState({ number: 0 });
    const alertNumber = () => {
        setTimeout(() => {
            alert(info.number);
        }, 3000);
    }
    return (
        <div>
            <p>{info.number}</p>
            <button onClick={() => setInfo({ number: info.number + 1 })}>+</button>
            <button onClick={alertNumber}>alertNumber</button>
        </div>
    )
}

2.2函数式更新

如果新的state需要使用先前的state计算得出,可以将函数传递给setState, 该函数将接收先前的state值,并返回一个更新后的值

function Counter2() {
    let [info, setInfo] = useState({ number: 0 });
    function lazy() {
        setTimeout(() => {
            setInfo({ number: info.number + 1 })
        }, 2000);
    }
    function lazyFunction() {
        setTimeout(() => {
            setInfo(info => ({ number: info.number + 1 }));
            setInfo(info => ({ number: info.number + 1 }));
        }, 3000);
    }
    return (
        <div>
            <p>{info.number}</p>
            <button onClick={() => setInfo({ number: info.number + 1 })}>+</button>
            <button onClick={lazy}>lazy</button>
            <button onClick={lazyFunction}>lazyFunction</button>
        </div>
    )
}

2.3惰性初始化

  • initState参数只会在初始渲染中起作用,后续渲染会被忽略
  • 如果初始state需要通过复杂计算获得,则可以传入一个函数,在函数中计算返回初始state, 此函数只在初始渲染时被调用
  • 与class的setState方法不同,useState不会自动合并更新对象,可以用函数式的setState结合展开运算符达到合并对象的效果
function Counter3() {
    let [info, setInfo] = useState(function () {
      console.log('初始状态-----')
      return { number: 0, name: "计数器" };
    });
    console.log("Counter5 render");
    return (
      <div>
        <p>
          {info.name}:{info.number}
        </p>
        <button onClick={() => setInfo({ ...info, number: info.number + 1 })}>
          +
        </button>
        <button onClick={() => setInfo(info)}>+</button>
      </div>
    );
}

3.useEffect

useEffect(callback, array):副作用处理的钩子;它也是componentDidMount()、componentDidUpdate()、componentWillUnmount()、这几个生命周期方法的统一,一个顶三个!

  • 第二个参数如果不写,只要状态改变都会执行。
  • 第二个参数是个空数组时,不管哪个状态改变都不执行,只在组件初始时执行一次。
  • 当第一个回调函数中有return返回值时,表示componentWIllUnmount时执行

4.性能优化

  • 把回调函数和依赖项数组作为参数传入 useCallback, 将返回该回调函数的memoized版本,该回调函数仅在某个依赖项改变时才会更新。 (控制组件什么时候更新)
  • 把创建函数和依赖项数组作为参数传入 useMemo 仅会在某个依赖项改变时才重新计算memoized值 这种优化有助于避免在每次渲染时都进行高开销的计算。 (控制组件是否更新)
let lastAddClick;
let lastChangeName;
function Counter4() {
    let [number, setNumber] = useState(0);
    let [name, setName] = useState('zhufeng');
    //会在每次渲染的时候都 会生成一个新的函数
    //只有在依赖的变量发生变化的时候才会重新生成
    const addClick = useCallback(() => setNumber(number + 1), [number]);
    console.log(lastAddClick === addClick);
    lastAddClick = addClick;
    const changeName = useCallback(() => setName(Date.now()), [name]);
    console.log(lastChangeName === changeName);
    lastChangeName = changeName;
    return (
        <div>
            <p>{name}:{number}</p>
            <button onClick={addClick}>addClick</button>
            <button onClick={changeName}>changeName</button>
        </div>
    )
}

5.Hooks规则

自定义 Hook 必须以 “**use**” 开头吗?必须如此。这个约定非常重要。不遵循的话,由于无法判断某个函数是否包含对其内部 Hook 的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则。

  • 只能在react的函数组件使用调用hook
  • 需要在组件顶层调用,不能在判断啊,循环里调用,因为hook是按顺序执行的,加入放在判断里,第一个调用了,第二次没调用,后面的hook调用提前执行,会导致bug。

每一个 Hook 都有的两个相关函数:mountXxx() 和 updateXxx(),它们分别是 Hook 在 Mount 阶段(即组件的挂载、或者说初始化阶段、又或者说是第一次执行 useXxx()的时候)和 Update阶段(即组件的更新、或者说组件重新渲染阶段)的逻辑。为了方便管理和调用,react 的工程师把 Hook 在 Mount 阶段的逻辑存到 (HooksDispatcherOnMount) 对象中,把 Update 阶段的逻辑存到 (HooksDispatcherOnUpdate) 对象中

const HooksDispatcherOnMount: Dispatcher = {
  readContext,

  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
  useResponder: createDeprecatedResponderListener,
  useDeferredValue: mountDeferredValue,
  useTransition: mountTransition,
};

const HooksDispatcherOnUpdate: Dispatcher = {
  readContext,

  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  useDebugValue: updateDebugValue,
  useResponder: createDeprecatedResponderListener,
  useDeferredValue: updateDeferredValue,
  useTransition: updateTransition,
};

Hook 在 Mount 阶段干了啥?

用 useState 为例。useState 在 Mount 阶段的逻辑写在 mountState() 方法中:

  1. 获取当前 Hook 节点,同时将当前 Hook 添加到 Hook 链表中
  2. 初始化 Hook 的状态,即读取初始 state 值
  3. 创建一个新的链表作为更新队列,用来存放更新操作(setXxx())
  4. 创建一个 dispatch 方法(即 useState 返回的数组的第二个参数:setXxx()),该方法的用途是用来修改 state,并将此更新操作添加到更新队列中,另外还会将该更新和当前正在渲染的 fiber 绑定起来
  5. 返回当前 state 和 修改 state 的方法(dispatch)
function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {

  // 获取当前 Hook 节点,同时将当前 Hook 添加到 Hook 链表中
  const hook = mountWorkInProgressHook();
  
  // 初始化 Hook 的状态,即读取初始 state 值
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  
  // 创建一个新的链表作为更新队列,用来存放更新(setXxx())
  const queue = (hook.queue = {
    pending: null,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  });
  
  // 创建一个 dispatch 方法(即 useState 返回的数组的第二个参数:setXxx()),
  // 该方法的作用是用来修改 state,并将此更新添加到更新队列中,另外还会将改更新和当前正在渲染的 fiber 绑定起来
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  
  // 返回当前 state 和 修改 state 的方法
  return [hook.memoizedState, dispatch];
}

存储 Hook 的数据结构——链表

一个函数组件中的所有 Hook 是以 链表 的形式存储的。链表中的每个节点就是一个 Hook

export type Hook = {
    memoizedState: any, // Hook 自身维护的状态
    ...
    queue: UpdateQueue<any, any> | null, // Hook 自身维护的更新队列
    next: Hook | null, // next 指向下一个 Hook
};
const [firstName, setFirstName] = useState('尼古拉斯');
const [lastName, setLastName] = useState('赵四');
useEffect(() => {})


useState 如何处理 state 更新

更新队列链表 queue,用来存放更新操作,链表中的每一个节点就是一次更新 state 的操作(就是调用了一次 setXxx()),以便后面 Update 阶段可以拿到最新的 state。

const queue = (hook.queue = {
      pending: null,
      dispatch: null,
      lastRenderedReducer: basicStateReducer,
      lastRenderedState: (initialState: any),
});

pending:最近一个等待执行的更新

dispatch:更新 state 的方法(setXxx)

lastRenderedReducer: 组件最近一次渲染时用的 reducer (useState 实际上是一个简化版的 useReducer,之所以用户在使用 useState 时不需要传入 reducer,是因为 useState 默认使用 react 官方写好的 reducer:basicStateReducer)

lastRenderedState:组件最近一次渲染的 state


来自:https://segmentfault.com/a/1190000042233578

链接: https://www.fly63.com/article/detial/11960

React将引入Hooks,你怎么看?

近日,据 MIT Technology Review 报道,一位名为“Repairnator”的机器人在 GitHub 上“卧底”数月,查找错误并编写和提交修复补丁,结果有多个补丁成功通过并被采纳,这位 Repairnator 到底是如何拯救程序员于水火的呢?

精通React今年最劲爆的新特性——React Hooks

你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗?你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗?你在还在为组件中的this指向而晕头转向吗?这样看来,说React Hooks是今年最劲爆的新特性真的毫不夸张。

使用react hooks实现自己的context-redux

我们将userReducer函数返回的原始dispath命名为origin_dispatch,自定义dispatch函数,当action为函数的时候,我们执行action函数,并将origin_dispatch当作参数传进去;action不是函数,直接调用origin_dispatch,不做处理

useEffect Hook 是如何工作的?

使用useEffect 就像瑞士军刀。它可以用于很多事情,从设置订阅到创建和清理计时器,再到更改ref的值。与 componentDidMount、componentDidUpdate 不同的是,在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。

React Hooks 你真的用对了吗?

从 React Hooks 正式发布到现在,我一直在项目使用它。但是,在使用 Hooks 的过程中,我也进入了一些误区,导致写出来的代码隐藏 bug 并且难以维护。这篇文章中,我会具体分析这些问题,并总结一些好的实践,以供大家参考

如何用 Hooks 来实现 React Class Component 写法?

Hooks 的 API 可以参照 React 官网。本文主要是结合 Demo 详细讲解如何用 Hooks 来实现 React Class Component 写法,让大家更深的理解 Hooks 的机制并且更快的入门。 注意:Rax 的写法和 React 是一致的

React-Hooks

以下是上一代标准写法类组件的缺点,也正是hook要解决的问题,型组件很难拆分和重构,也很难测试。业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。

React Hooks与setInterval

Hooks出来已经有段时间了,相信大家都用过段时间了,有没有小伙伴们遇到坑呢,我这边就有个 setInterval 的坑,和小伙伴们分享下解决方案。写个 count 每秒自增的定时器,如下写法结果,界面上 count 为 1 ?

React Hooks 底层解析[译]

对于 React 16.7 中新的 hooks 系统在社区中引起的骚动,我们都有所耳闻了。人们纷纷动手尝试,并为之兴奋不已。一想到 hooks 时它们似乎是某种魔法,React 以某种甚至不用暴露其实例

React Hooks实践

9月份开始,使用了React16.8的新特性React Hooks对项目进行了重构,果然,感觉没有被辜负,就像阮一峰老师所说的一样,这个 API 是 React 的未来。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!