react的新特性之react-hooks

1,699 阅读7分钟

最近组内大佬给小编安排了一个任务,整理一份关于react-hooks的简单的使用教程。

看到消息的时候有点欣喜,因为小编之前就想整理,奈何抵不过惰性,正好趁着这次机会,把hooks相关的知识点学习并巩固一下(虽然没有用过,但是期待在以后的项目中尝试)。

这篇文章会围绕hook的特性以及基本使用方式,State hookEffect hook,useContext以及自定义hook展开,有兴趣的童鞋可以停下脚步,稍微了解一下。


Hook是什么?

首先,我们需要知道的是,hookreact 16.8的新增特性,他的最特别的一点就是命名的时候一定要以use开头。不要问我为什么,可能是为了便于识别是否为hooks的一种手段吧。

在这里,不得不说一下linter这个插件,这是专门用来检查hook格式的一个插件,pick它吧。

其次,hook是一些可以让你在函数组件内“钩入” React state 以及生命周期等特性的函数。(例如,useState 是允许你在 React 函数组件中添加 stateHook)需要注意的是,hook不能在class组件中使用。

最后,hook 100%向下兼容。hook也可以将组件中相互关联的部分拆分成更小的函数(比如设置订阅,请求数据等)。

⚠️hook就是javascript函数,但是使用其有两个额外的原则:

1.只能在函数最外层调用hook

2.只能在react的函数组件中调用hook

3.不要在循环,条件或嵌套以及普通的javascript中调用hook


基本用法

首先,看官网给的一个比较简单的计数器示例:

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>
  );
}

可以看到,它引用了useState,然后声明了一个初始值为0的count的变量,通过setCount这个方法去直接改变count的值。

这里的onClick={() => setCount(count + 1)}在没有hook之前,可以理解为onClick={this.setcount},然后在声明的setCount方法内进行this.setState({count: count + 1})操作。

以上可以看出,使用hook方便很多,在精简了代码的基础上,还可以去掉传统this指向等繁琐的问题。

我们再来看另一个配合ant design使用的例子:

function Example() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <Button type="primary" onClick={() => setOpen(true)}>
        Open Modal
      </Button>
      <Modal
        visible={open}
        onOk={() => setOpen(false)}
        onCancel={() => setOpen(false)}
      />
    </>
  );
}

拓展一下,我们可以通过给按钮设置初始值来控制弹窗的弹出与关闭等等。


State hook

使用useState时,我们需要知道的总结一下大概有以下几点:

1.state是只读的;

2.useState函数的第一个参数,是state变量的初始值;

3.每次进行渲染时,多个State Hook的顺序、数量都必须一样;

4.state变量发生变化时,会触发新的渲染;state变量没变化,不触发新的渲染;

使用state hook的主要作用就是获取需要的 state和 更新state的方法,以官网例子为例:

const [state, setState] = useState(initialState);

参数: initialState可以作为state的一个初始值,同时,他也可以是一个函数,返回值作为state的初始值,该参数只会在初始渲染时起作用。

返回值: 会返回一个数组,一个是state的值,另一个是更新state值的一个方法。(这里的setState不会合并state的值)。

如果要定义多个变量,可以多次使用useState

小编觉得,官网关于state hook部分有一个小技巧值得一提,如下:

  var fruitStateVariable = useState('banana'); // 返回一个有两个元素的数组
  var fruit = fruitStateVariable[0]; // 数组里的第一个值
  var setFruit = fruitStateVariable[1]; // 数组里的第二个值

使用数组解构来将定义的变量通过[0],[1],解构到原来setState返回的两个值的数组当中。


Effect hook

数据获取,设置订阅以及手动更改 React 组件中的 DOM都属于副作用。Effect Hook可以让我们在函数组件中通过使用useEffect提升操作副作用的能力,它在渲染之后执行,我们可以把它看做 componentDidMountcomponentDidUpdatecomponentWillUnmount这三个函数的组合。

简单的理解就是:useEffect跟以上三个生命周期用途相同,只不过被合并成了一个API

在使用useEffect的时候,需要考虑两点:需要清除和不需要清除。那么什么情况下需要清除,什么情况下又不需要清除呢?小编带你来了解

需要清除副作用的情况

当订阅外部数据源的时候,需要清除数据。数据需要清除,可以返回一个函数。

以官网的例子为例:

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

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

为了不会引起内存泄漏,订阅外部数据源时需要清除数据。effect返回一个函数是因为这是effect 可选的清除机制,同时,React会在组件卸载的时候执行清除操作。

当然,在hook还没出来之前,使用class创建组件订阅外部数据源时,清除方式如下(此处依旧引用官网代码):

class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  
  componentDidUpdate(prevProps) {
    // 取消订阅之前的 friend.id
    ChatAPI.unsubscribeFromFriendStatus(
      prevProps.friend.id,
      this.handleStatusChange
    );
    // 订阅新的 friend.id
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }

  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}

当使用class创建一个组件,并且使用了componentDidMount订阅,在componentDidUpdate中更新订阅,在componentWillUnmount清除。

可以看到,较使用effect来说,class清除副作用时代码较为冗余。

无需清除副作用的情况

effect内只有单行代码,或者我们只想在React更新DOM 之后运行一些额外的代码。比如发送网络请求,手动变更 DOM,记录日志,这些无需清除的操作。 如官网代码所示:

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

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

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

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

这种情况下无需清除副作用。

还有一点我们应该要注意的是,我们需要跳过一些不必要副作用函数。那么,如何才能跳过呢?这里就要讲到useEffect的第二个参数。

我们需要给useEffect的传入第二个参数,当第二个参数改变时,执行副作用;当参数不改变时,可直接跳过副作用不执行。例子如下:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 当count的值更新时,才会重新执行`document.title`

useContext的使用

我们先来看个例子:

import React from 'react';

const appContext = React.createContext();  //创建一个context
functuion App() {
    return (
       <appContext.Provider value={888}> //这使用了Provider主要是为所有后代提供value值
           <div>
               <Show />
           </div>
       
       </appContext.Provider>
    )
}

function Show() {
    return (
        <appContext.Consumer> //使用Consumer获取到value的值
            {value => <div>{value}</div>}
        </appContext.Consumer>
    )
}

首先我们可以看到,使用contextAPI的方式下,可以通过创建一个context上下文,返回一个带有{Provider, Consumer}的值的对象,然后通过使用Provider主要是为所有后代提供value值,这避免了以往手动一个个向子孙组件传递prop

然后,可以使用Consumer可以从上下文获取value的值。需要注意的一点是,这个Consumer 子组件,在这里只能是唯一的一个函数。

再来看一下使用useContext的情况

import React, { useContext } from 'react';

const appContext = React.createContext();  //创建一个context
functuion App() {
    return (
       <appContext.Provider value={888}> //这使用了Provider主要是为所有后代提供value值
           <div>
               <Show />
           </div>
       
       </appContext.Provider>
    )
}

function Show() {
    const apps = useContext(appContext);
    return (
        {value => <div>{apps}</div>}
    )
}

这里调用useContext,传入从React.createContext()获取到的上下文对象。需要注意的一点是,useContext 的参数必须是 context 对象本身,即useContext(appContext),而不是useContext(appContext.Provider)useContext(appContext.consumer)


自定义hook

前面已经讲到,hook一般以use开头,自定义hook也一样。自定义 hook 是一个函数,其名称以 use开头(必须),函数内部可以调用其他的hook

当项目中,我们想要共享组件之间的状态逻辑时,最常见的方法是使用高阶组件或者 render props。但是,使用自定义的hook可以是你在不增加组件的前提之下,把公用逻辑提取出来进行复用。

(详情可查看官网:react.docschina.org/docs/hooks-…

以上就是小编这次对于react-hooks知识点的一个大概的梳理,若有错误,欢迎指出,一起学习,一起进步。(若觉尚可,可暗戳戳笔芯)