什么是 Hook?
Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React特性。
Hook 和函数组件
函数组件又称为“无状态组件”
const Example = (props) => {
// 你可以在这使用 Hook
return <div />;
}
function Example(props) {
// 你可以在这使用 Hook
return <div />;
}
Hook 在 class 内部是不起作用的。但你可以使用它们来取代 class 。
class 中的
this.setState:更新 state 变量替换
Hook中的
useState:更新 state 变量合并
State Hook
const HookStudy = () =>{
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
等价于
class HookStudy 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>
);
}
调用 useState 方法:定义一个 “state 变量”。示例中变量叫 count。这是一种在函数调用时保存变量的方式。
useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同。一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。
useState 需要的参数: useState() 方法里面唯一的参数就是初始 state。不同于 class 的是,可以按照需要使用数字或字符串对其进行赋值,而不一定是对象。在示例中,只需使用数字来记录用户点击次数,所以传了 0 作为变量的初始 state。(如想要在 state 中存储两个不同的变量,只需调用 useState() 两次即可。)
useState 方法的返回值:当前 state 以及更新 state 的函数。这与 class 里面 this.state.count 和 this.setState 类似,唯一区别就是需要成对的获取它们。数组结构。
let fruitStateVariable = useState('banana'); // 返回一个有两个元素的数组
let fruit = fruitStateVariable[0]; // 数组里的第一个值
let setFruit = fruitStateVariable[1]; // 数组里的第二个值
Effect Hook
Effect Hook 可以让你在函数组件中执行副作用操作
在 React 更新 DOM 之后运行一些额外的代码
const HookStudy = () =>{
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>
);
}
等价于
class HookStudy 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 (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.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 会在每次渲染后都执行吗? 是的,默认情况下,它在第一次渲染之后和每次更新之后都会执行。不用再考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
清除的 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';
}
等价于
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 中返回一个函数: 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。
React 何时清除 effect: React 会在组件卸载的时候执行清除操作。
声明多个effect
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => { document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => { function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// ...
}
React 将按照 effect 声明的顺序依次调用组件中的每一个 effect
跳过 Effect 进行性能优化
Effect 在 每次更新之后都会执行,因某些需求需要,可以通过传递数组作为 useEffect 的第二个可选参数,达到特定情况下进行更新
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
如果执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。使 React 的 effect 不依赖于 props 或 state 中的任何值,它永远都不需要重复执行。
(遵循依赖数组的工作方式)
自定义 Hook
自定义 Hook 必须以 “use” 开头。这个约定非常重要。不遵循的话,由于无法判断某个函数是否包含对其内部 Hook 的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则。
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
return isOnline;
}
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
const friendList = [
{ id: 1, name: 'Phoebe' },
{ id: 2, name: 'Rachel' },
{ id: 3, name: 'Ross' },
];
function ChatRecipientPicker() {
const [recipientID, setRecipientID] = useState(1); const isRecipientOnline = useFriendStatus(recipientID);
return (
<>
<Circle color={isRecipientOnline ? 'green' : 'red'} /> <select
value={recipientID}
onChange={e => setRecipientID(Number(e.target.value))}
>
{friendList.map(friend => (
<option key={friend.id} value={friend.id}>
{friend.name}
</option>
))}
</select>
</>
);
}
自定义 Hook 是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),所以每次使用自定义 Hook 时,其中的所有 state 和副作用都是完全隔离的。
自定义 Hook 是一种自然遵循 Hook 设计的约定,而并不是 React 的特性。
结合reducer方式的Hook
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch];
}
组件中使用
function Todos() {
const [todos, dispatch] = useReducer(todosReducer, []);
function handleAddClick(text) {
dispatch({ type: 'add', text });
}
// ...
}
参考资料 https://react.docschina.org/docs/hooks-intro.html