学习视频
12. Props, Immutability, and One-Way Data Flow_哔哩哔哩_bilibili
React笔记
前置知识 这里写了常用到的 js知识
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);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;const publicationDateYear = (str ) => str.publicationDate .split ("-" )[0 ];console .log (publicationDateYear (book));console .log ( true && "nihao" );console .log ( false || "nihao" );const count = book.reviews .librarything ?? 0 ; count;function getTotalReviewcount (book ){ const goodreads = book.reviews ?.goodreads ; const librarything = book.reviews ?.librarything ?? 0 ; return goodreads + librarything; }const totalReviewCount = getTotalReviewcount (book); totalReviewCount;const x =[1 ,2 ,3 ,4 ,5 ].map (num => num * 2 );console .log (x);const arr = [1 ,2 ,3 ,4 ,5 ];const sum = arr.reduce ((sum, cur ) => sum + cur, 0 );console .log (sum);const arr3 = [1 , 3 , 2 , 4 , 5 ];const filteredArr = arr3.filter ((num ) => num % 2 === 0 ); filteredArr;const arr1 = [1 , 3 , 2 , 4 , 5 ];const sortedArr = arr1.sort ((a, b ) => a - b); sortedArr; arr1;const arr2 = [1 , 3 , 2 , 4 , 5 ];const slicedArr2 = arr2.slice (0 , 5 ).sort ((a, b ) => a - b);console .log (slicedArr2); arr2; onClick={() => onDeleteItem (item.id )} function greet ( ) { console .log ("Hello!" ); }setTimeout (greet, 2000 );
概述 React 是用于构建用户界面的 JavaScript 库。
它提供了组件、状态管理、生命周期方法等功能来帮助开发者构建复杂的前端应用
其主要功能是实现后端数据与前端页面的即时更新,同时减少单一使用js
时的繁冗工作量
项目结构 创建项目: 方法1: a. 安装 Node.js 和 npm(Node.js 包管理器)。
b. 打开终端或命令提示符。
c. 运行以下命令来创建一个新的 React 应用程序:
npx create-react-app my-app cd my-app npm start
方法2:使用 Vite Vite 是一个轻量级的构建工具,它提供了快速的冷启动和热更新。它支持 TypeScript、CSS 预处理器和许多其他功能。
npm init vite@latest my-app -- --template react cd my-app npm install npm run dev
文件结构如下
my-app/ ├── node_modules/ ├── public/ │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ └── robots.txt ├── src/ │ ├── App.css │ ├── App.tsx │ ├── App.test.tsx │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── reportWebVitals.ts │ └── setupTests.ts ├── .gitignore ├── package.json ├── README.md ├── tsconfig.json └── yarn.lock / package-lock.json
React 三要素 1. 组件(Component) 组件是 React 的基础构建块,每个 React 应用都是由一个个组件组合而成的。React为每一个组件渲染一个视图,这些视图组成UI。每个组件都拥有自己的 “数据 ” “(js)逻辑 ” “外观 ”
类组件 :通过 ES6 的类来定义,包含状态(state)和生命周期方法。
函数组件 :React 16.8 之后推荐的方式,使用函数定义组件并结合钩子(Hooks)来管理状态和生命周期。
组件不可嵌套
示例 :
function Welcome (props ) { return <h1 > Hello, {props.name}</h1 > ; }
什么是 JSX ? jsx是一种声明语法,用来描述组件的外观,根据数据和逻辑工作。通常与 React 一起使用,允许在 JavaScript 代码中编写类似 HTML 的标签
类 HTML 的语法 :
JSX 允许在 JavaScript 代码中嵌入类似 HTML 的标签,使得编写用户界面时更加直观和简洁。虽然看起来像 HTML,但 JSX 实际上会被编译成 JavaScript 函数调用。
支持表达式 :
必须返回一个父元素 :
样式和属性的处理 :
2. 状态(State) 状态是组件内部的数据,它决定了组件的行为和显示的内容。
状态是可变的,随着用户的交互或其他事件触发,它会发生变化并重新渲染 组件。
在类组件中,状态通过 this.state
来管理,在函数组件中使用 useState
钩子 来管理。
什么是钩子?
在 React 中,钩子就是use开头的状态组件,允许你在函数组件中使用状态和其他 React 特性,而不需要编写类组件。
常见的 React 钩子有:
useState
:用于在函数组件中添加状态变量。
useEffect
:用于执行副作用操作(如数据获取、订阅或手动更改 DOM)。
useContext
:用于在组件树中共享数据,而无需逐层传递 props。
怎么使用状态 什么时候创建状态?
需要存储数据?
数据会发生变化吗
是否可以从现有的道具/状态中计算?
更新状态是否需要重新渲染组件?
不会 => 使用Ref (像普通状态一样持久的保持数据,但无需重新渲染组件)
使用useState创建一个状态,并放置在组件中
在哪里使用状态?
当前组件 、通过道具传递给子组件 、将状态传递给同级组件的公共父组件 中去(状态上移)、所有组件使用(全局状态 )
派生状态 派生状态就是简单地从一个现有状态或者道具中计算出来的状态
const [total,setTotal] = useState (0 );const tip = total /2 ;
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> ); }
子代道具 在 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 > ); }
三要素关系总结:
组件 是 UI 的基本单元,通过组合形成完整的应用。
状态 是组件内部的动态数据,控制着组件的行为和显示。
属性(props) 用于在组件间传递数据,通常是父组件传递给子组件的数据。
状态与道具有什么区别?
状态(state) :状态是组件内部管理的数据,数据归创建他的组件所有,它可以看作组件的存储,可以用来长期保存数据。状态可以在组件内部被修改(通过setState
或useState
),并会在变化时触发组件重新渲染,用来控制组件的行为、渲染和交互。
用于存储组件内部动态变化的数据 ,比如用户输入的表单值、加载数据的结果、组件的交互状态(如打开或关闭某个UI元素)。
当状态发生变化时,React会自动触发两个组件的重新渲染
道具(props) :道具是从父组件传递给子组件的数据,数据被父组件所有,可以把它想象函数参数。因为它是只读的,子组件无法直接修改它 。当子组件收到更新的props时,也会重新渲染组件
道具主要用来让组件间进行静态数据传 递,通常是父组件将数据通过道具传递给子组件,子组件根据道具的值渲染。
当道具的值在父组件中发生变化时,子组件会重新渲染。
怎么渲染列表(rendering a list) 概念:可以理解为创建一个数组,并为数组里的每个元素创建组件
通常我们会使用数组的 map()
方法来生成列表项。每个列表项需要一个唯一的 key
属性,以帮助 React 识别和优化列表中的元素
示例:
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中使用表单? 怎么判断是否需要使用表单?
用户输入 :如果你需要用户输入数据,比如添加待办事项,那么你需要一个表单。表单可以包含输入框、文本域、选择框等元素,让用户输入信息。
提交操作 :如果你需要用户提交数据,比如添加待办事项后保存到列表中,那么你需要一个表单。表单通常包含一个提交按钮,用户点击后触发数据提交。
数据验证 :如果你需要对用户输入的数据进行验证,比如检查待办事项是否为空,那么你需要一个表单。表单可以包含验证逻辑,确保用户输入的数据符合要求。
状态管理 :如果你需要管理用户输入的状态,比如输入框的值,那么你需要一个表单。表单可以包含状态管理逻辑,确保用户输入的数据能够被正确处理。
事件处理 :如果你需要处理用户输入的事件,比如输入框的 onChange
事件,那么你需要一个表单。表单可以包含事件处理逻辑,响应用户的操作。
表单完整代码(以ToDoList为例):
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 > ); }
渲染列表
{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
获取表单元素的新值。
Router React Router
是一个用于 React 应用的库,用于管理应用的路由和导航。它的主要作用是帮助开发者构建单页面应用(SPA),通过动态加载组件而不是每次跳转都重新加载整个页面。
React Router 的作用和意义
单页面应用(SPA)支持 :
React Router 允许应用在不刷新页面的情况下切换视图 ,提升用户体验。
通过动态加载组件,减少页面加载时间,提升性能。
URL 与 UI 同步 :
React Router 保持 URL 与 UI 同步,用户可以通过 URL 直接访问特定页面,便于分享和书签。
嵌套路由 :
支持嵌套路由,允许在父组件中嵌套子组件,便于构建复杂的 UI 结构。
动态路由 :
支持动态路由,允许根据 URL 参数动态加载不同内容。
编程式导航 :
提供编程式导航,开发者可以通过代码控制页面跳转,而不是依赖用户点击链接。
路由守卫 :
支持路由守卫,可以在用户访问特定路由前进行权限检查或数据加载。
为什么不直接用链接
页面刷新 :
使用普通链接会导致页面刷新,破坏 SPA 的无刷新体验。
状态丢失 :
页面刷新会导致应用状态丢失,而 React Router 可以在不刷新页面的情况下保持状态。
复杂路由管理 :
普通链接无法处理复杂的路由逻辑,如嵌套路由、动态路由等。
编程式导航 :
普通链接无法实现编程式导航,React Router 提供了更多控制权。
import React from 'react' ;import { BrowserRouter as Router , Route , Switch , Link } from 'react-router-dom' ;function Home ( ) { return <h1 > Home</h1 > ; }function About ( ) { return <h1 > About</h1 > ; }function App ( ) { return ( <div > <nav > <Link to ="/" > Home</Link > <Link to ="/about" > About</Link > </nav > <Switch > <Route path ="/" exact component ={Home} /> <Route path ="/about" component ={About} /> </Switch > </div > ); }function Root ( ) { return ( <Router > <App /> </Router > ); }export default Root ;
useEffect 什么是useEffact? useEffect
是 React 的一个 Hook,用于在函数组件中处理副作用 。
useEffect
让函数组件能在渲染后 执行副作用,并通过依赖项和清理函数精准控制执行时机 ,替代类组件的生命周期方法(如 componentDidMount
/ componentDidUpdate
/ componentWillUnmount
)。它的核心作用是:
1. 什么是副作用?
副作用 是指那些可能影响其他组件或与外部世界交互的操作,例如:
数据获取(API 请求)
手动修改 DOM
订阅事件(如 window.addEventListener
)
定时器(setTimeout
/ setInterval
)
用法 useEffect (() => { return () => { }; }, [依赖项数组]);
依赖项数组这一列中,不用纠结太多,一般把所有的外部值都添加进来就可以。
无依赖数组([]
) :只在组件首次渲染后 执行一次(类似 componentDidMount
)。
有依赖项([a, b]
) :当 a
或 b
变化时重新执行。
无第二个参数 :每次组件更新后 都执行(谨慎使用!)。
清理函数 :用于取消订阅、清除定时器等(类似 componentWillUnmount
)。
为什么需要useEffect? 以获取数据为例:
const { useState } = require ("react" );function backend ( ){ const [isLoading,setIsLoading] = useState (true ); const [list,setList] = useState ([]); fetch ( 'https://react-getting-started-48dec-default-rtdb,firebaseio.com/meetups.json' ).then ((response )=> { return response.json () }).then ((data )=> { setIsLoading (false ); setLoadedMeetups (data);}) if (isLoading){ return ( <div > Loading...</div > ) } return ( <div > <List date ={list} /> </div > ) }export default backend;
这样的写法会陷入死循环 ,原因是 fetch
请求被放在了函数组件的顶层作用域中,而每次组件渲染时都会重新执行 fetch
请求,导致状态更新(setIsLoading
和 setList
),进而触发组件重新渲染,然后再次执行 fetch
请求,形成无限循环。
采用useEffct:
const { useState } = require ("react" );function backend ( ){ const [isLoading,setIsLoading] = useState (true ); const [list,setList] = useState ([]); useEffect ()=>{ fetch ( 'https://react-getting-started-48dec-default-rtdb,firebaseio.com/meetups.json' ).then ((response )=> { return response.json () }).then ((data )=> { setIsLoading (false ); setLoadedMeetups (data); }); },[isLoading]) return ( <div > <List date ={list} /> </div > ) }export default backend;
Context - 上下文 上下文(Context) 是 React 提供的一种 跨组件层级传递数据 的机制,用于避免繁琐的逐层 props 传递(俗称“props 钻取”)。它特别适合全局共享的数据(如主题、用户登录状态、多语言配置等)。
核心 API
React.createContext()
:创建一个 Context 对象(包含 Provider
和 Consumer
)。
Context.Provider
:提供数据的组件,通过 value
属性传递数据。
useContext()
:函数组件中消费 Context 数据的 Hook(替代 Consumer
)
使用示例:
import { createContext, useContext,useState } from "react" ;const TestContext = createContext ({ favorites : userFavorites, number : userFavorites.length , }); function TestProvider ({ children } ) { const [userFavorites,setUserFavorites] = useState ([]); const context = { favorites : userFavorites, number : userFavorites.length , }; return ( <TestContext.Provider value ={context} > {children} </TestContext.Provider > ); }export function useTest ( ) { return useContext (TestContext ); }function App ( ) { return ( <TestProvider > <ChildComponent /> </TestProvider > ); }function ChildComponent ( ) { const data = useTest (); return <div > {data}</div > ; }
当多个页面都需要使用当前上下文时,可以用一个provider文件专门封装:
'use client' import { QueryClient , QueryClientProvider } from "@tanstack/react-query" ;import { SuiClientProvider , WalletProvider } from "@mysten/dapp-kit" ;import { networkConfig, network } from "@/contracts" import "@mysten/dapp-kit/dist/index.css" ;import { ThemeProvider } from "next-themes" ;import { UserProfileProvider } from "@/contexts/user-profile-context" ;import { AppProvider } from "@/contexts/app-context" ;const queryClient = new QueryClient ();export function Providers ({ children }: { children: React.ReactNode } ) { return ( <ThemeProvider attribute ="class" defaultTheme ="system" enableSystem > <QueryClientProvider client ={queryClient} > <SuiClientProvider networks ={networkConfig} defaultNetwork ={network} > <AppProvider > <UserProfileProvider > <WalletProvider autoConnect stashedWallet ={ { name: "Sui Passport ", network: network , } }> {children} </WalletProvider > </UserProfileProvider > </AppProvider > </SuiClientProvider > </QueryClientProvider > </ThemeProvider > ); }
🤔 那什么时候不用全局包裹? 如果 profile 只在某些特定路由中使用,比如 /profile/*
,那你可以只在某个 layout 里包裹:
tsxCopyEdit// app/profile/layout.tsx import { UserProfileProvider } from '@/contexts/UserProfileProvider'; export default function ProfileLayout({ children }) { return ( <UserProfileProvider> {children} </UserProfileProvider> ); }