React Hooks简介
React Hooks是在React 16.8版本引入的全新API,程序员可以在不编写class的情况下使用state以及其他React中的特性。在16.8版本之前,组件的标准写法是类,也就是class。
hook为已知的react概念提供了更为直接的API:props、state、context、refs以及一些新的生命周期钩子函数。
Hook概览
当我们在编写函数组件并意识到需要向组件中添加以下state时,现在可以在现有的函数组件中使用Hook
Hook是一些可以让你在函数组件中“钩入”React state及生命周期等特性的函数。hook不能再class组件中使用。
State Hook
按照官方文档中计数器例子做一个小demo。
**方法一:**使用class方式
export default class App extends 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>
)
}
}
**方法二:**使用
import {useState} from 'react'
//引入 React 中的 useState Hook。它让我们在函数组件中存储内部 state。
function App() {
let [count,setCount] = useState(0);
// useState会返回一对值,当前的状态count和一个可以更新count的函数setCount,并将初始的state设置为0。
return (
<div className="App">
<p>You clicked {count} times</p>
<button onClick={()=>{
setCount(count++)
}}> click me
</button>
</div>
);
}
export default App;
我们也可以在一个组件中多次使用State Hook
function Example(){
const [age,setAge] = useState(20);
//设置年龄的初始值为20
const [fruit, setFruit] = useState('apple')
const [todos, setTodos] = useState({text:'Learning'})
//初始值可以设置为number、string、object、boolean...
}
数组解构的语法让我们在调用useState时可以给state变量取不同的名字。
那么我们在调用useState方法的时候,React做了些什么呢?
首先,它会定义一个“state变量”,这里我们起名叫做count,这个count和class中的this.state中的count功能完全相同。但是一般来说在函数退出后变量就会消失,而state中的变量会被React保留。
useState的参数有哪些?
useState()方法里面唯一的参数就是初始State,如果我们想要在state中存储两个不同的变量,只需要调用两次setState即可。
useState返回的值是什么?
返回值为当前的state和更新state的函数。state只会在组件首次渲染的时候被创建。
Effect Hook
Effect Hook 可以让你在函数组件中执行副作用操作。数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。
useEffect就是一个Effect Hook,给函数组件中增加了操作副作用的能力。她跟class中的componentDidMount(挂载后)、componentDidUpdate(更新后)、componentWillUnmount(卸载前)有相同的用途,只不过被合成了一个新的api。
react组件中有两种常见副作用操作:需要清除的和不需要清除的。
无需清除的effect
当我们只想在React更新Dom之后运行一些额外的代码。比如发送网络请求,手动变更DOM,记录日志等等这些都是无需清除的effect。
按照官方文档中计数器例子做一个小demo。
方法一:
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
count:0
};
}
//在这个class中,我们需要在两个生命周期函数中编写重复的代码,这是因为我们希望组件加载和更新时执行同样的操作。也就是我们希望它在每次渲染之后执行。
componentDidMount(){
document.title = `You Clicked ${this.state.count} times`
}
componentDidUpdate(){
document.title = `You Clicked ${this.state.count} times`
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={()=>{
this.setState({
count:this.state.count+1
})
}}>Click me</button>
</div>
)
}
}
**方法二: **
function App() {
let [count, setCount] = useState(0);
// useState会返回一对值,当前的状态count和一个可以更新count的函数setCount,并将初始的state设置为0。
useEffect(()=>{
document.title = `You clicked ${count} times`
})
//我们可以在 effect 中获取到最新的 count 值,因为他在函数的作用域内。当 React 渲染组件时,会保存已使用的 effect,并在更新完 DOM 后执行它。这个过程在每次渲染时都会发生,包括首次渲染。
return (
<div className="App">
<p>You clicked {count} times</p>
<button onClick={()=>{
setCount(count+1)
}}> click me
</button>
</div>
);
}
useEffect做了些什么呢?
通过使用这个hook,你可以告诉React组件需要在渲染之后执行哪些操作。React会保存你传递的函数(称之为Effect),并且在执行dom更新之后调用它,在这个effect中,我们设置了document的title属性,也可以执行数据获取或调用其他命令式的API。
为什么会在组件内部调用useEffect?
将useEffect放在组件内部让我们可以在effect中直接访问count state变量或者其他的props。不需要使用其他的特殊API来读取它,它已经被保存在了函数的作用域中。Hook 使用了 JavaScript 的闭包机制,而不用在 JavaScript 已经提供了解决方案的情况下,还引入特定的 React API。
useEffect会在每次渲染后都执行吗?
是的,在默认情况下,它在第一次渲染之后和每次更新之后都会执行。你可能会更容易接受 effect 发生在“渲染之后”这种概念,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
需要清除的effect
订阅外部数据源这种情况下,清除工作是非常重要的,可以防止内存泄漏。
例如,假设我们有一个 ChatAPI 模块,它允许我们订阅好友的在线状态。 订阅和显示该状态
方法一:
import React, { Component } from 'react';
export default class Clear 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
});
}
//componentWillUnmount和componentDidMount函数内部做的操作一模一样。
render() {
if (this.state.isOnline === null) {
return 'Loading...';
}
return this.state.isOnline ? 'Online' : 'Offline';
}
}
方法二:
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);
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
为什么在effect种返回一个函数?
每个effect都可以返回一个清除函数,如此可以将添加和删除订阅的逻辑放在一块,它们都属于effect的一部分。
React如何清除effect?
React会在组件卸载的时候执行清除操作。
useEffect需要注意的地方:
1.每次更新的时候都要运行 Effect
2.通过跳过Effect进行性能优化。
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同样适用:
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
}, [props.friend.id]); // 仅在 props.friend.id 发生变化时,重新订阅
但是如果你要使用此优化方式,必须确保数组中包含所有外部作用域会随事件变化,并且在effect种使用的变量。
如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。