学习视频

12. Props, Immutability, and One-Way Data Flow_哔哩哔哩_bilibili

React笔记


前置知识

这里写了常用到的 js知识

//对象解构赋值(Object Destructuring),允许你从对象中提取属性,并将它们赋值给变量
const { title,author,publicationDate,hasMovieAdaptation,genres,reviews,translations } = book;
console.log(title, author);

//数组解构赋值允许你将数组中的值提取到单独的变量中,语法类似于对象解构赋值,但使用的是方括号 [];
const [firstBook, secondBook, ...otherBooks ] = books;
console.log(firstBook, secondBook, otherBooks);
//添加新元素 在解构赋值中,... 操作符可以用来收集剩余的元素。它可以用于数组解构时将剩余的元素赋值给一个新数组
const newGenres = [...book.genres, "horror"];
console.log(newGenres);

//adding new genre to books可以覆盖原数组,也可以添加新的元素
//先展开
const updateBook = { ...books, data:"123" };
updateBook;

//模板字符串
const summary = `${title} is a book,and published in ${publicationDate.split("-")[0]}`;
summary;

//三元运算符
const hasMovie = books.hasMovieAdaptation ? "Yes" : "No";
hasMovie;

//箭头函数 split() 方法用于分割字符串,并返回一个包含分割后的子字符串的数组。
const publicationDateYear = (str) => str.publicationDate.split("-")[0];
console.log(publicationDateYear(book));
//运算符
// && 短路逻辑与 || 短路逻辑
console.log( true && "nihao");
console.log( false || "nihao");
// console.log( true || "nihao");
// console.log( false && "nihao");

//?? false短路逻辑 总评
const count = book.reviews.librarything ?? 0;
count;

//falsy values: false, null, undefined, 0, NaN, ""
//truthy values: true, non-empty string, non-empty array, object, function


//optonal chaining
function getTotalReviewcount(book){
const goodreads = book.reviews?.goodreads;
const librarything = book.reviews?.librarything ?? 0;
return goodreads + librarything;
}
const totalReviewCount = getTotalReviewcount(book);
totalReviewCount;

//map会遍历数组中的每一个元素,并按照函数式,对原数组的每一项进行操作,返回一个新的数组
const x =[1,2,3,4,5].map(num => num * 2);
console.log(x);

// reduce 的效果 是将数组中的元素合并成一个值 相较于 filter、map
const arr = [1,2,3,4,5];
const sum = arr.reduce((sum, cur) => sum + cur, 0);
console.log(sum);

//filer 过滤数组中的元素,返回一个新的数组
const arr3 = [1, 3, 2, 4, 5];
const filteredArr = arr3.filter((num) => num % 2 === 0);
filteredArr;

//sort 会改变原数组,返回排序后的数组
const arr1 = [1, 3, 2, 4, 5];
const sortedArr = arr1.sort((a, b) => a - b);
sortedArr;
arr1;

// slice 可以返回一个新数组,不会改变原数组
const arr2 = [1, 3, 2, 4, 5];
const slicedArr2 = arr2.slice(0, 5).sort((a, b) => a - b);
console.log(slicedArr2);
arr2;

//箭头函数 为了延迟执行 onDeleteItem(item.id),即在点击时才调用它
onClick={() => onDeleteItem(item.id)}

//回调函数 作为参数传递给另一个函数
function greet() {
console.log("Hello!");
}

setTimeout(greet, 2000); // 2秒后调用greet函数


概述

React 是用于构建用户界面的 JavaScript 库。

它提供了组件、状态管理、生命周期方法等功能来帮助开发者构建复杂的前端应用

其主要功能是实现后端数据与前端页面的即时更新,同时减少单一使用js时的繁冗工作量

项目结构

采用 npx create-react-app my-app,创建react项目文件夹

my-app/
├── node_modules/ # 项目依赖的第三方库
├── public/
│ ├── favicon.ico # 网站图标
│ ├── index.html # HTML 入口文件
│ ├── manifest.json # PWA 应用配置文件
│ └── robots.txt # 爬虫协议文件
├── src/
│ ├── App.css # 应用的样式文件
│ ├── App.tsx # 应用的主组件文件 (或 .js)
│ ├── App.test.tsx # 测试文件,使用 Jest 测试框架
│ ├── index.css # 全局样式文件
│ ├── index.tsx # 应用入口文件 (或 .js)
│ ├── logo.svg # 应用 logo 图标
│ ├── reportWebVitals.ts # 性能报告 (可选)
│ └── setupTests.ts # 设置测试环境的配置文件
├── .gitignore # Git 忽略文件配置
├── package.json # 项目配置文件,包含依赖信息和脚本
├── README.md # 项目的 README 文件
├── tsconfig.json # TypeScript 配置文件(如果使用 TypeScript)
└── yarn.lock / package-lock.json # 锁定依赖版本的文件

React 三要素

1. 组件(Component)

组件是 React 的基础构建块,每个 React 应用都是由一个个组件组合而成的。React为每一个组件渲染一个视图,这些视图组成UI。每个组件都拥有自己的 “数据” “(js)逻辑” “外观

image-20240920204607063
  • 类组件:通过 ES6 的类来定义,包含状态(state)和生命周期方法。
  • 函数组件:React 16.8 之后推荐的方式,使用函数定义组件并结合钩子(Hooks)来管理状态和生命周期。

组件不可嵌套

示例

// 函数组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

什么是 JSX ?

jsx是一种声明语法,用来描述组件的外观,根据数据和逻辑工作。通常与 React 一起使用,允许在 JavaScript 代码中编写类似 HTML 的标签

  1. 类 HTML 的语法
  • JSX 允许在 JavaScript 代码中嵌入类似 HTML 的标签,使得编写用户界面时更加直观和简洁。虽然看起来像 HTML,但 JSX 实际上会被编译成 JavaScript 函数调用。
  1. 支持表达式
  • JSX 中可以使用 JavaScript 表达式,通过{}包裹

    const name = 'Alice';
    const element = <h1>Hello, {name}!</h1>;
  1. 必须返回一个父元素
  • 在 JSX 中,所有标签必须被一个父级元素包裹。例如:

    return (
    <div>
    <h1>Title</h1>
    <p>Description</p>
    </div>
    );
  1. 样式和属性的处理
  • 在 JSX 中,class属性被替换为className,style属性接受一个对象。例如:

    <div className="container" style={{ color: 'red' }}>Hello</div>

2. 状态(State)

状态是组件内部的数据,它决定了组件的行为和显示的内容。

状态是可变的,随着用户的交互或其他事件触发,它会发生变化并重新渲染组件。

在类组件中,状态通过 this.state 来管理,在函数组件中使用 useState 钩子来管理。

示例

// 函数组件使用状态
import React, { useState } from 'react';

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

//字符串参数
{/*const [test, setTest] = useState(name)
setTest({name:"john"}*/}

return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

什么是钩子?

在 React 中,钩子就是use开头的状态组件,允许你在函数组件中使用状态和其他 React 特性,而不需要编写类组件。

常见的 React 钩子有:

  • useState:用于在函数组件中添加状态变量。
  • useEffect:用于执行副作用操作(如数据获取、订阅或手动更改 DOM)。
  • useContext:用于在组件树中共享数据,而无需逐层传递 props。

3. 属性(Props)

props 是组件之间传递数据的方式。

父组件可以通过 props 向子组件传递数据,子组件无法修改 props,它们是只读的。props 的主要作用是让组件的渲染更加灵活和可复用

在传递中,我们可以使用 js 中的重构来传递对象的名称而不是props,但是不要忘记使用{}

注 : React是单向数据流,数据只能从父组件流向子组件

示例

// 父组件向子组件传递数据
function Menu() {
return (
<main className="menu">
<h2>our pizzas</h2>
<Pizza name= "Focaccia"
ingredients= "Bread with italian olive oil and rosemary"
price="6"
photoName="pizzas/focaccia.jpg"/>
</main>

function Pizza(props) {
return (
<div className="pizza">
<img src={props.photoName} alt={props.name} />
<div className="pizzas">
<h3>{props.name}</h3>
<p>{props.ingredients}</p>
<span>{props.price}</span>
</div>
</div>
);
}

三要素关系总结:

  • 组件 是 UI 的基本单元,通过组合形成完整的应用。
  • 状态 是组件内部的动态数据,控制着组件的行为和显示。
  • 属性(props) 用于在组件间传递数据,通常是父组件传递给子组件的数据。

状态与道具有什么区别?

状态(state):状态是组件内部管理的数据,数据归创建他的组件所有,它可以看作组件的存储,可以用来长期保存数据。状态可以在组件内部被修改(通过setStateuseState),并会在变化时触发组件重新渲染,用来控制组件的行为、渲染和交互。

  • 用于存储组件内部动态变化的数据,比如用户输入的表单值、加载数据的结果、组件的交互状态(如打开或关闭某个UI元素)。
  • 当状态发生变化时,React会自动触发两个组件的重新渲染

道具(props):道具是从父组件传递给子组件的数据,数据被父组件所有,可以把它想象函数参数。因为它是只读的,子组件无法直接修改它。当子组件收到更新的props时,也会重新渲染组件

  • 道具主要用来让组件间进行静态数据传递,通常是父组件将数据通过道具传递给子组件,子组件根据道具的值渲染。
  • 当道具的值在父组件中发生变化时,子组件会重新渲染。

怎么渲染列表(rendering a list)

概念:可以理解为创建一个数组,并为数组里的每个元素创建组件

通常我们会使用数组的 map() 方法来生成列表项。每个列表项需要一个唯一的 key 属性,以帮助 React 识别和优化列表中的元素

示例:

//const pizzaData = [{...}]
function Menu() {
return (
<main className="menu">
<h2>our pizzas</h2>
<div>
{pizzaData.map((pizzas) => (
<Pizza pizzaObj={pizzas} key={pizzas.name} /> ))}
{/*这一段代码是 React 中通过数组的 map() 方法渲染列表的常用方式。它的作用是遍历 pizzaData 数组,为数组中的每个对象生成一个对应的 Pizza 组件*/}
</div>
</main>
);
}
function Pizza(props) {
return (
<div className="pizza">
<img src={props.pizzaObj.photoName} alt={props.pizzaObj.name} />
<div className="pizzas">
<h3>{props.pizzaObj.name}</h3>
<p>{props.pizzaObj.ingredients}</p>
<span>{props.pizzaObj.price}</span>
</div>
</div>
);
}

条件渲染

方法一:使用 && 运算符的短路逻辑,在特定条件下选择内容

示例:

function Footer() {
const data = new Date().getHours();
console.log(data);
const open = 20;
const close = 22;
const isOpen = data >= open && data < close;
return (
<footer className="footer">
{isOpen && <p> Open</p> }
</footer>
);
}

方法2:使用三元运算符

示例:

function Footer() {
const data = new Date().getHours();
console.log(data);
const open = 20;
const close = 22;
const isOpen = data >= open && data < close;
return (
<footer className="footer">
{isOpen = true ?(<p>Open</p>): null}
</footer>
);
}

方法三:多重返回

示例:

function Footer() {
const data = new Date().getHours();
console.log(data);
const open = 20;
const close = 22;
const isOpen = data >= open && data < close;

if (!isOpen) {
return (<p>Close</p>);
}

return (
<footer className="footer">
{/* {isOpen && */}
<div className="order">
<p> We're open for orders from {open} to {close}. Call us at 123-456-7890.</p>
<button className="btn" style={{color: 'yellow'}} >Order</button>
</div>
</footer>
);
}

React Fragment(react 片段)

概念:React.Fragment 是 React 中用于包裹多个子元素而不额外生成 HTML 元素的组件。

通常在 JSX 中,如果你返回多个元素,它们需要被一个父级元素<div></div>包裹,而 React.Fragment 可以在不生成额外 DOM 节点的情况下包裹这些元素。

示例:

function App() {
return (
<react.fragment>
<Header/>
<Menu />
<Footer/>
</react.fragment>
);
}
//或者
function App() {
return (
<>
<Header/>
<Menu />
<Footer/>
</>
);
}

如何在React中使用表单?

从代码理解

function Form (){
const [description, setDescription] = useState('');
function handleSubmit(e){
e.preventDefault();// 阻止默认表单提交行为,防止重新加载页面
}

return(
<form className="add-form" onSubmit={handleSubmit}>
<h3>What do you need for your trip</h3>
<select>
{Array.from({length: 20}, (_, i) => i+1).map
((num) => (
<option value={num} key={num}>
{num}
</option>))}
</select>
<input type="text"
placeholder="Item..."
value={description}
onChange={(e) => setDescription(e.target.value)}/>
<button>Add</button>
</form>
);
}

{Array.from({length: 20}, (_, i) => i+1).map ((num) => ( <option value={num} key={num}> {num} </option>))}:

利用Array.from()生成一个数组,并通过map()来遍历数组,动态生成一组<option>标签

onChange:当用户在输入框中输入内容时,onChange事件会触发,更新React状态,从而动态更新表单值

如何将表单中的数据导入事件?

常见的React事件类型:

  • **onClick**:点击事件
  • **onChange**:表单元素(如输入框、选择框)的值改变事件
  • **onSubmit**:表单提交事件
  • **onKeyDownonKeyUp**:键盘按下、松开事件
  • **onMouseEnteronMouseLeave**:鼠标进入、离开事件

受控元素

在React中,使用受控组件可以将HTML表单中的数据与组件的状态(state)直接绑定,从而避免直接操作DOM。这样做的好处是表单数据的更新和管理完全交由React来处理,无需手动获取DOM元素的值

三个步骤:

  • 设置状态 const [description, setDescription] = useState('');

    • 目的:创建一个变量来存储用户输入的值,并在用户与表单交互时,React能够追踪和管理这个值。
  • 绑定变量 通过value属性,将表单元素(例如<input><textarea>)的值与刚刚创建的状态变量绑定。这样,表单的值由状态控制,React状态和UI之间建立了双向绑定关系。

    • 操作:在表单元素上使用value={description},使输入框的值与description状态保持同步
  • 更新状态 onChange={(e) => setDescription(e.target.value)},当用户在表单元素中输入内容时,onChange事件会触发。此时,你可以使用setDescription函数更新状态,确保状态随用户输入的变化而改变。事件处理函数将接收事件对象,通过e.target.value获取表单元素的新值。

    • 操作:在onChange事件处理函数中调用setDescription(e.target.value),将表单的新值更新到状态中

完整代码:

function Form (){

const [description, setDescription] = useState('');
const [quantity, setQuantity] = useState(1);

function handleSubmit(e){
e.preventDefault();
}

return(
<form className="add-form" onSubmit={handleSubmit}>
<h3>What do you need for your trip</h3>
<select value={quantity} onChange={(e) => setQuantity(Number(e.target.value)} >
{Array.from({length: 20}, (_, i) => i+1).map
((num) => (
<option value={num} key={num}>
{num}
</option>))}
</select>
<input type="text"
placeholder="Item..."
value={description}
onChange={(e) => setDescription(e.target.value)}/>
<button>Add</button>
</form>
);
}

什么时候选择使用状态?

什么时候创建状态?

  1. 需要存储数据?

  2. 数据会发生变化吗

    • 不会 => const 常量即可
  3. 是否可以从现有的道具/状态中计算?

    • 会 => 派生状态(derive state)
  4. 更新状态是否需要重新渲染组件?

  • 不会 => 使用Ref (像普通状态一样持久的保持数据,但无需重新渲染组件)
  1. 使用useState创建一个状态,并放置在组件中

在哪里使用状态?

当前组件、通过道具传递给子组件、将状态传递给同级组件的公共父组件中去(状态上移)、所有组件使用(全局状态

什么是派生状态?

派生状态就是简单地从一个现有状态或者道具中计算出来的状态

const [total,setTotal] = useState(0);
const tip = total /2;

子代道具

在 React 中,子代道具(Children Props) 是指通过 props.children 传递的内容,它允许父组件将嵌套在其内部的 JSX 代码或组件传递给子组件。它提供了一种灵活的方式来构建可复用的组件,使得父组件可以决定子组件的内部内容,而不需要在子组件中明确指定。

示例:

function Wrapper(props) {
return <div className="wrapper">{props.children}</div>;
}

function App() {
return (
<Wrapper>
<h1>Hello, World!</h1>
<p>This is a paragraph inside the Wrapper component.</p>
</Wrapper>
);
}