前言

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

  1. Hook React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

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

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

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

为什么使用Hook?

让我们先看看别人怎么说。

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

  1. import React, { Component } from "react";
  2. export default class Button extends Component {
  3. constructor() {
  4. super();
  5. this.state = { buttonText: "Click me, please" };
  6. this.handleClick = this.handleClick.bind(this);
  7. }
  8. handleClick() {
  9. this.setState(() => {
  10. return { buttonText: "Thanks, been clicked!" };
  11. });
  12. }
  13. render() {
  14. const { buttonText } = this.state;
  15. return <button onClick={this.handleClick}>{buttonText}</button>;
  16. }
  17. }

并且提出:

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

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

  1. import React, { Component } from "react";
  2. export default class Button extends Component {
  3. state = {
  4. buttonText: "Click me, please"
  5. }
  6. handleClick = () => {
  7. this.setState(() => {
  8. return { buttonText: "Thanks, been clicked!" };
  9. });
  10. }
  11. render() {
  12. const { buttonText } = this.state;
  13. return <button onClick={this.handleClick}>{buttonText}</button>;
  14. }
  15. }

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

  1. import React, { useState } from "react";
  2. export default function Button() {
  3. const [buttonText, setButtonText] = useState("Click me, please");
  4. function handleClick() {
  5. return setButtonText("Thanks, been clicked!");
  6. }
  7. return <button onClick={handleClick}>{buttonText}</button>;
  8. }

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

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

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

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

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

而且Hook并不只是这么简单,通过自定义Hook,我们可以将原有组件的逻辑提取出来实现复用。

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

上面举的几个缺点,第一点和第三点你可能很容易理解,第二点就不容易理解了,所以我们需要深入到具体的代码中去理解这句话。

我们看看下面这段代码:

  1. import React, { Component } from "react";
  2. export default class Match extends Component {
  3. state={
  4. matchInfo:''
  5. }
  6. componentDidMount() {
  7. this.getMatchInfo(this.props.matchId)
  8. }
  9. componentDidUpdate(prevProps) {
  10. if (prevProps.matchId !== this.props.matchId) {
  11. this.getMatchInfo(this.props.matchId)
  12. }
  13. }
  14. getMatchInfo = (matchId) => {
  15. // 请求后台接口获取赛事信息
  16. // ...
  17. this.setState({
  18. matchInfo:serverResult // serverResult是后台接口的返回值
  19. })
  20. }
  21. render() {
  22. const { matchInfo } = this.state
  23. return <div>{matchInfo}</div>;
  24. }
  25. }

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

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

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

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

但是在讲这个之前,我们需要先了解到React带来的一个新的思想:同步。

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

所以在使用Hook之前,我们需要先摒弃生命周期的思想,而用同步的思想去思考这个问题。

现在再让我们看看改造后的代码:

  1. import React, { Component } from "react";
  2. export default function Match({matchId}) {
  3. const [ matchInfo, setMatchInfo ] = React.useState('')
  4. React.useEffect(() => {
  5. // 请求后台接口获取赛事信息
  6. // ...
  7. setMatchInfo(serverResult) // serverResult是后台接口的返回值
  8. }, [matchId])
  9. return <div>{matchInfo}</div>;
  10. }

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

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

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

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

而如果不传第二个参数,那么无论加载还是更新,都会执行Effect函数。

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

  1. import React, { Component } from "react";
  2. export default function Match({matchId}) {
  3. const [ matchInfo, setMatchInfo ] = React.useState('')
  4. React.useEffect(() => {
  5. // 请求后台接口获取赛事信息
  6. // ...
  7. setMatchInfo(serverResult) // serverResult是后台接口的返回值
  8. return ()=>{
  9. // 组件卸载后的执行代码
  10. }
  11. }, [matchId])
  12. return <div>{matchInfo}</div>;
  13. }
  14. }

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

用自定义Hook解决高阶组件

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

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

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

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

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

  1. import React from "react";
  2. function hocMatch(Component) {
  3. return class Match React.Component {
  4. componentDidMount() {
  5. this.getMatchInfo(this.props.matchId)
  6. }
  7. componentDidUpdate(prevProps) {
  8. if (prevProps.matchId !== this.props.matchId) {
  9. this.getMatchInfo(this.props.matchId)
  10. }
  11. }
  12. getMatchInfo = (matchId) => {
  13. // 请求后台接口获取赛事信息
  14. }
  15. render () {
  16. return (
  17. <Component {...this.props} />
  18. )
  19. }
  20. }
  21. }
  22. const MatchDiv=hocMatch(DivUIComponent)
  23. const MatchSpan=hocMatch(SpanUIComponent)
  24. <MatchDiv matchId={1} matchInfo={matchInfo} />
  25. <MatchSpan matchId={1} matchInfo={matchInfo} />

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

又比如:

  1. hocPage(
  2. hocMatch(
  3. hocDiv(DivComponent)
  4. )
  5. )

毫无疑问高阶组件能让我们复用很多逻辑,但是过于复杂的高阶组件会让之后的维护者望而却步。

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

  1. import React, { Component } from "react";
  2. export default function Match({matchId}) {
  3. const [ matchInfo, setMatchInfo ] = React.useState('')
  4. React.useEffect(() => {
  5. // 请求后台接口获取赛事信息
  6. // ...
  7. setMatchInfo(serverResult) // serverResult是后台接口的返回值
  8. }, [matchId])
  9. return <div>{matchInfo}</div>;
  10. }

然后,自定义Hook:

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

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

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

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

现在我们再来处理以下这种情况:

  1. hocPage(
  2. hocMatch(
  3. hocDiv(DivComponent)
  4. )
  5. )

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

  1. export default function PageA({matchId}) {
  2. const [pageInfo]=usePage(pageId)
  3. const [matchInfo]=useMatch(matchId)
  4. const [divInfo]=useDiv(divId)
  5. return <ul>
  6. <li>{pageInfo}</li>
  7. <li>{matchInfo}</li>
  8. <li>{divInfo}</li>
  9. </ul>
  10. }

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

现在我们了解到了Hook的好,所以就需要去改造旧的class组件。

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

实际上我们也没有必要专门去改造旧项目中的class组件,因为工作量并不小。

但是我们完全可以在新的项目或者新的组件中去使用它。

总结

Hook是对函数式组件的一次增强,使得函数式组件可以做到class组件的state和生命周期。

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

然而Hook并没有让函数式组件能做到class组件做不到的事情,它只是让很多事情变得更加简单而已。

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

关于为什么使用React新特性Hook的一些实践与浅见的更多相关文章

  1. react新特性hook

    一.hook示例.   import React, { useState } from 'react'; function Example() { // 声明一个叫 “count” 的 state 变 ...

  2. react新特性 react hooks

    本文介绍的是react新特性react hooks,本文面向的是有一定react开发经验的小伙伴,如果你对react还不是很熟悉的话我建议你先学习react并多多联系. 首先我们都知道react有3种 ...

  3. React 新特性学习

    1 context 2 contextType 3 lazy 4 suspense 5 memo 6 hooks 7 effect hooks =========== 1 Context 提供了一种方 ...

  4. React劲爆新特性Hooks 重构去哪儿网火车票PWA

    React劲爆新特性Hooks 重构去哪儿网火车票PWA 获取课程资料链接:点击这里获取 本课程先带你细数最近一年来React的新特性,如Hooks.Redux API,让你从头理解Hooks对传统R ...

  5. C++ 新特性-右值引用

    作为最重要的一项语言特性,右值引用(rvalue references)被引入到 C++0x中.我们可以通过操作符“&&”来声明一个右值引用,原先在C++中使用“&”操作符声明 ...

  6. 30分钟精通React今年最劲爆的新特性——React Hooks

    你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗? --拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function. 你还在为搞不清使用哪个生命周 ...

  7. [译文]React v16(新特性)

    [译文]React v16(新特性) 查看原文内容 我们很高兴的宣布React v16.0发布了! 这个版本有很多长期被使用者期待的功能,包括: fragments (返回片段类型) error bo ...

  8. React 16.x 新特性思维导图

    React 16版本相对于以前的版本做了很大的改动,下面是我整理的React 16.x 新特性的思维导图文件,欢迎围观和指导:

  9. 使用React Hooks新特性useReducer、useContext替代传统Redux高阶组件案例

    当我们使用redux进行数据管理的时候,一般都是在根组件通过Provider的方式引入store,然后在每个子组件中,通过connect的方式使用高阶组件进行连接,这样造成的一个问题是,大量的高阶组件 ...

随机推荐

  1. 函数的应用_python

    一.各类函数应用 1.高阶函数:函数里面调用函数 例子: def add(a): return (a*a) def print_info(fun,c,d): #传入函数与参数 return fun(c ...

  2. 通过css修改input的边框

    input{ border:1px solid #d2d2d2; background-color:transparent; } 可以看到主要是把背景色调成透明的颜色,从而来进行边框的设置

  3. html中特殊符号对应表

    html中特殊符号的对应表 符号 说明 编码 ‘'  双引号  "  &  and符  &  <  小于号  <  >  大于号  >    空格   ...

  4. 边捆绑: Content Importance Based Edge Bundling for Graph Visualization

    Problem 当图所要表达的信息较多时, 图中可能会充满交叉的线[1-2], 甚至整个显示空间都被点.线所覆盖, 这时想通过观察来获取图中的重要信息将会变得非常困难, 这种现象称为图的视觉混乱. K ...

  5. [MC] 我的世界 craftbukkit-1.12.2 卡爆

    昨天晚上的时候,和朋友玩我的世界 结果我这边卡爆了,牛圈里面的牛都是一动一动的... 然后我登陆服务器,发现CPU爆炸了... 100%的使用率 mstsc都卡爆了 内存占用了800多MB (服务器是 ...

  6. PHP随机生成名字 电话号码

    封装函数 随机生成电话号码 function generate_name($count,$type="array",$white_space=false) {$arr = arra ...

  7. 514 ,css不同选择器的权重(css层叠的规则)

    !important规则最重要,大于其它规则 行内样式规则,加1000 eg,<html>   <head>  </head>  <body>    & ...

  8. AC3 encoder flow

    AC3 encoder flow 如下: 1.input PCM PCM在进入encoder前会使用high pass filter来移除信号的DC部分来达到更有效的编码. 2.Transient d ...

  9. Redis 配置文件杂项。

    protected-mode -----------------保护模式 redis3.2版本后新增protected-mode配置,默认是yes,即开启.设置外部网络连接redis服务,设置方式如下 ...

  10. Makefile的编写及四个特殊符号的意义@、$@、$^、$

    https://www.cnblogs.com/sky-heaven/p/9450435.html Makefile一般的格式是: target:components rule 一.@ 这个符串通常用 ...