# 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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://1425816423.gitbook.io/my-knowledge-base/qian-duan-ji-shu/react/react-hook-todo.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
