React Hooks

如果你刚开始接触 Hook,那么可能需要先查阅 Hook 概览。你也可以在 Hooks FAQ 章节中获取有用的信息。

# 一、什么是 Hooks

  • React 一直都提倡使用 **** 函数组件 **,但是有时候需要使用 state 或者其他一些功能时,只能使用 ** 类组件 ****,因为函数组件没有实例,没有生命周期函数,只有类组件才有

  • Hooks 是 React 16.8 新增的特性,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性

  • 如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其它转化为 class。现在你可以直接在现有的函数组件中使用 Hooks

  • 凡是 use 开头的 React API 都是 Hooks

import React, { useState } from "react";

function Example() {
  // 声明一个新的叫做 “count” 的 state 变量
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

# 二、Hooks 解决的问题

# 1. 类组件的不足

  • 状态逻辑难复用: 在组件之间复用状态逻辑很难,可能要用到 render props渲染属性)或者 HOC高阶组件),但无论是渲染属性,还是高阶组件,都会在原先的组件外包裹一层父容器(一般都是 div 元素),导致层级冗余

  • 趋向复杂难以维护:

    • 在生命周期函数中混杂不相干的逻辑(如:在 componentDidMount 中注册事件以及其他的逻辑,在 componentWillUnmount 中卸载事件,这样分散不集中的写法,很容易写出 bug )
    • 类组件中到处都是对状态的访问和处理,导致组件难以拆分成更小的组件
  • this 指向问题

    :父组件给子组件传递函数时,必须绑定 this

    • react 中的组件四种绑定 this 方法的区别
class App extends React.Component<any, any> {
  handleClick2;

  constructor(props) {
    super(props);
    this.state = {
      num: 1,
      title: " react study",
    };
    this.handleClick2 = this.handleClick1.bind(this);
  }

  handleClick1() {
    this.setState({
      num: this.state.num + 1,
    });
  }

  handleClick3 = () => {
    this.setState({
      num: this.state.num + 1,
    });
  };

  render() {
    return (
      <div>
        <h2>Ann, {this.state.num}</h2>
        <button onClick={this.handleClick2}>btn1</button>
        <button onClick={this.handleClick1.bind(this)}>btn2</button>
        <button onClick={() => this.handleClick1()}>btn3</button>
        <button onClick={this.handleClick3}>btn4</button>
      </div>
    );
  }
}

前提:子组件内部做了性能优化,如(React.PureComponent

  • 第一种是在构造函数中绑定 this:那么每次父组件刷新的时候,如果传递给子组件其他的 props 值不变,那么子组件就不会刷新;
  • 第二种是在 render () 函数里面绑定 this:因为 bind 函数会返回一个新的函数,所以每次父组件刷新时,都会重新生成一个函数,即使父组件传递给子组件其他的 props 值不变,子组件每次都会刷新;
  • 第三种是使用箭头函数:父组件刷新的时候,即使两个箭头函数的函数体是一样的,都会生成一个新的箭头函数,所以子组件每次都会刷新;
  • 第四种是使用类的静态属性:原理和第一种方法差不多,比第一种更简洁

综上所述,如果不注意的话,很容易写成第三种写法,导致性能上有所损耗。

# 2. Hooks 优势

  • 能优化类组件的三大问题
  • 能在无需修改组件结构的情况下复用状态逻辑(自定义 Hooks )
  • 能将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
  • 副作用的关注点分离副作用指那些没有发生在数据向视图转换过程中的逻辑,如 ajax 请求、访问原生 dom 元素、本地持久化缓存、绑定 / 解绑事件、添加订阅、设置定时器、记录日志等。以往这些副作用都是写在类组件生命周期函数中的。而 useEffect 在全部渲染完毕后才会执行, useLayoutEffect 会在浏览器 layout 之后, painting 之前执行。

# 三、注意事项

# 四、useState & useMemo & useCallback

  • React 假设当你多次调用 useState 的时候,你能保证每次渲染时它们的 **** 调用顺序 **** 是不变的。
  • 通过在函数组件里调用它来给组件添加一些内部 state,React 会 在重复渲染时保留这个 state
  • useState 唯一的参数就是初始 state
  • useState 会返回一个数组:一个 state,一个更新 state 的函数
    • 在初始化渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同
    • 你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并,而是直接替换
// 这里可以任意命名,因为返回的是数组,数组解构
const [state, setState] = useState(initialState);

# 4.1 使用例子

import React, { useState } from "react";

function Example() {
  // 声明一个叫 "count" 的 state 变量
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

state 初始值为 { count: 0 } ,当用户点击按钮后,我们通过调用 this.setState() 来增加 state.count 。整个章节中都将使用该 class 的代码片段做示例。

等价的 class 示例

如果你之前在 React 中使用过 class,这段代码看起来应该很熟悉:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

# 4.2 每次渲染都是独立的闭包

  • 每一次渲染都有它自己的 Props 和 State
  • 每一次渲染都有它自己的事件处理函数
  • 当点击更新状态的时候,函数组件都会重新被调用,那么每次渲染都是独立的,取到的值不会受后面操作的影响
function Counter2() {
  let [number, setNumber] = useState(0);
  function alertNumber() {
    setTimeout(() => {
      // alert 只能获取到点击按钮时的那个状态
      alert(number);
    }, 3000);
  }
  return (
    <>
      <p>{number}</p>
      <button onClick={() => setNumber(number + 1)}>+</button>
      <button onClick={alertNumber}>alertNumber</button>
    </>
  );
}

# 4.3 函数式更新

  • 如果新的 state 需要通过使用先前的 state 计算得出,那么可以将回调函数当做参数传递给 setState。该回调函数将接收先前的 state,并返回一个更新后的值。
function Counter() {
  let [number, setNumber] = useState(0);
  function lazy() {
    setTimeout(() => {
      // setNumber(number+1);
      // 这样每次执行时都会去获取一遍 state,而不是使用点击触发时的那个 state
      setNumber((number) => number + 1);
    }, 3000);
  }
  return (
    <>
      <p>{number}</p>
      <button onClick={() => setNumber(number + 1)}>+</button>
      <button onClick={lazy}>lazy</button>
    </>
  );
}

# 4.4 惰性初始化 state

  • initialState 参数只会在组件的初始化渲染中起作用,后续渲染时会被忽略
  • 如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用
function Counter5(props) {
  console.log("Counter5 render");
  // 这个函数只在初始渲染时执行一次,后续更新状态重新渲染组件时,该函数就不会再被调用
  function getInitState() {
    return { number: props.number };
  }
  let [counter, setCounter] = useState(getInitState);
  return (
    <>
      <p>{counter.number}</p>
      <button onClick={() => setCounter({ number: counter.number + 1 })}>
        +
      </button>
      <button onClick={() => setCounter(counter)}>setCounter</button>
    </>
  );
}

# 4.5 性能优化

# 4.5.1 Object.is (浅比较)

  • Hook 内部使用 Object.is 来比较新 / 旧 state 是否相等
  • 与 class 组件中的 setState 方法不同,如果你修改状态的时候,传的状态值没有变化,则不重新渲染
  • 与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果
function Counter() {
  const [counter, setCounter] = useState({ name: "计数器", number: 0 });
  console.log("render Counter");
  // 如果你修改状态的时候,传的状态值没有变化,则不重新渲染
  return (
    <>
      <p>
        {counter.name}:{counter.number}
      </p>
      <button
        onClick={() => setCounter({ ...counter, number: counter.number + 1 })}
      >
        +
      </button>
      <button onClick={() => setCounter(counter)}>++</button>
    </>
  );
}
j;

# 4.5.2 减少渲染次数

  • 默认情况,只要父组件状态变了(不管子组件依不依赖该状态),子组件也会重新渲染
  • 一般的优化:
    1. 类组件:可以使用 pureComponent
    2. 函数组件:使用 React.memo ,将函数组件传递给 memo 之后,就会返回一个新的组件,新组件的功能:如果接受到的属性不变,则不重新渲染函数
  • 但是怎么保证属性不会变尼?这里使用 useState ,每次更新都是独立的const [number,setNumber] = useState(0) 也就是说每次都会生成一个新的值(哪怕这个值没有变化),即使使用了 React.memo ,也还是会重新渲染
import React, { useState, memo, useMemo, useCallback } from "react";

function SubCounter({ onClick, data }) {
  console.log("SubCounter render");
  return <button onClick={onClick}>{data.number}</button>;
}
SubCounter = memo(SubCounter);
export default function Counter6() {
  console.log("Counter render");
  const [name, setName] = useState("计数器");
  const [number, setNumber] = useState(0);
  const data = { number };
  const addClick = () => {
    setNumber(number + 1);
  };
  return (
    <>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <SubCounter data={data} onClick={addClick} />
    </>
  );
}
  • 更深入的优化:
    1. useCallback:接收一个内联回调函数参数和一个依赖项数组(子组件依赖父组件的状态,即子组件会使用到父组件的值) ,useCallback 会返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新
    2. useMemo:把创建函数和依赖项数组作为参数传入 useMemo ,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算

# 基础 Hook

# useState

import React, { useEffect } from "react";
import { useState } from "react";

const Hook = () => {
  const [count, setCount] = useState({
    name: "zc",
    age: 18,
  });
  const [arr, setArr] = useState([1, 2, 3]);
  const [func, setFunc] = useState(() => {
    return 1;
  });

  const handle = () => {
    setCount({
      ...count,
      age: count.age + 1,
    });
  };
  return (
    <>
      <h1>{count.name}</h1>
      <h1>{count.age}</h1>
      <h1>{arr}</h1>
      <h1>{func}</h1>
      <button onClick={handle}>增加</button>
      <button
        onClick={() => {
          setArr(() => {
            arr.push(4);
            return [...arr];
          });
        }}
      >
        增加
      </button>
    </>
  );
};

export default Hook;

# useEffect

  • effect(副作用):指那些没有发生在数据向视图转换过程中的逻辑,如 ajax 请求、访问原生 dom 元素、本地持久化缓存、绑定 / 解绑事件、添加订阅、设置定时器、记录日志等。
  • 副作用操作 **** 可以分两 **** 类:**** 需要清除的和不需要清除的 ****。
  • 原先在函数组件内(这里指在 React 渲染阶段)改变 dom 、发送 ajax 请求以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性
  • useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不过被合并成了一个 API
  • useEffect 接收一个函数,该函数会在组件渲染到屏幕之后才执行,该函数有要求:要么返回一个能清除副作用的函数,要么就不返回任何内容
  • componentDidMountcomponentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。
import React from "react";
import { useEffect, useState } from "react";

const Hook1 = () => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log(1);
  }, []); // []不监听任何状态

  return (
    <div>
      <h1>useEffect</h1>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>点击</button>
    </div>
  );
};

export default Hook1;

清除 effect

通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源。要实现这一点, useEffect 函数需返回一个清除函数。以下就是一个创建订阅的例子:

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // 清除订阅
    subscription.unsubscribe();
  };
});

# useContext

useContext & createContext. 一起使用

  1. 先使用 createContext 创建父容器

  2. 暴露给子组建

    <MyContext.Provider value={count}>
      <Comson />
    </MyContext.Provider>
  3. 子组件使用 useContext 接受父容器后使用 const value = useContext(MyContext);

全局变量,跨级传值

const value = useContext(MyContext);

接收一个 context 对象( React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>value prop 决定。

当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。即使祖先使用 React.memo shouldComponentUpdate ,也会在组件本身使用 useContext 时重新渲染。

别忘记 useContext 的参数必须是 context 对象本身

  • 正确: useContext(MyContext)
  • 错误: useContext(MyContext.Consumer)
  • 错误: useContext(MyContext.Provider)

调用了 useContext 的组件总会在 context 值变化时重新渲染。如果重渲染组件的开销较大,你可以 通过使用 memoization 来优化

import React, { useContext, useState, createContext } from "react";

const MyContext = createContext();

const Comson = () => {
  const count = useContext(MyContext);
  return (
    <>
      <h1>子组件</h1>
      <p>我是子组件 ---- {count}</p>
    </>
  );
};

const HookuseContext = () => {
  const [count, setCount] = useState(0);

  const handle = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <h1>{count}</h1>
      <MyContext.Provider value={count}>
        <Comson />
      </MyContext.Provider>
      <button onClick={handle}>点击</button>
    </div>
  );
};

export default HookuseContext;

提示

如果你在接触 Hook 前已经对 context API 比较熟悉,那应该可以理解, useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>

useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context。

# 额外的 Hook

以下介绍的 Hook,有些是上一节中基础 Hook 的变体,有些则仅在特殊情况下会用到。不用特意预先学习它们。

# useRef

const refContainer = useRef(initialValue);

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数( initialValue )。返回的 ref 对象在组件的整个生命周期内保持不变。

一个常见的用例便是命令式地访问子组件:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

本质上, useRef 就像是可以在其 .current 属性中保存一个可变值的 “盒子”。

你应该熟悉 ref 这一种访问 DOM 的主要方式。如果你将 ref 对象以 <div ref={myRef} /> 形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。

然而, useRef()ref 属性更有用。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。

这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: ...} 对象的唯一区别是, useRef 会在每次渲染时返回同一个 ref 对象。

请记住,当 ref 对象内容发生变化时, useRef不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现

# useMemo

执行时机不同,**useEffect ** 是在 componentDidMount 以后执行的,而 useMemo 是在组件渲染过程中执行的。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

返回一个 memoized 值。

把 “创建” 函数和依赖项数组作为参数传入 useMemo ,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo

如果没有提供依赖项数组, useMemo 在每次渲染时都会计算新的值。

** 你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。** 将来,React 可能会选择 “遗忘” 以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 useMemo 的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo ,以达到优化性能的目的。

注意

依赖项数组不会作为参数传给 “创建” 函数。虽然从概念上来说它表现为:所有 “创建” 函数中引用的值都应该出现在依赖项数组中。未来编译器会更加智能,届时自动创建数组将成为可能。

我们推荐启用 eslint-plugin-react-hooks 中的 exhaustive-deps 规则。此规则会在添加错误依赖时发出警告并给出修复建议。

import React, { useMemo, useEffect, useState } from "react";
const Hook2 = () => {
  const [count, setCount] = useState(0);

  let res = useMemo(() => {
    return count;
  }, []);
  
  const handle = () => {
    setCount(count + 1);
  };
  
  return (
    <div>
      <div>{count}</div>
      <p>{res}</p>

      <button onClick={handle}>点击</button>
    </div>
  );
};

export default Hook2;

# useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

返回一个 memoized 回调函数。

把内联回调函数及依赖项数组作为参数传入 useCallback ,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate )的子组件时,它将非常有用。

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

注意

依赖项数组不会作为参数传给回调函数。虽然从概念上来说它表现为:所有回调函数中引用的值都应该出现在依赖项数组中。未来编译器会更加智能,届时自动创建数组将成为可能。

我们推荐启用 eslint-plugin-react-hooks 中的 exhaustive-deps 规则。此规则会在添加错误依赖时发出警告并给出修复建议。

import React from "react";
import { useCallback } from "react";
import { useState } from "react";

const HookUseCallbak = () => {
  const [count, setCount] = useState(0);

  let callback = useCallback(() => {
    console.log("callback");
    return count;
  }, [count]);

  return (
    <div>
      <p>{count}</p>
      <h2>{callback()}</h2>
      <button onClick={() => setCount(count + 1)}>点击</button>
    </div>
  );
};

export default HookUseCallbak; 

# forwardRef

配合 useRef 获取子组件的 ref 引用

import React, { useRef, forwardRef } from "react";

const Com = forwardRef((props, ref) => {
  return (
    <div>
      <p ref={ref}> 我是子组建</p>
    </div>
  );
});

const HookUseCallbak = () => {
  const refdemo = useRef(null);
  return (
    <div>
      <h1>213</h1>
      <Com ref={refdemo} />

      <button onClick={() => console.log(refdemo)}>点击</button>
    </div>
  );
};

export default HookUseCallbak;

# useImperativeHandle

自定义 暴露~

useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle 可以让你在使用 ref自定义暴露给父组件的实例值 。在大多数情况下,应当避免使用 ref 这样的命令式代码。 useImperativeHandle 应当与 forwardRef 一起使用:

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

在本例中,渲染 <FancyInput ref={inputRef} /> 的父组件可以调用 inputRef.current.focus()

import React, { useRef, forwardRef } from "react";
import { useImperativeHandle } from "react";

const Com = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    name: "zc",
    add:()=>{
        console.log('111');
    }
  }));

  return (
    <div>
      <p> 我是子组建</p>
    </div>
  );
});

const HookUseCallbak = () => {
  const refdemo = useRef(null);
  return (
    <div>
      <h1>213</h1>
      <Com ref={refdemo} />

      <button onClick={() => console.log(refdemo)}>点击</button>
    </div>
  );
};

export default HookUseCallbak;

# useLayoutEffect

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前, useLayoutEffect 内部的更新计划将被同步刷新。

尽可能使用标准的 useEffect 以避免阻塞视觉更新。

import React from "react";
import { useEffect } from "react";
import { useLayoutEffect } from "react";
import { useState } from "react";

const HookuseLayoutEffect = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("useEffect");
    return () => {
      console.log("useEffect-return");
    };
  });

  // useEffect 是在 componentDidMount 以后执行的,useLayoutEffect在浏览器执行绘制之前执行(会阻塞组件挂载,慎用)
  // 慎用 -- 会阻塞组件的挂载
  useLayoutEffect(() => {
    console.log("useLayoutEffect");
    return () => {
      console.log("useLayoutEffect-return");
    };
  });

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(2)}>点击</button>
    </div>
  );
};

export default HookuseLayoutEffect;

MLNOuG

# 自定义 Hook(*)

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。

  1. 自定义的 hook,必须以 use 开头
  2. 自定义的 hook,可以使用这些 hook(useSate,useEffect…)来封装
import React, { useState } from "react";

const useCus = (val, num) => {
  const [count, setCount] = useState(val);

  const add = () => {
    setCount(count + num);
  };

  return {
    count,
    add,
  };
};

const Hook3 = () => {
  let { count, add } = useCus(10, 2);

  return (
    <div>
      <div>{count}</div>
      <button onClick={() => add()}>点击</button>
    </div>
  );
};

export default Hook3;

useInstance.ts

import { useRef, useCallback, useEffect } from "react";

export default function useInstance<P extends {}, S extends {}, C extends {}>(props: P, state: S, custom?: C ) {
  const instanceRef = useRef({
    props,
    state,
    custom,
  });

  useEffect(() => {
    instanceRef.current.props = props;
    instanceRef.current.state = state;
    instanceRef.current.custom = custom;
  });

  const getProps = useCallback(():P => {
    return instanceRef.current.props;
  }, [instanceRef]);

  const getState = useCallback(():S => {
    return instanceRef.current.state;
  }, [instanceRef]);

  const getCustom = useCallback(() => {
    return instanceRef.current.custom;
  }, [instanceRef]);

  return { getProps, getState, getCustom }
}

useTimeout.ts

import { useRef, useCallback, useEffect } from "react";

export default function useTimeout() {
  const timeoutRef = useRef<null | number>(null);

  useEffect(() => {
    return () => {
      if (timeoutRef.current !== null) {
        window.clearTimeout(timeoutRef.current);
      }
    }
  }, [timeoutRef]);

  const setTimeout = useCallback((func, delay) => {
    if (timeoutRef.current !== null) {
      window.clearTimeout(timeoutRef.current);
    }
    timeoutRef.current = window.setTimeout(() => {
      timeoutRef.current = null;
      func();
    }, delay);
  }, [timeoutRef]);

  const clearTimeout = useCallback((func, delay) => {
    if (timeoutRef.current !== null) {
      window.clearTimeout(timeoutRef.current);
    }
  }, [timeoutRef]);

  return [setTimeout, clearTimeout];
}

useCacheBind.js

import { useRef, useCallback } from "react";

export default function useCachebind() {
  const cacheBindRef = useRef<any>({});

  const getCacheBind = useCallback((name: string, fn: Function, ...aArgs: any[]) => {
    const cachedBind = cacheBindRef.current;
    let cacheKey = `${name}_${aArgs.join('_')}`;
    let cache = cachedBind[cacheKey];
    if (cache) {
      let argsEqual = cache.oriFn === fn;
      for (let i = 0; i < cache.args.length; i++) {
        if (cache.args[i] !== aArgs[i]) {
          argsEqual = false;
          break;
        }
      }
      if (argsEqual) {
        return cache.fn;
      }
    }

    cache = {
      fn: fn.bind(null, ...aArgs),
      oriFn: fn,
      args: aArgs
    };
    cachedBind[cacheKey] = cache;


    return cache.fn;
  }, [cacheBindRef]);
  return { getCacheBind };
}
Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2015-2021 zhou chen
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信