初识React(二)响应事件、state、useState

(一)响应事件

使用 React 可以在 JSX 中添加 事件处理函数。其中事件处理函数为自定义函数,它将在响应交互(如点击、悬停、表单输入框获得焦点等)时触发

1.给组件添加事件处理函数

就像vue的@click="handleClick"一样 

但这里要特别注意的是,传入的是一个函数,而不是函数的调用

// 正确写法
<button onClick={handleClick}>点我</button>
// 错误写法 会导致渲染时直接调用函数
<button onClick={handleClick()}>点我</button>// 正确写法
<button onClick={()=>{alert('!')}}>点我</button>
// 错误写法
<button onClick={alert('!')}>点我</button>

2.将事件处理函数作为props传递

父组件的自定义函数可以通过prop传递给子组件,就像vue的@handleClick="handleClick"一样 

function Button({ handleClick, children}) {return (<button onClick={handleClick}>{children}</button>);
}export default function App() {return (<div><Button handleClick={() => alert('点击!')}>点击按钮</Button></div>);
}

3.事件传播

和原生js一样,事件也会冒泡

当父组件和子组件都有点击事件时,点击子组件会先触发子组件事件,再触发父组件事件

阻止原生默认行为 

 当不想触发冒泡传播时,可以通过 e.stopPropagation() 阻止事件冒泡

function Button({ onClick, children }) {return (<button onClick={e => {e.stopPropagation();onClick();}}>{children}</button>);
}export default function Toolbar() {return (<div className="Toolbar" onClick={() => {alert('你点击了 toolbar !');}}><Button onClick={() => alert('你点击了button!')}>上传图片</Button></div>);
}

当点击表单提交按钮,不想触发浏览器自带的提交并刷新事件,可以通过 e.preventDefault() 来阻止

export default function Signup() {return (<form onSubmit={e => {e.preventDefault();alert('提交表单!');}}><input /><button>发送</button></form>);
}

(二)State

1.介绍 

组件通常需要根据交互更改屏幕上显示的内容。输入表单应该更新输入字段,单击轮播图上的“下一个”应该更改显示的图片,单击“购买”应该将商品放入购物车。组件需要“记住”某些东西:当前输入值、当前图片、购物车。在 React 中,这种组件特有的记忆被称为 state

与vue的响应式原理不同,vue通过proxy劫持数据的getter和setter实现响应式,如vue3的reactive()

State的创建使用了 const [变量名,变量setter] = useState(初始值) 的形式定义响应式数据

State是隔离且私有的

渲染多个相同组件,其state值是独立的,不受其他组件的影响

2.渲染和提交

  • 应用启动时进行第一次渲染,届时所有组件和节点都会进行渲染;
  • 当数据改变后,react会重新渲染数据有所改变的组件,尽可能小的减少渲染量(diff算法) 
  • 提交阶段,将所有节点绘制到浏览器页面 

当使用state变量setter函数改变数据后,会引起组件的重新渲染

设置 state 只会为下一次渲染变更 state 的值 

如下代码,点击按钮后只会引起一次组件的渲染,number的值是1而不是3 

const [number, setNumber] = useState(0);return (<><h1>{number}</h1><button onClick={() => {setNumber(number + 1);setNumber(number + 1);setNumber(number + 1);}}>+3</button></>)

因为渲染时的操作是进行了三次的 setNumber( 0 +1) ,即渲染后number=1!!!

使用异步函数,例如设置一个定时器,在其内部输出number的话,number该是多少呢?

const [number, setNumber] = useState(0);return (<><h1>{number}</h1><button onClick={() => {setNumber(number + 1);setTimeout(()=>{alert(number);},3000);}}>+3</button></>)

在3秒后,弹出的number值还是为0,这就意味着,state像是拍下的一张照片,在重新拍照片之前,state都不会发生改变

在下次渲染前多次更新同一个 state

那如何实现在统一渲染前就更改number的值呢?

const [number, setNumber] = useState(0);return (<><h1>{number}</h1><button onClick={() => {setNumber(n => n + 1);setNumber(n => n + 1);setNumber(n => n + 1);}}>+3</button></>)

使用箭头表达式(叫作更新函数)就可以啦,但是这个操作并不常用

n只是一个形参,用来指代number,取别的名字也是ok的

3.更新State的对象

应当将state视为只读的,不能直接更改state本身,而是将state复制出来,对复制体进行更改后再替换state进行重新渲染

直接改变本身(如position.x=100)后,并不会引起组件的重新渲染

(1)用展开语法复制对象

setPerson({firstName: e.target.value, // 从 input 中获取新的 first namelastName: person.lastName,email: person.email
});
// 需要更改的数据不多时 使用展开语法
setPerson({...person, // 复制上一个 person 中的所有字段firstName: e.target.value // 但是覆盖 firstName 字段 
});

展开语法(...)本质是浅拷贝,只会复制浅层数据,当对象嵌套过多时就不太适用了

(2)更新一个嵌套对象 

const [person, setPerson] = useState({name: 'Niki de Saint Phalle',artwork: {title: 'Blue Nana',city: 'Hamburg',image: 'https://i.imgur.com/Sd1AgUOm.jpg',}
});// 使用展开语法
setPerson({...person, // 复制其它字段的数据 artwork: { // 替换 artwork 字段 ...person.artwork, // 复制之前 person.artwork 中的数据city: 'New Delhi' // 但是将 city 的值替换为 New Delhi!}
});
使用Immer库实现扁平化 

Immer可以让你使用简便但可以直接修改的语法编写代码,并会帮你处理好复制的过程。通过使用 Immer,你写出的代码看起来就像是你“打破了规则”而直接修改了对象 

下载immer库:npm install use-immer

使用Immer: 

import { useImmer } from 'use-immer';updatePerson(draft => {draft.artwork.city = 'Lagos';
});

Immer的作用就是使用proxy劫持person数据,将其包装为draft,当draft数据发生改变时Immer能感知到并作出对应的改变,和vue3的reactive函数原理相同

4.更新State的数组

在JS中,数组也是对象的一种,因此使用state时还是要保证只读

直接使用数组方法对数组进行修改就违背了只读规定了,如pop、shift、push等

(1)向数组添加元素

无需使用push、unshift方法,可以直接用展开运算符进行添加

setArtists( // 替换 state[ // 是通过传入一个新数组实现的...artists, // 新数组包含原数组的所有元素{ id: nextId++, name: name } // 并在末尾添加了一个新的元素]
);

(2)从数组中删除元素

无需使用shift、pop、slice等方法,可以通过filter将需要删除的数据过滤,生成的新数组即为目标数据

setArtists(artists.filter(a => a.id !== artist.id)
);

(3)转换数组、替换数组元素

使用map方法,根据需求修改数组元素后返回数组即可 

const nextCounters = counters.map((c, i) => {if (i === index) {// 递增被点击的计数器数值return c + 1;} else {// 其余部分不发生变化return c;}});
setCounters(nextCounters);

(4)向数组中插入元素

通过展开运算符...和slice方法

const insertAt = 1; // 可能是任何索引
const nextArtists = [// 插入点之前的元素:...artists.slice(0, insertAt),// 新的元素:{ id: nextId++, name: name },// 插入点之后的元素:...artists.slice(insertAt)];
setArtists(nextArtists);

(5)拷贝数组

需要进行修改数组元素数据的操作时,可以先拷贝一份,对复制品进行修改再更新state

// 展开运算符
const newPerson =  [...person]
// 使用slice方法
const newPerson = person.slice()

(6)使用Immer 

不多说了,跟state对象是一样的操作,但是可以直接使用更新数组的方法了 

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

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

相关文章

Redis单线程 VS 多线程

一、Redis 为什么选择单线程&#xff1f; 这种说法其实并不严谨&#xff0c;为什么这么说呢&#xff1f; Redis的版本有很多 3.x、4.x、6.x&#xff0c;版本不同架构也不同的&#xff0c;不限定版本问是否单线程也是不太严谨。 版本3.x&#xff0c;最早版本&#xff0c;也就…

SQLite 4.9的 OS 接口或“VFS”(十三)

返回&#xff1a;SQLite—系列文章目录 上一篇:SQLite字节码引擎&#xff08;十二&#xff09; 下一篇:SQLite 4.9的虚拟表机制(十四) 1. 引言 本文介绍了 SQLite OS 可移植性层或“VFS” - 模块位于 SQLite 实现堆栈底部 提供跨操作系统的可移植性。 VFS是Virtual File…

mac老版本如何升级到最新版本

mac老版本如何升级到最新版本 老macbook升级新版本&#xff08;Big sur、Monterey&#xff09; 首先介绍我的电脑的机型及情况&#xff1a; 2015年初的MacBook Air 处理器是1.6Hz 双核Interl Core i5 内存4G 老版本只能升到10.13 想要升到最高版本的原因&#xff1a;想要注册…

解决Xshell连接不上虚拟机

相信有很多同学和我一样遇到这个问题&#xff0c;在网上看了很多教程基本上都让先在虚拟机输入ifconfig命令查看ip地址&#xff0c;弄来弄去最后还是解决不了&#x1f62d;&#x1f62d;&#xff0c;很大概率是我们的虚拟机没有开启网卡&#xff0c;默认Centos是不启用网卡的&a…

麒麟系统ARM安装rabbitmq

简单记录下&#xff0c;信创服务器&#xff1a;麒麟系统&#xff0c;安装rabbitmq的踩坑记录。 本文章参考了很多大佬文章&#xff0c;我整理后提供。 一、安装基础依赖 yum -y install make gcc gcc-c kernel-devel m4 ncurses-devel openssl-devel unixODBC-devel 二、下载…

python gui 实现多个pdf文件合并成一个文件

这是gui截图&#xff0c;汉字都能看懂吧 上代码之前需要安装两个库 pip install PyPDF2 pip install PySimpleGUI然后直接运行代码 import os from PyPDF2 import PdfReader, PdfWriter import PySimpleGUI as sg import tkinter as tk from tkinter import filedialogdef Ge…

八数码问题(bfs)

方式一&#xff1a;string存储状态 题目传送门&#xff1a;845. 八数码 - AcWing题库 BFS适用于边权为1的最短路问题 &#xff0c;而这题要求最少的交换次数&#xff0c;将每一次的九宫格状态当作一个“状态结点”&#xff0c;由当前这个结点可以扩展出其它状态【即 x 可以与其…

基于R语言BIOMOD2模型的物种分布模拟

随着生物多样性全球大会的举办&#xff0c;不论是管理机构及科研单位、高校都在积极准备&#xff0c;根据国家林草局最新工作指示&#xff0c;我国将积极整合、优化自然保护地&#xff0c;加快推进国家公园体制试点&#xff0c;构建以国家公园为主体的自然保护地体系。针对我国…

layui在上传多图时,allDone方法只是在第一次全部成功时调用了

问题点&#xff1a;在使用layui框架做多张图片上传时&#xff0c;遇见只有第一次操作上传图片时&#xff0c;触发了allDone全部上传成功的方法&#xff0c;后面再添加图片时&#xff0c;就不会调用这个方法 原因&#xff1a;是因为我删除了 choose 方法&#xff0c;并且也没有将…

flutter升级3.10.6Xcode构建报错

flutter sdk 升级Xcode报错收集&#xff0c;错误信息如下&#xff1a; Error (Xcode): Cycle inside Runner; building could produce unreliable results.没问题版本信息&#xff1a; Xcode&#xff1a;15.3 flutter sdk &#xff1a;3.7.12 dart sdk&#xff1a;2.19.6 …

真实的招生办对话邮件及美国高校官网更新的反 AI 政策

这两年 ChatGPT 的热度水涨船高&#xff0c;其编写功能强大&#xff0c;且具备强大的信息整合效果&#xff0c;所以呈现的内容在一定程度上具备可读性。 那么&#xff0c;美国留学文书可以用 ChatGPT 写吗&#xff1f;使用是否有风险&#xff1f;外网博主 Kushi Uppu 在这个申…

自动化高并发抓取淘宝平台商品数据(内附接入key密钥API响应示例)

通过API接口&#xff08;接入key&#xff0c;密钥&#xff09;&#xff0c;可以获取商品的标题、价格、图片、描述等详细信息。 item_get 获得淘宝商品详情item_get_pro 获得淘宝商品详情高级版item_review 获得淘宝商品评论item_fee 获得淘宝商品快递费用item_password 获得…