React Hooks——基础解析

Hooks的由来

React Hooks 是从 v16.8 引入的又一开创性的新特性,它可以让我们在不编写class的情况下使用state以及其他React特性。

Hooks的出现是为了解决React长久使用以来存在的问题:

  • 组件间的逻辑很难复用:

react中没有提供将可复用的逻辑附加到组件的方法,为了解决复用的问题,需要引入高阶组件(如redux中的connect方法)或render props 这样的设计模式。这是往往需要改造组件的结构,十分麻烦。

  • 复杂组件难以理解

react提供了很多生命周期函数,但是通常大量的业务逻辑需要写在不同的生命周期函数内,例如在componentDidMount中监听数据,并在componentWillUnmount中取消该数据的监听。同时一个生命周期函数内会存在多个业务逻辑。这些都让我们的组件变得复杂且难以理解。

  • JavaScript class的缺陷

JavaScript中的this指向是在运行时决定的,这和其他面向对象语言都不一样。同时react中还存在class componentfunction component的概念,什么时候应该用什么组件也是一件纠结的事情。代码优化方面,class component在预编译和压缩代码时会比普通函数困难很多。

Hooks特性

  • 完全可选。
  • 100%向后兼容。使用Hooks不会影响之前的代码功能。
  • 现在可用。Hooks已在react v16.8.0发布。

State Hooks

使用Hooks让我们可以从class component转换到function component,同时也不妨碍我们使用react的其他特性。下面写一个简单的例子

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 component的写法是:

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>
    );
  }
}
复制代码

两者实现的功能一样,但是写法上,Hooks的写法会更加简洁一些。接下来,我们了解一个State Hooks——useState

通过使用useState这个Hooks可以在组件内部添加一些内部state数据。用法如下:

const [count, setCount] = useState(0);
复制代码

useState函数的唯一入参是创建的state的初始值,函数返回值为:当前state以及更新state的函数。上述代码执行干了什么?它创建了一个state变量——count(可以声明为任意名称),并将传入的参数0作为count的初始值,同时返回一个用来改变count的函数——setCount(这里的函数名称也可以命名为任意名称)。如果我们需要在页面中展示或逻辑中使用count,可以直接使用count(而不再需要使用this.state.count)来正常访问count的值;如果我们需要更改state 中 count的值,可以执行如下代码:

setCount(count+1)
复制代码

等效于(采取我们常用的setState的写法如下):

this.setState(this.state.count+1)
复制代码

也就是说,在我们调用setCount时,react帮我们实现了this.setState(),从而实现了state中count值的改变。

这里,我们调用useState时,为什么返回值要写成数组的形式呢?这里涉及到ES6中的数组解构,这是因为useState返回值是一个数组,数组的第一项是state变量,数组的第一个变量是改变state的函数。等价于下面的代码:

var countStateVariable = useState(0); // 返回一个有两个元素的数组
var count = countStateVariable[0]; // 数组里的第一个值
var setCount = countStateVariable[1]; // 数组里的第二个值
复制代码

Effect Hooks

Effect Hook 可以让你在函数组件中执行副作用操作,网络请求、订阅某个模块或者DOM操作都是副作用的例子。react中常见的副作用分为需要清除的和不需要清除的。

无需清除的effect

有时候我们需要在DOM更新之后做一些操作,比如发送请求、更改DOM、记录日志,这些都是常见的不需要清除的操作。以上面计数器的例子,增加一个小功能:将document的title设置为包含点击次数的消息。

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 这个 Hook 告诉 React 组件需要在每次渲染后执行的操作。React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。在这里,我们告诉 React 在每次渲染后将 document 的 title 设置为点击次数。这里我们可以看到,useEffect 想到与结合了 componentDidMount 和 componentDidUpdate 两个生命周期,原来需要写在这两个生命周期函数里的逻辑,现在使用 React Hooks 之后,只需要写一次就可以了。

需要清除的effect

像订阅外部数据这种副作用是需要清除的,此时清除工作可以防止引起内存泄漏。

在 React class 中,我们通常会在 componentDidMount 中订阅数据,同时需要在 componentWillUnmount 中清除订阅,也就是订阅和清除订阅这一套逻辑被这些生命周期函数拆分开来。

那么如何使用 Hook 来实现这个功能呢?你可能认为我们需要在一个useEffect 中进行订阅数据的操作,还需要另一个useEffect 来处理清除的操作。但由于添加和删除订阅之间的紧密性,useEffect 设计让这些关联紧密的逻辑可以在同一个地方执行:如果你的effect返回一个函数,那么React将会在执行清除操作时调用它。上述添加和删除订阅功能可以写成:

useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    //订阅数据
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // 返回函数,包含清除订阅数据的操作
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
复制代码

effect会在何时执行清除操作? React会在组件卸载的时候执行清除操作。

如果觉得每次渲染都执行一次effect会虽损耗性能,我们也可以设定:如果某写特定值在两次渲染之间没有发生变化,可以让 React 跳过对effect的调用,只要传递数组作为useEffect 的第二个可选参数,此时 React 会依据该参数是否变化来确定是否执行 effect。

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
复制代码

此时,我们传入 [count] 作为第二个参数。如果count=5并且没有在组件重新渲染时没有改变,React将对组件前一次渲染的[5]和后一次渲染的[5]进行比较。此时数组里的所有元素是相等的(5 === 5), React 会跳过这个effect,这样就能实现性能的优化。

依据以上原理,如果我们需要只执行一次的effect,即尽在组件挂载和卸载时执行,此时我们可以传递一个空数据([])作为第二个参数。因为传递的是空数组,每次渲染比较的时候结果都是不变的,也就是告诉 React,这个effect永远都不需要重复执行。

总结

本文介绍了 React Hooks 的由来,并详细介绍了2个重要的 Hooks:*useState *和 UseEffect。Hooks让我们可以在函数组件中使用state以及其他的 React特性,同时让我们可以摒弃繁琐的生命周期函数,让我们可以以完整的逻辑为颗粒设置多个effect,代码逻辑更加清晰。同时,Hooks还有更多的API等着我们去探索~

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享