react 数组新增_React 新特性 Hooks 讲解及实例(二)

本文是 React 新特性系列的第二篇,第一篇请点击这里:

React 新特性讲解及实例

什么是 Hooks

Hook 是 React 16.8 的新增特性。它可以让你在不编写 类组件 的情况下使用 state以及其他的 React 特性。

类组件的不足

状态逻辑复用难

  • 缺少复用机制

  • 渲染属性和高阶组件导致层级冗余

趋向复杂难以维护

  • 生命周期函数混杂不相干逻辑

  • 相干逻辑分散在不同生命周期

this 指向困扰

  • 内联函数过度创建新句柄

  • 类成员函数不能保证 this

Hooks 优势

优化类组件的三大问题

  • 函数组件无 this 问题

  • 自定义 Hook 方便复用状态逻辑

  • 副作用的关注点分离

使用 State Hook

import React, {Component} from 'react'

class App extends Component {

state = {

count: 0

};

render() {

const {count} = this.state;

return (

<button type="button"

onClick={() => {

this.setState({

count: count + 1

})

}}

>Click({count})button>

)

}

}

export default App;

以上代码很好理解,点击按钮让 count 值加 1

接下来我们使用 useState 来实现上述功能。

import React, {useState} from 'react'

function App () {

const [count, setCount] = useState(0)

return (

<button type="button"

onClick={() => {setCount(count + 1) }}

>Click({count})button>

)

}

在这里, useState 就是一个 Hook。通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state

useState 会返回一对值:当前状态和一个让你更新它的函数。你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并。 useState 唯一的参数就是初始 state

useState 让代码看起来简洁了,但是我们可能会对组件中,直接调用 useState 返回的状态会有些懵。既然 userState 没有传入任何的环境参数,它怎么知道要返回的的是 count 的呢,而且还是这个组件的 count 不是其它组件的 count

初浅的理解: useState 确实不知道我们要返回的 count,但其实也不需要知道,它只要返回一个变量就行了。数组解构的语法让我们在调用 useState 时可以给 state 变量取不同的名字。

useState 怎么知道要返回当前组件的 state?

因为 JavaScript 是单线程的。在 useState 被调用时,它只能在唯一一个组件的上下文中。

有人可能会问,如果组件内有多个 usreState,那 useState 怎么知道哪一次调用返回哪一个 state 呢?

这个就是按照第一次运行的次序来顺序来返回的。

接着上面的例子我们在声明一个 useState:

...

const [count, setScount] = useState(0)

const [name, setName] = useState('小智')

...

然后我们就可以断定,以后 APP组件每次渲染的时候, useState 第一次调用一定是返回 count,第二次调用一定是返回 name。不信的话来做个实验:

let id = 0

function App () {

let name,setName;

let count,setCount;

id += 1;

if (id & 1) {

// 奇数

[count, setCount] = useState(0)

[name, setName] = useState('小智')

} else {

// 偶数

[name, setName] = useState('小智')

[count, setCount] = useState(0)

}

return (

<button type="button"

onClick={() => {setCount(count + 1) }}

>

Click({count}), name ({name})

button>

)

}

首先在外部声明一个 id,当 id为奇数和偶数的时候分别让 useState 调用方式相反,运行会看到有趣的现象。

acc2357c820ab6df682da19f6431c8d1.gif

当前版本如果写的顺序不一致就会报错。

会发现 countname 的取值串了。我们希望给 count 加 1,现在却给 name 加了 1,说明 setCount 函数也串成了 setName 函数。

为了防止我们使用 useState 不当,React 提供了一个 ESlint 插件帮助我们检查。

  • eslint-plugin-react-hooks

优化点

通过上述我们知道 useState 有个默认值,因为是默认值,所以在不同的渲染周期去传入不同的值是没有意义的,只有第一次传入的才有效。如下所示:

...

const defaultCount = props.defaultCount || 0

const [count, setCount] = useState(defaultCount)

...

state 的默认值是基于 props,在 APP 组件每次渲染的时候 constdefaultCount=props.defaultCount||0 都会运行一次,如果它复杂度比较高的话,那么浪费的资料肯定是可观的。

useState 支持传入函数,来延迟初始化:

const [count, setCount] = useState(() => {

return props.defaultCount || 0

})

使用 Effect Hook

Effect Hook 可以让你在函数组件中执行副作用操作。数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。不管你知不知道这些操作,或是"副作用"这个名字,应该都在组件中使用过它们。

副作用的时机

  • Mount 之后 对应 componentDidMount

  • Update 之后 对应 componentDidUpdate

  • Unmount 之前 对应 componentWillUnmount

现在使用 useEffect 就可以覆盖上述的情况。

为什么一个 useEffect 就能涵盖 Mount,Update,Unmount 等场景呢。

useEffect 标准上是在组件每次渲染之后调用,并且会根据自定义状态来决定是否调用还是不调用。

第一次调用就相当于 componentDidMount,后面的调用相当于 componentDidUpdateuseEffect 还可以返回另一个回调函数,这个函数的执行时机很重要。作用是清除上一次副作用遗留下来的状态。

eb55b08ff540692fab0befd53fa6dfe5.png

比如一个组件在第三次,第五次,第七次渲染后执行了 useEffect 逻辑,那么回调函数就会在第四次,第六次和第八次渲染之前执行。严格来讲,是在前一次的渲染视图清除之前。如果 useEffect 是在第一次调用的,那么它返回的回调函数就只会在组件卸载之前调用了,也就是 componentWillUnmount

如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

举粟说明一下:

class App extends Component {

state = {

count: 0,

size: {

width: document.documentElement.clientWidth,

height: document.documentElement.clientHeight

}

};

onResize = () => {

this.setState({

size: {

width: document.documentElement.clientWidth,

height: document.documentElement.clientHeight

}

})

}

componentDidMount () {

document.title = this.state.count;

window.addEventListener('resize', this.onResize, false)

}

componentWillMount () {

window.removeEventListener('resize', this.onResize, false)

}

componentDidUpdate () {

document.title = this.state.count;

}

render() {

const {count, size} = this.state;

return (

<button type="button"

onClick={() => {this.setState({count: count + 1})}}

>

Click({count})

size: {size.width}x{size.height}

button>

)

}

}

上面主要做的就是网页 title 显示 count 值,并监听网页大小的变化。这里用到了 componentDidMountcomponentDidUpdate 等副作用,因为第一次挂载我们需要把初始值给 title, 当 count 变化时,把变化后的值给它 title,这样 title 才能实时的更新。

注意,我们需要在两个生命周期函数中编写重复的代码。

这边我们容易出错的地方就是在组件结束之后要记住销毁事件的注册,不然会导致资源的泄漏。现在我们把 App 组件的副作用用 useEffect 实现。

function App (props) {

const [count, setCount] = useState(0);

const [size, setSize] = useState({

width: document.documentElement.clientWidth,

height: document.documentElement.clientHeight

});

const onResize = () => {

setSize({

width: document.documentElement.clientWidth,

height: document.documentElement.clientHeight

}

)

}

useEffect(() => {

document.title = count;

})

useEffect(() => {

window.addEventListener('resize', onResize, false);

return () => {

window.removeEventListener('resize', onResize, false)

}

}, [])

return (

<button type="button"

onClick={() => {setCount(count + 1) }}

>

Click({count})

size: {size.width}x{size.height}

button>

)

}

对于上述代码的第一个 useEffect,相比类组件,Hooks 不在关心是 mount 还是 update。用 useEffect统一在渲染后调用,就完整追踪了 count 的值。

对于第二个 useEffect,我们可以通过返回一个回调函数来注销事件的注册。回调函数在视图被销毁之前触发,销毁的原因有两种:重新渲染和组件卸载

这边有个问题,既然 useEffect 每次渲染后都执行,那我们每次都要绑定和解绑事件吗?当然是完全不需要,只要使用 useEffect 第二个参数,并传入一个空数组即可。第二个参数是一个可选的数组参数,只有数组的每一项都不变的情况下, useEffect才不会执行。第一次渲染之后,useEffect 肯定会执行。由于我们传入的空数组,空数组与空数组是相同的,因此 useEffect 只会在第一次执行一次。

这也说明我们把 resize 相关的逻辑放在一直写,不在像类组件那样分散在两个不同的生命周期内。同时我们处理 title 的逻辑与 resize 的逻辑分别在两个 useEffect 内处理,实现关注点分离。

我们在定义一个 useEffect,来看看通过不同参数,第二个参数的不同作用。

...

useEffect(() => {

console.log('count:', count)

}, [count])

...

第二个参数我们传入 [count], 表示只有 count 的变化时,我才打印 count 值, resize 变化不会打印。

运行效果如下:

6410bdcbc967efc2ddbdcc758533bb75.gif

第二个参数的三种形态, undefined,空数组及非空数组,我们都经历过了,但是咱们没有看到过回调函数的执行。

现在有一种场景就是在组件中访问 Dom 元素,在 Dom元素上绑定事件,在上述的代码中添加以下代码:

...

const onClick = () => {

console.log('click');

}

useEffect(() => {

document.querySelector('#size').addEventListener('click', onClick, false);

},[])

return (

...

<span id="size">size: {size.width}x{size.height}span>

div>

)

新增一个 DOM 元素,在新的 useEffect 中监听 span 元素的点击事件。

运行效果:

04babe3f6436e8f10ba262d67ec4b4c9.gif

假如我们 span 元素可以被销毁重建,我们看看会发生什么情况,改造一下代码:

return (

...

button>

{

count%2

? <span id="size">我是spanspan>

: <p id='size'>我是pp>

}

div>

运行效果:

4d0266c81f5ffed4318c6ba137cbb43c.gif

可以看出一旦 dom 元素被替换,我们绑定的事件就失效了,所以咱们始终要追踪这个dom 元素的最新状态。

使用 useEffect ,最合适的方式就是使用回调函数来处理了,同时要保证每次渲染后都要重新运行,所以不能给第二次参数设置 [],改造如下:

useEffect(() => {

document.querySelector('#size').addEventListener('click', onClick, false);

return () => {

document.querySelector('#size').removeEventListener('click', onClick, false);

}

})

运行结果:

2e7066a2755eb6251ed02e8e198f127d.gif

参考

[React 官方文档][9]

《React劲爆新特性Hooks 重构去哪儿网》

交流

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/281954.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

7天学会python_7天学会Python最佳可视化工具Seaborn(五):结构化展示多维数据

当探索具有中等数量(不多不少的意思……)维度的数据集时&#xff0c;一个很好的方式是基于不同的子数据集构建不同的实例&#xff0c;并将它们以网格的方式组织在一张图之中。这种技术有时被称为“lattice”或“trellis”(大概是格子图、网格图)&#xff0c;这跟“small multip…

面对峰值响应冲击,解决高并发的三大策略

2019独角兽企业重金招聘Python工程师标准>>> 当前在互联网的大潮下&#xff0c;众所周知淘宝、京东这些交易系统每天产生的数据量都是海量的&#xff0c;每天的交易并发也是惊人的&#xff0c;尤其是“双11”、“6.18”这些活动&#xff0c;对系统的峰值响应提出了非…

hibernate mysql 主从_MYSQL主从复制和写分离

基础篇https://edu.51cto.com/course/19845.htmlhttps://edu.51cto.com/course/19845.htmlhttps://edu.51cto.com/course/19841.htmlhttps://edu.51cto.com/course/21197.htmlhttps://edu.51cto.com/course/19886.htmlhttps://edu.51cto.com/course/19887.htmlhttps://edu.51ct…

全新升级的AOP框架Dora.Interception[6]: 框架设计和实现原理

本系列前面的五篇文章主要介绍Dora.Interception的编程模式以及对它的扩展定制&#xff0c;现在我们来聊聊它的设计和实现原理。目录一、调用链抽象二、基于约定的拦截器定义三、基于调用上下文的依赖注入容器四、拦截器的提供五、调用链的构建六、方法拦截的实现原理七、依赖注…

完成登录与注册页面的前端

完成登录与注册页面的HTMLCSSJS&#xff0c;其中的输入项检查包括&#xff1a; 用户名6-12位 首字母不能是数字 只能包含字母和数字 密码6-12位 注册页两次密码是否一致 JS&#xff1a; function fnLogin() {var uSer document.getElementById("user");var pAss do…

WPF效果第二百零一篇之实现合并单元格

早一段时间又一次出差青海省西宁市;回来又是总结又是各种琐事,也没顾得上去分享点东西;大周末的就在家分享一下,这二天再次基于ListBox实现的合并单元格的效果:1、ListBox嵌套ListBox的前台布局:<ListBox ItemsSource"{Binding LCPListData}" x:Name"Manufac…

ASP.NET Core中使用EasyCaching作为缓存抽象层

简介做后端开发&#xff0c;缓存应该是天天在用&#xff0c;很多时候我们的做法是写个帮助类&#xff0c;然后用到的时候调用一下。这种只适合简单层次的应用&#xff1b;一旦涉及到接口实现调整之类的&#xff0c;这种强耦合的做法很不合适。有些其他的功能又要去重复造轮子。…

visual studio开启多核编译方法

先按http://blog.csdn.net/acaiwlj/article/details/50240625的方法进行了VS多线程的启动。 原本以为按以下步骤设置就OK了&#xff0c;但是编译中无意间发些了一个warning&#xff1a;“/Gm”与多处理不兼容&#xff1b;忽略 /MP 开关&#xff01;&#xff01;&#xff01;&am…

聊聊storm nimbus的LeaderElector

为什么80%的码农都做不了架构师&#xff1f;>>> 序 本文主要研究一下storm nimbus的LeaderElector Nimbus org/apache/storm/daemon/nimbus/Nimbus.java public static void main(String[] args) throws Exception {Utils.setupDefaultUncaughtExceptionHandler();…

如果我去深圳,你会见我吗

▲图/ 深圳夜景初次见易小姐&#xff0c;还是21年的春节回老家的时候。想来20年因为疫情没有回家&#xff0c;家母几次三番电话里头表达的思念以及建议一些不靠谱的回家计划&#xff0c;着实有些不忍&#xff0c;确实有似“儿行千里母担忧”之理&#xff0c;索性拿着年假和加班…

开源轻量的 .NET 监控工具 - 看门狗

你好&#xff0c;这里是 Dotnet 工具箱&#xff0c;定期分享 Dotnet 有趣&#xff0c;实用的工具或组件&#xff0c;希望对您有用&#xff01;简介WatchDog 是一个使用 C# 开发的开源的轻量监控工具&#xff0c;它可以记录和查看 ASP.Net Core Web 和 WebApi 的实时消息、事件、…

BZOJ 3231: [Sdoi2008]递归数列 (JZYZOJ 1353) 矩阵快速幂

http://www.lydsy.com/JudgeOnline/problem.php?id3231和斐波那契一个道理在最后加一个求和即可1 #include<cstdio>2 #include<cstring>3 #include<iostream>4 //using namespace std;5 const int maxn10010;6 const double eps1e-8;7 long long modn;8 lon…

马斯克的火箭上天了,SpaceX开源项目也登上了热榜!

python知识手册SpaceX于美国东部时间5月30日下午3&#xff1a;22分将两位美国宇航员送往国际空间站&#xff0c;虽然这只是Demo任务&#xff0c;但SpaceX已经以其卓越工程优势、低廉的发射成本赢得了全球航天产业的信赖。同时也是除美俄中这些航天国家队以外&#xff0c;唯一独…

机器视觉Halcon教程(1.介绍)

前言本期教程主要教大家如何使用Halcon机器视觉&#xff0c;通过使用Halcon, 我们可以实现一些机器视觉的应用开发。例如: OCR识别、视觉定位、缺陷检测等内容。什么是halcon&#xff1f;简单来说, Halcon就是一款应用于机器视觉的软件&#xff0c;它提供了一套开发工具&#x…

网络时间的那些事及 ntpq 详解

2019独角兽企业重金招聘Python工程师标准>>> GMT (Greenwich Mean Time)格林威治时间 UTC (Coordinated Universal Time) 协调世界时 IAT (International Atomic Time),TAI 国际原子时 CST (Chinese Standard Time), 北京时间Gentoo&#xff08;也许其他发行版也是&…

【前端芝士树】Javascript的原型与原型链

【前端芝士树】Javascript的原型、原型链以及继承机制 前端的面试中经常会遇到这个问题&#xff0c;自己也是一直似懂非懂&#xff0c;趁这个机会整理一下0. 为什么会出现原型和原型链的概念 1994年&#xff0c;网景公司&#xff08;Netscape&#xff09;发布了Navigator浏览器…

C# 反射之Activator用法举例

概述程序运行时&#xff0c;通过反射可以得到其它程序集或者自己程序集代码的各种信息&#xff0c;包括类、函数、变量等来实例化它们&#xff0c;执行它们&#xff0c;操作它们&#xff0c;实际上就是获取程序在内存中的映像&#xff0c;然后基于这个映像进行各种操作。Activa…

MyBatis批量插入

转载于:https://blog.51cto.com/12701034/1929672

狐狸文│区块链发展的正路

&#xff08;图片出自网络&#xff0c;版权归原作者所有&#xff09;最近看了一本书&#xff1a;《美国增长的起落》。这本书是大部头&#xff0c;但看起来很过瘾。通过对这本书的阅读&#xff0c;我更新了自己对区块链发展的理解。这一年&#xff0c;“区块链”很热&#xff0…

Qt之水平/垂直布局(QBoxLayout、QHBoxLayout、QVBoxLayout)

简述 QBoxLayout可以在水平方向或垂直方向上排列控件&#xff0c;由QHBoxLayout、QVBoxLayout所继承。 QHBoxLayout&#xff1a;水平布局&#xff0c;在水平方向上排列控件&#xff0c;即&#xff1a;左右排列。 QVBoxLayout&#xff1a;垂直布局&#xff0c;在垂直方向上排列控…