特此注明:由于 React 官网给出了比较详细的 Hooks 学习教程,我也是第一次正式开始使用Hooks,所以这里暂时先在粗略看过官方教程的同时随便留下点小笔记,详细的Hooks 使用及踩坑注意点将在后面再来记录。
官方教程
1.React 为什么要新增 Hooks 属性?Introducing Hooks:https://reactjs.org/docs/hooks-intro.html#no-breaking-changes
Hooks 一瞥
- Hooks 为 React v16.8.0 版本的新增点,可以在不用 Class 组件的前提下,使用 React 的 state 或其他特性。
- Hooks 给我们直观的形式为函数式组件,而这种函数式组件(function components,也就是我们之前所称的无状态组件 stateless components)可以使用 React 的 state
1.State Hook
import React, { useState } from 'react'
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
You clicked {count} times
)
}
通过 useState 声明一个变量,会返回一个数组,通过数组的解构,第一个元素为 current value,第二个元素为 update it 的函数。
这里 useState 接受一个 count 的初始默认值 0,同时返回 count 的 current state value,并支持修改(setCount)。这种使用方式类似于 Class 组件的 this.setState 方法,只不过没把新旧的 state 合在一起。
2.Effect Hook
在 Class 组件中,我们常常在 componentDidMount、componentDidUpdate、componentWillUnmount 中进行数据请求(data fetching)、订阅(subscriptions)、或手动改动 DOM。这种措施会影响其他的组件,但是在 render 的时候却不会触发,所以被称为 “side effects” or “effects” for short。在函数式组件,Effect Hook 可以完成以上操作。默认情况下,React 在每次 render 和视图 update 后都会触发 useEffect,包括第一次 render。若想自定义 useEffect 触发时刻(https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects)。
import React, { useState, useEffect } from 'react'
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null)
function handleStatusChange(status) {
setIsOnline(status.isOnline)
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange)
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange)
}
})
if (isOnline === null) {
return 'Loading...'
}
return isOnline ? 'Online' : 'Offline'
}
同 useState 一样,可以使用多个 useEffect
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0)
useEffect(() => {
document.title = `You clicked ${count} times`
})
const [isOnline, setIsOnline] = useState(null)
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange)
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
}
})
function handleStatusChange(status) {
setIsOnline(status.isOnline)
}
// ...
遗留:如何避免重复订阅。https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
举个栗子:
Class 组件
class Example extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`
}
render() {
return (
You clicked {this.state.count} times
)
}
}
Hooks 的 useEffects
import React, { useState, useEffect } from 'react'
function Example() {
const [count, setCount] = useState(0)
useEffect(() => {
document.title = `You clicked ${count} times`
})
return (
You clicked {count} times
);
}
对比
上述例子中,我们希望组件每次 render 之后都更新一次 title。在 Class 组件中,虽然 set the document title 的动作可以抽离出来,但是仍然需要调用两次。而在函数式组件中使用 Hooks ,只需要放在 useEffect 中即可。
优化
若希望 count 值无变化,就不执行 effect:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
相当于 Class 组件中我们常用的:
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `You clicked ${this.state.count} times`;
}
}
订阅 subscribe
在 Class 组件中,我们常常在 componentDidMount 中发起订阅,为了防止内存泄漏,需要在 componentWillUnmount 中销毁。在 Hooks 中,如果在 effect 中返回一个 function,那么 React 会在清空视图的时候自动执行该函数。例子如下:
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';
}
Note:组件在 unmount 时,React 会执行 cleanup。同时,我们在前面提到过,effects 每次执行都会重新 render 一次视图,而且可能会不止一次。所以在前一次 render 后及下一次 effect 执行前,React 同样会 cleanup effects,why this helps avoid bugs and how to opt out of this behavior in case it creates performance issues ,例子如下:
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
}, [props.friend.id]); // Only re-subscribe if props.friend.id changes
Tips for using effects
- separate unrelated logic into different effects
3. Hooks 使用规则
4. 构建你自己的 Hooks
有时候我们想在两个组件之间复用一些状态性逻辑,通常会有两种常用方式:高阶组件(higher-order components)和渲染 props(render props)。
在两个组件中使用同一个 Hook,这两个组件的 state 也会是各自独立的。