> For the complete documentation index, see [llms.txt](https://gb.akanote.cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gb.akanote.cn/react/chapter4.md).

# Part4-React Hooks

## 预习视频

[【最新】零基础快速入门React 17.x](https://www.bilibili.com/video/BV1U5411T7hY)

> 视频P56-P73

## 今日学习目标

1、掌握函数式组件

2、掌握Hooks的写法

3、掌握Fragment

4、了解错误边界

5、掌握PureComponent

6、了解HOC高阶组件

7、掌握LazyLoad与Suspense、fallback

## 一、React Hooks介绍【了解】

官网地址：<https://react.docschina.org/docs/hooks-intro.html>

[![image-20210420155440826](https://tva1.sinaimg.cn/large/008eGmZEgy1gpq9ido7olj31j80u0n8p.jpg)](https://tva1.sinaimg.cn/large/008eGmZEgy1gpq9ido7olj31j80u0n8p.jpg)

## 二、函数式组件【掌握】

纯函数组件有以下特点：

* 没有状态
* 没有生命周期
* 没有 this

因存在如上特点，使得纯函数组件只能做UI展示的功能， 涉及到状态的管理与切换就不得不用到类组件或者redux。 但因为简单的页面也是用类组件，同时要继承一个React实例，使得代码会显得很重。

以前我们可以使用class来声明一个组件，其实使用function也可以定义一个组件：

创建 `App1.js` ：

```jsx
import React from 'react'

function App1(){
    return (
        <div>
            <h1>function 声明的App</h1>
        </div>
    )
}

export default App1;
```

**备注：**

在vscode中，如果安装过 `ES7 React/Redux/GraphQL/React-Native snippets` 这个插件，即可直接使用 `rfc` 快捷键敲出以下模板：

```jsx
import React from 'react';

const App = () => {
    return (
        <div>
            
        </div>
    );
}

export default App;
```

在 `index.js` 中调用：

```jsx
import ReactDOM from 'react-dom'
import App from './App1'

ReactDOM.render(
    <App />,
    document.getElementById('root')
)
```

效果如下：

[![](https://tva1.sinaimg.cn/large/008eGmZEgy1gpqbkzeyouj31j70u0tdv.jpg)](https://tva1.sinaimg.cn/large/008eGmZEgy1gpqbkzeyouj31j70u0tdv.jpg)

## 三、常用Hooks【掌握】

按照我们之前的类声明编程，写一个累加器如下：

```jsx
import React, { Component } from 'react'

export default class App extends Component {
    constructor(p){
        super(p)
        this.state = {num: 0}
    }
    render() {
        return (
            <div>
                <h2>{this.state.num}</h2>
                <button onClick={this.addNum.bind(this)}>累加</button>
            </div>
        )
    }
    addNum(){
        this.setState({
            num: this.state.num+1
        })
    }
}
```

效果如下：

[![Jietu20210420-160027-HD](https://tva1.sinaimg.cn/large/008eGmZEgy1gpq9orin2lg303g06cawo.gif)](https://tva1.sinaimg.cn/large/008eGmZEgy1gpq9orin2lg303g06cawo.gif)

### 1、useState

现在我们改成函数式编程：

```jsx
// useState就是hooks提供的一个api
import React, { useState } from 'react'

function App(){
  	// 这里useState(0)中的0，就是定义num的初始值
    const [num, setNum] = useState(0);

    return (
        <div>
            <h2>{num}</h2>
            <button onClick={()=>{setNum(num+1)}}>累加</button>
        </div>
    )
}

export default App;
```

累加效果一样可以实现。

### 2、Hooks开发注意点【注意】

Hooks的定义不能放在条件判断的语句中，如：

```js
if(true){
	const [num, setNum] = useState(0);
}
```

这种写法会报错：

[![image-20210420162411335](https://tva1.sinaimg.cn/large/008eGmZEgy1gpqad2ktpoj32l80fkjx0.jpg)](https://tva1.sinaimg.cn/large/008eGmZEgy1gpqad2ktpoj32l80fkjx0.jpg)

这里注意：

Hooks的API只能在函数内的最外层声明。

### 3、useEffect

React函数式编程没有生命周期，因此需要借助useEffect来实现。

#### \* componentDidMount与componentDidUpdate

如果使用class编程，我们检测数据变化就需要这么做：

```jsx
import React, { Component } from 'react'

export default class App extends Component {
    constructor(p){
        super(p)
        this.state = {num: 0}
    }
    render() {
        return (
            <div>
                <h2>{this.state.num}</h2>
                <button onClick={this.addNum.bind(this)}>累加</button>
            </div>
        )
    }
    componentDidMount(){
        console.log('Mount')
    }
    componentDidUpdate(){
        console.log('Update')
    }
    addNum(){
        this.setState({
            num: this.state.num+1
        })
    }
}
```

结合componentDidMount与componentDidUpdate两个生命周期函数来检测。

[![1](https://tva1.sinaimg.cn/large/008eGmZEgy1gpqb8c6pw7g30gn091b2e.gif)](https://tva1.sinaimg.cn/large/008eGmZEgy1gpqb8c6pw7g30gn091b2e.gif)

我们来看看function编程与Hooks怎么解决这一问题：

```jsx
import React, { useState, useEffect } from 'react'

function App1(){
    const [num, setNum] = useState(0);
    
    useEffect(()=>{
        console.log('useEffect')
    })
    
    return (
        <div>
            <h2>{num}</h2>
            <button onClick={()=>{setNum(num+1)}}>累加</button>
        </div>
    )
}

export default App1;
```

一个useEffect就可以替代两个生命周期函数：

[![2](https://tva1.sinaimg.cn/large/008eGmZEgy1gpqba5r73tg30gn091qva.gif)](https://tva1.sinaimg.cn/large/008eGmZEgy1gpqba5r73tg30gn091qva.gif)

这里注意：

> useEffect是异步的，不会阻断试图更新。

如果有两个字段都在页面中有更新，但只想指定其中某个来检测，那么可以给 `useEffect` 添加第二个参数，第二个参数是个数组，数组为空时，表示不检测，数组项为某个字段名称时，表示只检测指定字段的更新:

```jsx
import {useState, useEffect} from 'react'

function App1(){
  const [num, setNum] = useState(0)
  const [num1, setNum1] = useState(1)

  useEffect(()=>{
    console.log('视图更新了')
  }, [num])		// 这里只检测num更新，num1的更新不会检测到

  return (
    <div>
      <h1>{num}</h1>
      <button onClick={()=>setNum(num+1)}>累加</button>
      <h1>{num1}</h1>
      <button onClick={()=>setNum1(num1+1)}>累加</button>
    </div>
  )
}

export default App1;
```

#### \* componentWillUnmount

当我们需要在组件销毁时做一些清除工作，就需要使用componentWillUnmount。但函数式编程没有这个生命周期，因此也同样需要借助useEffect实现。

由于这里需要做页面的销毁，在 `index.js` 中最后添加：

```jsx
setTimeout(()=>{
    ReactDOM.render(
        <p>你好</p>,
        document.getElementById('root')
    )
}, 3000)
```

创建 `App2.js` :

```jsx
import {useState, useEffect} from 'react'

export default function App2() {
    let [num, setNum] = useState(0);
    let [num1, setNum1] = useState(0);

  	// 这个hook检测num更新
    useEffect(()=>{
        console.log('渲染了num');
    }, [num])	// [num] 代表这个useEffect只检测num这个字段的更新

  	// 这个hook检测离开组件
    useEffect(()=>{
        return ()=>{
            console.log('离开了App1组件')
        }
    },[])		// [] 代表这个useEffect不检测任何一个字段更新

    return (
        <div>
            <h2>num：{num}</h2>
            <button onClick={() => setNum(num+1)}>累加</button>
            <hr />
            <h2>num1：{num1}</h2>
            <button onClick={() => setNum1(num1+1)}>累加</button>
        </div>
    )
}
```

⚠️ 这里注意：

> 同个useEffect下，在检测销毁和检测字段更新之间，只能二选一。留下空数组，可以检测到return中的销毁。数组非空时，视图更新会带动return返回值，因此如果要检测字段更新，就无法检测销毁。

### 4、useContext

函数式的组件，如果需要父传子，那么就需要使用useContext。

#### \* 用法一：

useContext有两种用法，第一种是使用useContext来调用上下文内容。代码如下：

```jsx
/* 
    createContext用于创建上下文
    useContext用于调用上下文
*/
import {useState, createContext, useContext} from 'react'

// 1、创建上下文
const NumContext = createContext();

// 子组件
function Count(){
    // 3、调用上下文内容
    const num = useContext(NumContext)
    return (
        <h3>{num}</h3>
    )
}

function App3(){
    const [num, setNum] = useState(0)
    return (
        <>
            {/* 2、设置提供器，并传入value，包含子组件 */}
            <NumContext.Provider value={num}>
                <Count />
            </NumContext.Provider>
            <button onClick={()=>{setNum(num+1)}}>累加</button>
        </>
    )
}

export default App3;
```

#### \* 用法二：

使用 `Consumer` 来接收数据，这种方法还可以使用 Chrome 扩展 `react-context-devtool3.2.crx` 来观测数据。

首先，将 `react-context-devtool3.2.crx` 直接拽入 Chrome 的扩展中。

扩展下载链接: <https://pan.baidu.com/s/1ejGlj6_Sierd6w6GQ74mYw> 提取码: 1wv6

其次，安装 `react-context-devtool`：

```shell
npm install react-context-devtool

# or 
yarn add react-context-devtool
```

然后在项目入口文件 `src>index.js` 中：

```jsx
// 将原本代码改写为：
import ReactDOM from 'react-dom'
import App from './App5'
// 引入react-context-devtool
import { debugContextDevtool } from 'react-context-devtool';

let container = document.getElementById('root')

ReactDOM.render(
    <App />,
    container
)

debugContextDevtool(container);
```

> 这里注意：
>
> debugContextDevtool方法有第二个参数，是用来传入配置项的。主要配置项有：

| Name              | Type    | Default | Description                                         |
| ----------------- | ------- | ------- | --------------------------------------------------- |
| `debugReducer`    | boolean | `true`  | enable/disable useReducer debug                     |
| `debugContext`    | boolean | `true`  | enable/disable context debug                        |
| `disable`         | boolean | `false` | disable react-context-devtool including manual mode |
| `disableAutoMode` | boolean | `false` | disable auto mode only                              |

> 但我们直接忽略不填就行了。具体参考网址：
>
> [Debug ReactJS Context and useReducer hook with React Context Devtool](https://dev.to/deeppatel234/debug-reactjs-context-and-usereducer-hook-with-react-context-devtool-2ia)

引入组件并在Provider使用：

```jsx
import { ContextDevTool } from 'react-context-devtool';

<NumContext.Provider value={num}>
  <ContextDevTool context={NumContext} />
  <Sub />
</NumContext.Provider>
```

然后在子组件中修改：

```jsx
const Sub = () => {
    return (
        <NumContext.Consumer>
            {
                num => {
                    if (window._REACT_CONTEXT_DEVTOOL) {
                      window._REACT_CONTEXT_DEVTOOL({ num });
                    }
                    return <h1>{num}</h1>;
                  }
            }
        </NumContext.Consumer>
    )
}
```

#### \* 最终代码：

重新修改 `App3.jsx`：

```jsx
import React, {useState, useContext, createContext} from 'react';
import { ContextDevTool } from 'react-context-devtool';

// 创建上下文
const NumContext = createContext()

const Sub = () => {
    // const num = useContext(NumContext)
    return (
        <NumContext.Consumer>
            {
                num => {
                    if (window._REACT_CONTEXT_DEVTOOL) {
                      window._REACT_CONTEXT_DEVTOOL({ id: 'uniqContextId', displayName: 'Context Display Name', num });
                    }
                    return <h1>{num}</h1>;
                  }
            }
        </NumContext.Consumer>
    )
}

const App5 = () => {
    const [num, setNum] = useState(0);
    return (
        <div>
            <NumContext.Provider value={num}>
                <ContextDevTool context={NumContext} id="uniqContextId" displayName="Context Display Name" />
                <Sub />
            </NumContext.Provider>
            <button onClick={()=>setNum(num+1)}>累加</button>
        </div>
    );
}

export default App5;
```

### 5、useReducer

useReducer与useContext不太一样，有点类似于Redux。

```jsx
import {useReducer} from 'react'

// 1、定义一个reducer
function numReducer(state, action){
    // 3、进行一步深拷贝，因为state是不允许直接修改的
    let newState = JSON.parse(JSON.stringify(state));
    switch(action.type){
        case 'add':
            newState.num++;
            return newState;
        case 'cut':
            newState.num--;
            return newState;
        default:
            return newState;
    }
}

function App4(){
    // 2、使用reducer并解构出state与dispatch, {num:0}表示state的默认值
    const [state, dispatch] = useReducer(numReducer, {num: 0});

    return (
        <>
            <h2>{state.num}</h2>
            {/* 使用dispatch调用 */}
            <button onClick={()=>{dispatch({type: 'add'})}}>累加</button>
            <button onClick={()=>{dispatch({type: 'cut'})}}>减少</button>
        </>
    )
}

export default App4;
```

### 6、useContext结合useReducer实现redux

#### \* 第一步

将以上 `1、定义一个reducer` 这一步的函数抽离到 `store>reducer.js` 中：

```js
// 1、定义一个reducer
export const numReducer = function(state, action){
    // 3、进行一步深拷贝，因为state是不允许直接修改的
    let newState = JSON.parse(JSON.stringify(state));
    switch(action.type){
        case 'add':
            newState.num++;
            return newState;
        case 'cut':
            newState.num--;
            return newState;
        default:
            return newState;
    }
}
```

#### \* 第二步

引入reducer，并引入useContext与createContext

```js
import {useReducer, useContext, createContext} from 'react'
import {numReducer} from './store/reducer'

// 创建上下文
const NumContext = createContext()
```

#### \* 第三步

定义Sub1和Sub2子组件，Sub1用于累加，Sub2用于减少。

```jsx
// 定义Sub1子组件
function Sub1(){
    const dispatch = useContext(NumContext)		// 从提供器中传过来
    return <button onClick={()=>{dispatch({type: 'add'})}}>累加</button>
}

// 定义Sub2子组件
function Sub2(){
    const dispatch = useContext(NumContext)
    return <button onClick={()=>{dispatch({type: 'cut'})}}>减少</button>
}
```

#### \* 第四步

在App4组件中调用Sub1和Sub2，并使用Provider：

```jsx
function App4(){
    // 2、使用reducer并解构出state与dispatch, {num:0}表示state的默认值
    const [state, dispatch] = useReducer(numReducer, {num: 0});

    return (
        <NumContext.Provider value={dispatch}>
            <h2>{state.num}</h2>
            {/* 使用dispatch调用 */}
            <Sub1 />
            <Sub2 />
        </NumContext.Provider>
    )
}
```

#### \* 完整代码

```jsx
import {useReducer, useContext, createContext} from 'react'
import {numReducer} from './store/reducer'

// 创建上下文
const NumContext = createContext()

// 定义Sub1子组件
function Sub1(){
    const dispatch = useContext(NumContext)
    return <button onClick={()=>{dispatch({type: 'add'})}}>累加</button>
}

// 定义Sub2子组件
function Sub2(){
    const dispatch = useContext(NumContext)
    return <button onClick={()=>{dispatch({type: 'cut'})}}>减少</button>
}


function App4(){
    // 2、使用reducer并解构出state与dispatch, {num:0}表示state的默认值
    const [state, dispatch] = useReducer(numReducer, {num: 0});

    return (
        <NumContext.Provider value={dispatch}>
            <h2>{state.num}</h2>
            {/* 使用dispatch调用 */}
            <Sub1 />
            <Sub2 />
        </NumContext.Provider>
    )
}

export default App4;
```

#### \* 提升：

如果还想把h2标签抽离成组件，那么代码要改为：

```jsx
import {useReducer, useContext, createContext} from 'react'
import {numReducer} from './store/reducer'

// 创建上下文
const NumContext = createContext()

// 定义Sub1子组件
function Sub1(){
    const {dispatch} = useContext(NumContext)
    return <button onClick={()=>{dispatch({type: 'add'})}}>累加</button>
}

// 定义Sub2子组件
function Sub2(){
    const {dispatch} = useContext(NumContext)
    return <button onClick={()=>{dispatch({type: 'cut'})}}>减少</button>
}

// 定义Sub3子组件——h2标签组件
function Sub2(){
    const {state} = useContext(NumContext)
    return <h2>{state.num}</h2>
}


function App4(){
    // 2、使用reducer并解构出state与dispatch, {num:0}表示state的默认值
    const [state, dispatch] = useReducer(numReducer, {num: 0});

    return (
      	{/* 这里注意：value中要传对象过去context，因此useContext获得的内容要解构 */}
        <NumContext.Provider value={{state, dispatch}}>
            <Sub3 />
            {/* 使用dispatch调用 */}
            <Sub1 />
            <Sub2 />
        </NumContext.Provider>
    )
}

export default App4;
```

这里再提供Consumer写法：

> 可以把子组件的useContext改为：

```jsx
function Sub1() {
    // const {dispatch} = useContext(NumContext)
    return (
        <NumberContext.Consumer>
            {
                ({dispatch}) => {
                    return <button onClick={() => dispatch({ type: 'addNum', value: 1 })}>累加</button>
                }
            }
        </NumberContext.Consumer>
    )
}

function Sub2() {
    // const {state} = useContext(NumContext)
    return (
        <NumberContext.Consumer>
            {
                ({state}) => {
                    return <h2>{state.num}</h2>
                }
            }
        </NumberContext.Consumer>
    )
}
```

### 7、useRef

ref就是类似于id的属性，用于获取dom元素，比较简单：

```jsx
import {useRef} from 'react'

function App5(){
    const element = useRef(null)

    const handleClick = () => {
        console.log(element.current)    		// 获取input
        console.log(element.current.value)  // 获取到input中的值
        
    }

    return (
        <>
            <input type="text" ref={element} />
            <button onClick={handleClick}>获取input标签</button>
        </>
    )
}

export default App5;
```

> 除此之外还有useMemo等多个Hooks，但我们没必要全部掌握，了解常用的即可。

## 四、Fragment与空标签【掌握】

组件中每个return都必须包含一个根标签，通常我们会使用 `<div></div>` ，但这也比较麻烦，毕竟有时真的不想套一个div，那我们可以这样：

```jsx
// 使用空标签
function Sub2(){
    const dispatch = useContext(NumContext)
    return (
        <>
            <button>减少</button>
        </>
    )
}
```

或者是：

```jsx
import {Fragment} from 'react'

// 使用Fragment，Fragment不会渲染成为标签节点
function Sub2(){
    const dispatch = useContext(NumContext)
    return (
        <Fragment>
            <button>减少</button>
        </Fragment>
    )
}
```

#### \* 注意：

> <>\</> 语法不能接受键值或属性，而 `<Fragment>` 可以。**key** 和 **children** 是唯一可以传递给 `Fragment` 的属性，主要用在做for循环时可以拿key来做唯一标识。未来可能会添加对其他属性的支持，例如事件。

## 五、错误边界【了解】

官网地址：<https://react.docschina.org/docs/error-boundaries.html>

错误边界是一种 React 组件，可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误，并且渲染出备用 UI。错误边界在渲染期间、生命周期和整个组件树的构造函数中捕获错误。

它无法在以下场景捕获错误：

* 事件处理（了解更多）
* 异步代码（例如 setTimeout 或 requestAnimationFrame 回调函数）
* 服务端渲染
* 它自身抛出来的错误（并非它的子组件）

错误边界有两种，一种是`componentDidCatch()`，另一种是 `static getDerivedStateFromError` 。这是两种生命周期方法，只要class组件用了任意一种，那它就变成一个错误边界组件。

### 1、componentDidCatch()

#### 声明一个错误边界组件 `ErrorBoundary.jsx`，这个组件可以直接应用到全局

```jsx
import React, { Component } from 'react'

export default class ErrorBoundary extends Component {
    state = {
        hasError: false
    }
    // 捕获错误
    componentDidCatch(error, errorInfo){
        this.setState({
            error,
            errorInfo,
          	hasError: true
        })
    }
    render() {
        if(this.state.hasError){
            return (
                <div>
                		<h2>出错了！！</h2>
                </div>
            )
        }else{
            return this.props.children
        }
    }
}
```

#### 声明一个测试组件 `Test.jsx`，只要输入的是非字母就会报错：

```jsx
import React, { Component } from 'react'

export default class Test extends Component {
    state = {
        value: ""
    }
    /*
        * JS中的||符号：
        1、运算方法：
            只要“||”前面为false,不管“||”后面是true还是false，都返回“||”后面的值。
            只要“||”前面为true,不管“||”后面是true还是false，都返回“||”前面的值。
        2、总结：真前假后

        * throw语句会抛出一个错误。当错误发生时， JavaScript 会停止执行并抛出错误信息。
    */
    render() {
        const value = this.state.value;
        if(/^[a-zA-Z]+$/.test(value) || value===""){
            return <input type="text" placeholder="只能填写字母" value={value} onChange={this.changeFn.bind(this)} />
        }
    }
    changeFn(e){
        this.setState({
            value: e.currentTarget.value
        })
    }
}
```

#### 在整个App中的组件最外层，套上这个错误边界组件：

```jsx
import React, { Component } from 'react'
import ErrorBoundary from './ErrorBoundary'
import Test from './Test'

export default class App6 extends Component {
    render(){
        return(
            <ErrorBoundary>
                <Test />
            </ErrorBoundary>
        )
    }
}
```

如上，可以测试到，只要输入的是非字母，就会报错:

[![](https://tva1.sinaimg.cn/large/008i3skNgy1gprbxlsdq1g30gn091x6y.gif)](https://tva1.sinaimg.cn/large/008i3skNgy1gprbxlsdq1g30gn091x6y.gif)

### 2、static getDerivedStateFromError()

将错误边界组件中的 `componentDidCatch()` 改为：

```jsx
import React from 'react'
export default class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false };
    }
  	// 使用static getDerivedStateFromError()
    // 意思为：获得从错误边界中产生的状态
    static getDerivedStateFromError(error) {
        return { hasError: true };
      	// 这里最好写为： return {hasError: error}
      	// 本质上是根据error是否存在来判断是否设置 hasError 为 true，但其实能够进入这个函数的话，也就代表一定会有错误，因此直接设置 hasError 为 true 也未尝不可
    }
    render() {
        if (this.state.hasError) {
            return (
                <div>
                	<h2>出错了！！</h2>
                </div>
            )
        }
        //正常则返回子元素，即该组件包裹的元素
        return this.props.children;
    }
}
```

## 六、PureComponent【熟悉】

我们在学习生命周期的时候，了解过 `shouldComponentUpdate` ，它是用来判断字段修改前后值是否相等的，而且必须返回true或false，我们来看：

```jsx
import React, { Component } from 'react'

// 子组件
class Sub extends Component {
  	// 通过shouldComponentUpdate返回的boolean，决定是否触发UI修改
    shouldComponentUpdate(nextProps, nextState){
        return nextProps.num !== this.props.num;
    }
    render(){
        return <h1>{this.props.num}</h1>
    }
}

export default class App7 extends Component {
    state = {
        num: 1
    }
    render() {
        return (
            <div>
                {/* 传值 */}
                <Sub num={this.state.num} />
                <button onClick={this.handleClick.bind(this)}>修改num</button>
            </div>
        )
    }
    handleClick(){
        this.setState({
            num: 2		// 想改成相同的值，可以num: 1
        })
    }
}
```

我们有个替代的方案，那就是 `PureComponent` 。它其实就是在帮我们做这样一件事:**自动的帮我们编写`shouldComponentUpdate`方法, 避免我们为每个组件都编写一次的麻烦**。我们只需要这样, 就可以一步到位：

```jsx
import React, { Component, PureComponent } from 'react'

class Sub extends PureComponent {
    render(){
        return <h1>{this.props.num}</h1>
    }
}

export default class App7 extends Component {
    state = {
        num: 1
    }
    render() {
        return (
            <div>
                {/* 传值 */}
                <Sub num={this.state.num} />
                <button onClick={this.handleClick.bind(this)}>修改num</button>
            </div>
        )
    }
    handleClick(){
        this.setState({
            num: 1			// 当为1时，你会发现改不动
        })
    }
}
```

## 七、高阶组件HOC【掌握】

官网地址：<https://react.docschina.org/docs/higher-order-components.html>

\*\*高阶组件（HOC）\*\*是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分，它是一种基于 React 的组合特性而形成的设计模式。

可以简单将其理解成 “类工厂”，一般在class组件时使用。我们先来写个简单的案例：

```jsx
import React, { Component } from 'react'

class Sub1 extends Component {
    state = {
        num: 1
    }
    addNumFn(){
      	// 一秒后将num修改为2
        setTimeout(()=>{
            this.setState({num: 2})
        }, 1000)
    }
    render(){
        return <button onClick={this.addNumFn.bind(this)}>Sub1数字为：{this.state.num}</button>
    }
}

class Sub2 extends Component {
    state = {
        num: 1
    }
    addNumFn(){
      	// 两秒后将num修改为2
        setTimeout(()=>{
            this.setState({num: 2})
        }, 2000)
    }
    render(){
        return <button onClick={this.addNumFn.bind(this)}>Sub2数字为：{this.state.num}</button>
    }
}

export default class App8 extends Component {
    render() {
        return (
            <div>
                <Sub1></Sub1>
                <hr/>
                <Sub2></Sub2>
            </div>
        )
    }
}
```

上面的Sub1和Sub2都是在定时器结束后修改num，大家可以看到代码几乎完全一样。那么这时候你肯定会想到代码抽离，所以我们定义一个函数来抽离：

```jsx
import React, { Component } from 'react'

// 定义一个高阶组件HOC（本质就是高阶函数HOF）
// 参数传入一个Component，也就是调用这个函数时需要传入的组件
const hocFunc = (Component, timer) => {
    // 返回一个组件，这里class后面可以不跟组件名
    return class extends Component {
        state = {
            num: 1
        }
        addNumFn(){
          	// 两秒后将num修改为2
            setTimeout(()=>{
                this.setState({num: 2})
            }, 2000)
        }
        render(){
            // 将state的值传递给组件，并返回这个组件
            return <Component num={this.state.num} addNumFn={this.addNumFn.bind(this)} />
        }
    }
}

class Sub1 extends Component {
    render(){
        // 要用props接收
        return <button onClick={this.props.addNumFn}>Sub1数字为：{this.props.num}</button>
    }
}

class Sub2 extends Component {
    render(){
        return <button onClick={this.props.addNumFn}>Sub2数字为：{this.props.num}</button>
    }
}

const Sub1Com = hocFunc(Sub1, 1000);	// 使用高阶组件返回出来的是组件，可以在App8中直接调用
const Sub2Com = hocFunc(Sub2, 2000);

export default class App8 extends Component {
    render() {
        return (
            <div>
            		{/* 渲染的是返回出来的值 */}
                <Sub1Com></Sub1Com>
                <hr/>
                <Sub2Com></Sub2Com>
            </div>
        )
    }
}
```

如此，咱们就实现了高阶组件。

## 八、LazyLoad【熟悉】

React有组件懒加载的功能。

我们先定义一个 `Sub.js` 子组件：

```jsx
import React, { Component } from 'react'

export default class Sub extends Component {
    render() {
        return (
            <h2>Sub</h2>
        )
    }
}
```

再在父组件引用：

```jsx
import React, { Component } from 'react'
// 使用 React.lazy引入
const Sub = React.lazy(()=>import('./Sub'))

export default class App9 extends Component {
    render() {
        return (
           <Sub />
        )
    }
}
```

但以上代码是无效的，因为：懒加载必须结合Suspense一起使用。

## 九、Suspense【熟悉】

Suspense主要解决的就是网络IO问题，是用来包裹异步组件，添加loading效果等。

我们添加Suspense代码：

```jsx
import React, { Component, Suspense } from 'react'
// 懒加载
const Sub = React.lazy(() => import('./Sub'));

export default class App9 extends Component {
    render() {
        return (
            // Suspense包裹异步组件
            <Suspense fallback={<div>loading...</div>}>
                <Sub />
            </Suspense>
        )
    }
}
```

至此，我们将network的 `No throtting` 调为 `slow 3G` ，然后刷新页面，就可以看到loading的效果。

## 十、作业

### 作业一（初级）

使用 Function Component结合Hooks完成Tab栏切换。

### 作业二（中级）

使用Function Component结合Hooks完成微博发布。

项目效果：<http://codesohigh.com/vue-weibo/>

### 作业三（高级）

开始搭建 [《IT猿题库》](http://codesohigh.com/yuantiku/#/home)项目，并完成首页布局。（打开项目时记得先清空localStorage）

项目psd设计稿：链接: <https://pan.baidu.com/s/1h5Zz45ZpX6fspbYWqsBtdA> 提取码: 8w1g
