学习视频
12. Props, Immutability, and One-Way Data Flow_哔哩哔哩_bilibili
React笔记
前置知识
这里写了常用到的 js知识
1 | //对象解构赋值(Object Destructuring),允许你从对象中提取属性,并将它们赋值给变量 |
概述
React 是用于构建用户界面的 JavaScript 库。
它提供了组件、状态管理、生命周期方法等功能来帮助开发者构建复杂的前端应用
其主要功能是实现后端数据与前端页面的即时更新,同时减少单一使用js
时的繁冗工作量
项目结构
创建项目:
方法1:
a. 安装 Node.js 和 npm(Node.js 包管理器)。
b. 打开终端或命令提示符。
c. 运行以下命令来创建一个新的 React 应用程序:
1 | npx create-react-app my-app |
方法2:使用 Vite
Vite 是一个轻量级的构建工具,它提供了快速的冷启动和热更新。它支持 TypeScript、CSS 预处理器和许多其他功能。
1 | npm init vite@latest my-app -- --template react |
文件结构如下
1 | my-app/ |
React 三要素
1. 组件(Component)
组件是 React 的基础构建块,每个 React 应用都是由一个个组件组合而成的。React为每一个组件渲染一个视图,这些视图组成UI。每个组件都拥有自己的 “数据” “(js)逻辑” “外观”
类组件:通过 ES6 的类来定义,包含状态(state)和生命周期方法。
- 函数组件:React 16.8 之后推荐的方式,使用函数定义组件并结合钩子(Hooks)来管理状态和生命周期。
组件不可嵌套
示例:
1 | // 函数组件 |
什么是 JSX ?
jsx是一种声明语法,用来描述组件的外观,根据数据和逻辑工作。通常与 React 一起使用,允许在 JavaScript 代码中编写类似 HTML 的标签
- 类 HTML 的语法:
- JSX 允许在 JavaScript 代码中嵌入类似 HTML 的标签,使得编写用户界面时更加直观和简洁。虽然看起来像 HTML,但 JSX 实际上会被编译成 JavaScript 函数调用。
- 支持表达式:
JSX 中可以使用 JavaScript 表达式,通过{}包裹
1
2 const name = 'Alice';
const element = <h1>Hello, {name}!</h1>;
- 必须返回一个父元素:
在 JSX 中,所有标签必须被一个父级元素包裹。例如:
1
2
3
4
5
6 return (
<div>
<h1>Title</h1>
<p>Description</p>
</div>
);
- 样式和属性的处理:
在 JSX 中,class属性被替换为
className
,style属性接受一个对象。例如:
1 <div className="container" style={{ color: 'red' }}>Hello</div>
2. 状态(State)
状态是组件内部的数据,它决定了组件的行为和显示的内容。
状态是可变的,随着用户的交互或其他事件触发,它会发生变化并重新渲染组件。
在类组件中,状态通过 this.state
来管理,在函数组件中使用 useState
钩子来管理。
什么是钩子?
在 React 中,钩子就是use开头的状态组件,允许你在函数组件中使用状态和其他 React 特性,而不需要编写类组件。
常见的 React 钩子有:
useState
:用于在函数组件中添加状态变量。useEffect
:用于执行副作用操作(如数据获取、订阅或手动更改 DOM)。useContext
:用于在组件树中共享数据,而无需逐层传递 props。
怎么使用状态
什么时候创建状态?
需要存储数据?
数据会发生变化吗
- 不会 => const 常量即可
是否可以从现有的道具/状态中计算?
- 会 => 派生状态(derive state)
更新状态是否需要重新渲染组件?
- 不会 => 使用Ref (像普通状态一样持久的保持数据,但无需重新渲染组件)
- 使用useState创建一个状态,并放置在组件中
在哪里使用状态?
当前组件、通过道具传递给子组件、将状态传递给同级组件的公共父组件中去(状态上移)、所有组件使用(全局状态)
派生状态
派生状态就是简单地从一个现有状态或者道具中计算出来的状态
1 | const [total,setTotal] = useState(0); |
3. 属性(Props)
props
是组件之间传递数据的方式。
父组件可以通过 props
向子组件传递数据,子组件无法修改 props
,它们是只读的。props
的主要作用是让组件的渲染更加灵活和可复用。
在传递中,我们可以使用 js 中的重构
来传递对象的名称而不是props
,但是不要忘记使用{}
注 : React是单向数据流,数据只能从父组件流向子组件
示例:
1 | // 父组件向子组件传递数据 |
子代道具
在 React 中,子代道具(Children Props) 是指通过 props.children
传递的内容,它允许父组件将嵌套在其内部的 JSX 代码或组件传递给子组件。它提供了一种灵活的方式来构建可复用的组件,使得父组件可以决定子组件的内部内容,而不需要在子组件中明确指定。
示例:
1 | //子组件 |
三要素关系总结:
- 组件 是 UI 的基本单元,通过组合形成完整的应用。
- 状态 是组件内部的动态数据,控制着组件的行为和显示。
- 属性(props) 用于在组件间传递数据,通常是父组件传递给子组件的数据。
状态与道具有什么区别?
状态(state):状态是组件内部管理的数据,数据归创建他的组件所有,它可以看作组件的存储,可以用来长期保存数据。状态可以在组件内部被修改(通过
setState
或useState
),并会在变化时触发组件重新渲染,用来控制组件的行为、渲染和交互。
- 用于存储组件内部动态变化的数据,比如用户输入的表单值、加载数据的结果、组件的交互状态(如打开或关闭某个UI元素)。
- 当状态发生变化时,React会自动触发两个组件的重新渲染
道具(props):道具是从父组件传递给子组件的数据,数据被父组件所有,可以把它想象函数参数。因为它是只读的,子组件无法直接修改它。当子组件收到更新的props时,也会重新渲染组件
- 道具主要用来让组件间进行静态数据传递,通常是父组件将数据通过道具传递给子组件,子组件根据道具的值渲染。
- 当道具的值在父组件中发生变化时,子组件会重新渲染。
怎么渲染列表(rendering a list)
概念:可以理解为创建一个数组,并为数组里的每个元素创建组件
通常我们会使用数组的 map()
方法来生成列表项。每个列表项需要一个唯一的 key
属性,以帮助 React 识别和优化列表中的元素
示例:
1 | //const pizzaData = [{...}] |
条件渲染
方法一:使用 && 运算符的短路逻辑,在特定条件下选择内容
示例:
1 | function Footer() { |
方法2:使用三元运算符
示例:
1 | function Footer() { |
方法三:多重返回
示例:
1 | function Footer() { |
React Fragment(react 片段)
概念:React.Fragment
是 React 中用于包裹多个子元素而不额外生成 HTML 元素的组件。
通常在 JSX 中,如果你返回多个元素,它们需要被一个父级元素<div></div>
包裹,而 React.Fragment
可以在不生成额外 DOM 节点的情况下包裹这些元素。
示例:
1 | function App() { |
如何在React中使用表单?
怎么判断是否需要使用表单?
- 用户输入:如果你需要用户输入数据,比如添加待办事项,那么你需要一个表单。表单可以包含输入框、文本域、选择框等元素,让用户输入信息。
- 提交操作:如果你需要用户提交数据,比如添加待办事项后保存到列表中,那么你需要一个表单。表单通常包含一个提交按钮,用户点击后触发数据提交。
- 数据验证:如果你需要对用户输入的数据进行验证,比如检查待办事项是否为空,那么你需要一个表单。表单可以包含验证逻辑,确保用户输入的数据符合要求。
- 状态管理:如果你需要管理用户输入的状态,比如输入框的值,那么你需要一个表单。表单可以包含状态管理逻辑,确保用户输入的数据能够被正确处理。
- 事件处理:如果你需要处理用户输入的事件,比如输入框的
onChange
事件,那么你需要一个表单。表单可以包含事件处理逻辑,响应用户的操作。
表单完整代码(以ToDoList为例):
1 | function 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
**:表单提交事件- **
onKeyDown
、onKeyUp
**:键盘按下、松开事件- **
onMouseEnter
、onMouseLeave
**:鼠标进入、离开事件
受控元素
在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)
,将表单的新值更新到状态中js小知识
在
onChange={(e) => setDescription(e.target.value)}
这段代码中,e
是一个事件对象(Event Object),它代表了当前正在发生的事件。在 JavaScript 中,当事件(如点击、输入等)发生时,浏览器会创建一个事件对象,该对象包含了与该事件相关的所有信息。在这个特定的例子中,
e
是一个合成事件(Synthetic Event)对象,它是 React 为了兼容不同浏览器而创建的一个抽象层。合成事件对象封装了原生浏览器事件,并提供了一个统一的接口,使得开发者可以编写跨浏览器的代码。e.target
是事件的目标元素(Event Target),即触发事件的 DOM 元素。在这个例子中,e.target
是输入框(input)元素。e.target.value
是输入框元素的当前值(Value),即用户输入的内容。
Router
React Router
是一个用于 React 应用的库,用于管理应用的路由和导航。它的主要作用是帮助开发者构建单页面应用(SPA),通过动态加载组件而不是每次跳转都重新加载整个页面。
React Router 的作用和意义
单页面应用(SPA)支持:
- React Router 允许应用在不刷新页面的情况下切换视图,提升用户体验。
- 通过动态加载组件,减少页面加载时间,提升性能。
URL 与 UI 同步:
- React Router 保持 URL 与 UI 同步,用户可以通过 URL 直接访问特定页面,便于分享和书签。
嵌套路由:
- 支持嵌套路由,允许在父组件中嵌套子组件,便于构建复杂的 UI 结构。
动态路由:
- 支持动态路由,允许根据 URL 参数动态加载不同内容。
编程式导航:
- 提供编程式导航,开发者可以通过代码控制页面跳转,而不是依赖用户点击链接。
路由守卫:
- 支持路由守卫,可以在用户访问特定路由前进行权限检查或数据加载。
为什么不直接用链接
页面刷新:
- 使用普通链接会导致页面刷新,破坏 SPA 的无刷新体验。
状态丢失:
- 页面刷新会导致应用状态丢失,而 React Router 可以在不刷新页面的情况下保持状态。
复杂路由管理:
- 普通链接无法处理复杂的路由逻辑,如嵌套路由、动态路由等。
编程式导航:
- 普通链接无法实现编程式导航,React Router 提供了更多控制权。
1 | import React from 'react'; |
useEffect
什么是useEffact?
useEffect
是 React 的一个 Hook,用于在函数组件中处理副作用。
useEffect
让函数组件能在渲染后执行副作用,并通过依赖项和清理函数精准控制执行时机,替代类组件的生命周期方法(如 componentDidMount
/ componentDidUpdate
/ componentWillUnmount
)。它的核心作用是:
1. 什么是副作用?
- 副作用是指那些可能影响其他组件或与外部世界交互的操作,例如:
- 数据获取(API 请求)
- 手动修改 DOM
- 订阅事件(如
window.addEventListener
) - 定时器(
setTimeout
/setInterval
)
用法
1 | useEffect(() => { |
依赖项数组这一列中,不用纠结太多,一般把所有的外部值都添加进来就可以。
- 无依赖数组(
[]
):只在组件首次渲染后执行一次(类似componentDidMount
)。 - 有依赖项(
[a, b]
):当a
或b
变化时重新执行。 - 无第二个参数:每次组件更新后都执行(谨慎使用!)。
- 清理函数:用于取消订阅、清除定时器等(类似
componentWillUnmount
)。
为什么需要useEffect?
以获取数据为例:
1 | const { useState } = require("react"); |
这样的写法会陷入死循环,原因是 fetch
请求被放在了函数组件的顶层作用域中,而每次组件渲染时都会重新执行 fetch
请求,导致状态更新(setIsLoading
和 setList
),进而触发组件重新渲染,然后再次执行 fetch
请求,形成无限循环。
采用useEffct:
1 | const { useState } = require("react"); |
Context - 上下文
上下文(Context) 是 React 提供的一种 跨组件层级传递数据 的机制,用于避免繁琐的逐层 props 传递(俗称“props 钻取”)。它特别适合全局共享的数据(如主题、用户登录状态、多语言配置等)。
- 核心 API
React.createContext()
:创建一个 Context 对象(包含Provider
和Consumer
)。Context.Provider
:提供数据的组件,通过value
属性传递数据。useContext()
:函数组件中消费 Context 数据的 Hook(替代Consumer
)
使用示例:
1 | import { createContext, useContext,useState } from "react"; |
当多个页面都需要使用当前上下文时,可以用一个provider文件专门封装:
1 | 'use client' |
🤔 那什么时候不用全局包裹?
如果 profile 只在某些特定路由中使用,比如 /profile/*
,那你可以只在某个 layout 里包裹:
1 | tsxCopyEdit// app/profile/layout.tsx |