今天抽空学习了react Hook的相关知识,于是整理成博客记录一下相关比较重要的知识点,如果有什么不对的地方欢迎指正,废话不多说,我们马上进入正题。
1.什么是Hook
官网上的解释是:Hook是React16.8的新增特性,它可以让你在不编写class的情况下使用state以及其他的React特性。我们知道在函数式声明的组件中是没有类声明组件中所用到的state和生命周期等等特性的,Hook帮助我们解决了这样一个问题,在函数式声明的组件中也可以使用state以及其他的React特性(如useEffect,useReducer,useContext等,具体用法可参阅官方文档)。
2.State Hook
2.1基本用法
如果你在编写函数组件的时候需要向其添加一些state,以前的做法是必须将其转化为class,现在你可以在现有的函数组件中使用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>
);
}
这个段代码与下面的类组件写法类似:
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>
);
}
}
这里我们声明了一个count的state变量,并把它初始化为0;useState方法用来定义state变量,它接收唯一的参数,也就是初试的state,接收的参数形式可以是对象、数字、字符串、数组等,useState方法返回当前的state以及更新state的函数,这就是我们写
const [count, setCount] = useState()
的原因,setCount提供给我们更新state的方法,每次点击按钮count的值加一,不同于类组件的this.setState方法,Hook的setState方法每次都是拿新的state直接去替换旧的state,而this.setState方法是把新的state和旧的state进行合并,这就是为什么我们在this.setState中不修改某些值时,我们不需要在this.setState中调用它,然后这个值依然存在于state中
当我们读取state的值时,直接读取变量count就可以了,而不需要像类组件一样this.state.count,同样调用更新的方法也不需要加this
这个段代码与下面的类组件写法类似:
的原因,这是ES6的解构赋值的写法,当然你也可以按照下面的写法:
const temp = useState(0);
const count = temp[0];
const setCount = temp[1];
不过显然第一个的写法更加简洁。
2.2使用多个state变量
我们可以通过多次声明的方式来使用多个state的变量:
const [age, setAge] = useState(20);
const [name, setName] = useState('Gavell');
const [todos, setTodos] = useState([{ text: '学习 Hook' }]);
由于state可以存储对象和数组,当我们在声明多个state变量时,我们也可以把相关的数据一起声明到一个对象或者数组中,而不用多次的声明
那么当我们声明多个state变量的时候,React怎么知道哪个state对应哪个useState呢?答案是 React 靠的是 Hook 调用的顺序。因为我们的示例中,Hook 的调用顺序在每次渲染中都是相同的,所以它能够正常工作
比如上面声明的三个state变量:
const [age, setAge] = useState(20);
const [name, setName] = useState('Gavell');
const [todos, setTodos] = useState([{ text: '学习 Hook' }]);
// ------------
// 首次渲染
// ------------
useState(20) // 1. 使用 20 初始化变量名为 age 的 state
useState('Gavell') // 2. 使用 'Gavell' 初始化变量名为 name 的 state
useState([{ text: '学习 Hook' }]) //3.使用一个数组来初始化变量名为 todos 的state
// -------------
// 二次渲染
// -------------
useState(20) // 1. 读取变量名为 age 的 state(参数被忽略)
useState('Gavell') // 2. 读取变量名为 name 的 state (参数被忽略)
useState([{ text: '学习 Hook' }]) // 3. 读取变量名为 todos 的 state(参数被忽略)
// ...
只要Hook的调用顺序在多次渲染之间保持一直,React就能正确的将内部的state和对应的Hook进行关联,并且第二次渲染开始,Hook传入的参数将被忽略(因为那只是初始化的值)
也因为这样,Hook给我们制定了规则:
只在函数的最顶层使用Hook,不要在循环,条件或者嵌套中调用Hook。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。
3.Effect Hook
你之前可能已经在 React 组件中执行过数据获取、订阅或者手动修改过 DOM。我们统一把这些操作称为“副作用”,或者简称为“作用”。useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。
例如,下面这个组件在 React 更新 DOM 后会设置一个页面标题:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 相当于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 使用浏览器的 API 更新页面标题
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
当你调用 useEffect 时,就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数。由于副作用函数是在组件内声明的,所以它们可以访问到组件的 props 和 state。默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。
与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。
在React class中,我们通常在componentDidMount 中设置订阅,并在 componentWillUnmount 中清除它,例如,假设我们有一个 ChatAPI 模块,它允许我们订阅好友的在线状态。以下是我们如何使用 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
);
}
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';
}
}
而当我们使用了Effect Hook之后,如果你的effect返回的是一个函数,React将会在组件卸载的时候执行这个函数,而我们就可以在这个返回的函数中进行清除的相关操作,effect在每次重新渲染组件的时候都会执行,所以React会在执行当前的effect之前对上一个effect进行清除:
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会很方便,但是也会造成性能的下降——每次都调用需要开销。如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
上面这个示例中,我们传入 [count] 作为第二个参数。这个参数是什么作用呢?如果 count 的值是 5,而且我们的组件重渲染的时候 count 还是等于 5,React 将对前一次渲染的 [5] 和后一次渲染的 [5] 进行比较。因为数组中的所有元素都是相等的(5 === 5),React 会跳过这个 effect,这就实现了性能的优化。
当渲染时,如果 count 的值更新成了 6,React 将会把前一次渲染时的数组 [5] 和这次渲染的数组 [6] 中的元素进行对比。这次因为 5 !== 6,React 就会再次调用 effect。如果数组中有多个元素,即使只有一个元素发生变化,React 也会执行 effect。
如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。
关于Hook的知识学习就到这里,更多其他的Hook,如useContext、useReducer、useRef等,可以通过查阅相关文档进行学习。
转载于:https://blog.csdn.net/qq_41056833/article/details/105817061