React学习笔记——初识React Hooks篇

593 阅读7分钟

自从React提出Hook这个概念到现在已经有一年多的时间了,在Reactv16.8.0版本中正式发布,到目前为止,Hook的特性已经比较稳定,同时Hook的出现也解决了我们长期使用React所带来的的一些痛点,最近在项目中初步接触到React Hooks,所以借着这个时间来细致的学习一下React Hooks。

你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗? ——拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function。

你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗? ——拥有了Hooks,生命周期钩子函数可以先丢一边了。

你在还在为组件中的this指向而晕头转向吗? ——既然Class都丢掉了,哪里还有this?你的人生第一次不再需要面对this。

我们常写的React组件一般分为两种,有状态组件和无状态组件,即Class组件和函数组件,函数组件相对于Class组件来说,主要具有这些特性:

  1. 不需要声明Class, 也就避免了extends constructor等一系列代码
  2. 不需要显示的声明this,没有声明周期
  3. 不需要维护一个组件内的状态(state),所有需要的数据都是通过props传进来的

从这些点来看,函数组件无疑是更好的选择,没有任何副作用,组件是可预测的,但在实际开发中,我们因为复杂的业务逻辑而不得不选择Class组件,并且随着项目的不断维护更改,整个组件内部的逻辑也会越来越复杂,代码越来越臃肿,所以这个时候的React Hooks出现了,Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。,这是官方对Hook的解释,按照我自己的理解,它的出现,使得我们能够在函数组件的基础上,维护一个state,并且能够代替我们需要在生命周期中所完成的一些操作。

首先来看一下官方示例

使用Class的示例

import React, { useState } from 'react';
class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

使用React Hooks的示例

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 "count" 的 state 变量
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

整段代码是不是简洁了很多!组件从Class组件变成函数组件,并且在使用useState这个hook以后,拥有了自己的状态,还可以通过setCount随时改变改变自己的状态。当然,React Hooks除了useState这个hook以外,还有包括useEffect(提供类似生命周期函数的作用), useContext(提供上下文的功能)等一系列其他hook,并且还可以自定义hook来满足我们不同的需求。

React Hook出现的目的

1、在组件之间复用状态逻辑很难

React在组件层面做到了高内聚,低耦合,组件可以说是react的一个核心,一个页面由大大小小不同的许多个组件构成,但是在一个实际项目中,一个组件的代码其实是很长的,加上组件本身的state和声明周期函数中的一些操作,使得我们真正去复用一个组件变的不是很简单,之前官方推荐的解决方法是渲染属性(Render Pros)和高阶组件(HOC)

渲染属性指的是使用一个值为函数的 prop 在 React 组件之间的代码共享。说白了就是通过传递props给一个组件以后返回一个组件给我们,就像这样 :

<DataProvider>
    <Component data={data} />
</DataProvider>

至于高阶组件其实就是一个函数,一个以组件为参数,并且返回一个新的组件的函数。说白了就是一个函数对传入的组件进行了处理以后在返回,就像这样

function logProps(InputComponent) {
  InputComponent.prototype.componentWillReceiveProps = function(nextProps) {
    console.log('Current props: ', this.props);
    console.log('Next props: ', nextProps);
  };
  // 返回原始的 input 组件,暗示它已经被修改。
  return InputComponent;
}

// 每次调用 logProps 时,增强组件都会有 log 输出。
const EnhancedComponent = logProps(InputComponent);

这两种模式乍看起来确实觉得不错,作为一个小白,说实话我一直写的也挺爽的,但是使用这两种设计模式,会不可避免的增加我们的代码层级,导致组件嵌套过深 ,随便打开一个网页都能看到这种嵌套很深的dom结构,所以随着今天的深入,也发现了这两种模式并非最优解。

2、复杂组件变得难以理解

写了也有接近半年的React了,但是直到现在,除了最常用的几个生命周期函数以外,每次用到其他的生命周期函数,我都要去查一下生命周期表单来确认有没有写错,更不用说复杂组件中生命周期函数的使用了,我们需要在生命周期中实现获取数据,监听事件,取消订阅等等一系列的逻辑,随着组件的不断维护扩充,组件也就变得越来越难以理解

3、难以理解的 class

class中最难以理解的应该是this了吧,为了保证this的指向符合预期,this.funtion = this.funtion.bind(this)写了无数遍, <div onClick={(data) => this.function(data)}> </div>也经常会写,数不清的this写起来确实很麻烦

为了消灭这些开发过程中的问题,Hooks来了!(他来了他来了,他踩着祥云走来了!)

如何使用React Hooks

export default function CountDown(props) {
    const tmr = useRef(0);
    const [stop, setStop] = useState(false);
    const [stopTime, setStopTime] = useState(0);
    const [refresh, setRefresh] = useState(props.refresh);

    const handleRefresh = useCallback(() => {
        clearTimeout(tmr.current);
        tmr.current = setTimeout(() => {
            setRefresh(refresh - 1);
            if (refresh == 0) {
                const { onFinish } = props;
                onFinish && onFinish();
                setRefresh(props.refresh);
            }
        }, 1000);
    }, [setRefresh, refresh, props]);

    const handleClick = useCallback(() => {
        if (!stop && refresh > 0) {
            clearTimeout(tmr.current);
            setStop(true);
            setStopTime(refresh);
        } else if (stop) {
            setStop(false);
            // setRefresh(stopTime);
            handleRefresh();
        }
    }, [stop, refresh, stopTime, setRefresh]);

    useEffect(() => {
        handleRefresh();
    }, [refresh]);

    return (
        <div className={styles.wrapper}>
            {
                stop ? (
                    <div onClick={handleClick} >
                        {
                            stopTime < 10 ? '0' + stopTime : stopTime
                        }
                    </div>
                ) : (
                        <div onClick={handleClick}>
                            {
                                refresh < 10 ? '0' + refresh : refresh
                            }
                        </div>
                    )
            }
        </div>
    );
}

这是一个倒计时组件,当倒计时结束时会调用回调函数,执行然后重新计时,让我们一起看一下它是怎么实现的吧: 首先,声明我们需要维护的变量stop、stopTime、refresh,声明tmr时注意,如果我们要声明一个普通变量,最好使用useRef来声明,声明以后初始值会存储在tmr.current中,以后每次修改也都是修改tmr.current,这样做的好处是能够确保tmr不会丢失,因为整个函数每次都会完全重新渲染,所以很容易造成我们最新渲染出来的变量没有指向先前的修改从而导致bug的产生, 接下来,使用useCallback来声明两个我们需要用到的函数分别用来处理点击事件和回调事件。useCallback(() => {},[]), []中一定要将我们函数中需要用到的依赖也就是用到的变量声明,这里的目的也是为了数据的指向精准,最后声明useEffect(() => {},[]) []这里的数组里面需要写什么呢? 你希望当哪个变量发生变化时执行这个useEffect呢? 希望哪个就写入哪个, 如果写入一个空数组,那么作用就相当于componentDidMount只会执行一次,如果像这样什么也没有写,useEffect(() => {}),那么每次组件重新渲染时,这个useEffect都会执行

总结,React Hooks的出现,极大的解决了我们目前在用react实现复杂项目时的一些痛点,但是里面的坑也比较多,这篇文章只是初步科普一下React Hooks的使用,至于更深一步比如为什么会有精准依赖,Hooks的数据是怎么保持的等等要抽出更多的时间来了解Hooks的源码实现,未完待续~