关于为何使用React新特性Hook的一些实践与浅见

前言

关于Hook的定义官方文档是这么说的:html

Hook 是 React 16.8 的新增特性。它可让你在不编写 class 的状况下使用 state 以及其余的 React 特性。

简单来讲,就是在使用函数式组件时能用上state,还有一些生命周期函数等其余的特性。react

若是想了解Hook怎么用,官方文档和阮一峰的React Hooks 入门教程都讲得很清楚了,我建议直接看官方文档和阮大神的文章便可。编程

本篇博客只讲为何要用React的Hook新特性,以及它解决了什么问题。redux

为何使用Hook?

让咱们先看看别人怎么说。数组

阮大神的文章中给了一个示例代码:函数

import React, { Component } from "react";

export default class Button extends Component {
  constructor() {
    super();
    this.state = { buttonText: "Click me, please" };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    this.setState(() => {
      return { buttonText: "Thanks, been clicked!" };
    });
  }
  render() {
    const { buttonText } = this.state;
    return <button onClick={this.handleClick}>{buttonText}</button>;
  }
}

而且提出:测试

这个组件类仅仅是一个按钮,但能够看到,它的代码已经很"重"了。
真实的 React App 由多个类按照层级,一层层构成,复杂度成倍增加。
再加入 Redux,就变得更复杂。

实际上,上面这个代码的“重”有部分来源于写法问题,他可能并无“重”,让咱们看看下面这种class写法:this

import React, { Component } from "react";

export default class Button extends Component {
  state = {
    buttonText: "Click me, please"
  }
  handleClick = () => {
    this.setState(() => {
      return { buttonText: "Thanks, been clicked!" };
    });
  }
  render() {
    const { buttonText } = this.state;
    return <button onClick={this.handleClick}>{buttonText}</button>;
  }
}

而后再对比下使用了Hook的函数式组件:spa

import React, { useState } from "react";

export default function Button() {
  const [buttonText, setButtonText] = useState("Click me,   please");

  function handleClick() {
    return setButtonText("Thanks, been clicked!");
  }

  return <button onClick={handleClick}>{buttonText}</button>;
}

即便是咱们简化过的class写法,比起Hook的看起来好像也确实“重”了点。code

Hook的语法确实简练了一些,可是这个理由并非那么充分。

阮大神同时列举了Redux 的做者 Dan Abramov 总结了组件类的几个缺点:

  • 大型组件很难拆分和重构,也很难测试。
  • 业务逻辑分散在组件的各个方法之中,致使重复逻辑或关联逻辑。(这里我认为阮大神写的可能有点问题,应该是是各个生命周期方法更为准确)
  • 组件类引入了复杂的编程模式,好比 render props 和高阶组件。

这三点都是事实,因而有了函数化的组件,但以前的函数化组件没有state和生命周期,有了Hook那么就能够解决这个痛点。

并且Hook并不仅是这么简单,经过自定义Hook,咱们能够将原有组件的逻辑提取出来实现复用。

用useEffect解决生命周期致使的重复逻辑或关联逻辑

上面举的几个缺点,第一点和第三点你可能很容易理解,第二点就不容易理解了,因此咱们须要深刻到具体的代码中去理解这句话。

咱们看看下面这段代码:

import React, { Component } from "react";

export default class Match extends Component {
  state={
    matchInfo:''
  }
  componentDidMount() {
    this.getMatchInfo(this.props.matchId)
  }
  componentDidUpdate(prevProps) {
    if (prevProps.matchId !== this.props.matchId) {
      this.getMatchInfo(this.props.matchId)
    }
  }
  getMatchInfo = (matchId) => {
    // 请求后台接口获取赛事信息
    // ...
    this.setState({
      matchInfo:serverResult // serverResult是后台接口的返回值
    })
  }

  render() {
    const { matchInfo } = this.state
    return <div>{matchInfo}</div>;
  }
}

这样的代码在咱们的业务中常常会出现,经过修改传入赛事组件的ID,去改变这个赛事组件的信息。

在上面的代码中,受生命周期影响,咱们须要在加载完毕和Id更新时都写上重复的逻辑和关联逻辑。

因此如今你应该比较好理解这句话:业务逻辑分散在组件的各个生命周期方法之中,致使重复逻辑或关联逻辑

为了解决这一点,React提供了useEffect这个钩子。

可是在讲这个以前,咱们须要先了解到React带来的一个新的思想:同步。

咱们在上面的代码中所作的实际上就是在把组件内的状态和组件外的状态进行同步。

因此在使用Hook以前,咱们须要先摒弃生命周期的思想,而用同步的思想去思考这个问题。

如今再让咱们看看改造后的代码:

import React, { Component } from "react";

export default function Match({matchId}) {
  const [ matchInfo, setMatchInfo ] = React.useState('')
  React.useEffect(() => {
    // 请求后台接口获取赛事信息
    // ...
    setMatchInfo(serverResult) // serverResult是后台接口的返回值
  }, [matchId])
  
  return <div>{matchInfo}</div>;
}

看到这个代码,再对比上面的代码,你心中第一反应应该就是:简单。

React.useEffect接受两个参数,第一个参数是Effect函数,第二个参数是一个数组。

组件加载的时候,执行Effect函数。

组件更新会去判断数组中的各个值是否变更,若是不变,那么不会执行Effect函数。

而若是不传第二个参数,那么不管加载仍是更新,都会执行Effect函数。

顺便提一句,这里有组件加载和更新的生命周期的概念了,那么也应该是有组件卸载的概念的:

import React, { Component } from "react";

export default function Match({matchId}) {
  const [ matchInfo, setMatchInfo ] = React.useState('')
  React.useEffect(() => {
    // 请求后台接口获取赛事信息
    // ...
    setMatchInfo(serverResult) // serverResult是后台接口的返回值

    return ()=>{
      // 组件卸载后的执行代码
    }
  }, [matchId])
  
  return <div>{matchInfo}</div>;
}
}

这个经常使用于事件绑定解绑之类的。

用自定义Hook解决高阶组件

React的高阶组件是用来提炼重复逻辑的组件工厂,简单一点来讲就是个函数,输入参数为组件A,输出的是带有某逻辑的组件A+。

回想一下上面的Match组件,假如这个组件是页面A的首页头部用来展现赛事信息,而后如今页面B的侧边栏也须要展现赛事信息。

问题就在于页面A的这块UI须要用div,而页面B侧边栏的这块UI须要用到span。

保证今天早点下班的作法是复制A页面的代码到页面B,而后改下render的UI便可。

保证之后早点下班的作法是使用高阶组件,请看下面的代码:

import React from "react";

function hocMatch(Component) {
  return class Match React.Component {
    componentDidMount() {
      this.getMatchInfo(this.props.matchId)
    }
    componentDidUpdate(prevProps) {
      if (prevProps.matchId !== this.props.matchId) {
        this.getMatchInfo(this.props.matchId)
      }
    }
    getMatchInfo = (matchId) => {
      // 请求后台接口获取赛事信息
    }
    render () {
      return (
        <Component {...this.props} />
      )
    }
  }
}

const MatchDiv=hocMatch(DivUIComponent)
const MatchSpan=hocMatch(SpanUIComponent)

<MatchDiv matchId={1} matchInfo={matchInfo} />
<MatchSpan matchId={1} matchInfo={matchInfo} />

可是实际上有的时候咱们的高阶组件可能会更复杂,好比react-redux的connect,这就是高阶组件的复杂化使用方式。

又好比:

hocPage(
  hocMatch(
    hocDiv(DivComponent)
  )
)

毫无疑问高阶组件能让咱们复用不少逻辑,可是过于复杂的高阶组件会让以后的维护者望而却步。

而Hook的玩法是使用自定义Hook去提炼这些逻辑,首先看看咱们以前使用了Hook的函数式组件:

import React, { Component } from "react";

export default function Match({matchId}) {
  const [ matchInfo, setMatchInfo ] = React.useState('')
  React.useEffect(() => {
    // 请求后台接口获取赛事信息
    // ...
    setMatchInfo(serverResult) // serverResult是后台接口的返回值
  }, [matchId])
  
  return <div>{matchInfo}</div>;
}

而后,自定义Hook:

function useMatch(matchId){
  const [ matchInfo, setMatchInfo ] = React.useState('')
  React.useEffect(() => {
    // 请求后台接口获取赛事信息
    // ...
    setMatchInfo(serverResult) // serverResult是后台接口的返回值
  }, [matchId])
  return [matchInfo]
}

接下来,修改原来的Match组件

export default function Match({matchId}) {
  const [matchInfo]=useMatch(matchId)
  return <div>{matchInfo}</div>;
}

相比高阶组件,自定义Hook更加简单,也更加容易理解。

如今咱们再来处理如下这种状况:

hocPage(
  hocMatch(
    hocDiv(DivComponent)
  )
)

咱们的代码将不会出现这种不断嵌套状况,而是会变成下面这种:

export default function PageA({matchId}) {
  const [pageInfo]=usePage(pageId)
  const [matchInfo]=useMatch(matchId)
  const [divInfo]=useDiv(divId)

  return <ul>
    <li>{pageInfo}</li>
    <li>{matchInfo}</li>
    <li>{divInfo}</li>
  </ul>
}

是否须要改造旧的class组件?

如今咱们了解到了Hook的好,因此就须要去改造旧的class组件。

官方推荐不须要专门为了hook去改造class组件,而且保证将继续更新class相关功能。

实际上咱们也没有必要专门去改造旧项目中的class组件,由于工做量并不小。

可是咱们彻底能够在新的项目或者新的组件中去使用它。

总结

Hook是对函数式组件的一次加强,使得函数式组件能够作到class组件的state和生命周期。

Hook的语法更加简练易懂,消除了class的生命周期方法致使的重复逻辑代码,解决了高阶组件难以理解和使用困难的问题。

然而Hook并无让函数式组件能作到class组件作不到的事情,它只是让不少事情变得更加简单而已。

class组件并不会消失,但hook化的函数式组件将是趋势。

相关文章
相关标签/搜索