重学React(二):添加交互

背景:第一部分更多的是React的基本渲染规则,面向UI的规则居多,当UI页面处理完毕后,接下来就是对数据以及交互的处理,接下来就继续吧~

学习内容:

React官网教程:https://zh-hans.react.dev/learn/adding-interactivity

其他辅助资料(看到再补充)

补充说明:这次学习更多的是以学习笔记的形式记录,看到哪记到哪

响应事件

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

js 复制代码
export default function Button() {
// 声明了一个函数,在这个函数里做的就是弹出一个弹窗,显示你点击了我
  function handleClick() {
    alert('你点击了我!');
  }
// 在button这个组件里,handleClick作为一个prop被传入
// button里触发onClick(点击)事件,就会调用这个函数,执行函数里面的操作
  return (
    <button onClick={handleClick}>
      点我
    </button>
  );
}
// 当然,函数也可以直接以内联的方式存在
 <button onClick={
 	function handleClick() {
  		alert('你点击了我!');
	}}>
      点我
 </button>
// 或者更加简单的箭头函数
 <button onClick={()=> alert('你点击了我!')}>
   点我
 </button>

按照惯例,通常将事件处理程序命名为 handle,后接事件名。所以会经常看到 onClick={handleClick},onMouseEnter={handleMouseEnter} 等,这是约定俗成的命名方式,不是必须的

传递给事件处理函数的函数应直接传递,而非调用

传递一个函数(正确) 调用一个函数(错误)
<button onClick={handleClick}> <button onClick={handleClick()}>
<button onClick={() => alert('...')}> <button onClick={alert('...')}>

这两者的区别在于,右边在传递函数时多了一个(),就使得这个函数变成了一个立即执行函数,渲染时就会触发,而不是点击才执行

由于事件处理函数声明于组件内部,可以直接访问组件的 props。

甚至我们可以把事件处理函数作为prop传到组件中去,

按照惯例,事件处理函数 props 应该以 on 开头,后跟一个大写字母,我们也可以自定义,但内置的组件(比如button,div这种html元素)仅支持浏览器事件名称

js 复制代码
// 可以直接读到message的值,不用再额外声明什么,这个例子里会根据传入的message不同展示不一样的结果
function AlertButton({ message, children }) {
  return (
    <button onClick={() => alert(message)}>
      {children}
    </button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <AlertButton message="正在播放!">
        播放电影
      </AlertButton>
      <AlertButton message="正在上传!">
        上传图片
      </AlertButton>
    </div>
  );
}

// 甚至我们可以把事件处理函数作为prop传到组件中去,
// 这个例子里PlayButton和UploadButton点击时就会触发不一样的函数,展示不一样的结果
// 因为它们共用了一个Button组件,所以两个按钮的样式是一样的,遇到需要样式一样的场景可以使用这种模式


function Button({ onClick, children }) {
  return (
  // 这里button小写,说明是html内置元素,必须用onClick,但是大括号内的props没有这个规定,可以叫onA,onB都可以,只要统一就行
    <button onClick={onClick}>
      {children}
    </button>
  );
}

function PlayButton({ movieName }) {
  function handlePlayClick() {
    alert(`正在播放 ${movieName}!`);
  }

  return (
    <Button onClick={handlePlayClick}>
      播放 "{movieName}"
    </Button>
  );
}

function UploadButton() {
  return (
    <Button onClick={() => alert('正在上传!')}>
      上传图片
    </Button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <PlayButton movieName="魔女宅急便" />
      <UploadButton />
    </div>
  );
}
事件传播

事件处理函数还将捕获任何来自子组件的事件。事件会沿着树向上"冒泡"或"传播":它从事件发生的地方开始,然后沿着树向上传播

在 React 中所有事件都会传播,除了 onScroll,它仅适用于你附加到的 JSX 标签

事件处理函数接收一个 事件对象 作为唯一的参数。它通常被称为 e ,代表 "event"(事件),可以使用此对象来读取有关事件的信息。

js 复制代码
// 在这个例子中,点击了播放电影这个按钮,不单触发了按钮的点击事件,还会触发div的点击事件,也就是事件传播到了div这里
// 想象一下,button也是div的一部分,你点了button,也就相当于点了div,
// 但如果只点击div,就不会触发button的点击
export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => {
      alert('你点击了 toolbar !');
    }}>
      <button onClick={() => alert('正在播放!')}>
        播放电影
      </button>
      <button onClick={() => alert('正在上传!')}>
        上传图片
      </button>
    </div>
  );
}
// 如果想要阻止这个冒泡,就需要event事件的帮忙
// 此时可以在触发的事件中添加 e.stopPropagation()
export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => {
      alert('你点击了 toolbar !');
    }}>
      <button onClick={(e) => {
      // 添加这一句等于告诉浏览器我只需要触发这一个函数,别帮我往上传话
      e.stopPropagation()
      alert('正在播放!')
      }}>
        播放电影
      </button>
      <button onClick={() => alert('正在上传!')}>
        上传图片
      </button>
    </div>
  );
}

// 如果想在子组件里执行一些操作,同时又触发父组件的一些行为,可以在子组件的函数中添加来自父组件的props
// 这是事件传播的另一种替代方案,在这段代码里又能执行子组件事件也能执行父组件事件
function Button({ onClick, children }) {
  return (
    <button onClick={e => {
      e.stopPropagation();
      onClick();
    }}>
      {children}
    </button>
  );
}

//极少数情况下,需要捕获子元素上的所有事件,即便它们阻止了传播。
// 例如,对每次点击进行埋点记录,那可以通过在事件名称末尾添加 Capture 来实现这一点
<div onClickCapture={() => { /* 这会首先执行 */ }}>
  <button onClick={e => e.stopPropagation()} />
  <button onClick={e => e.stopPropagation()} />
</div>
// 每个事件分三个阶段传播:
// 它向下传播,调用所有的 onClickCapture 处理函数。
// 它执行被点击元素的 onClick 处理函数。
// 它向上传播,调用所有的 onClick 处理函数。
阻止默认行为

某些浏览器事件具有与事件相关联的默认行为。例如,点击 表单内部的按钮会触发表单提交事件,默认情况下将重新加载整个页面

可以调用事件对象中的 e.preventDefault() 来阻止这种情况发生

js 复制代码
export default function Signup() {
  return (
    <form onSubmit={e => {
    // 不加这句的话,触发完表单事件,会重新加载页面
    // 加上后只出现弹窗,没有任何后续的行为
      e.preventDefault();
      alert('提交表单!');
    }}>>
      <input />
      <button>发送</button>
    </form>
  );
}

再次强调一下

  • e.stopPropagation() 阻止触发绑定在外层标签上的事件处理函数。
  • e.preventDefault() 阻止少数事件的默认浏览器行为。

State: 组件的记忆

组件通常需要根据交互更改屏幕上显示的内容,通常需要"记住"当前的一些东西,比如当前展示什么图片,当前翻页在哪一页,在 React 中,这种组件特有的记忆被称为 state

js 复制代码
// 在理想情况下,每次进行点击,index都会加1,页面上的数字也会随着增大
// 但现实却是,页面永远展示1
// 原因如下:
// index是作为局部变量存在的
// 局部变量无法在多次渲染中持久保存。 当 React 再次渲染这个组件时,它会从头开始渲染------不会考虑之前对局部变量的任何更改。
// 更改局部变量不会触发渲染。 React 没有意识到它需要使用新数据再次渲染组件。
export default function Gallery() {
  let index = 0;

  function handleClick() {
    index = index + 1;
  }

  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h3>  
        {index + 1}
      </h3>
     
    </>
  );
}

要修改这个问题,需要保存渲染间的数据,还需要触发 React 使用新数据渲染组件(重新渲染的时候展示新数据),这就引出最基础我们最常用的一个hook------useState

在 React 中,useState 以及任何其他以"use"开头的函数都被称为 Hook

Hook 是特殊的函数,只在 React 渲染时有效。它们能让你 "hook" 到不同的 React 特性中去

Hooks ------以 use 开头的函数------只能在组件或自定义 Hook 的最顶层调用。 你不能在条件语句、循环语句或其他嵌套函数内调用 Hook。

Hook 是函数,但将它们视为关于组件需求的无条件声明会很有帮助。在组件顶部 "use" React 特性,类似于在文件顶部"导入"模块

(详细的解析在后面,这里只需要有那么一个概念就好)

useState Hook 提供了这两个功能:

  1. State 变量 用于保存渲染间的数据。
  2. State setter 函数 更新变量并触发 React 再次渲染组件。
js 复制代码
// 添加 state 变量,先从文件顶部的 React 中导入 useState
import { useState } from 'react';

export default function Gallery() {
// index 是一个 state 变量,setIndex 是对应的 setter 函数
// useState 的唯一参数是 state 变量的初始值
// 这里代表的是,index的初始化为0,每次修改index都需要调用setIndex才触发
// [ 和 ] 语法称为数组解构,它允许你从数组中读取值。 useState 返回的数组总是正好有两项
// 惯例是将这对返回值命名为 const [thing, setThing],这样容易理解,但也支持自定义
  const [index, setIndex] = useState(0);

  function handleClick() {
    setIndex(index + 1);
  }
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h3>  
        {index + 1}
      </h3>
    </>
  );
}
// 多次点击时发生的state变化如下:
// 组件进行第一次渲染。 因为0作为 index 的初始值传递给 useState,它将返回 [0, setIndex]。 React 记住 0 是最新的 state 值。
// 当用户点击按钮时,调用 setIndex(index + 1)。 index 是 0,所以它是 setIndex(1)。这告诉 React 现在记住 index 是 1 并触发下一次渲染。
// 组件进行第二次渲染。React 仍然看到 useState(0),但是因为 React 记住 了你将 index 设置为了 1,它将返回 [1, setIndex]。这个时候1就是最新的state值

我们可以在一个组件中拥有任意多种类型的 state 变量,但state变量越多,意味着越难管理,因此可以在一定程度上进行变量合并,比如可能同时会有多个变量一起改变,可以改用一个state统一管理。

为了使语法更简洁,在同一组件的每次渲染中,Hooks 都依托于一个稳定的调用顺序。这在实践中很有效,因为如果你遵循只在顶层调用 Hooks的原则,Hooks 将始终以相同的顺序被调用

在 React 内部,为每个组件保存了一个数组,其中每一项都是一个 state 对。它维护当前 state 对的索引值,在渲染之前将其设置为 "0"。每次调用 useState 时,React 都会为你提供一个 state 对并增加索引值。

State 是屏幕上组件实例内部的状态。换句话说,如果你渲染同一个组件两次,每个副本都会有完全隔离的 state!改变其中一个不会影响另一个。

State 不依赖于特定的函数调用或在代码中的位置,它的作用域"只限于"屏幕上的某块特定区域

state 完全私有于声明它的组件。父组件无法更改它。这使你可以向任何组件添加或删除 state,而不会影响其他组件。

js 复制代码
import Gallery from './Gallery.js';

// 比如之前的Gallery组件,同时渲染两个的话,点击第一个的按钮,不会改变第二个钻的index
// 这个Page组件,完全不会知道Gallery组件有什么state,也没办法去干预它
// 如果想实现两个Gallery的index同步改变,可以把state声明放到Page组件中,以props的形式保存
export default function Page() {
  return (
    <div className="Page">
      <Gallery />
      <Gallery />
    </div>
  );
}
相关推荐
浪裡遊1 小时前
Sass详解:功能特性、常用方法与最佳实践
开发语言·前端·javascript·css·vue.js·rust·sass
哈市雪花1 小时前
相机:Camera原理讲解(使用OpenGL+QT开发三维CAD)
qt·3d·交互·相机·图形学·opengl·视角
夏梦春蝉2 小时前
ES6从入门到精通:常用知识点
前端·javascript·es6
马特说2 小时前
React金融数据分析应用性能优化实战:借助AI辅助解决18万数据量栈溢出Bug
react.js·金融·数据分析
归于尽2 小时前
useEffect玩转React Hooks生命周期
前端·react.js
G等你下课2 小时前
React useEffect 详解与运用
前端·react.js
我想说一句2 小时前
当饼干遇上代码:一场HTTP与Cookie的奇幻漂流 🍪🌊
前端·javascript
南屿im2 小时前
基于 Promise 封装 Ajax 请求:从 XMLHttpRequest 到现代化异步处理
前端·javascript
杨进军2 小时前
前端线上问题的那些事儿
前端·javascript·前端框架
每天开心2 小时前
深入探索 React Hooks: useState 与 useEffect 的力量 🌟
前端·javascript·ai编程