Hooks动机

在过去,函数式组件不支持state,所以被称为functional stateless components FSC
随着react hooks的发展,函数式组件可以支持state和side-effect

那么 Hooks 的出现是为了解决什么问题呢?我们可以试图总结一下类组件颇具代表性的痛点

  1. 令人头疼的 this 管理,容易引入难以追踪的 Bug
  2. 生命周期的划分并不符合“内聚性”原则,例如 setInterval 和 clearInterval 这种具有强关联的逻辑被拆分在不同的生命周期方法中
  3. 组件复用(数据共享或功能复用)的困局,从早期的 Mixin,到高阶组件(HOC),再到 Render Props,始终没有一个清晰直观又便于维护的组件复用方案

下面是一个比较简单的函数式组件的demo

  1. import React from "react";
  2. import { v4 as uuidv4 } from "uuid";
  3. import "./style.css";
  4. const App = () => {
  5. const greeting = "Hello Function Component!";
  6. return <h1>{greeting}</h1>;
  7. };
  8. export default App;

可以看到,与类组件相比 :

  • (props) => JSX.
  • 没有render => return JSX
  • statless无状态
  • 没有this
  • 没有生命周期

理解函数式组件的运行过程

我们知道,Hooks 只能用于 React 函数式组件。因此理解函数式组件的运行过程对掌握 Hooks 中许多重要的特性很关键,请看下图:
v2-d65c12ac397ba4e789e9675afe047605_b.gif
可以看到,函数式组件严格遵循 UI = render(data) 的模式。当我们第一次调用组件函数时,触发初次渲染;然后随着 props 的改变,便会重新调用该组件函数,触发重渲染

你也许会纳闷,动画里面为啥要并排画三个一样的组件呢?因为我想通过这种方式直观地阐述函数式组件的一个重要思想:

每一次渲染都是完全独立的。

函数式组件和类组件区别

  • 书写形式
  • 状态维护 hooks useState
  • 副作用处理 - 生命周期 useEffect
  • this指向
  • ref
  • 状态异步更新处理

看下面例子:

  1. import React from "react";
  2. import "./style.css";
  3. class App extends React.Component {
  4. constructor(props) {
  5. super(props);
  6. this.state = {
  7. count: 0
  8. };
  9. }
  10. handleIncrease = () => {
  11. setTimeout(() => {
  12. this.setState({ count: this.state.count + 1 });
  13. }, 2000);
  14. };
  15. render() {
  16. return (
  17. <div>
  18. {this.state.count}
  19. <button onClick={this.handleIncrease}>Increase</button>
  20. </div>
  21. );
  22. }
  23. }
  24. export default App;
  1. import React from "react";
  2. import "./style.css";
  3. export default function App() {
  4. const [count, setCount] = React.useState(0);
  5. const ref = React.useRef(0);
  6. const handleIncrease = () => {
  7. setTimeout(() => {
  8. setCount(count + 1);
  9. }, 2000);
  10. };
  11. console.log("RE-RENDER");
  12. return (
  13. <div>
  14. {count}
  15. <button onClick={handleIncrease}>Increase</button>
  16. </div>
  17. );
  18. }

发现类组件 连续点击5次,2s后,数字依次变化0,1,2,3,4
函数式组件 连续点击5次,2s后,数字从0变化1

为什么?

快照(闭包) vs 最新值(引用)
class 组件里面可以通过 this.state 引用到 count,所以每次 setTimeout 的时候都能通过引用拿到上一次的最新 count,所以点击多少次最后就加了多少。

在 function component 里面每次更新都是重新执行当前函数,也就是说 setTimeout 里面读取到的 count 是通过闭包获取的,而这个 count 实际上只是初始值,并不是上次执行完成后的最新值,所以最后只加了1次。

如何使得行为和类组件一致?

  1. import React from "react";
  2. import "./style.css";
  3. export default function App() {
  4. const [count, setCount] = React.useState(0);
  5. const ref = React.useRef(0);
  6. const handleIncrease = () => {
  7. setTimeout(() => {
  8. //setCount(count => count + 1);
  9. setCount((ref.current += 1));
  10. }, 2000);
  11. };
  12. console.log("RE-RENDER");
  13. return (
  14. <div>
  15. {count}
  16. <button onClick={handleIncrease}>Increase</button>
  17. </div>
  18. );
  19. }
  • 给useState传递一个回调函数
  • useRef 在每次重新渲染后都保持不变

(为什么useRef可以访问到最新值?为什么useState传一个回调函数就可以访问最新值?参看Hooks源码

Reference:

https://www.robinwieruch.de/react-function-component
https://zhuanlan.zhihu.com/p/130299058
https://mp.weixin.qq.com/s/fF-H3Lr0aP3Ld8jfJrrwQg