react基础(四)
Hook好强大 真香!
# react Hook
Hook是
React 16.8的新增特性。它可以让你在不编写class的情况下使用state以及其他的React特性。Hook是一些可以让你在函数组件里“钩入”
React state及生命周期等特性的函数。Hook 不能在 class 组件中使用。
# State Hook
简单示例:
import { useState } from "react";
import ReactDOM from 'react-dom';
function ExampleHook() {
const [ count, setCount ] = useState(0);
return (
<div>
<p>已点击{count}次</p>
<button onClick={() => setCount(count + 1)}>
点击
</button>
</div>
);
}
ReactDOM.render(
<ExampleHook />,
document.getElementById('root')
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
效果:
这里导入的useState就是一个Hook,通过在函数中调用会给组件添加一些内部state。注意点:
useState接收的唯一参数就是初始state,这个初始state参数只有在第一次渲染时会被用到,而且不同于this.state,使用useState时声明的state不一定要是一个对象useState会返回一个当前状态和一个更新状态的函数更新状态的函数,通过数组解构我们调用useState的时候可以给state变量取任意名称,更新函数同理。
与class组件中state变量的区别:
- 声明方式不同,class中我们需要在
构造函数中设置this.state;在函数组件中,通过调用useStateHook来声明。 - 读取方式不同,class中我们通过
this.state.count读取我们声明在state中的count值;在函数组件中我可以直接使用count。 - 更新state状态的方式不同,class中我们需要调用
setState()来更新值;在函数组件中,我们需要调用useState时声明的更新函数。
# Effect Hook
Effect Hook 可以让你在函数组件中执行副作用操作,它跟class组件中的componentDidMount、componentDidUpdate和componentWillUnmount具有相同的用途,可以看成将它们合并成了一个API。
将上面的例子稍加改造:
import { useState, userEffect } from "react";
import ReactDOM from 'react-dom';
function ExampleHook() {
const [ count, setCount ] = useState(0);
useEffect(() => {
console.log(`按钮被点击${count}次`)
});
return (
<div>
<p>已点击{count}次</p>
<button onClick={() => setCount(count + 1)}>
点击
</button>
</div>
);
}
ReactDOM.render(
<ExampleHook />,
document.getElementById('root')
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
实现效果:
当你调用useEffect时,就是在告诉React在完成对DOM的更改后运行你的“副作用”函数。注意点:
- 由于副作用函数是在组件内(函数作用域中)声明的,所以它们可以访问到组件的props和state。
- 默认情况下,React会在每次渲染后调用副作用函数(包括第一次渲染的时候)。
# 无需清除的effect
有时候,我们只想在React更新DOM之后运行一些额外的代码。比如发送网络请求,手动变更DOM,记录日志,这些都是常见的无需清除的操作。上面的例子便是无需清除的。
# 需要清除的effect
我们在组件的操作,有些在组件销毁时是应该被清除的,例如订阅外部数据源,在class组件中,我们一般在componentDidMount生命周期中绑定 ,在componentWillUnmount生命周期内清除它。
在Hook中,由于添加和删除订阅的代码的紧密性,所以 useEffect 的设计是在同一个地方执行。effect函数还可以通过返回一个函数来指定如何“清除”副作用(可以同时清除多个),React将会在执行清除操作时调用它。例如,同样继续改造上面的例子:
import { Component, useState, userEffect } from "react";
import ReactDOM from 'react-dom';
function ExampleHook() {
const [ count, setCount ] = useState(0);
useEffect(() => {
console.log(`按钮被点击${count}次`)
let interval = setInterval(() => {
console.log('当前时间戳', new Date().getTime())
}, 1000);
return () => {
clearInterval(interval)
}
});
return (
<div>
<p>已点击{count}次</p>
<button onClick={() => setCount(count + 1)}>
点击
</button>
</div>
);
}
class App extends Component {
constructor() {
super();
this.state = {
hookComponentFlag: true
};
};
changeHookFlag() {
this.setState(newState => {
return {
hookComponentFlag: !newState.hookComponentFlag
}
})
};
render(){
return (
<div>
<button onClick={this.changeHookFlag.bind(this)}>{ this.state.hookComponentFlag ? '移除' : '添加'}hook组件</button>
{this.state.hookComponentFlag ? <ExampleHook /> : null}
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
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
38
39
40
41
42
43
44
45
46
47
48
49
50
在这个示例中,React会在组件销毁时清除定时器,然后在后续渲染时重新执行副作用函数创建定时器。
效果:
# useEffect使用技巧
将useEffect用于一个单一功能
在React Hooks,可以有多个useEffect函数。这样我们可以简洁代码,每一个函数对应一个单一功能,同时将useEffect拆分为单独的用途函数,可以防止意外发生(使用依赖数组时)。
继续改动上面的例子:
import { Component, useState, userEffect } from "react";
function ExampleHook() {
const [ count, setCount ] = useState(0);
const [ number, setNumber ] = useState(0);
useEffect(() => {
console.log(`按钮被点击${count}次`)
})
useEffect(() => {
let interval = setInterval(() => {
console.log('当前时间戳', new Date().getTime())
}, 1000);
return () => {
clearInterval(interval)
}
});
useEffect(() => {
let interval = setInterval(() => {
// setNumber(number + 1)
setNumber(number => number + 1) //当变量依赖先前状态时建议这样使用
console.log('当前数量', number)
}, 1000);
return () => {
clearInterval(interval)
}
});
return (
<div>
<p>已点击{count}次</p>
<button onClick={() => setCount(count + 1)}>
点击
</button>
</div>
);
}
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
通过跳过 Effect 进行性能优化
在某些情况下,每次渲染后都执行清理或者执行 effect 可能会导致性能问题。在 class 组件中,我们可以通过在 componentDidUpdate 中添加对 prevProps 或 prevState的比较逻辑解决:
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
//进行操作
}
}
2
3
4
5
useEffect的Hook API中同样支持,如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可:
useEffect(() => {
document.title = `已点击 ${count} 次`;
}, [count]); // 仅在 count 更改时更新
2
3
这里传入[count]作为第二个参数,当组件重新渲染的时候如果count的值没有改变,则React会跳过这个effect。
加条件执行Effect 有些业务条件下,除了比对值是否改变的情况,还有是否满足某些条件才执行Effect。
我们可以在Effect的头部添加条件返回:
function ExampleHook() {
const [ number, setNumber ] = useState(0);
useEffect(() => {
if(number > 5) return;
let interval = setInterval(() => {
// setNumber(number + 1)
setNumber(number => number + 1) //当变量依赖先前状态时建议这样使用
console.log('当前数量', number)
}, 1000);
return () => {
clearInterval(interval)
}
}, [number]);
return (
<div>
<p>已点击{count}次</p>
<button onClick={() => setCount(count + 1)}>
点击
</button>
</div>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Ref Hook
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。
# 常规用法
实例代码:
import React, { useState, useEffect, useMemo, useRef } from 'react';
function ExampleHook(props){
const [count, setCount] = useState(0);
const doubleCount = useMemo(() => {
return 2 * count;
}, [count]);
const couterRef = useRef();
useEffect(() => {
document.title = `The value is ${count}`;
console.log(couterRef.current);
}, [count]);
return (
<>
<button ref={couterRef} onClick={() => {setCount(count + 1)}}>Count: {count}, double: {doubleCount}</button>
</>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
上述代码使用useRef可以创建一个对象couterRef,将这个对象赋给组件的ref属性,就可以通过该对象的current属性访问DOM元素button
# 跨渲染周期保存数据
一般我们将组件的状态属性放在state中保存,但当我们改变state中的属性时,就会触发组件重新渲染。如果我们有更新数据而不触发组件重新渲染的需求时,就可以使用userRef来跨越渲染周期存储数据。
import React, { useState, useEffect, useMemo, useRef } from 'react';
function ExampleHook(props){
const [count, setCount] = useState(0);
const doubleCount = useMemo(() => {
return 2 * count;
}, [count]);
const timerID = useRef();
useEffect(() => {
timerID.current = setInterval(()=>{
setCount(count => count + 1);
}, 1000);
}, []);
useEffect(()=>{
if(count > 10){
clearInterval(timerID.current);
}
});
return (
<>
<button ref={couterRef} onClick={() => {setCount(count + 1)}}>Count: {count}, double: {doubleCount}</button>
</>
);
}
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
这里用ref对象的current属性来存储定时器的ID,这样便可以在多次渲染之后依旧保存定时器ID,从而能正常清除定时器。同时这里出现了一个新的hookuseMemo,我们下面说。
# 其他Hook
# useMemo
这里承接上面代码的useMemo。
官网定义:函数返回一个 memoized 值。把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
总结一下就是:这个hook的功能是判断组件中的函数逻辑是否重新执行,用来优化性能。
//第一个参数是要执行的函数
//第二个参数是执行函数依赖的变量组成的数据
//这里只有count发生变化double才会重新计算
const doubleCount = useMemo(() => {
return 2 * count;
}, [count]);
2
3
4
5
6
上述代码,只有当count的值发生改变了,doubleCount才会重新渲染,感觉有点类似vue中的computed属性。 可以将代码稍微改变一下
const doubleCount = useMemo(() => {
return 2 * count;
}, [count === 3]);
2
3
4
这样的话只有当count的值为3时,doubleCount才会重新渲染。
综上,只有当满足传入函数中第二个参数(依赖项数组)的条件满足时,这个hook创建的数据才能够被更改,减少频繁渲染来优化性能。
# useContext
可以让你不使用组件嵌套就可以订阅 React 的 Context。
function Example() {
const locale = useContext(LocaleContext);
const theme = useContext(ThemeContext);
// ...
}
2
3
4
5
# useReducer
可以让你通过reducer来管理组件本地的复杂state。
function Example() {
function Todos() {
const [todos, dispatch] = useReducer(todosReducer);
// ...
}
2
3
4
5
React 内置的 Hook API索引 (opens new window)
# 自定义Hook
自定义Hook是一个函数,其名称以use开头,函数内部可以调用其他的Hook。
示例:
function useVariable(porps) {
// const value =
const [ variable, setVariabl] = useState(null);
useEffect(() => {
variable = setVariabl(porps)
});
return variable
}
function getValue(porps) {
const value = useVariable(porps)
if(value === null) {
return 'error'
}
return value
}
function getResultByVariable(porps) {
const value = useVariable(porps)
if(value === null) {
return 'waiting'
}
return (
<div>
{`结果为:${value === 'success' :'成功' : '失败'}`}
</div>
)
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
上述组件getValue和getResultByVariable都使用了自定义Hook useVariable,但它们并没有共享state,每一次使用自定义Hook时,其中所有的state和副作用都是完全隔离的。
- 01
- 2021/10/23 00:00:00
- 02
- 2021/08/18 17:00:03
- 03
- 2021/07/04 15:26:36
