Hooks的由来
React Hooks 是从 v16.8 引入的又一开创性的新特性,它可以让我们在不编写class的情况下使用state以及其他React特性。
Hooks的出现是为了解决React长久使用以来存在的问题:
- 组件间的逻辑很难复用:
react中没有提供将可复用的逻辑附加到组件的方法,为了解决复用的问题,需要引入高阶组件(如redux中的connect方法)或render props 这样的设计模式。这是往往需要改造组件的结构,十分麻烦。
- 复杂组件难以理解
react提供了很多生命周期函数,但是通常大量的业务逻辑需要写在不同的生命周期函数内,例如在componentDidMount中监听数据,并在componentWillUnmount中取消该数据的监听。同时一个生命周期函数内会存在多个业务逻辑。这些都让我们的组件变得复杂且难以理解。
- JavaScript class的缺陷
JavaScript中的this指向是在运行时决定的,这和其他面向对象语言都不一样。同时react中还存在class component和function 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等着我们去探索~