一文告诉你 Why React Hook

16 篇文章 0 订阅
7 篇文章 0 订阅

  使用了将近一周的 react Hook,期间尝试将项目中原有的class component改造成Hook,比较Hookclass的区别,得出一些个人的思考与见解。

什么是Hook

   react官网上面对 Hook 是这样描述的。

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

Hook提供了react中函数式组件操作state,响应state的能力。

  最简单的todo,一个函数式组件实现功能一个按钮点击增加计数,一个p标签来同步显示计数的更新。不用Hook的情况下我们需要依赖class component来进行外部props的更新。

import React, { Component } from 'react';

function Demo({ num, addNum }) {
  return (
    <>
      <p>{num}</p>
      <button onClick={addNum}>点我增加</button>
    </>
  );
};

class UseDemo extends Component {
  state = { num: 0 };

  addNum = () => {
    this.setState({ num: this.state.num + 1 });
  };

  render() {
    return <Demo num={this.state.num} addNum={this.addNum} />;
  }
}

  使用Hook来进行对同样的todo来进行改造。

import React, { useState } from 'react';

function Demo() {
  const [num, setNum] = useState(0);
  return (
    <>
      <p>{num}</p>
      <button onClick={() => setNum(num + 1)}>点我增加</button>
    </>
  );
};

Hook提供了函数式组件类似于生命周期的方法

  在Hook之前的设计中,函数式组件的更新由props变化决定,自行完成更新到视图。我们先来不用Hook写一个倒计时功能。

import React, { Component } from 'react';

function Demo({ num }) {
  return <p>{num === 0 ? '倒计时结束' : num}</p>;
};

class UseDemo extends Component {
  timer = null;

  state = { num: 60 };

  render() {
    return <Demo num={this.state.num} />;
  }

  componentDidMount() {
    this.timer = setInterval(() => {
      if (this.state.num === 0) {
        clearInterval(this.timer);
        this.timer = null;
      } else {
        this.setState({ num: this.state.num - 1 });
      }
    }, 1000);
  }

  componentWillUnmount() {
    if (this.timer) {
      clearInterval(this.timer);
    }
  }

}

  我们完成了一个60秒倒计时的功能。在 UseDemo didMount 的时候生成一个60秒的计时器。为了防止用户在倒计时结束前退出当前组件渲染,在componentWillUnmount的时候,如果计时器还在计时,把它清空掉。

  我们使用Hook+函数组件来完成相同的功能。

function useIntervalCountDown(countNum) {
  const [num, setNum] = useState(countNum);
  const [timer, setTimer] = useState(null);
  useEffect(() => {
    if (num === 0 && timer) {
      clearInterval(timer);
    }
    if (!timer) {
      let timeId = setInterval(() => {
        setNum(num - 1);
      }, 1000);
      setTimer(timeId); 1000);
    }
    return () => {
      if (timer) {
        clearInterval(timer);
        setTimer(null);
      }
    };
  }, [num, timer]);
  return num;
}

function Demo() {
  const num = useIntervalCountDown(60);
  return <p>{num === 0 ? '倒计时结束' : num}</p>;
};

  我们使用useEffect来完成componentDidMountcomponentWillUnmount生命周期的模拟。useEffect接收两个参数,第一个参数为函数,第二参数是一个数组。数组中存在的变量变化时,useEffect会触发第一个入参的函数。第一个入参函数可以设置一个返回的函数值,这个函数将在组件取消挂载的前执行(近乎相当于componentWillUnmount)。

  Hook本质上就是给函数式组件提供各种类组件的能力。让你像写class Component一样来写functional Component。但是通过上面两个例子可以发现,Hook改造前后,代码量并没有减少多少,那么我们到底为什么需要react Hook。

Hook解决了什么问题

class component 逻辑复用不方便

  举一个最经常写的后台管理系统页面的例子。如下图:

常见的后台管理系统页面

  图中可以看到一个table呈现各个详情资料。然后最后一列是各种操作按钮。这种模式的页面一般会呈现在点击左侧菜单栏后出现,在一个后台管理系统会出现很多次。事实上,这些页面除了请求接口(url,入参)以及表格呈现(表格的标题,渲染逻辑)不同,其它有很多逻辑是相同的。比如:

  1. componentDidMount之后,请求表格的内容接口,设置到state。
  2. 翻页,改变页面尺寸,改变入参拉取请求列表。
  3. 请求列表前后,开启表格loading。

  在class component模式下,如果想要复用这部分的逻辑,操作到组件内部的state,只能使用继承的方式。

import React, { Component } from 'react';

// 仅仅举例
export class BaseTableComponent extends Component {
  // 拉取请求列表逻辑
  fetchList = async () => {
    const {
      url,
      params,
    } = this.getRequestParams(); // 继承子类自己内部实现
    const { list, total } = await fetch(url, params);
    this.setState({ tableList: list, total });
  };

  // 翻页逻辑
  handlePageChange = (current, size) => {
    this.setState({ current, size }, () => {
      this.fetchList();
    });
  };

  // ... 省略其它组件复用的逻辑
  componentDidMount() {
    this.fetchList();
  }
}

  在上面简单实现了一个抽象类BaseTableComponent,在这个类中实现了拉取接口部分逻辑的抽取,列表翻页逻辑的抽取。接下来写页面组件的时候,想要实现这部分逻辑都需要继承这个类。

import { BaseTableComponent } from './BaseTableComponent';

export default class UserPage extends BaseTableComponent {
  getRequestParams = () => {
    return {
      url: '/demo/',
      params: { page: 1, size: 10 },
    };
  };
  render() {
    const { list, total, current, size } = this.state;
    return (
      <Table
        dataSource={list}
        pagination={{
          total,
          current,
          size,
          onChange: this.handlePageChange,
          pagination: this.handlePageChange,
        }}
      />
    );
  }
}

  这样子复用模式在实际项目中会带来比较多的两个问题:

  1. 复用逻辑组合复用不方便。
    比如我所有页面都用到了input搜索请求页面列表的逻辑,而单单页面A,B没有用到,我抽象出来的方法,在页面AB组件中就存在冗余。extends class的继承模式不能很好的解决这个问题。

  2. 复用逻辑必须一直关注父类用到的state。
    因为父类帮你抽象出来操作state的逻辑,因此,这部分占用的state(比如list,total)在所有子类的方法中,都不能再使用了。随着抽象的公用的逻辑越来越多,父类维护操作的state也会越来越多,需要关注不能使用的state也就越来越多。

  Hook能很好的解决这个问题。函数式的组件和state调用方法可以很方便的排列组合给需要的功能。

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

export function useFetch({ url, params }) {
  const [list, setList] = useState([]);
  const [total, setTotal] = useTotal(0);
  useEffect(() => {
    fetch(url, params)
      .then(({ total, list }) => {
        setList(list);
        setTotal(total);
      })
  }, [params]);
  return { list, total };
}

// 这里为了举例简单不引入 useCallback 等渲染更新优化的逻辑
// 页面组件使用抽象的组件逻辑
export default () => {
  const [current, setCurrent] = useState(1);
  const [size, setSize] = useState(10);
  // 引入请求列表逻辑
  const { list, total } = useFetch({ url, params: { current, size } });
  return (
    <Table
      dataSource={list}
      pagination={{
        total,
        current,
        size,
        onChange: (current, size) => {
          setCurrent(current);
          setSize(size);
        },
      }}
    />
  );
};

  Hook给予了函数组件操作state,以及使用类似于class component生命周期的能力。函数式组件本身高度灵活,可以拆卸复用各种小功能,而不会像class一样产生冗余。

Hook实现逻辑的高聚合

  回到开头第二个计时器的例子。在使用class component来实现计时器的时候,在componentDidMountcomponentWillUnmount中分别进行了setIntervalclearInterval的操作。这就是class component的第二个缺点,有时候我们实现一个功能,需要把逻辑分散在多个生命周期当中。当外部的prop会和内部同步更新时我们还要带上getDerivedStateFromProps的生命周期方法。使得组件在后期的维护上存在很重的负担。接手代码的同学需要贯穿整个react数个生命周期方法才能明白你的一个数据处理逻辑。

  在计时器的例子中,我们抽取了useIntervalCountDown方法,把num, 和操作num的setNum逻辑放在一个函数里面,贯穿在一起。无论是读代码逻辑的连贯性,还是代码的聚合性都在一起,在维护度上的提升不是一星半点。

最后一点

  其实Hook加入对react社区建设的意义也是非常积极的。Hook鼓励你对数据以及操作数据的逻辑进行提取.既然你在日常工作中已经提取了不少逻辑,何不发布到社区当中进行开源.实际上react Hook发布之后,react社区的其它核心组件包诸如react-routerreact-redux都立即响应使用React Hook进行了包的更新编写。拥抱Hook的速度足以证明react Hook的积极意义。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值