---
title: "React 16.x 新特性, Suspense, Hooks, Fiber"
description: "React 在版本16.x发布了lazy，Suspense，以及重头戏Hooks。本文带你完整学习useState，useEffect，useCallbck，useRef以及自定义Hook。"
url: "https://hicc.pro/p/blog/whats-new-in-react-16x-suspense-hooks-fiber"
author: "tyler wang"
published: "2019-04-07T12:09:30.000Z"
updated: "2021-07-03T09:23:31.000Z"
category: "blog"
tags:
  - "React"
  - "useState"
  - "useEffect"
  - "useCallbck"
  - "Capture Value"
  - "Capture props"
  - "useRef"
image: "https://image.hicc.pro/pro/20210703172323Fryz0G70n2PWM7YVb6X9kZu1chODi9r4nYJF6LmwpHmAEszK-.jpg"
lang: "zh-CN"
site: "Hipo日志"
---

# React 16.x 新特性, Suspense, Hooks, Fiber

对于眼下2019来说，`React`, `Vue`,`Angular`已成三国鼎立之势，Vue忙着3.0， Angular忙着[Ivy](https://is-angular-ivy-ready.firebaseapp.com/#/status)， React则用Hooks再次改变了我们书写代码的方式。

React 在[官方博客](https://reactjs.org/blog/2018/11/27/react-16-roadmap.html)中公布了16.x的一系列计划：

![9E0B1216-C047-498A-B407-4B6ED170E503](https://image.hicc.pro/hicc_me1373771972_9E0B1216-C047-498A-B407-4B6ED170E503.png)

现在(2019-4-13)， 前两个已经发布，本文着重在前两个部分，甚至可以说重点在于Hooks， 大爱Hooks啊。

### React.lazy, Suspense

React 16.6.0 引入了`lazy`和`Suspense`。`React.lazy`函数可以渲染一个动态的import作为一个组件。`Suspense`悬停组件，它会在内容还在加载的时候先渲染`fallback`。它们组合就能实现之前主要是使用[loadable-components](https://github.com/smooth-code/loadable-components)，来异步加载组件，Code-Splitting。

```javascript
import React, {lazy, Suspense} from 'react';
const OtherComponent = lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <OtherComponent />
    </Suspense>
  );
}
```

可以查看[React lazy Demo](https://csb-zl65n5vzy4-xwopzuyklh.now.sh/)

需要注意的是：

- 暂时不支持SSR，*loadable支持*。
- `React.lazy`函数只支持动态`default`组件导入
- 它们组合出来代码分片使用Webpack, Babel时候，仍然需要配置Babel插件: `"plugins": ["@babel/plugin-syntax-dynamic-import"]`
- `Suspense`目前只支持Code-Splitting， 数据异步获取的支持需要到2019年中……

### React.memo

`React.memo`基本就是React为函数组件提供的`PrueComponent`或者`shouldComponentUpdate`功能。下面的例子：

```javascript
const MyComponent = React.memo(function MyComponent(props) {
  /* only rerenders if props change */
});
```

### 静态属性contextType

React 16.3 正式引入了Context API， 来方便跨组件共享数据，基本使用方式，按照官方例子：

```javascript
const ThemeContext = React.createContext('light');

class ThemeProvider extends React.Component {
  state = {theme: 'light'};

  render() {
    return (
      <ThemeContext.Provider value={this.state.theme}>
        {this.props.children}
      </ThemeContext.Provider>
    );
  }
}

class ThemedButton extends React.Component {
  render() {
    return (
      <ThemeContext.Consumer>
        {theme => <Button theme={theme} />}
      </ThemeContext.Consumer>
    );
  }
}
```

可以发现消费组件需要按照函数的方式来调用，很不方便，因此新的语法可以赋值给class组件的静态属性`contextType`，以此**能够在各个生命周期函数中得到`this.context`**:

```javascript
class MyClass extends React.Component {
  static contextType = MyContext;
  componentDidMount() {
    let value = this.context;
    /* perform a side-effect at mount using the value of MyContext */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* render something based on the value of MyContext */
  }
}
```

### 重头戏React Hooks

React 在版本16.8中发布了**Hooks，可以在函数式组件中使用state和其他的React 功能。**

React官方文档[Introducing Hooks – React](https://reactjs.org/docs/hooks-intro.html)花了8个章节来讲述Hooks😬，一定要读一读，本文不会那么详尽，只是试图做一些融汇和贯通。

#### 为什么需要hooks？

React从发布以来就是以单项数据流、搭积木的书写方式迅速流行，然后为了解决日益复杂的业务：

- 有状态的Class组件势必变得臃肿，难懂。
- 相同的逻辑在不同生命周期函数中重复，也容易漏写。
- 更复杂的模式，例如render props 和higher-order components， 为了逻辑的复用容易形成组件嵌套地狱。

更进一步来说，Class组件`this`加上生命周期函数的方式，难写，难读，易出错，而且AOT，树摇，[Component Folding](https://github.com/facebook/react/issues/7323)等先进的编译优化手段效果不好……

因此实际上**Hooks就是为函数式组件赋能，以此来优化上述问题**。

#### useState

`useState`的语法可能略微奇怪，但是却异常好用.
```javascript
const [state, setState] = useState(initialState);
```

- 不像`this.state`，`useState`可以多次使用
- `this.state`会自动合并对象，`useState`不会
- `useState`的中`setState`直接传值，同样也可以传一个函数，以此在函数中获取到上次的state
- `useState`的初始值如果需要一个耗时函数计算时候，给`useState`传入函数，这样只会在初次调用。
- 最重要的是，React内部使用数组的方式来记录`useState`，请不要在循环、条件或者嵌套函数中调用`useState`，其实[所有的Hooks你应该只在函数的顶层调用](https://reactjs.org/docs/hooks-rules.html)

Demo [react-useState - CodeSandbox](https://codesandbox.io/s/0p3z91qx80?fontsize=14)

#### useEffect

可以在`useEffect`里面做一些，获取，订阅数据，DOM等“副作用”，它也可以实现于Class Component中的`componentDidMount`， `componentDidUpdate`和 `componentWillUnmount`的调用，使用类似官方的例子：

```javascript
import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);

    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
```

一个函数内就搞定了，`componentDidMount`—> `componentDidUpdate`—>`componentWillUnmount`(注意Effect函数返回的函数)，易读，精简。

#### 自定义Hook

记住，Hooks就是一些常规的JavaScript函数，只是约定以`use`开头命名(方便阅读和Eslint)。因此Hooks自然就可以按照函数一样组合使用。

实际上这才是React Hooks真正释放想象，提高生产力的地方。

```typescript
import { useEffect, useState } from 'react';

const useWindowSize = () => {
  const [state, setState] = useState<{ width: number; height: number }>({
    width: window.innerWidth ,
    height: window.innerHeight,
  });

  useEffect(() => {
    const handler = () => {
      setState({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };
    window.addEventListener('resize', handler);
    return () => window.removeEventListener('resize', handler);
  }, []);
  
  return state;
};
```

更多的自定义Hooks可以查看： [GitHub - streamich/react-use: React Hooks — 👍](https://github.com/streamich/react-use)

#### 硬核的useEffect

在你高兴太早之前，`useEffect`还有可选的第二个参数，可以穿入一个`useEffect`内函数所依赖值的数组。

实际上所有的故事，所有的纠结都发生在这个参数😱。

##### 使用useEffect来替代生命周期函数

`useEffect`默认会在每次渲染后调用，如果你传传入一个`[]`，效果就和`componentDidMount`类似。

```typescript
import { EffectCallback, useEffect } from 'react';

const useMount = (effect: EffectCallback) => {
  useEffect(effect, []);
};

```

自然类似`componentWillUnmount`可以：

```typescript
const useUnmount = (fn: () => void | undefined) => {
  useEffect(() => fn, []);
};
```

不过Hook也没有覆盖所有的生命周期，`getSnapshotBeforeUpdate`和`componentDidCatch`暂时[没有对应的Hook](https://reactjs.org/docs/hooks-faq.html#do-hooks-cover-all-use-cases-for-classes)。

##### Capture Value props

来看如下的代码
```javascript

const FunName = () => {
  const [name, setName] = useState("init name");

  function log() {
    setTimeout(() => {
      console.log("FunName after 3000 ", name);
    }, 3000);
  }
  return (
    <div>
      <h2>Fun name log</h2>
      <input value={name} onChange={e => setName(e.target.value)} />
      <button onClick={log}>delay console.log</button>
    </div>
  );
};

class ClassNameView extends React.Component {
  state = { name: "init name" };
  log = () => {
    setTimeout(() => {
      console.log("ClassName after 3000 ", this.state.name);
    }, 3000);
  };
  render() {
    return (
      <div>
        <h2>class name log</h2>
        <input
          value={this.state.name}
          onChange={e => this.setState({ name: e.target.value })}
        />
        <button onClick={this.log}>delay console.log</button>
      </div>
    );
  }
}
```

两个功能一样的组件，一个函数组件，一个Class组件，在按钮点击后3000ms之内两者的行为却不一样。

![React-Hooks-Capture-value](https://image.hicc.pro/hicc_me1143539147_React-Hooks-Capture-value.gif)

类似同样的组件，使用父组件的props

```javascript

const FunName = () => {
  function log() {
    setTimeout(() => {
      console.log("FunName after 3000 ", name);
    }, 3000);
  }
  return (
    <div>
      <h2>Fun name log</h2>
      <button onClick={log}>delay console.log</button>
    </div>
  );
};

class ClassNameView extends React.Component {
  log = () => {
    setTimeout(() => {
      console.log("ClassName after 3000 ", this.state.name);
    }, 3000);
  };
  render() {
    return (
      <div>
        <h2>class name log</h2>
        <button onClick={this.log}>delay console.log</button>
      </div>
    );
  }
}

// 父组件
function App() {
  const [name, setName] = useState("init name");
  return (
    <div className="App">
      <h1>Hooks Capture props</h1>
      <input value={name} onChange={e => setName(e.target.value)} />
      <FunName name={name} />
      <ClassNameView name={name} />
    </div>
  );
}

```
同样行为不一样

![React-Hooks-Capture-props](https://image.hicc.pro/hicc_me1198663242_React-Hooks-Capture-props.gif)

普通javascript函数也有如下的行为：
```javascript
function sayHi(person) {
  const name = person.name;  
	setTimeout(() => {
    alert('Hello, ' + name);
  }, 3000);
}

let someone = {name: 'Dan'};
sayHi(someone);

someone = {name: 'Yuzhi'};
sayHi(someone);

someone = {name: 'Dominic'};
sayHi(someone);

//执行结果：
//Hello, Dan
//Hello, Yuzhi
//Hello, Dominic
```

也就是函数组件的行为才是“正确的”行为，而Class组件行为的原因在于React会修改，`this.state`和`this.props`使其指向最新的状态。

##### 使用useRef获取旧的props或者最新的state

`useRef`一般用作获取DOM的引用，根据[官方文档](https://reactjs.org/docs/hooks-reference.html#useref):

> useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

可变的对象会存在于组件的整个生命周期，因此可以用来保存值，保证拿到最新的值。

```javascript
function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  return (
    <h1>
      Now: {count}, before: {prevCount}
    </h1>
  );
}

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}
```

##### 告诉 React 如何对比 Effects

一般而言你需要将effects所依赖的内部state或者props都列入`useEffect`第二个参数，**不多不少**的告诉React 如何去对比Effects， 这样你的组件才会按预期渲染。

当然日常书写难免遗漏，这个[ESlint 插件](https://github.com/facebook/react/issues/14920)的`exhaustive-deps`规则可以辅助你做这些事情。

这里不再展开说，但是从我日常项目来看，这点还是需要费些心思的。

##### 使用useCallback来缓存你的函数

`useCallback`会根据传入的第二个参数来“记住”函数。 可以用它来避免函数被作为callback传入子组件时不必要渲染。

而且函数组件内的函数，如果需要在被不同的生命周期中调用，最好使用`useCallback`来处理，这样一方面拿到正确的值，一方面保证性能的优化。

```javascript
function SearchResults() {
  const [query, setQuery] = useState('react');
	const getFetchUrl = useCallback(() => {    
		return 'https://siet.com/search?query=' + query;  
	}, [query]); 

  useEffect(() => {
    const url = getFetchUrl();
    // ... Fetch data and do something ...
  }, [getFetchUrl]); 
}
```

总结来说Hooks的：

- 更彻底的函数化编程，粒度更细，也更精简
- 状态复用共享不会产生嵌套
- **Hooks可以调用Hooks**
- 更容易将组件的状态和UI分离。

可以更快速让大家写出，稳健，易测试，更易读的代码，enjoy～～

### Fiber

如果说Hooks改变了开发者如何写业务代码，那么Fiber就是React改变了如何渲染。简单来说，就是React 将任务切片，分优先级，然后按照一定策略来调度渲染，一举改变之前递归，不可打断式渲染。

更详尽的分析，等我搞懂了，再来说道～～～

---

原文 / Source: <https://hicc.pro/p/blog/whats-new-in-react-16x-suspense-hooks-fiber>
