> For the complete documentation index, see [llms.txt](https://1425816423.gitbook.io/my-knowledge-base/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://1425816423.gitbook.io/my-knowledge-base/qian-duan-ji-shu/react/react-hook-todo.md).

# React Hook（todo）

## React Hook

## useRef

### **作用**

**useRef返回一个包含current属性的对象，并且改变此对象的值不会导致重新渲染。**

useRef通常用于以下三种场景:

* 保存一个值，以便在下次重新渲染时仍能获取该值
* 获取DOM节点
* 避免重新创建引用内容

### 保存一个值

useRef()保存一个值，并在后面的重新渲染中仍能获取当前的值。

```jsx
import React, {useState, useRef} from 'react';

export function App(props) {
  const [val,setVal] = useState(0)
  const s = useRef(1)
  const a = {current: 0}

   setInterval(()=>{
     setVal(val+1)
     s.current += 1
     a.current += 1
     console.log(s,a)
   },3000);

  return (
    <div className='App'>
      <div> {val} </div>
    </div>
  );
}
```

### 获取DOM节点

如果要获取真实的DOM节点，那么需要：

1. useRef()初始值设置为null
2. 将ref对象传递到JSX 节点的ref属性上
3. ref对象的current属性就指向了该DOM节点，如果节点被删除或不存在，则current为null。

```jsx
import React, {useState, useRef} from 'react';

export function App(props) {
  const divRef = useRef(null)
  console.log(divRef)

  return (
    <div className='App'>
      <div ref={divRef}></div>
    </div>
  );
}
```

### 避免重新创建

React 会保存一次初始 ref 值，并在下一次渲染时忽略它。

```jsx
import React, {useState, useRef} from 'react';

function createData(){
  console.log("执行了createData函数")
  return 1
}

export function App(props) {
  const [val, setVal] = useState(0) 
  // 每次重新渲染都会执行createData，造成一定的性能浪费
  // const a = useRef(createData())
  // 可以通过useRef这样来避免重复创建
  const a = useRef(null)
  if(a.current === null){
    a.current = createData();
  }

  // 调用setVal来触发重新渲染
  setInterval(()=>{
    setVal(val+1);
  },1000)

  return (
    <div className='App'>
      <div></div>
    </div>
  );
}
```

## useMemo

useMemo是一个react Hook，它的作用是在重新渲染时缓存计算结果，来提升性能。

### 避免昂贵的重新计算

某些时候我们可能会有某个计算量非常庞大的函数，这个函数执行起来非常的慢，理想情况下是将这个计算的结果缓存起来，只有当某些变量改变时才执行函数重新计算，这个时候就可以使用useMemo。这其实和vue的computed相似。

```tsx
function slowFunction(num) {
    for (let i = 0; i < 10000; i++) {
      for (let i = 0; i < 10000; i++) {}
    }
    return num * 10;
  }

 const numValue = slowFunction(num);
```

```tsx
function slowFunction(num) {
    for (let i = 0; i < 10000; i++) {
      for (let i = 0; i < 10000; i++) {}
    }
    return num * 10;
  }

 const numValue = useMemo(() => slowFunction(num), [num]);
```

第一种情况，每次组件重新渲染时都会导致`slowFunction`函数重新计算，但是在第二种情况下，react会缓存`slowFunction`的计算结果，只有当`num`改变时，才会重新计算。

useMemo所带来的性能提升可以查看下面示例：

<https://codesandbox.io/s/usememobi-mian-ang-gui-de-chong-xin-ji-suan-ilf1dt>

### 避免子组件重新渲染

当父组件的Props或者state改变时，会导致组件自身和其所有子孙组件都重新渲染，但是有时候我们不需要让子组件重新渲染，这个时候可以使用`memo` ，通过memo缓存组件，如果该组件的所有Prop都没有改变，则不会重新渲染该组件。

```tsx
// App.jsx
import "./styles.css";
import Child from "./Child";
import { useState } from "react";

export default function App() {
  const [re, reRender] = useState(1);

  console.log("父组件更新");

  return (
    <div className="App">
      <h1>避免子组件重新渲染</h1>
      <button onClick={() => reRender((n) => n + 1)}>组件重新渲染</button>
      <div>
        <Child text="使用memo" />
      </div>
    </div>
  );
}

// Child.jsx
import { memo } from "react";

function Child({ text }) {
  console.log("子组件渲染");

  return <div>Child: {text + "的组件"}</div>;
}

export default memo(Child);
```

<https://codesandbox.io/s/memobi-mian-zu-jian-chong-xin-xuan-ran-ydmlgo>

## useCallback

useCallback是一个react Hook，用于在重新渲染期间缓存函数声明。

useCallback需要传递两个参数：一个需要被缓存的函数和一个依赖数组。

**react会通过`Object.is`来比较上一次渲染时的依赖数组的每一项与当前的值是否相同，如果全部相同则会返回上一次的函数，否则`useCallback`会返回此次渲染的函数。**

### 跳过组件重新渲染

之前说过react可以通过使用`memo` 来跳过组件重新渲染，以此来优化性能。

```tsx
// App.jsx
import { useState, memo } from "react";

const Child = memo(function ({ text }) {
  console.log("子组件渲染");

  return <div>Child: {text + "的组件"}</div>;
});

export default function App() {
  const [re, reRender] = useState(1);

  console.log("父组件更新");

  return (
    <div className="App">
      <h1>避免子组件重新渲染</h1>
      <button onClick={() => reRender((n) => n + 1)}>组件重新渲染</button>
      <div>
        <Child text="使用memo" />
      </div>
    </div>
  );
}

```

只有当组件Child的`props`有更改的情况下才会重新渲染，否则不会重新渲染组件。

但是有时候memo并不会按照我们想象的情况来工作，当我们传递一个函数作为prop到Child组件时，由于每次App组件重新渲染都会重新执行app组件函数，遇到函数声明或者箭头函数等都会创建一个新的函数，这时memo通过Object.is比较两个函数时会认为prop更改了，就会导致memo优化失效。

```tsx
import { useState, memo } from "react";

const Child = memo(function ({ fn}) {
  return <div>Child</div>;
});

export default function App() {
	const [num, setNum] = useState(1)
  // 每当App函数重新执行时遇到函数声明都会创建一个新的函数
  function fn(num){ return num * 2 }

  console.log("父组件更新");

  return (
    <div className="App">
      <h1>避免子组件重新渲染</h1>
      <button onClick={() => setNum((n) => n + 1)}>组件重新渲染</button>
      <div>
        <Child fn={fn} />
      </div>
    </div>
  );
}
```

这个时候需要利用`useCallback`缓存函数，将`num`作为依赖项，只有当`num`改变时才会重新执行函数声明，创建一个新的函数。

```tsx
import { useState, memo } from "react";

const Child = memo(function ({ fn}) {
  return <div>Child</div>;
});

export default function App() {
	const [num, setNum] = useState(1)
  // 只有当num改变时才会重新执行函数声明创建一个新的函数。
  const fn = useCallback(function fn(num){ return num * 2 }, [num])

  console.log("父组件更新");

  return (
    <div className="App">
      <h1>避免子组件重新渲染</h1>
      <button onClick={() => setNum((n) => n + 1)}>组件重新渲染</button>
      <div>
        <Child fn={fn} />
      </div>
    </div>
  );
}
```

### 从缓存函数中更新状态

有时，我们可能需要在缓存回调函数中基于之前的值来更新状态。

```tsx
import { useState, useCallback } from "react";

function Child({ todo }) {
  const [num, setNum] = useState(1);
  const fn = useCallback(
    function () {
      const n = num;
      setNum(n + todo);
    },
    [num]
  );

  return (
    <div>
      <h3>num: {num}</h3>
      <button onClick={fn}>num+1</button>
    </div>
  );
}
```

这看起来和useState更新状态很像，例如：

```tsx
const fn = () => {
  setNum((n) =>  n + todo);
};
```

这也能基于`num`上一次渲染的值来更新`num`状态，但是这里的`todo`却并不是上一次渲染的值，如果我们需要根据上一次渲染的`todo`和`num`来更新`num`，我们需要`useCallback`。

```tsx
 // 只有当num更新后，缓存函数中的todo才会获取最新值。
 const fn = useCallback(
    function () {
      const n = num;
      setNum(n + todo);
    },
    [num]
  );
```

<https://codesandbox.io/s/usecallbackcong-huan-cun-han-shu-zhong-geng-xin-zhuang-tai-upzqft>

### 避免Effect频繁执行

以下面的代码为例：

```tsx
import "./styles.css";
import { useEffect, useState } from "react";

export default function App() {
  const [re, reRender] = useState(0);
	const [roomId, setRoomId] = useState(0);

  function createOptions() {
    return {
      serverUrl: "https://localhost:1234",
      roomId: roomId
    };
  }

  useEffect(() => {
    const options = createOptions();
    // ...
    console.log("useEffect执行");
  }, [createOptions]);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button onClick={() => reRender((n) => n + 1)}>重新渲染</button>
    </div>
  );
}
```

当组件重新渲染后，会声明一个新的`createOptions`函数，这就导致了`useEffect`会重复执行。

```tsx
const createOptions = useCallback(
  function createOptions() {
    return {
      serverUrl: "https://localhost:1234",
      roomId
    };
  },
  [roomId]
);

useEffect(() => {
  const options = createOptions();
  // ...
  console.log("useEffect执行");
}, [createOptions]);
```

当然，也可以直接将函数声明放到`useEffect`中。

```tsx
useEffect(() => {
  function createOptions() {
    return {
      serverUrl: "https://localhost:1234",
      roomId
    };
  }
  const options = createOptions();
  // ...
  console.log("useEffect执行");
}, [roomId]);
```

## useReducer
