hooks解决的问题
- 带组件状态的逻辑难以重用;
- 复杂组件难以理解;
- class组件难以理解;
什么是hooks
简单理解,hooks是函数,每个hook都是function component提供使用react状态和生命周期特性的通道。hooks不能在class component中使用。
react提供了一些预定好的hooks,本文主要介绍useState和useEffect。
1、state hook
传统使用
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>
);
}
}
state hook改写后
import React, { useState } from 'react';
function Example() {
// 定义一个 state 变量 count,变量值可以通过 setCount 来改变,变量初始值为 0
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
简单介绍:
useState的入参只有一个,就是state的初始值,可以是数字、字符串或对象,甚至函数(这一函数只会在组件初始化渲染时执行)。
// 入参为函数形式时的处理
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
useState的返回值是一个数组,数组的第一个元素是state的当前值,第二个元素是改变state的方法。每次更新都会被记录状态,并可以在全局使用。实现原理如下
var _state; // 把 state 存储在外面
function useState(initialValue) {
_state = _state || initialValue; // 如果没有 _state,说明是第一次执行,把 initialValue 复制给它
function setState(newState) {
_state = newState;
render(); // 模拟 reRender,不需要关心
}
return [_state, setState];
}
2、effect hook
传统使用
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 (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
effect hook改写后
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>
);
}
简单介绍:
传统写法中,副作用代码写在了componentDidMount和componentDidUpdate中,而且两个生命周期中的代码一样,effect hook改写后就不会有这个问题。
useEffect会在每次DOM渲染后执行,不会阻塞页面渲染,而且同时具备componentDidMount、componentDidUpdate和componentWillUnmount三个生命周期函数的执行时机。
最简单的用法
useEffect(() => {
console.log(count);
}, [count])
由上述代码可以知道useEffect的几个特点:
- 有两个参数callback和dependencies数组;
- 如果dependencies数组不存在,那么callback每次render都会执行;
- 如果dependencies数组存在,只有当它发生变化,callback才会执行;
实现原理如下
let _deps; // _deps 记录 useEffect 上一次的 依赖
function useEffect(callback, depArray) {
const hasNoDeps = !depArray; // 如果数组不存在
const hasChangedDeps = _deps
? !depArray.every((el, i) => el === _deps[i]) // 两次的数组是否完全相等
: true;
// 数组不存在,或数组发生改变
if (hasNoDeps || hasChangedDeps) {
callback();
_deps = depArray;
}
}
由上述代码可以解答一个问题:
Q:为什么第二个参数是空数组,相当于componentDidMount?
A:因为依赖一直不变(数组存在且一直为空,即两次的数组总是相等),所以callback不会执行第二次。
还有一些额外的清理工作,如订阅某个功能:
传统使用
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';
}
}
在componentDidMount订阅后,需要在componentWillUnmount取消订阅,使用effect hook改写后
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';
}
当useEffect的返回值是一个函数时,react会在下一次执行这个副作用之前执行一遍清理工作,简单理解为:
组件挂载 --> 执行副作用 --> 组件更新 --> 执行清理函数 --> 执行副作用 --> 组件更新 --> 执行清理函数 --> 组件卸载
FROM:
https://segmentfault.com/a/1190000018928587
https://github.com/brickspert/blog/issues/26
http://www.ruanyifeng.com/blog/2019/09/react-hooks.html