【React】React学习:从初级到高级(二)

这篇具有很好参考价值的文章主要介绍了【React】React学习:从初级到高级(二)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

2 添加交互

在 React 中,随时间变化的数据被称为状态(state)。

2.1 响应事件

事件处理程序是开发者自己写的的函数,它将在用户交互时被触发,如点击、悬停、焦点在表单输入框上等等。

<button> 等内置组件只支持内置浏览器事件,如 onClick。但是,开发者也可以创建自己的组件,并给它们的事件处理程序 props 指定名称。

2.1.1 添加事件处理函数

如果需要添加一个事件处理函数,需要先定义一个函数,然后将其作为prop传入合适的JSX标签.

事件处理函数有如下特点:

  1. 通常在组件内部定义
  2. 名称以handle开头,后跟事件名称

当函数体较短时,内联事件处理函数会很方便。比如:

<button onClick={function handleClick() {
  alert('你点击了我!');
}}>

// 箭头函数 
<button onClick={() => {
  alert('你点击了我!');
}}>

传递给事件处理函数的函数应直接传递<button onClick={handleClick}>,而非调用<button onClick={handleClick()}>。加上()后函数会立即执行,而不是点击按钮时才执行。传递内联函数时,应该将内联事件处理函数包装在匿名函数中。

2.1.2 在事件处理函数中读取props

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

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

将组件从父组件接收的 prop 作为事件处理函数传递。

function Button({ onClick, children }) {
  return (
    <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>
  );
}

2.1.4 命名事件处理函数prop

  • 按照惯例,事件处理函数 props 应该以 on 开头,后跟一个大写字母。
  • 确保为事件处理程序使用适当的 HTML 标签。

2.1.5 事件传播

如果子组件定义了一个函数,那么在子组件函数被触发后,会向上冒泡到父级组件层级。

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

2.1.6 阻止传播

事件处理函数接收一个 事件对象 作为唯一的参数。按照惯例,它通常被称为 e ,代表 “event”(事件)。这个事件对象还允许阻止传播。如果想阻止一个事件到达父组件,需要调用 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 onClick={() => alert('正在上传!')}>
        上传图片
      </Button>
    </div>
  );
}

当点击按钮时:

  1. React 调用了传递给 <button>onClick 处理函数。
  2. 定义在Button中的处理函数执行了如下操作:
    • 调用 e.stopPropagation(),阻止事件进一步冒泡。
    • 调用 onClick 函数,它是从 Toolbar 组件传递过来的 prop。
  3. Toolbar 组件中定义的函数,显示按钮对应的 alert。
  4. 由于传播被阻止,父级 <div>onClick 处理函数不会执行。

若想对每次点击进行埋点记录,可以通过在事件名称末尾添加 Capture 来实现。

<div onClickCapture={() => { /* 这会首先执行 */ }}>
  <button onClick={e => e.stopPropagation()} />
  <button onClick={e => e.stopPropagation()} />
</div>

每个事件分三个阶段传播:

  1. 它向下传播,调用所有的 onClickCapture 处理函数。
  2. 它执行被点击元素的 onClick 处理函数。
  3. 它向上传播,调用所有的 onClick 处理函数。

捕获事件对于路由或数据分析之类的代码很有用。

2.1.7 传递处理函数作为事件传播的替代方案

此处的点击事件处理函数先执行了一行代码,然后调用了父组件传递的 onClick prop:

function Button({ onClick, children }) {
  return (
    <button onClick={e => {
      e.stopPropagation();
      onClick();
    }}>
      {children}
    </button>
  );
}

也可以在调用父元素 onClick 函数之前,向这个处理函数添加更多代码。

2.1.8 阻止默认行为

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

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

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

export default function SignUp() {
    return (
        <form onSubmit={e => {
                e.preventDefault();
                alert('提交表单!');
            }}>
            <input />
            <button>发送</button>
        </form>
    );
}
  • e.stopPropagation() 阻止触发绑定在外层标签上的事件处理函数。
  • e.preventDefault() 阻止少数事件的默认浏览器行为。

2.2 State: 组件的记忆

要使用新数据更新组件,需要做两件事:

  1. 保留 渲染之间的数据。
  2. 触发 React 使用新数据渲染组件(重新渲染)。

useState Hook 提供了这两个功能:

  1. State 变量 用于保存渲染间的数据。
  2. State setter 函数 更新变量并触发 React 再次渲染组件。

2.2.1 添加一个state变量

import { useState } from 'react'

const [index, setIndex] = useState[0];

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

2.2.2 Hook函数

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

Hooks ——以 use 开头的函数——只能在组件或自定义 Hook 的最顶层调用。 你不能在条件语句、循环语句或其他嵌套函数内调用 Hook。Hook 是函数,但将它们视为关于组件需求的无条件声明会很有帮助。在组件顶部 “use” React 特性,类似于在文件顶部“导入”模块。

2.2.3 剖析useState

注意:惯例是将这对返回值命名为 const [thing, setThing]

useState 的唯一参数是 state 变量的初始值

每次你的组件渲染时,useState 都会给你一个包含两个值的数组:

  1. state 变量 (index) 会保存上次渲染的值。
  2. state setter 函数 (setIndex) 可以更新 state 变量并触发 React 重新渲染组件。

2.2.3 赋予一个组件多个state变量

可以在一个组件中拥有任意多种类型的 state 变量。

useState的实现依靠的是数组:

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

2.2.4 State是隔离且私有的

如果你渲染同一个组件两次,每个副本都会有完全隔离的 state!改变其中一个不会影响另一个。

props 不同,state 完全私有于声明它的组件

State 变量仅用于在组件重渲染时保存信息。在单个事件处理函数中,普通变量就足够了。当普通变量运行良好时,不要引入 state 变量。比如:

export default function FeedbackForm() {
  function handleClick() {
    const name = prompt('What is your name?');
    alert(`Hello, ${name}!`);
  }

  return (
    <button onClick={handleClick}>
      Greet
    </button>
  );
}

2.3 渲染和提交

React请求和提供UI的过程总共包括三个步骤:

  1. 触发渲染
    • 组件的 初次渲染。
    • 组件(或者其祖先之一)的 状态发生了改变。
  2. 渲染组件
    • 在进行初次渲染时, React 会调用根组件root
    • 对于后续的渲染, React 会调用那些使内部状态更新从而触发渲染的函数组件。
  3. 提交到DOM
    • 对于初次渲染, React 会使用 appendChild() DOM API 将其创建的所有 DOM 节点放在屏幕上。
    • 对于重复渲染, React 将只执行必要渲染操作,以使得 DOM节点 与最新的渲染输出结果匹配一致。

2.4 state在渲染时不会发生更改

一个 state 变量的值永远不会在一次渲染的内部发生变化, 即使其事件处理函数的代码是异步的。

2.5 把一系列state更新加入队列

2.5.1 React会对state更新进行批处理

React 会等到事件处理函数中的 所有 代码都运行完毕再处理你的 state 更新。

比如

setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);

组件的重新渲染只会发生在这三次setNumber()调用之后。

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

若多次更新同一个stateReact会将每一次state的更新状态存入队列,并把最后的结果更新到state中。这称为批处理。

以下是可以考虑传递给 setNumber state 设置函数的内容:

  • 一个更新函数(例如:n => n + 1)会被添加到队列中。
  • 任何其他的值(例如:数字 5)会导致“替换为 5”被添加到队列中,已经在队列中的内容会被忽略。

2.5.3 state更新函数的命名惯例

通常可以通过相应 state 变量的第一个字母来命名更新函数的参数,也可以用更明晰的命名:

setEnabled(e => !e);
setLastName(ln => ln.reverse());
setFriendCount(fc => fc * 2);

2.6 更新state中的对象

应该 把所有存放在 state 中的 JavaScript 对象都视为只读的。在改变state时,不能改变state中现有的对象,要重新创建一个对象把原来的对象替换掉。比如下面两种写法是正确且等价的:

// 第一种
const nextPosition = {};
nextPosition.x = e.clientX;
nextPosition.y = e.clientY;
setPosition(nextPosition);

// 第二种
setPosition({
  x: e.clientX,
  y: e.clientY
});

2.6.1 使用展开语法复制对象

通常,你会希望把 现有 数据作为你所创建的新对象的一部分。例如,你可能只想要更新表单中的一个字段,其他的字段仍然使用之前的值。那么此时就可以用展开语法...

import { useState } from 'react';

export default function Form() {
  const [person, setPerson] = useState({
    firstName: 'Barbara',
    lastName: 'Hepworth',
    email: 'bhepworth@sculpture.com'
  });

  function handleFirstNameChange(e) {
    setPerson({
      ...person,
      firstName: e.target.value
    });
  }

  function handleLastNameChange(e) {
    setPerson({
      ...person,
      lastName: e.target.value
    });
  }

  function handleEmailChange(e) {
    setPerson({
      ...person,
      email: e.target.value
    });
  }

  return (
    <>
      <label>
        First name:
        <input
          value={person.firstName}
          onChange={handleFirstNameChange}
        />
      </label>
      <label>
        Last name:
        <input
          value={person.lastName}
          onChange={handleLastNameChange}
        />
      </label>
      <label>
        Email:
        <input
          value={person.email}
          onChange={handleEmailChange}
        />
      </label>
      <p>
        {person.firstName}{' '}
        {person.lastName}{' '}
        ({person.email})
      </p>
    </>
  );
}

请注意 ... 展开语法本质是是“浅拷贝”——它只会复制一层。这使得它的执行速度很快,但是也意味着当你想要更新一个嵌套属性时,你必须得多次使用展开语法。

2.6.2 使用一个事件处理函数来更新多个字段

import { useState } from 'react';

export default function Form() {
  const [person, setPerson] = useState({
    firstName: 'Barbara',
    lastName: 'Hepworth',
    email: 'bhepworth@sculpture.com'
  });

  function handleChange(e) {
    setPerson({
      ...person,
      [e.target.name]: e.target.value  // 重点是这里,使用 DOM 元素的 name属性
    });
  }

  return (
    <>
      <label>
        First name:
        <input
          name="firstName"
          value={person.firstName}
          onChange={handleChange}
        />
      </label>
      <label>
        Last name:
        <input
          name="lastName"
          value={person.lastName}
          onChange={handleChange}
        />
      </label>
      <label>
        Email:
        <input
          name="email"
          value={person.email}
          onChange={handleChange}
        />
      </label>
      <p>
        {person.firstName}{' '}
        {person.lastName}{' '}
        ({person.email})
      </p>
    </>
  );
}

在这里,e.target.name 引用了 <input> 这个 DOM 元素的 name 属性。

2.6.3 更新一个嵌套的对象

如果对象拥有多层嵌套,那么可以创建新的对象:

const nextArtwork = { ...person.artwork, city: 'New Delhi' };
const nextPerson = { ...person, artwork: nextArtwork };
setPerson(nextPerson);

或者写成一个函数调用:

setPerson({
  ...person, // 复制其它字段的数据 
  artwork: { // 替换 artwork 字段 
    ...person.artwork, // 复制之前 person.artwork 中的数据
    city: e.target.value // 但是将 city 的值替换为 New Delhi!
  }
});

对象并非真正嵌套,只是属性"指向"彼此而已。

2.6.4 使用Immer编写更简洁的更新逻辑

Immer 提供的 draft 是一种特殊类型的对象,被称为 Proxy,它会记录你用它所进行的操作。从原理上说,Immer 会弄清楚 draft 对象的哪些部分被改变了,并会依照你的修改创建出一个全新的对象。

使用Immer:

  1. 运行 npm install use-immer 添加 Immer 依赖
  2. import { useImmer } from 'use-immer' 替换掉 import { useState } from 'react'
import { useImmer } from 'use-immer';

export default function Form() {
  const [person, updatePerson] = useImmer({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    }
  });

  function handleNameChange(e) {
    updatePerson(draft => {
      draft.name = e.target.value;
    });
  }

  function handleTitleChange(e) {
    updatePerson(draft => {
      draft.artwork.title = e.target.value;
    });
  }

  function handleCityChange(e) {
    updatePerson(draft => {
      draft.artwork.city = e.target.value;
    });
  }

  function handleImageChange(e) {
    updatePerson(draft => {
      draft.artwork.image = e.target.value;
    });
  }

  return (
    <>
      <label>
        Name:
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Title:
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        City:
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        Image:
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' by '}
        {person.name}
        <br />
        (located in {person.artwork.city})
      </p>
      <img 
        src={person.artwork.image} 
        alt={person.artwork.title}
      />
    </>
  );
}

为什么在 React 中不推荐直接修改 state?

  1. 调试时使用console.log()可以很容易发现前后两次渲染发生了什么变化.
  2. React常见的优化策略依赖于如果之前的 props 或者 state 的值和下一次相同就跳过渲染。
  3. 如果用户需求变更,可以很容易恢复到以前的版本。

2.7 更新State中的数组

同对象一样,当想要更新存储于 state 中的数组时,需要创建一个新的数组(或者创建一份已有数组的拷贝值),并使用新数组设置 state

当操作 React state 中的数组时,需要避免使用左列能改变原数组的方法,而首选右列能返回一个新数组的方法:

避免使用(会改变原始数组) 推荐使用(返回一个新数组)
添加元素 push/unshift concat/[...arr]展开语法
删除元素 pop/shift/splice filter/slice
替换元素 splice/arr[i]=...赋值 map
排序 reverse/sort 先将数组复制一份、toSorted

或者使用Immer

2.7.1 更新数组内部的对象

即使拷贝了数组,还是不能直接修改其内部的元素。这是因为数组的拷贝是浅拷贝——新的数组中依然保留了与原始数组相同的元素。

比如这样就不行:

const nextList = [...list];
nextList[0].seen = true; // 问题:直接修改了 list[0] 的值
setList(nextList);

正确的做法是再次拷贝一份,然后进行修改,我们可以使用map函数:

setMyList(myList.map(artwork => {
  if (artwork.id === artworkId) {
    // 创建包含变更的*新*对象
    return { ...artwork, seen: nextSeen };
  } else {
    // 没有变更
    return artwork;
  }
}));

或者使用更简洁的immer

updateMyTodos(draft => {
  const artwork = draft.find(a => a.id === artworkId);
  artwork.seen = nextSeen;
});

最简单的一种就是使用 ... 数组展开 语法:

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

数组展开运算符还允许你把新添加的元素放在原始的 ...artists 之前:

setArtists([
  { id: nextId++, name: name },
  ...artists // 将原数组中的元素放在末尾
]);

这样一来,展开操作就可以完成 push()unshift() 的工作,将新元素添加到数组的末尾和开头.

从数组中删除一个元素最简单的方法就是将它过滤出去。可以通过 filter 方法实现:

// 使用filter方法删除元素
setArtists(
    artists.filter(a =>a.id !== artist.id)
);

如果想改变数组中的某些或全部元素,可以先用 map() 创建一个数组。再使用新的数组进行重新渲染。

import { useState } from 'react';

let initialShapes = [
  { id: 0, type: 'circle', x: 50, y: 100 },
  { id: 1, type: 'square', x: 150, y: 100 },
  { id: 2, type: 'circle', x: 250, y: 100 },
];

export default function ShapeEditor() {
  const [shapes, setShapes] = useState(
    initialShapes
  );

  function handleClick() {
    const nextShapes = shapes.map(shape => {
      if (shape.type === 'square') {
        // 不作改变
        return shape;
      } else {
        // 返回一个新的圆形,位置在下方 50px 处
        return {
          ...shape,
          y: shape.y + 50,
        };
      }
    });
    // 使用新的数组进行重渲染
    setShapes(nextShapes);
  }

  return (
    <>
      <button onClick={handleClick}>
        所有圆形向下移动!
      </button>
      {shapes.map(shape => (
        <div
          key={shape.id}
          style={{
          background: 'purple',
          position: 'absolute',
          left: shape.x,
          top: shape.y,
          borderRadius:
            shape.type === 'circle'
              ? '50%' : '',
          width: 20,
          height: 20,
        }} />
      ))}
    </>
  );
}

插入元素

向数组特定位置插入一个元素。可以将数组展开运算符 ...slice() 方法一起使用。

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

其他改变数组中元素的情况,可以先拷贝这个数组,再改变这个拷贝后的值。文章来源地址https://www.toymoban.com/news/detail-692958.html

到了这里,关于【React】React学习:从初级到高级(二)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据分析师初级—中级—高级,每个阶段都需要学习什么?

    先你需要看下这张图,这是一张数据分析师能力体系图: 通过图片,我们可以比较清晰的看到这三个阶段的数据分析师在各方面能力的差别了,那下面我们就来具体侃侃他们的区别。 初级水平 什么是初学者?如果解析学和数据科学对你来说是全新的领域,你也不知该行业的

    2024年02月10日
    浏览(51)
  • 前端学习C语言 - 初级指针

    本篇主要介绍: 指针和变量的关系 、指针类型、 指针的运算符 、空指针和野指针、 指针和数组 、 指针和字符串 、const 和指针、以及 gdb 调试段错误 。 指针是一种特殊的变量。存放地址的变量就是指针。 int num = 1; 会申请4个字节的内存来存放数字1,每次访问 num 就是访问

    2024年02月10日
    浏览(43)
  • 前端框架学习-React(一)

    React 应用程序是由 组件 组成的。 react 程序是用的jsx语法,使用这种语法的代码需要由babel进行解析,解析成js代码。 jsx语法: 只能返回一个根元素 所有的标签都必须闭合(自闭和或使用一对标签的方式闭合) 使用驼峰式命名法给大部分属性命名如:className 大写字母开头的

    2024年02月12日
    浏览(42)
  • 【学习前端第七十四课】React生命周期

    我们这里所说的生命周期是基于类式组件的,其中主要分为三个阶段 挂载 更新 卸载 以上每一个阶段内都有自己的一系列在当前生命周期中可被自动执行的生命周期函数(类似于vue的钩子函数) 挂载阶段(初始化) 在挂载阶段按照执行顺序分别有以下几个生命周期函数 co

    2024年02月21日
    浏览(45)
  • 前端TypeScript学习day03-TS高级类型

    (创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,请留下您的足迹)            目录 TypeScript 高级类型 class 类 class继承  extends implements  类成员可见性  public  protected  private   readonly 兼容性 类型兼容性 接口兼容性  函数兼容性  TypeScri

    2024年02月08日
    浏览(37)
  • 丁鹿学堂:前端学习进阶指南之react入门(react在html中使用数据绑定和修改)

    在html中使用react 今天跟大家分享react的基础语法。 我们采用最简单的方法,就是在html中引入react 因为一上来就使用脚手架的话,很多配置大家不一定清楚。 而在html中使用react的话,可以直接去学习react最基本的语法。 这是零基础学习react的最佳实践。 引入react的依赖 react也

    2024年02月14日
    浏览(63)
  • 逍遥自在学C语言 | 函数初级到高级解析

    函数是C语言中的基本构建块之一,它允许我们将代码组织成可重用、模块化的单元。 本文将逐步介绍C语言函数的基础概念、参数传递、返回值、递归以及内联函数和匿名函数。 第一位闪亮登场,有请今后会一直教我们C语言的老师 —— 自在。 第二位上场的是和我们一起学

    2024年02月11日
    浏览(38)
  • 逍遥自在学C语言 | 函数初级到高级解析

    函数是C语言中的基本构建块之一,它允许我们将代码组织成可重用、模块化的单元。 本文将逐步介绍C语言函数的基础概念、参数传递、返回值、递归以及内联函数和匿名函数。 第一位闪亮登场,有请今后会一直教我们C语言的老师 —— 自在。 第二位上场的是和我们一起学

    2024年02月12日
    浏览(43)
  • 2023年软件测试工程师,初级到高级进阶路线指南,测试之路...

    提到软件测试工程师时,很多人依然会联想到那些“点点点”并企图在“点点点”中找到缺陷的人,也就是大家常说的依照测试规范和测试案例来对软件进行测试,检查软件是不是有缺陷,判断软件是不是稳定。但这其实是一个很不好的观点。 近年来,随着各大互联网企业的

    2024年02月09日
    浏览(63)
  • 13个终极大数据项目理念-初级or 高级总有一款适合你

    “大数据”一词对我们来说并不陌生,尤其近些年来随着数字经济的高速发展,大数据已经与我们完美地结合在一起,充斥于生活的点点滴滴:刷脸结算、实时监控、智能快递、精准营销、DNA 序列解码、运动健康管理……大数据是一个令人兴奋的话题,它能帮我们探究出意想

    2024年02月16日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包