React Hook 一览

React Hook

原帖
付费课程

1、useState:让函数式组件拥有状态

用法示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 计数器
import { useState } from 'react'
const Test = () => {
const [count, setCount] = useState(0);
return (
<>
<h1>点击了{count}次</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
</>
);
}
export default Test
PSclass组件中this.setState更新是state是合并, useState中setState是替换。例如:
// 错误示例
import { useState } from 'react'
const Test = () => {
const [counts, setCounts] = useState({
num1: 0,
num2: 0
});
return (
<>
<h1>num1:{counts.num1}</h1>
<h1>num2:{counts.num2}</h1>
<button onClick={() => setCounts({ num1: counts.num1 + 1})}>num1+1</button>
<button onClick={() => setCounts({ num2: counts.num2 + 1})}>num2+1</button>
</>
);
}
export default Test

可以看到 useState 中 setState 是替换,不会合并,正确更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { useState } from "react";
const Test = () => {
const [counts, setCounts] = useState({
num1: 0,
num2: 0,
});
return (
<>
<h1>num1:{counts.num1}</h1>
<h1>num2:{counts.num2}</h1>
<button onClick={() => setCounts({ ...counts, num1: counts.num1 + 1 })}>
num1+1
</button>
<button onClick={() => setCounts({ ...counts, num2: counts.num2 + 1 })}>
num2+1
</button>
</>
);
};
export default Test;

2、useEffect:副作用,取代生命周期

用法示例,在 class 组件中如果需要在组件挂载后和数据更新后做同一件事,我们会这样做:

1
2
3
4
5
6
componentDidMount() {
// 做一些事
}
componentDidUpdate() {
// 做一些事
}

可以看出来,如果逻辑复杂后,代码看起来不优雅,且容易造成逻辑混乱,而使用 useEffect:

1
2
3
useEffect(() => {
// 做一些事
});

此刻已经看到了 useEffect 的基本用法,除此之外,他还可以绑定触发更新的依赖状态,默认是状态中任何数据发生变化副作用都会执行,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { useState, useEffect } from "react";
const Test = () => {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
useEffect(() => {
console.log("useEffect触发了");
});
return (
<>
<h1>count1:{count1}</h1>
<h1>count2:{count2}</h1>
<button onClick={() => setCount1(count1 + 1)}>count1+1</button>
<button onClick={() => setCount2(count2 + 1)}>count2+1</button>
</>
);
};
export default Test;

将上述代码 useEffect 第二个参数传入需要绑定的状态,可绑定多个:

// 语法:useEffect(回调函数,[依赖值])

1
2
3
useEffect(() => {
console.log("useEffect触发了");
}, [count1]);

可以看到,只有绑定的 count1 发生变化才会触发,如果传空数组则任何状态发生变化都不会触发,此时 useEffect 的作用就类似 class 组件中的 componentDidMount,所以发送请求通常也会在此执行。

清理副作用

在上面的操作中都不用清理的副作用,然而,有些副作用是需要去清理的,不清理会造成异常甚至内存泄漏,比如开启定时器,如果不清理,则会多次开启,从上面可以看到 useEffect 的第一个参数是一个回调函数,可以在回调函数中再返回一个函数,该函数可以在状态更新后第一个回调函数执行之前调用,具体实现:

1
2
3
4
5
6
useEffect(() => {
// 设置副作用
return () => {
// 清理副作用
};
});

3、useContext:跨组件共享数据

React.createContext();创建一个 TestContext 对象

TestContext.Provider 包裹子组件

数据放在<font style="color:#23263B;"><TestContext.Provider value={value}></font>的 value 中

子组件中通过 useContext(TestContext)获取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React, { useContext, useState } from "react";
const TestContext = React.createContext();
const Parent = () => {
const [value, setValue] = useState(0);
return (
<div>
{(() => console.log("Parent-render"))()}
<button onClick={() => setValue(value + 1)}>value + 1</button>
<TestContext.Provider value={value}>
<Child1 />
<Child2 />
</TestContext.Provider>
</div>
);
};
const Child1 = () => {
const value = useContext(TestContext);
return (
<div>
{(() => console.log("Child1-render"))()}
<h3>Child1-value: {value}</h3>
</div>
);
};
const Child2 = () => {
return (
<div>
{(() => console.log("Child2-render"))()}
<h3>Child2</h3>
</div>
);
};
export default Parent;

至此数据实现共享了,但是可以看到在 TestContext 中的共享数据只要发生变化,子组件都会重新渲染,Child2 并没有绑定数据,不希望他做无意义的渲染,可以使用 React.memo 解决,实现:

1
2
3
4
5
6
7
8
const Child2 = React.memo(() => {
return (
<div>
{(() => console.log("Child2-render"))()}
<h3>Child2</h3>
</div>
);
});

4、useCallback:性能优化

语法:

1
2
3
4
// useCallback(回调函数,[依赖值])
const handleClick = useCallback(() => {
// 做一些事
}, [value]);

useCallback 返回的是一个 memoized(缓存)函数,在依赖不变的情况下,多次定义的时候,返回的值是相同的,他的实现原理是当使用一组参数初次调用函数时,会缓存参数和计算结果,当再次使用相同的参数调用该函数时,会直接返回相应的缓存结果。

优化性能例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import React, { useState, useCallback, memo } from "react";
const Parent = () => {
const [value1, setValue1] = useState(0);
const [value2, setValue2] = useState(0);
const handleClick1 = useCallback(() => {
setValue1(value1 + 1);
}, [value1]);
const handleClick2 = useCallback(() => {
setValue2(value2 + 1);
}, [value2]);
return (
<>
{(() => console.log("Parent-render"))()}
<h3>{value1}</h3>
<h3>{value2}</h3>
<Child1 handleClick1={handleClick1} />
<Child2 handleClick2={handleClick2} />
</>
);
};
const Child1 = memo((props) => {
return (
<div>
{(() => console.log("Child1-render"))()}
<button onClick={() => props.handleClick1()}>value1 + 1</button>
</div>
);
});
const Child2 = memo((props) => {
return (
<div>
{(() => console.log("Child2-render"))()}
<button onClick={() => props.handleClick2()}>value2 + 1</button>
</div>
);
});
export default Parent;

useCallback 返回的是一个 memoized 回调函数,仅在其中绑定的一个依赖项变化后才更改可防止不必要的渲染,在跨组件共享数据中举例的事件是在父组件中点击触发,而现在是使用状态提升,在父组件中传递方法供子组件调用,每次 render 时函数也会变化,导致子组件重新渲染,上面例子 useCallback 将函数进行包裹,依赖值未发生变化时会返回缓存的函数,配合 React.memo 即可优化无意义的渲染。

5、useMemo:性能优化

语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// useMemo(回调函数,[依赖值])
useMemo(() => {
// 做一些事情
},[value]);
先看一个例子:
import React, { useState } from 'react'
const Test = ()=> {
const [value, setValue] = useState(0);
const [count, setCount] = useState(1);
const getDoubleCount = () => {
console.log('getDoubleCount进行计算了');
return count * 2;
};
return (
<div>
<h2>value: {value}</h2>
<h2>doubleCount: {getDoubleCount()}</h2>
<button onClick={() => setValue(value + 1)}>value+1</button>
</div>
)
}
export default Test

可以看到 getDoubleCount 依赖的是 count,但 value 发生变化它也重新进行了计算渲染,现在只需要将 getDoubleCount 使用 useMemo 进行包裹,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, { useState, useMemo } from "react";
const Test = () => {
const [value, setValue] = useState(0);
const [count, setCount] = useState(1);
const getDoubleCount = useMemo(() => {
console.log("getDoubleCount进行计算了");
return count * 2;
}, [count]);
return (
<div>
<h2>value: {value}</h2>
<h2>doubleCount: {getDoubleCount}</h2>
<button onClick={() => setValue(value + 1)}>value+1</button>
</div>
);
};
export default Test;

现在 getDoubleCount 只有依赖的 count 发生变化时才会重新计算渲染。

useMemo 和 useCallback 的共同点:

  • 接收的参数都是一样的,第一个是回调函数,第二个是依赖的数据
  • 它们都是当依赖的数据发生变化时才会重新计算结果,起到了缓存作用

useMemo 和 useCallback 的区别:

  • useMemo 计算结果是 return 回来的值,通常用于缓存计算结果的值
  • useCallback 计算结果是一个函数,通常用于缓存函数

6、useRef

用法:例如要实现点击 button 按钮使 input 输入框获得焦点:

1
2
3
4
5
6
7
8
9
10
11
import React, { useRef } from "react";
const Test = () => {
const inputEl = useRef();
return (
<>
<input ref={inputEl} />
<button onClick={() => inputEl.current.focus()}>focus</button>
</>
);
};
export default Test;

这样看起来非常像 React.createRef(),将上面代码中的 useRef()改成 React.createRef()也能实现同样的效果,那为什么要设计一个新的 hook?难道只是为了加上 use,统一 hook 规范?

事实上,它们确实不一样。

官网的说明如下:

useRef returns a mutable ref object whose .current property is initialized to the passed

argument (initialValue). The returned object will persist for the full lifetime of the component.

翻译:

简单来说,useRef 就像一个储物箱,你可以随意存放任何东西,再次渲染时它会去储物箱找,createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!