React Hooks 起源
- React 一直都提倡使用函数式组件。更轻便,更优雅,性能更佳。函数式组件又称无状态组件(FSC)。
- 以前,需要使用
state
,生命周期等React 特性,必须重构为 class 组件。 Hooks
是 React 16.8 新增的特性,它可以让你在不编写class
的情况下使用state
以及其他的 React 特性。- 现在,你可以直接在现有的函数式组件中使用
Hooks
,而无须重构为 class 组件。 - 全新的思维方式。no magic, just javascript and some rules。
类组件被诟病
- 类(累):自js开天辟地,就是面向函数式编程(FP), 面向对象编程(OOP)为何物。烦人的构造函数。super是什么?……
- this绑定:类方法不会自动绑定 this 到实例上。现有四种
bind
方式。不优雅,易出错,bind
还影响性能(使用箭头函数后有所改善)。 - setState(): 异步更新机制,
state
浅合并机制。不理解这些概念,很容易踩坑。 - 生命周期耦合:每个生命周期方法通常包含一堆不相关的逻辑;不同生命周期中的逻辑又有关联。
下面组件来自实际项目,经过简化和微调(方便演示和直观感受),基本上暴露出了上面所有问题。业务逻辑严谨性不用推敲:
class NumberInput extends React.Component {
constructor(props) {
// 为什么必须super,不传props会怎样
super(props)
this.state = {
focus: false
}
this.tradingpwd = ''
// 第一种bind,官方推荐
;['onBlur'].forEach(method => {
this[method] = this[method].bind(this)
})
}
// 下面两个生命周期得相互配合,实现某些功能
componentDidMount() {
this.tradingPwdHideInput.focus()
// 处理某类兼容问题
let bodyTop = document.body.getBoundingClientRect().top
const styleText = 'position: fixed; width: 100%; top: ' + bodyTop + 'px'
document.body.style.cssText = styleText
}
componentWillUnmount() {
this.tradingPwdHideInput.blur()
document.body.style.position = 'static'
}
tradingPwdChange(e) {
// ...
this.tradingpwd = e.target.value
this.props.inputChangeCallback(e.target.value)
// ...
}
// 第二种bind
onFocus = () => {
this.setState({
focus: true
})
}
onBlur() {
this.setState({
focus: false
})
}
render() {
return (
<div className={classNames('NumberInput')}>
<input
type='tel'
ref={ref => {
this.tradingPwdHideInput = ref
}}
id='tradingPwdHideInput'
/* 第三种bind */
onClick={() => {
this.tradingPwdHideInput.focus()
}}
onBlur={ this.onBlur }
onFocus={ this.onFocus }
/* 第四种bind,不推荐,在每次 render() 方法执行时绑定类方法,消耗性能*/
onChange={ this.tradingPwdChange.bind(this) }
/>
</div>
)
}
}
proposal-class-fields 新提案会改善上述情况,目前处于第三阶段。
随着类组件趋于复杂,还有其他诟病:
- 难拆分,本地state逻辑到处都是,当组件越来越复杂,想拆分比较难。
- 状态逻辑难复用:需要引入高阶特性进行代码重构,需要调整组件结构,成本高。
- 抽象地狱:大型React往往使用render props ,HOC,Context 等高阶特性,形成大量包装组件(wrapping components)。层级冗余,逻辑难追踪。
深度包装的组件长这样:
import { compose } from 'recompose';
import { withRouter } from 'react-router-dom';
function App({ history, state, dispatch }) {
return (
<ThemeContext.Consumer>
{theme =>
<Content theme={theme}>
...
</Content>
}
</ThemeContext.Consumer>
);
}
export default compose(
withRouter,
withReducer(reducer, initialState)
)(App);
确实很抽象。这是 React 中典型的抽象地狱(Abstraction hell)问题,也叫包装地狱(The wrapper hell)。
Hooks 优越性
Hooks
引入的一个重要的原因,就是类组件存在着种种诟病。那他必然存在一些优越性。
在说明这些优越性之前,先了解一个概念:
副作用:React 中主要指那些没有发生在数据向视图(M-V)转换过程中的逻辑,如
Ajax
请求、访问原生DOM
元素、本地持久化缓存、绑定/解绑事件、添加/取消订阅、设置定时器、记录日志等。
Hooks 的优越性:
- 函数式编程:No
class
, Nosuper
, Nothis
。对于不了解OOP
的 React 初学者更友好。 - 有状态逻辑易复用:可以通过
Custom Hook
(后面讲解)重构,而不用修改组件结构。 - 易拆分:状态管理和副作用管理松耦合,原子性强。很容易将一些相关联的逻辑拆分成更小的函数。
- 可逐步引入:
Hooks
向后兼容,与现有代码可并行工作,因此我们可以逐步采用它们。 - 副作用分组:很多副作用逻辑分散在类组件生命周期函数中。而
Hooks
可以将每个副作用的设置和清理封装在一个函数中。 - 副作用分离:副作用操作都在页面渲染之后。
抛弃类组件?
既然 Hooks
存在这么多优越性。那是不是就到了抛弃 class 组件的时候了。
对此,官方说:
- 新版本依然支持
class
相关API,在相当一段时期内,class 组件 和 Hooks 组件并存。 - 向后兼容,是加法。注意,是函数组件的加法,即
Hooks
只能用在函数组件中。 - 推荐使用 函数组件 +
Hooks
。
个人觉得:
- 当下:不抛弃,不放弃。class 组件将我们带到了 OOP 的世界,OOP在编程界举足轻重,其思想是值得学习的。即便 class 组件已然成为一种历史产物,但他的存量巨大,依然需要去维护,去慢慢消化。
- 未来:有可能弃用 class 组件及其生命周期。一方面,前端的世界本来变化就快。另一方面,class 组件确实存在一些弊端。随着
Hooks
的不断成熟(或新的技术诞生), 使得开发效率,代码可读性,维护性,性能等综合优势比较明显的时候,弃用是必然。
所以:
- 对于 React 老司机:拥抱Hooks,是拥抱变化。这个变化,是加法,是学习新的API,新的技能,新的思想。
- 对于 React 新手:拥抱Hooks,降低了学习门槛,可以更快入门。但是类组件也非常有必要去了解,理解。知己知彼,重构不殆。
说了这么多,来,我们先来和这些 React Hooks 的 API 见个面:
基础 Hook
- useState
- useEffect
其他 Hook
- useContext
- useReducer
- useCallback
- useMemo
- useRef
- useImperativeHandle
- useLayoutEffect
- useDebugValue
- … 还会增加
是不是有点多,其实useState
useEffect
这两个已经能应付多数场景了。Let‘s go👇
useState hook
- 功能:在函数组件中用来进行状态管理,创建一些本地 state。
- API:
const [currentState, setFunction] = useState(initialState);
。传一个参数,返回一个数组(包含两个值)- 三要素。 initialState
:参数,state 初始值。可以是任何类型: String,Object,Array,Bool,Number等。currentState
:返回值,state 当前最新值。可自主命名。setFunction
:返回值,state 更新函数。可自主命名。你可以在任意位置调用,来改变 state 的值。每次调用,会触发组件重新渲染(这也是返回值用const
非let
的原因)。- 特点:可使用多个
useState
,彼此独立。而类组件,只有一个 state,每次setState
要进行浅合并(内部实现)。
这个API很简单,请看下面示例(24行代码):
import React, { useState } from 'react';
function Form() {
// ES6 解构
const [name, setName] = useState('Mary'); // State 变量 1
const [surname, setSurname] = useState('Poppins'); // State 变量 2
const [width, setWidth] = useState(window.innerWidth); // State 变量 3
function handleNameChange(e) {
setName(e.target.value);
}
function handleSurnameChange(e) {
setSurname(e.target.value);
}
return (
<>
<input value={name} onChange={handleNameChange} />
<input value={surname} onChange={handleSurnameChange} />
<p>Hello, {name} {surname}</p>
<p>Window width: {width}</p>
</>
);
}
export default Form;
<></>
是React.Fragment
的简写语法。
用class类实现的话(31行代码),上述代码相当于:
import React from 'react';
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'Mary',
surname:'Poppins',
width: window.innerWidth
};
}
handleNameChange = (e) => {
this.setState({ name: e.target.value });
}
handleSurnameChange = (e) => {
this.setState({ surname: e.target.value });
}
render() {
const { name, surname, width } = this.state
return (
<>
<input value={name} onChange={ this.handleNameChange } />
<input value={surname} onChange={ this.handleSurnameChange } />
<p>Hello, {name} {surname}</p>
<p>Window width: {width}</p>
</>
);
}
}
export default Form;
useState「粒度」问题
看到这里,对于写过class组件的我们,很容易产生一个疑问。 实际工作中,一个类组件的 this.state
中往往有十几项,用 Hooks
改写的话难道要写十几个 useState
么?
对于这个常见问题,官方文档有解答。
根据官方文档,总结下来,有几点:
- 建议将
state
分割为多个useState
。粒度更细,更易于管理,更好复用。 - 可能一起改变的
state
可合并成一个useState
( 比如Dom
元素的top
left
)。 - 当
state
逻辑趋于复杂,建议使用reducer
或Custom Hook
管理(后面介绍)。
当组件的 state
很多的时候,为了提高代码的可读性,也可以把逻辑相关的一些 state
合并为一个 useState
( 比如分页参数 )。但这些 state
并不是一起改变的,所以当其中一个 state
改变,调用对应的 setFunction
的时候。你需要做对象合并(不合并就丢了):
const [ pageData, setPageDate ] = useState({ pageSize: 20, current: 1, total:0, })
const onPageChange = current => {
// 常规操作
setPageDate( Object.assign( {}, pageData, { current } ) )
// 官方建议
setPageDate(currentPageData => ({ ...currentPageData, current}));
}
知识点:调用 useState 的更新函数时,可以传一个箭头函数,这个函数的参数是当前最新的 state, 返回值是要设置的 state 。
useEffect hook
API 可抽象为: useEffect(arrowFunction, [depsArr])
arrowFunction
: 必须。执行函数,执行副作用操作。它决定了做什么。depsArr
: 非必须。一个依赖项数组。它决定了什么时候做(下面示例中介绍)。
根据实际情况,可细分为三种:
// 第一种
// 最基础的,只有箭头函数。没有依赖项,所以组件每次渲染都会执行。
// 相当于 componentDidMount + componentDidUpdate
useEffect(() => {
//side-effect
})
// 第二种
// 有依赖项,是一个空数组,因为它永远不会变,所以只会首次执行。
// 相当于 componentDidMount
useEffect(() => {
//side-effect
}, [])
// 第三种
// 有第二个参数,且非空数组。首次渲染会执行。重新渲染时,只有当依赖项的值改变了才会执行。
useEffect(() => {
//side-effect
}, [...state])
相当是
==
而非===
。
总结下来:
- 功能:管理 React 函数组件的副作用,赋予生命周期能力。
- 怎么管:组件每次渲染到屏幕之后,根据依赖项的情况判断是否调用执行函数。
- 二要素:执行函数,依赖项。
- 清理机制:你可以在执行函数中返回另一个函数-清理函数,清理函数会在组件卸载的时候,会在组件重新渲染,且useEffect的依赖项值改变的时候调用。起到了 class 组件中
componentWillUnmount
的作用, 后续会在场景实例中介绍。 - 使用上:和
useState
一样,可使用多个。建议一个副作用对应一个useEffect
。
根据副作用是否需要清理,useEffect
可分为 不需要清理的 useEffect
,和 需要清理的 useEffect
。下面,我们分别通过一些示例来直观的感受一下。
不需要清理的场景
有时,我们希望在 React 渲染页面之后运行一些额外的代码。 网络请求、手动修改DOM 和日志记录都是不需要清理 副作用 的常见例子。可以这么说,我们运行它们,然后可以马上忘记它们。
就拿官网的例子来说,一个计数器组件,计数发生改变以后,更新 Dom
标题。类组件是这样实现的:
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>
);
}
}
用 useEffect
实现如下:
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 第一种useEffect
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
规则:每一次渲染后都去运行所有的 effects 可能并不高效。(并且在某些场景下,它可能会导致无限循环。)– Dan Abramov
这条规则告诉我们,在写无依赖的 useEffect
的时候,多一点思考。上面代码现在看没有问题,后续增加了其他 state 和功能以后,这个 useEffect
就不高效了,可改写为:
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
},[count]);// 看这里
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
需要清理的场景
有一些场景,我们需要做副作用的清理,保证引起不必要内存泄漏。比如,手动绑定事件,订阅,定时器等。
以定时器为例,让我们来实现一个秒表组件。这是一个学习和理解 useEffect
非常有意思的例子。
这里,我们直接用 Hooks
来实现:
import React, { useState, useEffect } from 'react';
function App() {
// 秒表开关
const [isOn, setIsOn] = useState(false);
// 计数
const [timer, setTimer] = useState(0);
useEffect(() => {
let interval;
//开关打开的时候才执行
if (isOn) {
// 通过定时器增加计数
interval = setInterval(
() => setTimer(timer + 1),
1000,
);
}
// 需要清除定时器
// 不清理会如何?codesandbox中尝试,页面直接卡死
return () => clearInterval(interval);
});
return (
<>
<p>{timer}</p>
{!isOn && (
<button type="button" onClick={() => setIsOn(true)}>
Start
</button>
)}
{isOn && (
<button type="button" onClick={() => setIsOn(false)}>
Stop
</button>
)}
</>
);
}
export default App;
运行代码,你会发现,秒表效果实现了。但是,同样的错误,故意犯了2次:既然用了定时器,为什么还要 effect 每次执行。让我们来分析下上面代码的执行流程:
- 首次加载:effect执行。因为
isOn
是false
,所以 定时器 没有创建。 - 点击
start
打开开关(setIsOn(true)
)。isOn
这个 state 改变,组件重新渲染。effect再次执行,此时创建定时器。 - 定时器生效,1秒后执行
setTimer(timer + 1)
,timer
这个 state 改变,触发组件重新渲染(定时器也会清除)。effect再次执行,重新创建定时器。 - 一直重复上面步骤。
有没有发现问题,定时器在循环创建,清除。用什么定时器,用延时器(setTimeout
)好了。最糟糕的是,如果你忘了清除定时器,不光计数会错乱,页面也会奔溃。
怎么优化呢?同样的解决方案。很显然,我们的 effect 依赖 isOn
这个 state,所以我们可以把它作为 useEffect
的依赖项:
//...
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(
() => setTimer(timer + 1),
1000,
);
}
return () => clearInterval(interval);
},[isOn]); // 看这里!!!!!!!!!
// ...
这样是不是就ok了?拷贝代码到 codeSandBox 验证一下。what?点击 start
,计数器增加到 1
以后不动了!。
页面卡死了么?我们再分析一下流程:
- 首次加载:effect 执行。因为
isOn
是false
,所以定时器没有创建。 - 点击
start
打开开关。isOn
改变,组件重新渲染。effect 的依赖项isOn
也改变了,effect 再次执行。此时,isOn
是true
,定时器创建。 - 定时器生效,1秒后执行
setTimer(timer + 1)
,timer
改变,触发组件重新渲染。注意了,此时 effect 的依赖项isOn
并没有改变,所以定时器在重新渲染后不会清除,effect 也不会再次执行。看上去这就是我们想要的,定时器还在工作。那为什么一直是1
。
规则:React 约定 Effect 拿到的总是定义它的那次渲染中的
props
和state
。– Dan Abramov
我也注意到,上面的代码在 codeSandBox 中执行会看到一条告警信息:React Hook useEffect has a missing dependency: 'timer'. Either include it or remove the dependency array. You can also do a functional update 'setTimer(t => ...)' if you only need 'timer' in the 'setTimer' call. (react-hooks/exhaustive-deps) -- eslint
疑惑解开。这其实就是js常见的闭包,你也可以理解为这是 useEffect
的约定。这个非常非常重要,划重点。
上面的告警信息,已经明确的告诉了我们如何解决这个问题。
办法一: 增加依赖项 timer
,这样timer
改变也会触发重新渲染,然后 effect 都、会再次执行,定时器会拿到新的 timer
。
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(
() => setTimer(timer + 1),
1000,
);
}
return () => clearInterval(interval);
},[isOn,timer]); // 看这里!!!!!!!!!
规则: 我鼓励你诚实地告知 effect 依赖作为一条硬性规则,并且要列出所有依赖。– Dan Abramov
办法二:采用 更新函数 来改变 state。前面提到过,useState
的 setFunction
中 ,可以传一个箭头函数(更新函数),这个函数的参数是当前最新的 state
, 返回值是要设置的 state
。
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(
() => setTimer(val => val + 1),// 看这里!!!!
1000,
);
}
return () => clearInterval(interval);
},[isOn]); // 看这里
发现没有,使用更新函数后,我们相当于去除了对 timer
的依赖。
规则: 当我们不想增加更多依赖,可以尝试修改 effect 使得依赖更少。– Dan Abramov
所以 方法二 优于 方法一。
我们来看一个实际项目中常见的副作用 - Ajax请求。在class 组件中,我们经常会用生命周期 componentDidMount
来处理一些初始化的 Ajax 数据请求,现在我们用 useEffect 来实现。
比如用 axios
请求一个列表:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState([]);
useEffect(() => {
// 更优雅的方式
const fetchData = async () => {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
};
fetchData();
}, []);
return (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
export default App;
规则:useEffect 不能接收 async 作为执行函数。useEffect 接收的函数,要么返回一个能清除副作用的函数,要么就不返回任何内容。而 async 返回的是 promise。
useEffect 调用的函数如果依赖 state 或者 props。最好在执行函数中定义。这样依赖容易追踪。
useEffect 的使用,看起来很简单。但是要做到不滥用,正确使用也不是那么容易。主要在使用之前要多一些思考。
Custom Hooks
终于讲到它了, 前面已经提到过。它并不是 React hooks
的 API,而是自定义 hook。顾名思义,React允许你构建自己的 hooks
。在学习完前面两个最受欢迎的 hooks
以后,你完全具备了实现自定义 hooks 的能力。
官网定义: 自定义 Hook 是一个 JavaScript 函数,其名称以 ”use” 开头,可以调用其他 Hook。
为什么需要Custom Hooks?
useState
解决了函数组件无状态的问题。useEffect
实现了副作用管理,生命周期的功能。Custom Hooks
将解决有状态(stateful)逻辑共享的问题(相当于类组件中Hoc的功能)。👇
我们来到一个实际场景。如今 HTML5
移动应用或 Web app
中越来越普遍的使用了离线浏览技术,所以用 JS 检测浏览器在线/离线状态非常常见。首先,我们用 React Hooks
来实现这个功能:
import React, { useState, useEffect } from 'react';
function App() {
const [isOffline, setIsOffline] = useState(window.navigator.onLine);
// 离线事件处理方法
function onOffline() {
setIsOffline(true);
}
// 在线事件处理方法
function onOnline() {
setIsOffline(false);
}
useEffect(() => {
// 事件监听
window.addEventListener('offline', onOffline);
window.addEventListener('online', onOnline);
// 清理函数
return () => {
window.removeEventListener('offline', onOffline);
window.removeEventListener('online', onOnline);
};
}, []); // 只需要首次执行
return (
<>
{
isOffline
? <div>网断已断开 ...</div>
: <div>网络已连接 ...</div>
}
</>
)
}
export default App;
无论浏览器是否在线,
navigator.onLine
属性都会提供一个布尔值。 如果浏览器在线,则设置为true
,否则设置为false
。
OK,我们实现了一个很不错的功能。很明显,这个功能是可复用的,应该共享的。
我们把功能逻辑提取出来,把它封装成一个 Custom hook
就可以了:
import React, { useState, useEffect } from 'react';
// 自定义 hook
function useOffline() {
const [isOffline, setIsOffline] = useState(window.navigator.onLine);
function onOffline() {
setIsOffline(true);
}
function onOnline() {
setIsOffline(false);
}
useEffect(() => {
window.addEventListener('offline', onOffline);
window.addEventListener('online', onOnline);
return () => {
window.removeEventListener('offline', onOffline);
window.removeEventListener('online', onOnline);
};
}, []);
return isOffline; // 只暴露一个 state
}
// 函数组件
function App() {
const isOffline = useOffline();
return (
<>
{
isOffline
? <div>网断已断开 ...</div>
: <div>网络已连接 ...</div>
}
</>
)
}
export default App;
现在,你应该对 custom hooks
有了一个直观的认识:
- 一个函数。
- 一个use开头的函数。
- 一个使用 React hooks 封装的,处理副作用的函数。
- 一个在函数组件中引入简单,不需要调整组件结构的函数。
从重构层面来说,就是把组件中的一些 hooks
抽离到一个函数中,再使用这个函数。这个函数就是 React custom hooks
。
既然是函数,那肯定可以传参。我们来看一个常见场景:很多时候,为了用户体验,页面会本地存储用户数据,然后在页面返回的时候自动填充。现在,我们用一个传参的 custom hooks
来实现该场景:
import React, { useState, useEffect } from 'react';
// 自定义 hook,接收一个 localStorageKey 参数
const useStateWithLocalStorage = localStorageKey => {
const [value, setValue] = useState(
localStorage.getItem(localStorageKey) || '',
);
useEffect(() => {
localStorage.setItem(localStorageKey, value);
}, [value]);
return [value, setValue];
};
const App = () => {
// 使用带参数的 自定义 hooks
const [value, setValue] = useStateWithLocalStorage(
'myValueInLocalStorage',
);
const onChange = event => setValue(event.target.value);
return (
<div>
<input value={value} type="text" onChange={onChange} />
<p>{value}</p>
</div>
);
};
书写 custom hooks 需要注意些什么呢?看官网怎么说:
- 自定义 Hooks 是一种惯例,它自然地遵循 Hooks 设计的约定。即遵循所有你用到的
Hooks
的规则。 - 请使用 use 开头。这个习惯非常重要。如果没有它,我们就不能自动检查该 Hook 是否违反了 Hooks 的规则,因为我们无法判断某个函数是否包含对其内部 Hooks 的调用。
原理
顺序调用:每个组件都有一个 “内存单元” 的内部列表。它们只是 JavaScript
对象,你可以想象它是一个数组(实际上是一个单向链表),我们可以在其中放置一些数据。当调用 useState()
这样的 Hook
时,它读取当前单元格(或在第一次呈现时初始化它),然后将指针移动到下一个单元格。这就是多个 useState()
调用各自获取独立本地状态的方式。
Hooks
的状态值都被挂载在组件实例对象FiberNode
的属性中。Hooks
是用链表来保存状态的,属性保存的实际上是这个链表的头指针。useState / useReducer
的信息保存在FiberNode.memoizedState
属性.useEffect
也是以链表的形式挂载在FiberNode.updateQueue
属性中。
// react-reconciler/src/ReactFiberHooks.js
export type Hook = {
memoizedState: any, // 最新的状态值
baseState: any, // 初始状态值,如`useState(0)`,则初始值为0
baseUpdate: Update<any, any> | null,
queue: UpdateQueue<any, any> | null, // 临时保存对状态值的操作,更准确来说是一个链表数据结构中的一个指针
next: Hook | null, // 指向下一个链表节点
};
const effect: Effect = {
tag, // 用来标识依赖项有没有变动
create, // 用户使用useEffect传入的函数体
destroy, // 上述函数体执行后生成的用来清除副作用的函数
deps, // 依赖项列表
next: (null: any),
};
想更详细的理解,请点击:
React Hooks 揭秘
React Hooks 原理剖析
缺点
需要开发者遵从许多规则。理解并合理运用这些规则,能写出优雅的,可读性高的,性能好的代码。反之,很容易出现死循环,数据重复请求等问题。最让人担心的是性能,很多时候业务功能实现了,但是其实存在很多不必要的开销。