当前位置: 首页 > news >正文

初识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对象是一样的操作,但是可以直接使用更新数组的方法了 

相关文章:

  • 性能分析-CPU知识
  • 内外网数据交换发展进程:安全与便捷并行
  • 甘特图/横道图制作技巧 - 任务组
  • 为什么苹果 Mac 电脑需要使用清理软件?
  • 系统架构评估_1.相关概念
  • 神经网络中的超参数调整
  • 【Java】maven常用命令
  • 如何备考2025年AMC8竞赛?吃透2000-2024年600道真题(免费送题)
  • xilinx 7系列fpga上电配置
  • Svg Flow Editor 原生svg流程图编辑器(五)
  • 免费SSL通配符证书/SSL泛域名证书获取教程
  • 爬虫入狱笔记——xx政府网站公开政策数据
  • [挖坟]如何安装Shizuku和LSPatch并安装模块(不需要Root,非Magisk)
  • Samba实现windows和Linux共享文件,环境搭建
  • python爬虫学习第十六天--------URLError和HTTPError、cookie登录、Handler处理器
  • JS中 map, filter, some, every, forEach, for in, for of 用法总结
  • (十五)java多线程之并发集合ArrayBlockingQueue
  • 【面试系列】之二:关于js原型
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • 2017年终总结、随想
  • DOM的那些事
  • ECMAScript入门(七)--Module语法
  • JSONP原理
  • Python进阶细节
  • Python语法速览与机器学习开发环境搭建
  • spring boot下thymeleaf全局静态变量配置
  • 阿里云Kubernetes容器服务上体验Knative
  • 构建工具 - 收藏集 - 掘金
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 携程小程序初体验
  • 一个项目push到多个远程Git仓库
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • (四)Controller接口控制器详解(三)
  • (转)setTimeout 和 setInterval 的区别
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • (转载)PyTorch代码规范最佳实践和样式指南
  • ..回顾17,展望18
  • .NET Core 成都线下面基会拉开序幕
  • .net core 调用c dll_用C++生成一个简单的DLL文件VS2008
  • .NET/C# 避免调试器不小心提前计算本应延迟计算的值
  • .NET/C# 编译期能确定的字符串会在字符串暂存池中不会被 GC 垃圾回收掉
  • .net遍历html中全部的中文,ASP.NET中遍历页面的所有button控件
  • @Bean有哪些属性
  • [ 代码审计篇 ] 代码审计案例详解(一) SQL注入代码审计案例
  • [16/N]论得趣
  • [CISCN2019 华东南赛区]Web4
  • [EFI]MSI GF63 Thin 9SCXR电脑 Hackintosh 黑苹果efi引导文件
  • [Fri 26 Jun 2015 ~ Thu 2 Jul 2015] Deep Learning in arxiv
  • [HarekazeCTF2019]encode_and_encode 不会编程的崽
  • [IDF]啥?
  • [iOS]如何删除工程里面用cocoapods导入的第三方库
  • [Java安全入门]三.CC1链
  • [leetcode] 103. 二叉树的锯齿形层次遍历
  • [LeetCode] Contains Duplicate
  • [Linux] 一文理解HTTPS协议:什么是HTTPS协议、HTTPS协议如何加密数据、什么是CA证书(数字证书)...