React Hooks是React 16.8发布以来最吸引人的特性之一。在开始介绍React Hooks之前,让咱们先来理解一下什么是hooks。wikipedia是这样给hook下定义的:

In computer programming, the term hooking covers a range of techniques used to alter or augment the behaviour of an operating system, of applications, or of other software components by intercepting function calls or messages or events passed between software components. Code that handles such intercepted function calls, events or messages is called a hook.

通俗来说,Hook(钩子)就是通过拦截软件和系统内部函数调用和消息通信来增强原有功能的技术。而React Hooks想要增强哪些功能呢?设想你的项目中已经有一大堆组件,这些组件各自都拥有自己的状态。那么一旦你想重用某些特定的带状态逻辑,就得大幅度重构你的应用。现在有了React Hooks,你只需要抽离这些带状态的逻辑代码,然后它们可以更好地进行重用, 而且独立出来的代码也更容易进行测试和管理。有了React Hooks后,你可以在函数式组件中实现之前在带状态组件中能做到的任何事,你能够更灵活地实现你的应用代码。

接下来,让我们看看React Hooks在实际项目中到底怎么使用。

状态管理

对于业务性组件来说,状态管理肯定是不可避免的。以前,我们通常写Class组件来管理业务逻辑,或者使用redux来全局管理状态。现在我们可以利用React Hooks新提供的State Hook来处理状态,针对那些已经写好的Class组件,我们也可以利用State Hook很好地进行重构, 先来看下面这段代码:

import React from 'react';
class Person extends React.Component {
constructor(props) {
super(props);
this.state = {
username: "scq000"
};
} render() {
return (
<div>
<p>Welcome to homepage. {state.username}</p>
<input type="text" placeholder="input a username" onChange={(event) => this.setState({ username: event.target.value)})}></input>
</div>
);
}
}

接下来尝试将它重构成函数式组件:

import React, {useState} from 'react';

export const Person = () => {
const [state, setState] = useState({username: "scq000"}); return (
<div>
<p>Welcome to homepage. {state.username}</p>
<input type="text" placeholder="input a username" onChange={(event) => setState({username: event.target.value})}></input>
</div>
)
}

如上面这段代码,我们首先使用useState api 来声明一个内部状态,接着声明一个新的状态变量state,以及它的setter方法。在这里,为了减少重构的工作量我特意选择了state这个变量名,你也可以单独将每个独立的状态提取出来使用, 比如使用代码const [username, setUsername] = userState("scq000")。在随后的组件内部,我们就可以利用这个内部状态来处理业务逻辑了。由于是函数式组件的写法,我们也能够避免很多this绑定,而且这部分逻辑在后续使用过程中也可以抽离出来进行重用。不过这里有个需要注意的点是:当你使用set方法的时候,旧状态不会自动merge到新状态中去,所以你如果提取的状态是个对象,且有多个属性时,需要使用如下语法进行状态的更新:

setState({
...state,
username: event.target.value
});

生命周期管理

我们都知道,组件的生命周期管理是整个react组件的灵魂所在。利用生命周期函数,我们可以控制整个组件的加载、更新和卸载。React Hooks中提供了Effect钩子,使我们可以在函数式组件中实现这些功能。

为了便于理解,接下来我将分别演示如何利用Effect钩子实现原本在Class组件中的各个生命周期方法。下面这段代码是我们熟悉的Class组件:

import React from 'react';
class Person extends React.Component {
constructor(props) {
super(props);
this.state = {
username: "scq000"
};
} componentDidMount() {
console.log('componentDidMount: 组件加载后')
} componentWillUnmount() {
console.log('componentWillUnmount: 组件卸载, 做一些清理工作')
} componentDidUpdate(prevProps, prevState) {
if(prevState.username !== this.state.username) {
console.log('componentDidUpdate: 更新usernmae')
}
} render() {
return (
<div>
<p>Welcome to homepage. {state.username}</p>
<input type="text" placeholder="input a username" onChange={(event) => this.setState({ username: event.target.value)})}></input>
</div>
);
}
}

现在我们利用Effect重构一下:

import React, {useState, useEffect} from 'react';

export const Person = () => {
const [state, setState] = useState({username: "scq000"}); useEffect(() => {
console.log('componentDidMount: 组件加载后')
return () => {
console.log('componentWillUnmount: 组件卸载, 做一些清理工作')
}
}, []); useEffect(() => {
console.log('componentDidUpdate: 更新usernmae')
}, [state.username]); return (
<div>
<p>Welcome to homepage. {state.username}</p>
<input type="text" placeholder="input a username" onChange={(event) => setState({username: event.target.value})}></input>
</div>
)
}

可以看到,我们利用副作用钩子很好地实现了原本的生命周期方法。通常我们会利用组件的生命周期函数去获取数据,操作DOM等,而这些操作都被称作副作用(side effect)。这些副作用逻辑一般都比较复杂,也是bug频发的地段。 所以我们可以针对每一段逻辑单独使用一个Effect钩子,便于后期维护和调试。

在使用过程中,useEffect方法需要传入两个参数,第一个参数是回调函数:这个回调函数会在每次组件渲染后执行,包括初始化渲染以及每次更新时。另一个参数,则是状态依赖项(数组形式),一旦检测到依赖项数据变动,组件会更新,并且回调函数都会被再次执行一遍,从而实现componentDidUpdate的功能。如果你传入一个空依赖,就能实现原来componentDidMount的效果,即只会执行一次。回调函数中如果返回的是闭包,这个返回的闭包函数将会在组件重新渲染前执行,所以你可以在这个位置做一些清理操作,从而实现componentWillUnmount的功能。

还有要注意的是componentWillMountcomponentWillUpdate两个生命周期方法在新版本的React中已经不推荐使用了,具体原因可以查看这里

至此,我们就学会如何利用Effect钩子在函数式组件中实现所有生命周期方法,从而管理我们的应用了。

自定义Hook

重用和抽象一直都是编程中要解决的问题。我们可以自己封装想要的Hook, 从而实现代码逻辑的重用和抽象。

封装自定义hook其实很简单,就是包装一个自定义函数,然后根据功能将其状态和对应的effect逻辑封装进去:

export const useFetch = (url, dependencies) => {
const [isLoading, setIsLoading] = useState(false);
const [response, setResponse] = useState(null);
const [error, setError] = useState(null); useEffect(() => {
setIsLoading(true);
axios.get(url).then((res) => {
setIsLoading(false);
setResponse(res);
}).catch((err) => {
setIsLoading(false);
setError(err);
});
}, dependencies) return [isLoading, response, error];
}

这里我们简单地封装了一个请求数据的Hook,使用方法跟其他Hook类似,直接调用就可以了:

export const Person = () => {
const [isLoading, response, error] = useFetch("http://example.com/getPersonInfo", []); return (
<div>
{isLoading ?
<div>loading...</div>
:
(
error ? <div> There is an error happened. {error.message} </div>
: <div> Welcome, {response.userName} </div>
)
}
</div>
)
}

注意事项

在使用Hooks的过程中,需要注意的两点是:

  • 不要在循环,条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook。这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。

  • 只能在React函数式组件或自定义Hook中使用Hook。

为了避免我们无意中破坏这些规则,你可以安装一个eslint插件:

npm install eslint-plugin-react-hooks --save-dev

并在配置文件中使用它:

{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error"
}
}

这样,一旦你违法上述这些原则,就会获得相应的提示。

总结

本文介绍了React Hook的使用方式,并通过几个简单的例子演示了如何在函数式组件中进行状态管理和生命周期管理。官方目前提供了很多基础的Hook,如useContext, useReducer, useMemo等,大家可以酌情在项目中使用。

参考资料

https://reactjs.org/docs/hooks-reference.html

——本文首发于个人公众号,转载请注明出处———

最后,欢迎大家关注我的公众号,一起学习交流。

关于React Hooks,你不得不知的事的更多相关文章

  1. React Hooks用法大全

    前言 在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖 ...

  2. React Hooks究竟是什么呢?

    摘要: React Hooks原理解析. 原文:快速了解 React Hooks 原理 译者:前端小智 我们大部分 React 类组件可以保存状态,而函数组件不能? 并且类组件具有生命周期,而函数组件 ...

  3. 蒲公英 &#183; JELLY技术周刊 Vol.21 -- 技术周刊 &#183; React Hooks vs Vue 3 + Composition API

    蒲公英 · JELLY技术周刊 Vol.21 选 React 还是 Vue,每个人心中都会有自己的答案,有很多理由去 pick 心水的框架,但是当我们扪心自问,我们真的可以公正的来评价这两者之间的差异 ...

  4. react之react Hooks

    函数组件,没有 class 组件中的 componentDidMount.componentDidUpdate 等生命周期方法,也没有 State,但这些可以通过 React Hook 实现. Rea ...

  5. Java你可能不知道的事(3)HashMap

    概述 HashMap对于做Java的小伙伴来说太熟悉了.估计你们每天都在使用它.它为什么叫做HashMap?它的内部是怎么实现的呢?为什么我们使用的时候很多情况都是用String作为它的key呢?带着 ...

  6. java你可能不知道的事(2)--堆和栈

    在java语言的学习和使用当中你可能已经了解或者知道堆和栈,但是你可能没有完全的理解它们.今天我们就一起来学习堆.栈的特点以及它们的区别.认识了这个之后,你可能对java有更深的理解. Java堆内存 ...

  7. ES6 你可能不知道的事 – 基础篇

    序 ES6,或许应该叫 ES2015(2015 年 6 月正式发布),对于大多数前端同学都不陌生. 首先这篇文章不是工具书,不会去过多谈概念,而是想聊聊关于每个特性 你可能不知道的事,希望能为各位同学 ...

  8. overflow:hidden 你所不知道的事

    overflow:hidden 你所不知道的事 overflow:hidden这个CSS样式是大家常用到的CSS样式,但是大多数人对这个样式的理解仅仅局限于隐藏溢出,而对于清除浮动这个含义不是很了解. ...

  9. 通过 React Hooks 声明式地使用 setInterval

    本文由云+社区发表 作者:Dan Abramov 接触 React Hooks 一定时间的你,也许会碰到一个神奇的问题: setInterval 用起来没你想的简单. Ryan Florence 在他 ...

随机推荐

  1. nutch2.1+mysql+elasticsearch整合linux单机部署

    这次主要介绍下nutch2.1和mysql和elasticsearch的整合,是在单机上运行,并不是分布式部署.1.下载nutch2.1 nutch下载地址:http://labs.mop.com/a ...

  2. uni-app开发踩坑记录

    大部分问题是我在h5端看不到而在android.iOS平台上暴露出来的,不包含小程序 1.:class="['defaultStyle', dynamicStyle]" 不支持直接 ...

  3. Major Performance Impacts

    - Default opaque flags (3s) - Filter size (3s) - ??? (4s) - Image refresh performance (1s)

  4. webservice简单例子

    1.添加web服务. /// <summary> /// demo 的摘要说明 /// </summary> [WebService(Namespace = "htt ...

  5. vc6.0 Buile菜单下 Profile的作用

    Profile的作用 帮助你分析并发现程序运行的瓶颈,找到耗时所在,同时也能帮助你发现不会被执行的代码.从而最终实现程序的优化. Profile的组成 Profile包括3个命令行工具:PREP,PR ...

  6. 爬虫开发14.scrapy框架之分布式操作

    分布式爬虫 一.redis简单回顾 1.启动redis: mac/linux:   redis-server redis.conf windows: redis-server.exe redis-wi ...

  7. OCP 052题库全变,最新052考试题及答案整理-第11题

    11.Which three are true about UNDO data? A) It is used to roll back failed transactions. B) It is us ...

  8. bzoj4241: 历史研究(回滚莫队)

    传送门 这是一个叫做回滚莫队的神奇玩意儿 是询问,而且不强制在线,就决定是你了莫队 如果是每次插入一个数是不是很简单? 然而悲剧的是我们莫队的时候不仅要插入数字还要删除数字 那么把它变成只插入不就行了 ...

  9. 在通知栏上玩游戏,Steve iOS 游戏实现思路

    最近有一款游戏特别的火爆,叫做Steve ,一种可以在通知中心直接玩的游戏.作者的脑洞也是非常的大,实在让人佩服.其实实现起来也简单,就是用到了iOS8新特性 app extension(Today ...

  10. 实现反转的方法(reverse)

    1.最简单的方法: public static String reverse1(String str) { return new StringBuffer(str).reverse().toStrin ...