React hook是 React 团队发明的,用于在 Function 组件中引入状态管理和副作用。React Hooks使我们采用函数组件的写法编写 React 应用。因此,不再需要 Class 组件。

一、Hooks产生的动机:

1、方便重构函数组件

Hooks出现前,只有 Class 组件才能使用 state 状态和生命周期钩子函数,而生命周期函数是引入副作用(事件监听、接口请求)必不可少的。

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
import React, { Component } from 'react';

class Counter extends 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>
);
}
}

export default Counter;

如果一个组件即无状态又无副作用,就应该使用无状态组件 (stateless component)。

无状态组件写法简洁,逻辑清晰的优点,在项目中大量使用。这也带来一个缺点:
** 每次需要状态或生命周期时,都需要将函数组件重构为类组件 **

有了 Hooks 之后,就不需要这种重构了。函数式组件支持通过 Hooks 读取状态和副作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from 'react';

// how to use the state hook in a React function component
function Counter() {
const [count, setCount] = React.useState(0);

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

export default Counter;

2、代码组织更好

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 from 'react';

// side-effects in a React class component
class MyComponent extends React.Component {
// setup phase
componentDidMount() {
// add listener for feature 1
// add listener for feature 2
}

// clean up phase
componentWillUnmount() {
// remove listener for feature 1
// remove listener for feature 2
}

...
}

// side-effects in React function component with React Hooks
function MyComponent() {
React.useEffect(() => {
// add listener for feature 1 (setup)
// return function to remove listener for feature 1 (clean up)
});

React.useEffect(() => {
// add listener for feature 2 (setup)
// return function to remove listener for feature 2 (clean up)
});

...
}

Class 写法中:所有的 side-effects 方法都是按生命周期方法分组,代码比较散。

第二种写法明显更好,每个 side-effects 统一在一个钩子函数里面,同时每个钩子函数还提供了清理副作用的方法。

3、抽象地狱

Hooks出现之前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from 'react';
import { compose, withReducer } from 'recompose';
import { withRouter } from 'react-router-dom';

function App({ history, state, dispatch }) {
return (
<ThemeContext.Consumer>
{theme =>
<Content theme={theme}>
...
</Content>
}
</ThemeContext.Consumer>
);
}

export default compose(
withRouter,
withReducer(reducer, initialState)
)(App);

嵌套地狱:为完成指定的功能,多个组件嵌套到一起。导致代码可读性比较差,因为不知道究竟调用的是哪个父组件的功能。

Hooks出现后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from 'react';
import { useTheme } from 'styled-components';
import { useRouter } from 'react-router-dom';

function App() {
const theme = useTheme();
const history = useRouter();
const [state, dispatch] = React.useReducer(reducer, initialState);

return (
<Content theme={theme}>
...
</Content>
);
}

export default App;

4、Class 混乱?

js提供了两种编程理念:面向对象编程(OOP)、函数式编程(FP)。React 同时支持两大编程方式。

一方面,React大量引入了函数式编程(如高阶函数、JS内置方法map/filter)以及不可变性,副作用等。这些编程本质上与React无关,属于编程范式,在React中被大量的开发者使用。

另一方面,React 使用 Class 创建组件,同时提供了一系列生命周期函数:

  • componentWillMount
  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

还支持自定义事件,需要通过 this.bind 来改变 this 指向。

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
38
39
40
41
42
43
// WHY IS IT EXTENDING FROM SOMETHING?
class Counter extends Component {
// WAIT ... THIS WORKS???
// https://github.com/the-road-to-learn-react/react-alternative-class-component-syntax
state = { value: 0 };

// I THOUGH IT'S THIS WAY, BUT WHY DO I NEED PROPS HERE?
// constructor(props) {
// IS SUPER NECCESSARY???
// super(props);
//
// this.state = {
// value: 0,
// };
// }

// WHY DO I HAVE TO USE AN ARROW FUNCTION???
onIncrement = () => {
this.setState(state => ({
value: state.value + 1
}));
};

// SHOULDN'T IT BE this.onDecrement = this.onDecrement.bind(this); IN THE CONSTRUCTOR???
// WHAT'S this.onDecrement = this.onDecrement.bind(this); DOING ANYWAY?
onDecrement = () => {
this.setState(state => ({
value: state.value - 1
}));
};

render() {
return (
<div>
{this.state.value}

{/* WHY IS EVERYTHING AVAILABLE ON "THIS"??? */}
<button onClick={this.onIncrement}>+</button>
<button onClick={this.onDecrement}>-</button>
</div>
)
}
}

二、一些常用Hooks用法

1、useState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function App() {
const [list, setList] = React.useState(INITIAL_LIST);

function onRemoveItem(id) {
const newList = list.filter(item => item.id !== id);
setList(newList);
}

return (
<ul>
{list.map(item => (
<li key={item.id}>
<a href={item.url}>{item.title}</a>
<button type="button" onClick={() => onRemoveItem(item.id)}>
Remove
</button>
</li>
))}
</ul>
);
}

2、useEffect

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
38
39
40
41
42
43
44
45
46
47
48
import React from 'react';

function App() {
const [isOn, setIsOn] = React.useState(false);
const [timer, setTimer] = React.useState(0);

React.useEffect(() => {
let interval;

if (isOn) {
interval = setInterval(
() => setTimer(timer => timer + 1),
1000,
);
}

return () => clearInterval(interval);
}, [isOn]);

const onReset = () => {
setIsOn(false);
setTimer(0);
};

return (
<div>
{timer}

{!isOn && (
<button type="button" onClick={() => setIsOn(true)}>
Start
</button>
)}

{isOn && (
<button type="button" onClick={() => setIsOn(false)}>
Stop
</button>
)}

<button type="button" disabled={timer === 0} onClick={onReset}>
Reset
</button>
</div>
);
}

export default App;

三、自定义Hooks

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 from 'react';

function useOffline() {
const [isOffline, setIsOffline] = React.useState(false);

function onOffline() {
setIsOffline(true);
}

function onOnline() {
setIsOffline(false);
}

React.useEffect(() => {
window.addEventListener('offline', onOffline);
window.addEventListener('online', onOnline);

return () => {
window.removeEventListener('offline', onOffline);
window.removeEventListener('online', onOnline);
};
}, []);

return isOffline;
}

function App() {
const isOffline = useOffline();

if (isOffline) {
return <div>Sorry, you are offline ...</div>;
}

return <div>You are online!</div>;
}

export default App;

React hook的最大优点是提供代码复用性,这极有可能形成一个自定义 hook 的生态,可以从npm上下载所需要的 hook 来提升开发效率。

四、深入学习Hooks系列

  • useState
  • useReducer
  • useEffect
  • useCallback
  • useMemo
  • useRef
  • useContext