后生,React-Hooks了解一下

1,805 阅读8分钟

如果你觉得可以,请多点赞,鼓励我写出更精彩的文章🙏。如果你感觉有问题,也欢迎在评论区评论,三人行,必有我师焉

如果是你一个React开发者,或多或少接触了React 16.8的一些新特性。例如:React.memo()React.lazyReact.Suspense等一些比较好的新特性。但是,其中我认为,最爽的是React-Hooks的提出。

这意味着啥,React项目组,对性能要求更加严格(Hooks推崇的是函数组件,在渲染速度上是比类组件快不少)。不管你是否是一个React开发的老鸟,但是在实际项目开发中,肯定见过让人头疼的代码堆砌

尤其在一些公司早期的项目代码中,组件化的思维很匮乏,直接是按一个页面一个组件。一个生命周期的方法中嵌套着5-10个不相关的代码。原来我在某东的时候,有的组件甚至是1000行代码,就是纯粹的去堆砌一些逻辑。久而久之,就变成了一些魔鬼代码,让人望而生畏。

Hooks的出现,对一些魔鬼代码的出现,有了一些制约。(其实之所以会出现魔鬼代码,还是由于研发团队对如何进行组件划分或者是逻辑复用的认知度不够而导致的)。

虽然,不用Hooks,利用HOC/Render Props也可以实现组件化和逻辑复用。但是在实际项目开发中,发现不管是HOC还是Render Props将组件进行多次嵌套,就会陷入wrapper hell。如果你开发中用过React-dev-tools看页面。就会发现,一个简单的组件,被层层包裹。那场面简直是车祸现场,惨不忍睹。

和大家墨迹了半天,算是对现有的React的开发模式的吐槽吧。客官,不要着急离开,这就正式进入正题。

今天带大家来看看Hooks中比较基础的API:useState/useEffect。至于像其他的自定义hook,会专门有一篇文章来给大家详细讲述。

顺便提一句,这篇文章只是一个Hooks的简单介绍和入门。这个技术方案是针对函数组件的。想了解为什么FaceBook构建了这个技术方案,或者是解决了什么技术痛点。 可以参考官网

TL;DR

  • Hooks到底是啥?
  • State Hook
  • useState的语法解析
  • useState的返回值
  • 构建多个State Hooks
  • Effect Hook
  • 有条件的调用Effect Hook
  • componentWillUnmount()何时调用
  • 同时使用State和Effects
  • 汇总

Hooks到底是啥?

React Hooks是将React.Component的特性添加到函数组件的一种方式。

例如将

  • State
  • 组件生命周期

Hooks能够让开发者不使用class来使用React的特性

可能大家会问,是不是以后React官网就不会继续维护用class来构建组件的方式了。这一点大可不必担心。

同时也有一点需要大家清楚就是:Hook的出现只是新增了一种处理逻辑的方式,而不是让你将原有的类组件重写为函数组件。

State Hook

假如我们有如下的组件

传统的类组件

import React, { Component } from 'react';

class JustAnotherCounter extends Component {
  state = {
    count: 0
  };

  setCount = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <h1>{this.state.count}</h1>

        <button onClick={this.setCount}>计算</button>
      </div>
    );
  }
}

利用useState在函数组件中使用state

import React, { useState } from 'react';

function JustAnotherCounter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>计算</button>
    </div>
  );
}

useState的语法解析

你可能对useState()的语法不是很熟悉。但是发现,它是使用了数组的解构语法。这就像我们通过对象的解构从某个对象中剥取一些特定属性一样。

让我们通过比较对象的解构和数组的解构,来看看useState是如何选择了数组的解构。

对象解构


const users = { admin: 'chris', user: 'nick' };

// 获取admin和user并且为他们起一个别名 
const { admin: SuperAdmin, user: SuperUser } = users;

在进行对象结构的时候,如果需要对解构的属性起一个别名,就需要用额外的变量去接收。而我们通过数组结构的话,我们只需要定义需要接收数组值的变量即可。第一个变量就是数组中的第一个值

数组解构

// 
const users = ['chris', 'nick'];

// 获取值的同时为值起了别名
const [SuperAdmin, SuperUser] = users;

useState的返回值

useState返回了两个变量,并且通过上文的分析,我们可以给这两个变量随意起名字。

  • 第一个变量其实一个值,类似于类组件中的this.state
  • 第二个变量是一个函数,用于更新第一个变量的值。类似于类组件中的this.setState

在我们调用useState的时候,传入了一个值,这个值是作为第一个变量初始值

构建多个State Hooks

由于在实际开发中一个逻辑片段不可能细分到只有一个state去维护和控制,所以Hooks支持在一个函数组件中,多次调用useState添加多个状态值。

import React, { useState } from 'react';

function AllTheThings() {
  const [count, setCount] = useState(0);
  const [products, setProducts] = useState([{ name: 'Surfboard', price: 100 }]);
  const [coupon, setCoupon] = useState(null);

  return <div>{/此处可以随意使用已经构建的状态值/}</div>;
}

Effect Hook

State Hook让我们可以在函数组件中使用state,让函数组件在使用上变得更加灵活。而Effect Hook使得函数组件拥有了生命周期方法

函数组件中的Effects其实等同于类组件的componentDidMountcomponentDidUpdatecomponentWillUnmount的结合体

字如其名,Effect就是维护一些具有副作用的操作

  • 获取远程接口数据
  • 操作DOM
  • 响应订阅操作

Effects在每次render之后触发,不管组件是否是首次渲染

具有副操作的类组件

import React, { Component } from 'react';

class DoSomethingCrazy extends Component {
  componentDidMount() {
    console.log('我要起飞了!');
    document.title = '这是标题';
  }

  render() {
    return <div>做点疯狂的事</div>;
  }
}

useEffect修改组件

function DoSomethingCrazy() {
  useEffect(() => {
    console.log('我要起飞了');
    document.title = '这是标题';
  });

  return <div>做点疯狂的事</div>;
}

通过比较,是不是感觉利用useEffect在代码量上还有逻辑捆绑上比传统的组件都具有很大的优势。

有条件的调用Effect Hook

由于useEffect()在每次render的之后,总会被调用。那我们是否有一个方式,只限制它在首次加载时调用。

其实Effect Hook接收第二个参数(Array类型)。只有数组中的值发生变化了,才会调用useEffect()而不是每次在渲染的时候调用。

componentDidMount: 只运行一次

// 当第二个参数为空数组([])的时候,只是被调用一次(也就相当于类组件中componentDidMount)
useEffect(() => {
  // 值运行一次
}, []);

componentDidUpdate: 根据值是否变化来运行

// 只有count变化才运行
useEffect(
  () => {
  //只有count变化才运行
  },
  [count]
);

componentWillUnmount()何时调用

我们上文说过,函数组件中的Effects其实等同于类组件的componentDidMountcomponentDidUpdatecomponentWillUnmount的结合体。而通过控制第二个参数的值,可以模拟componentDidMountcomponentDidUpdate。但是componentWillUnmount如何才会调用呢。

我们只需要在useEffect()中返回一个函数,既可以模拟componentWillUnmount的功能,也就是在组件unmounts时调用。

useEffect(() => {
  UsersAPI.subscribeToUserLikes();

  // unsubscribe
  return () => {
    UsersAPI.unsubscribeFromUserLikes();
  };
});

同时使用State和Effects

既然在函数组件中可以单独使用useState来模拟类组件的this.state的功能,用useEffect来模拟类组件的生命周期。那我们可以同时利用useStateuseEffect将函数组件变成有状态组件

我们通过一个真实的例子来讲解一下如何共同使用这些API。我们通过useEffect()来获取GitHub API并且用useState()来存储。

使用useState

import React, { useState } from 'react';

function GitHubUsers() {
  const [users, setUsers] = useState([]);
}

我们通过useState([])的参数将users的值,赋为[]

使用useEffect来获取数据

import React, { useState } from 'react';

function GitHubUsers() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch('https://api.github.com/users')
      .then(response => response.json())
      .then(data => {
        setUsers(data); // 为users赋值
      });
  }, []); //通过useEffect的第二个参数,来约定,该操作只被调用一次
}

数据展示

import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';

function GitHubUsers() {
  // 刚才的代码

  return (
    <div className="section">
      {users.map(user => (
        <div key={user.id} className="card">
          <h5>{user.login}</h5>
        </div>
      ))}
    </div>
  );
}

这样一个简单的Hooks的例子就完成了。

汇总

上面的分析步骤,只是简单介绍了useState/useEffect的使用方式,我相信大家在使用过程中,肯定遇到了比这还复杂的。但是,万变不离其宗。都是利用hook的一些API。为函数组件赋予状态

还有一点,因为在使用useState或者useEffect的时候,我们可以将相关的代码进行统一处理,这样不管在业务开发还是代码维护上,变的很清楚。而不是像原来的开发一样,一个生命周期包含着无数的不相干的逻辑代码。

并且,hooks还支持自定义。本人认为,这才是hooks的真正强大之处。通过自定义一些业务代码,真正的实现逻辑复用。可以有效的减少wrapper hell。让代码实现切片化编程