react hook 新特性汇总
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react hook 新特性汇总相关的知识,希望对你有一定的参考价值。
参考技术A Hook是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数, 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。举个例子:
1.只在最顶层使用 Hook
不要在循环,条件或嵌套函数中调用 Hook,** 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。
2.只在 React 函数中调用 Hook
3.Hook 在 class 内部是不起作用的。但你可以使用它们来取代 class 。
1.没有破坏性改动
完全可选
100%向后兼容
现在可用
2.Hook 不会影响你对 React 概念的理解。
3只是换成了相关的函数方式更好的组合实现了现有的api
4.解决在组件之间复用状态逻辑很难
react没有提供将可复用性行为“附加”到组件的途径,
高阶组件倒是可以但是容易形成嵌套地狱
renderprops 需要修改相应的组件结构
但是hook 可以自定义相关的hook 把公共的状态抽离出来,如果需要单元测试,测试函数比测试类好侧的多,相信我,历史教训😂
5.解决复杂组件变得难以理解
复杂的组件常常有许多的状态逻辑和副作用(你之前可能已经在 React 组件中执行过数据获取、订阅或者手动修改过 DOM。我们统一把这些操作称为“副作用”,或者简称为“作用”),我们都知道react calss 模式 ,有一些基本的生命周期,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。这也是所有的状态都堆在一起。难以测试。不清晰。
不可能将组件拆分的更小颗粒化
为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),这个时候就用到了 effect-hook
可以将不同的逻辑写在不同的effecthook中
this的问题少了
7.趋势
const [count, setCount] = useState(0);
前者当前状态和后者一个让你更新它的函数
你可以在一个组件中多次使用 State Hook
与class不同点
1)不同于 class 的是初始值我们可以按照需要使用数字或字符串对其进行赋值,而不一定是对象
2)但是它不会把新的 state 和旧的 state 进行合并。
你不必使用多个 state 变量。State 变量可以很好地存储对象和数组,因此,你仍然可以将相关数据分为一组。然而,不像 class 中的 this.setState,更新 state 变量总是替换它而不是合并它。
3)更新 读取的时候比较方便 不需要this
函数 count
4)如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值
useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。(我们会在使用 Effect Hook 里展示对比 useEffect 和这些方法的例子。)
用法
1.可以通过return 清除相关的状态、副作用。
([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。
通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。
与 React 组件不同的是,自定义 Hook 不需要具有特殊的标识。我们可以自由的决定它的参数是什么,以及它应该返回什么(如果需要的话)。换句话说,它就像一个正常的函数。但是它的名字应该始终以 use 开头,这样可以一眼看出其符合 Hook 的规则。
在两个组件中使用相同的 Hook 会共享 state 吗?不会。自定义 Hook 是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),所以每次使用自定义 Hook 时,其中的所有 state 和副作用都是完全隔离的。
关于为什么使用React新特性Hook的一些实践与浅见
前言
关于Hook的定义官方文档是这么说的:
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
简单来说,就是在使用函数式组件时能用上state,还有一些生命周期函数等其他的特性。
如果想了解Hook怎么用,官方文档和阮一峰的React Hooks 入门教程都讲得很清楚了,我建议直接看官方文档和阮大神的文章即可。
本篇博客只讲为什么要用React的Hook新特性,以及它解决了什么问题。
为什么使用Hook?
让我们先看看别人怎么说。
阮大神的文章中给了一个示例代码:
import React, { Component } from "react";
export default class Button extends Component {
constructor() {
super();
this.state = { buttonText: "Click me, please" };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(() => {
return { buttonText: "Thanks, been clicked!" };
});
}
render() {
const { buttonText } = this.state;
return <button onClick={this.handleClick}>{buttonText}</button>;
}
}
并且提出:
这个组件类仅仅是一个按钮,但可以看到,它的代码已经很"重"了。
真实的 React App 由多个类按照层级,一层层构成,复杂度成倍增长。
再加入 Redux,就变得更复杂。
实际上,上面这个代码的“重”有部分来源于写法问题,他可能并没有“重”,让我们看看下面这种class写法:
import React, { Component } from "react";
export default class Button extends Component {
state = {
buttonText: "Click me, please"
}
handleClick = () => {
this.setState(() => {
return { buttonText: "Thanks, been clicked!" };
});
}
render() {
const { buttonText } = this.state;
return <button onClick={this.handleClick}>{buttonText}</button>;
}
}
然后再对比下使用了Hook的函数式组件:
import React, { useState } from "react";
export default function Button() {
const [buttonText, setButtonText] = useState("Click me, please");
function handleClick() {
return setButtonText("Thanks, been clicked!");
}
return <button onClick={handleClick}>{buttonText}</button>;
}
即使是我们简化过的class写法,比起Hook的看起来好像也确实“重”了点。
Hook的语法确实简练了一些,但是这个理由并不是那么充分。
阮大神同时列举了Redux 的作者 Dan Abramov 总结了组件类的几个缺点:
- 大型组件很难拆分和重构,也很难测试。
- 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。(这里我认为阮大神写的可能有点问题,应该是是各个生命周期方法更为准确)
- 组件类引入了复杂的编程模式,比如 render props 和高阶组件。
这三点都是事实,于是有了函数化的组件,但之前的函数化组件没有state和生命周期,有了Hook那么就可以解决这个痛点。
而且Hook并不只是这么简单,通过自定义Hook,我们可以将原有组件的逻辑提取出来实现复用。
用useEffect解决生命周期导致的重复逻辑或关联逻辑
上面举的几个缺点,第一点和第三点你可能很容易理解,第二点就不容易理解了,所以我们需要深入到具体的代码中去理解这句话。
我们看看下面这段代码:
import React, { Component } from "react";
export default class Match extends Component {
state={
matchInfo:''
}
componentDidMount() {
this.getMatchInfo(this.props.matchId)
}
componentDidUpdate(prevProps) {
if (prevProps.matchId !== this.props.matchId) {
this.getMatchInfo(this.props.matchId)
}
}
getMatchInfo = (matchId) => {
// 请求后台接口获取赛事信息
// ...
this.setState({
matchInfo:serverResult // serverResult是后台接口的返回值
})
}
render() {
const { matchInfo } = this.state
return <div>{matchInfo}</div>;
}
}
这样的代码在我们的业务中经常会出现,通过修改传入赛事组件的ID,去改变这个赛事组件的信息。
在上面的代码中,受生命周期影响,我们需要在加载完毕和Id更新时都写上重复的逻辑和关联逻辑。
所以现在你应该比较好理解这句话:业务逻辑分散在组件的各个生命周期方法之中,导致重复逻辑或关联逻辑。
为了解决这一点,React提供了useEffect这个钩子。
但是在讲这个之前,我们需要先了解到React带来的一个新的思想:同步。
我们在上面的代码中所做的实际上就是在把组件内的状态和组件外的状态进行同步。
所以在使用Hook之前,我们需要先摒弃生命周期的思想,而用同步的思想去思考这个问题。
现在再让我们看看改造后的代码:
import React, { Component } from "react";
export default function Match({matchId}) {
const [ matchInfo, setMatchInfo ] = React.useState('')
React.useEffect(() => {
// 请求后台接口获取赛事信息
// ...
setMatchInfo(serverResult) // serverResult是后台接口的返回值
}, [matchId])
return <div>{matchInfo}</div>;
}
看到这个代码,再对比上面的代码,你心中第一反应应该就是:简单。
React.useEffect接受两个参数,第一个参数是Effect函数,第二个参数是一个数组。
组件加载的时候,执行Effect函数。
组件更新会去判断数组中的各个值是否变动,如果不变,那么不会执行Effect函数。
而如果不传第二个参数,那么无论加载还是更新,都会执行Effect函数。
顺便提一句,这里有组件加载和更新的生命周期的概念了,那么也应该是有组件卸载的概念的:
import React, { Component } from "react";
export default function Match({matchId}) {
const [ matchInfo, setMatchInfo ] = React.useState('')
React.useEffect(() => {
// 请求后台接口获取赛事信息
// ...
setMatchInfo(serverResult) // serverResult是后台接口的返回值
return ()=>{
// 组件卸载后的执行代码
}
}, [matchId])
return <div>{matchInfo}</div>;
}
}
这个常用于事件绑定解绑之类的。
用自定义Hook解决高阶组件
React的高阶组件是用来提炼重复逻辑的组件工厂,简单一点来说就是个函数,输入参数为组件A,输出的是带有某逻辑的组件A+。
回想一下上面的Match组件,假如这个组件是页面A的首页头部用来展示赛事信息,然后现在页面B的侧边栏也需要展示赛事信息。
问题就在于页面A的这块UI需要用div,而页面B侧边栏的这块UI需要用到span。
保证今天早点下班的做法是复制A页面的代码到页面B,然后改下render的UI即可。
保证以后早点下班的做法是使用高阶组件,请看下面的代码:
import React from "react";
function hocMatch(Component) {
return class Match React.Component {
componentDidMount() {
this.getMatchInfo(this.props.matchId)
}
componentDidUpdate(prevProps) {
if (prevProps.matchId !== this.props.matchId) {
this.getMatchInfo(this.props.matchId)
}
}
getMatchInfo = (matchId) => {
// 请求后台接口获取赛事信息
}
render () {
return (
<Component {...this.props} />
)
}
}
}
const MatchDiv=hocMatch(DivUIComponent)
const MatchSpan=hocMatch(SpanUIComponent)
<MatchDiv matchId={1} matchInfo={matchInfo} />
<MatchSpan matchId={1} matchInfo={matchInfo} />
但是实际上有的时候我们的高阶组件可能会更复杂,比如react-redux的connect,这就是高阶组件的复杂化使用方式。
又比如:
hocPage(
hocMatch(
hocDiv(DivComponent)
)
)
毫无疑问高阶组件能让我们复用很多逻辑,但是过于复杂的高阶组件会让之后的维护者望而却步。
而Hook的玩法是使用自定义Hook去提炼这些逻辑,首先看看我们之前使用了Hook的函数式组件:
import React, { Component } from "react";
export default function Match({matchId}) {
const [ matchInfo, setMatchInfo ] = React.useState('')
React.useEffect(() => {
// 请求后台接口获取赛事信息
// ...
setMatchInfo(serverResult) // serverResult是后台接口的返回值
}, [matchId])
return <div>{matchInfo}</div>;
}
然后,自定义Hook:
function useMatch(matchId){
const [ matchInfo, setMatchInfo ] = React.useState('')
React.useEffect(() => {
// 请求后台接口获取赛事信息
// ...
setMatchInfo(serverResult) // serverResult是后台接口的返回值
}, [matchId])
return [matchInfo]
}
接下来,修改原来的Match组件
export default function Match({matchId}) {
const [matchInfo]=useMatch(matchId)
return <div>{matchInfo}</div>;
}
相比高阶组件,自定义Hook更加简单,也更加容易理解。
现在我们再来处理以下这种情况:
hocPage(
hocMatch(
hocDiv(DivComponent)
)
)
我们的代码将不会出现这种不断嵌套情况,而是会变成下面这种:
export default function PageA({matchId}) {
const [pageInfo]=usePage(pageId)
const [matchInfo]=useMatch(matchId)
const [divInfo]=useDiv(divId)
return <ul>
<li>{pageInfo}</li>
<li>{matchInfo}</li>
<li>{divInfo}</li>
</ul>
}
是否需要改造旧的class组件?
现在我们了解到了Hook的好,所以就需要去改造旧的class组件。
官方推荐不需要专门为了hook去改造class组件,并且保证将继续更新class相关功能。
实际上我们也没有必要专门去改造旧项目中的class组件,因为工作量并不小。
但是我们完全可以在新的项目或者新的组件中去使用它。
总结
Hook是对函数式组件的一次增强,使得函数式组件可以做到class组件的state和生命周期。
Hook的语法更加简练易懂,消除了class的生命周期方法导致的重复逻辑代码,解决了高阶组件难以理解和使用困难的问题。
然而Hook并没有让函数式组件能做到class组件做不到的事情,它只是让很多事情变得更加简单而已。
class组件并不会消失,但hook化的函数式组件将是趋势。
以上是关于react hook 新特性汇总的主要内容,如果未能解决你的问题,请参考以下文章
React劲爆新特性Hooks 重构去哪儿网火车票PWA云资源