React Hooks

React Hooks产生的原因

由于类组件存在一些缺点:

  • 大型组件很难拆分和重构,也很难测试。
  • 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
  • 组件类引入了复杂的编程模式,比如 render props 和高阶组件。

于是有了函数式组件。一般的编程原则是一个函数只做一件事,那么如果这个函数组件要进行日志存储、改变状态等操作时如何处理呢?
函数式编程将那些跟数据计算无关的操作,都称为 “副效应” (side effect) 。钩子(hook)就是 React 函数组件的副效应解决方案,用来为函数组件引入副效应。

组件类型

Functional(Stateless)Component

功能组件也叫无状态组件,一般只负责渲染。

1
2
3
4

function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

Class(Stateful)Component

类组件也是有状态组件,一般有交互逻辑和业务逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Welcome extends React.Component {
state = {
name: ‘tori’,
}
componentDidMount() {
fetch(…);

}
render() {
return (
<>
<h1>Hello, {this.state.name}</h1>
<button onClick={() => this.setState({name: ‘007’})}>改名</button>
</>
);
}
}

Presentational Component

和功能组件类似

1
2
3
4
5
6
7
8
const Hello = (props) => {
return (
<div>
<h1>Hello! {props.name}</h1>
</div>
)
}

常用Hooks

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
function App() {
return (
<div>
<ComponentA />
<ComponentB />
</div>
);
}

function ComponentA() {
const [count, setCount] = useState(0);

return (
<div>
<button onClick={() => setCount(count + 1)}>Click me</button> // ComponentA、ComponentC会重新渲染
<ComponentC />
</div>
);
}

function ComponentB() {
// ...
}

function ComponentC() {
// ...
}

useEffect

函数组件中执行副作用操作,方便,且避免不必要的bug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { useState, useEffect } from 'react';

function Example() {
const [count, setCount] = useState(0);

useEffect(() => {
const timerId = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);

// 返回一个清理函数,执行时机:1. 组件卸载时 2. 依赖数组发生变化时
return () => {
clearInterval(timerId);
};
}, []); // 依赖数组为空,副作用函数只在首次渲染后执行

return <div>{count}</div>;
}

useRef

用于在不进行渲染的情况下存储和访问最新的值

  1. 访问 DOM 元素:你可以将 ref 对象赋值给 JSX 元素的 ref 属性,然后在其他地方通过 .current 属性访问这个元素。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
  1. 存储可变值:ref 对象的 .current 属性是可变的,它可以用来存储任何可变值,而不仅仅是 DOM 元素。与在组件中使用 state 不同,修改 ref 对象的 .current 属性不会触发组件重新渲染。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Timer() {
const intervalId = useRef();

useEffect(() => {
intervalId.current = setInterval(() => {
console.log('Timer tick');
}, 1000);

return () => {
clearInterval(intervalId.current);
};
}, []);

// ...
}

useReducer

接受一个reducer函数和一个初始值,返回新的状态和一个dispatch函数,通过调用dispatch函数来更新状态

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

const initialState = {count: 0};

function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}

function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}

useMemo

useMemo(fn, deps);

缓存计算结果。fn 是产生所需数据的一个计算函数。通常来说,fn 会使用 deps 中声明的一些变量来生成一个结果,用来渲染出最终的 UI。

好处:

  • 避免重复计算
  • 避免不必要的渲染

useCallback

useCallback(fn, deps)

缓存函数。fn 是定义的回调函数,deps 是依赖的变量数组。只有当某个依赖变量发生变化时,才会重新声明 fn 这个回调函数。useCallback 的功能其实是可以用 useMemo 来实现的

1
2
3
4
5
6
7
8
9
10
import React, { useState, useCallback } from 'react'; 

function Counter() {
const [count, setCount] = useState(0);
const handleIncrement = useCallback(
() => setCount(count + 1),
[count], // 只有当 count 发生变化时,才会重新创建回调函数
);
return <button onClick={handleIncrement}>+</button>;
}

useContext

定义全局状态。React 提供了 Context 这样一个机制,能够让所有在某个组件开始的组件树上创建一个 Context。这样这个组件树上的所有组件,就都能访问和修改这个 Context 了。那么在函数组件里,我们就可以使用 useContext 这样一个 Hook 来管理 Context。

1
2
3
4
5
6
7
8
9
10


const MyContext = React.createContext(initialValue);


const value = useContext(MyContext);




useImperativeHandle

自定义Hooks

参考