前言

React Hooks 已经出现在人们的视野里很久了,不得不承认的是,这是 React 开发团队未来的发展方向,如果你是一个 函数式编程 的爱好者,你可能为之兴奋,如果你是一个 面向对象编程的爱好者,那么。..

要解救什么问题

很明显,这不是 一帮为了 KPI 的程序员搞出来的东东,也不是 这不是邓紫棋要帮你唱的 Hook.

React Hooks 要实现 状态逻辑复用, 在这之前还有三种熟悉的方案

  • render props

    1
    <CountDown>{(s) => `还有 ${s} 秒`}</CountDown>
  • HOC 高阶组件

    1
    2
    @B
    class A extends React.Component{}
  • 远古时期的 mixins 方案 (废弃)

从最早的的 React.createClass api . 再到 拥抱 ES6 的 class 再到 现在 抛弃 class 更加函数式编程的 React Hooks

React 团队一直提倡的是 视图与逻辑分离,很遗憾这样去做的人很少

有状态的组件没有渲染,有渲染的组件没有状态

这是 对 React Hooks 的一些思考 这篇文章中提到的观点,也是 React 团队引入 React Hooks 的想要达到的

好处是啥?

  1. immutable
  2. 更纯粹的函数式编程
  3. 代码量更少
  4. 减少逻辑复用的嵌套层级
  5. 同步

这是一个计数的 demo, 显示点击了按钮多少次

class 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Counter extends React.Component {
state = {
count: 0,
};
onSetCount = () => {
this.setState(({ count }) => ({
count: count + 1,
}));
};
render() {
return (
<>
你点击了 {this.state.count} 次
<button onClick={this.onSetCount}>点击</button>
</>
);
}
}

Hooks 的版本

1
2
3
4
5
6
7
8
9
10
function Counter() {
const [count, setCount] = useState(0);

return (
<>
你点击了 {count} 次
<button onClick={()=> setCount(count + 1)}>点击</button>
</>
);

是的在 Class 版本中 this.state.count 每一次改变,会触发视图更新,this 是可变的

而在 Hooks 版本中 , count 仅仅是一个数字,

函数第一次执行

1
2
3
4
5
6
7
8
9
function Counter() {
const count = 0

return (
<>
你点击了 {0} 次
<button onClick={()=> setCount(0 + 1)}>点击</button>
</>
);

点击按钮后函数第二次执行

1
2
3
4
5
6
7
8
9
function Counter() {
const count = 1

return (
<>
你点击了 {1} 次
<button onClick={()=> setCount(1 + 1)}>点击</button>
</>
);

每一次值都独立于其他渲染,不会随着时间改变

具体请看 Dan 的这篇文章,这里只是汉化了一下

在这里你可能会问?那么每次都是独立渲染,那么如果疯狂点击 1000 次 那么会执行 1000 次函数 ?

是的,你可以用 PureComponent 的函数式版本 React.memo 来优化重复渲染

1
2
3
4
5
6
7
8
9
10
11
function Counter() {
const count = 1

return (
<>
你点击了 {1} 次
<button onClick={()=> setCount(1 + 1)}>点击</button>
</>
);

export default React.memo(Counter)

生命周期咋整?

  • componentDidMount
  • componentWillReceiveProps
  • componentWillUnmount

这些都被吃了吗?是的 都被吃了 , 吐出了一个 useEffect Hooks 给你

在上面的计数例子中

1
2
3
4
5
componentDidUpdate() {
setTimeout(() => {
console.log(`你点击了 ${this.state.count} 次`);
}, 3000);
}

在之前讲到了 由于 this 是可变的,在点击 比如5 次 之后,一共打印了 5 次

1
你点击了 5 次  (打印了 5 次)

而在 Hooks 的版本中,由于当前渲染的 count 独立于其他渲染 (可以理解成闭包)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Counter() {
const [count, setCount] = useState(0);

useEffect(() => {
setTimeout(() => {
console.log(`你点击了 ${this.state.count} 次`);
}, 3000);
});

return (
<>
你点击了 {count} 次
<button onClick={() => setCount(count + 1)}>点击</button>
</>
);
}

那么得到的结果是

1
2
3
4
5
你点击了 1 次
你点击了 2 次
你点击了 3 次
你点击了 4 次
你点击了 5 次

useEffect(fn,[...args]) 有两个参数 , 第二个参数是一个数组,数组里面的值发生改变才会重新执行

如果不传的话,每次函数执行都会执行fn

如果是个空数组,则表示只执行一次,可以用来实现 componentDidMount

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Counter() {
const [count, setCount] = useState(0);

useEffect(() => {
console.log('componentDidMount');
}, []);

return (
<>
你点击了 {count} 次
<button onClick={() => setCount(count + 1)}>点击</button>
</>
);
}

想实现 componentDidMount 绑定一个事件,componentWillMount 取消绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Counter() {
const [count, setCount] = useState(0);

useEffect(() => {
window.addEvnetListener('resize', someHandler)
return () => {
window.removeEvnetListener('resize', someHandler)
}
},[]);

return (
<>
你点击了 {count} 次
<button onClick={()=> setCount(count + 1)}>点击</button>
</>
);
}

可以看到 useEffect 里面有一个 经典的 柯里化 组件卸载时 这会执行

对于另外一个经典的场景,组件propsstate 更新后 执行某一个函数 或者重新初始化一个东西,相比看到这 你也懂了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Counter() {
const [count, setCount] = useState(0);

useEffect(() => {
someFn(...)
},[count]);

return (
<>
你点击了 {count} 次
<button onClick={()=> setCount(count + 1)}>点击</button>
</>
);
}

在这里 每次 count 更新后 就会重新执行 someFn()

当然 React 还提供了很多美味的 Hook 供我们品尝 更多的花样等着我们把玩

官方内置 Hooks

社区精选 Hooks

在这里可以抛出一些问题

  • 为什么 Hook 都是以 use 开头,有什么特殊含义吗?
  • 哪些生命周期还没有 hooks 的实现
  • class 组件 可以用在 hooks 里面吗?
  • 怎么才能写出合理的 hooks 呢?有哪些我们需要遵守的约定呢?
  • 怎么实现 一些常用的 自定义 Hook?
  • 项目上的实践分享?

参考文章

精读《React Hooks

useEffect 完整指南

对 React Hooks 的一些思考