当前位置: 首页 > news >正文

从零开始的web前端学习-React

目录
  • 1初始化
  • 2组件
  • 3卡片式组件
  • 4样式
  • 5React Props
  • 6条件渲染
  • 7列表渲染
  • 8onClick事件
  • 9useState
  • 10onChange事件
  • 11updater机制
  • 12对象更新
  • 13数组更新
  • 14对象数组更新
  • 15useEffect
  • 16useContext
  • 17useRef

1初始化

npm create vite@latest(npm 安装 vite,通过 vite 对 react 项目进行构建)

在通过 vite 构建好 react 项目文件夹后,输入命令行指令对 react 项目进行初始化

2组件

将 App.jsx 内容更新,仅保留函数与导出模块

// App.jsx
function App() {}export default App

接着,我们尝试编写 Header 组件

// Header.jsx
function Header() {return (<header><h1>react demo</h1><nav><ul><li><a href="#">Home</a></li><li><a href="#">About</a></li><li><a href="#">Services</a></li><li><a href="#">Contact</a></li></ul></nav><hr /></header>)
}export default Header

在 App.jsx 中 import Header 组件,并在函数中调用

// App.jsx
import Header from "./Header"function App() {return (<Header></Header> // <Header/> 同样适用)
}export default App

同样,我们可以编写 Footer 组件

// Footer.jsx
function Footer() {return (<footer><p>&copy; react demo</p></footer>)
}export default Footer

编写完成后,我们同样能够在 App.jsx 中调用 Footer 组件,但是如下直接返回会报错

return (
<Header></Header>
<Footer />
)

这是因为 JSX 仅支持单一返回,所以需要将两个组件封装成为一个组件

// App.jsx
import Header from "./Header"
import Footer from "./Footer"function App() {return (<><Header></Header><Footer /></>)
}export default App

通过上述封装后,报错解决,两个组件都能够正常运行,接着,我们试着对 Footer 组件进行丰富

<p>&copy; {new Date().getFullYear()} react demo</p>

可以看到,我们在 JSX 中插入 JavaScript 脚本时,需要通过大括号,而不在 JSX 中时,大括号是非必须的

下一步,我们尝试在 Header 与 Footer 之间添加一些内容,这同样需要借助组件实现

// Food.jsx
function Food() {const food1 = 'orange'const food2 = 'apple'return (<ul><li>banana</li><li>{food1}</li><li>{food2.toUpperCase()}</li></ul>)
}export default Food

3卡片式组件

卡片式组件相对于组件而言,更加完整,卡片式组件通常包含图片、标题以及内容

// Card.jsx
import profilePic from './assets/react.svg'function Card() {return (<div className="card"><img className='card-image' src={profilePic} alt="profile picture" /><h2 className='card-title'>Card Component Demo</h2><p className='card-text'>This is a Card Component Demo of React!</p></div>)
}export default Card

请注意,react 支持对象式设计,所以 class 是一个关键字,我们在 JavaScript 中使用的 class 类名设置需要改为 className

在设计好卡片式组件后,能够通过样式文件对其进行美化

/* index.css */
.card {border: 1px solid hsl(0, 0%, 80%);border-radius: 10px;box-shadow: 5px 5px 5px hsla(0, 0%, 0%, 0.1);padding: 20px;margin: 10px;text-align: center;max-width: 250px;display: inline-block;
}.card .card-image {max-width: 60%;height: auto;border-radius: 50%;margin-bottom: 10px;
}.card .card-title {font-family: Arial, sans-serif;margin: 0;color: hsl(0, 0%, 20%);
}.card .card-text {font-family: Arial, sans-serif;color: hsl(0, 0%, 30%);
}

4样式

react 中导入样式文件存在三种方式:外部导入、模块导入以及内联导入

// Button.jsx
function Button() {return (<button className="button">Click</button>)
}export default Button

外部导入,即利用 css 样式文件进行导入,我们可以在默认的 index.css 中为 button 类添加样式,也可以另外添加样式文件并导入

/*index.css*/
.button {background-color: hsl(200, 100%, 50%);color: white;padding: 10px 20px;border-radius: 5px;border: none;cursor: pointer;
}

外部导入在一些小型项目中适用,但项目扩大时,可能会出现命名冲突,并且对于相同标签的不同类,管理成本与难度会大大增加

模块导入能够很好地防止出现上述问题,模块导入需要将 JSX 文件与模块样式(module.css)文件放置在同一文件夹下,当然也可以不这样做,但是会显得非常杂乱

我们将上述 index.css 中为 button 类添加的样式写入至 Button.module.css 中,然后为 Button.jsx 做一些改变以实现模块导入

// Button.jsx
import styles from './Button.module.css'function Button() {return (<button className={styles.button}>Click</button>)
}export default Button

模块导入不用担心同名冲突,因为最终应用是通过哈希编码实现的,不过文件数量确实会增加不少

内联导入与通过 JavaScript 设置样式类似,在 JSX 文件中为类设置样式

// Button.jsx
function Button() {const styles = {backgroundColor: "hsl(200, 100%, 50%)",color: "white",padding: "10px 20px",borderRadius: "5px",border: "none",cursor: "pointer",}return (<button style={styles}>Click</button>)
}export default Button

内联导入样式会更适用于一些简单元素,但内联导入样式会让代码的可读性以及可维护性变得较差,注意破折号非法,需要换为驼峰命名

5React Props

React Props 是只读属性,是用于将数据从父组件传递到子组件的机制,在卡片组件中,若我们添加多个,则这些卡片组件呈现的内容会相同,但通过 props 对象,我们能够进行差异化

// Student.jsx
function Student(props) {return (<div><p>Name: {props.name}</p><p>Age: {props.age}</p><p>isStudent: {props.isStudent ? "Yes" : "No"}</p></div>)
}export default Student

在 App.jsx 中调用 Student 组件

<Student name={"Jack"} age={18} isStudent={true} />(字符串可省略大括号)

name、age、isStudent 属性将会自动组成 props 对象输入至 Student 组件中

对于不同的属性,数据类型可能不同,react 提供 React PropTypes 机制对 props 输入数据类型进行检查(需要通过包管理器 npm、yarn 等安装 prop-types 包),当 props 输入数据类型与设定不一致,则会在控制台报错

// Student.jsx
import PropTypes from "prop-types"function Student(props) {return (<div><p>Name: {props.name}</p><p>Age: {props.age}</p><p>isStudent: {props.isStudent ? "Yes" : "No"}</p></div>)
}Student.propTypes = {name: PropTypes.string,age: PropTypes.number,isStudent: PropTypes.bool
}export default Student

注意 Student 的 propTypes 属性与 PropTypes 导入包指向不同一,并且 prop-types 包在 react 19 中已经被弃用,我们接着介绍 React defaultProps,即默认属性值,在对应属性无输入数据时默认传递的值

// Student.jsx
import PropTypes from 'prop-types'function Student(props) {return (<div><p>Name: {props.name}</p><p>Age: {props.age}</p><p>isStudent: {props.isStudent ? "Yes" : "No"}</p></div>)
}Student.propTypes = {name: PropTypes.string,age: PropTypes.number,isStudent: PropTypes.bool,
}Student.defaultProps = {name: "default student",age: 0,isStudent: false
}export default Student

react 19 弃用 prop-types 包后,可以换用另一种方式实现 propTypes 和 defaultProps,如 TypeScript 编程代替 JavaScript 编程,因为 TypeScript 对类型的要求更规范、更严格

6条件渲染

// UserGreeting.jsx
function UserGreeting(props) {if (props.isLoggedIn) {return (<h2>Welcome {props.username}</h2>)} else {return (<h2>Please log in to continue</h2>)}
}export default UserGreeting

对于条件渲染,一般是通过 if-else 选择结构进行实现,但是也可以省略 else,直接在 if 判断完所有情况后 return,或者对于二值判断,可以使用三元表达式简化

7列表渲染

// List.jsx
function List() {const fruitsArr = ["apple", "banana", "pineapple", "orange"]const fruitItems = fruitsArr.map(fruit => <li>{fruit}</li>)const fruitObjs = [{ id: 1, name: "apple", calories: 95 },{ id: 2, name: "banana", calories: 105 },{ id: 3, name: "pineapple", calories: 37 },{ id: 4, name: "orange", calories: 45 }]const fruitObjItems = fruitObjs.map(fruitObj => <li key={fruitObj.id}>{fruitObj.name}: &nbsp;{fruitObj.calories}</li>)return (<><ul>{fruitItems}</ul><ol>{fruitObjItems}</ol></>)
}export default List

注意需要为 li 标签添加 key 属性值以区分不同的 li 标签,所以代码中的 fruitItems 渲染会在浏览器控制台中报错

不仅仅是可以通过上述形式实现列表渲染,我们也可以通过 React Props 机制实现可重用的列表渲染模块

// List.jsx
function List(props) {const category = props.categoryconst itemList = props.itemsconst listItems = itemList.map(item => <li key={item.id}>{item.name}: &nbsp;{item.calories}</li>)return (<><h3>{category}</h3><ol>{listItems}</ol></>)
}export default List
// App.jsx
import List from "./List"function App() {const fruits = [{ id: 1, name: "apple", calories: 95 },{ id: 2, name: "banana", calories: 105 },{ id: 3, name: "pineapple", calories: 37 },{ id: 4, name: "orange", calories: 45 }]return (<><List items={fruits} category={"Fruit"} /></>)
}export default App

8onClick事件

// Click.jsx
function Click() {const handleClick = () => console.log("Hello!")let count = 0const handleDoubleClick = () => {count++console.log(count)}const handleClick2 = function (name) {console.log(`Welcome ${name}!`)}return (<><button onClick={handleClick} onDoubleClick={handleDoubleClick}>Click one</button><button onClick={() => handleClick2("Jack")}>Click two</button></>)
}export default Click

注意,带参函数的点击事件绑定需要更换形式

<button onClick={handleClick2("Jack")}>Click two</button>

直接绑定带参函数会导致点击事件绑定的是函数返回值,点击后可能无响应,点击前出现的情况会是执行一遍或两边带参函数,执行两遍是因为 React 在严格模式下执行两遍以检测副作用

点击事件还可以通过 event 对象实现更多的可能性

9useState

useState 允许我们向组件添加一个状态变量,根据状态变量我们能够更新状态变量

// UseState.jsx
import { useState } from "react";function UseState() {const [name, setName] = useState("NULL")const [age, setAge] = useState(0)const [isEmpty, setIsEmpty] = useState(false)const update = () => {setName("Jack")}const increment = function () {setAge(age + 1)}const toggleEmptyStatus = () => {setIsEmpty(!isEmpty)}return (<div><p>Name: {name}</p><button onClick={update}>set name</button><p>Age: {age}</p><button onClick={increment}>increment</button><p>Is empty: {isEmpty ? "Yes" : "No"}</p><button onClick={toggleEmptyStatus}>toggle</button></div>)
}export default UseState

如果不用 useState 机制更新,而是直接在 update 中更新变量,那么将无法正常渲染

10onChange事件

onChange 事件针对于表单元素,会在每次 input 值改变时触发

// Change.jsx
import { useState } from "react";function Change() {const [name, setName] = useState("")const [age, setAge] = useState(0)const [description, setDescription] = useState("")const [gender, setGender] = useState("")const [job, setJob] = useState("")function handleNameChange(event) {setName(event.target.value)}function handleAgeChange(event) {setAge(event.target.value)}function handleDescriptionChange(event) {setDescription(event.target.value)}function handleGenderChange(event) {setGender(event.target.value)}function handleJobChange(event) {setJob(event.target.value)}return (<div><input value={name} onChange={handleNameChange} type="text" /><p>Name: {name}</p><input value={age} onChange={handleAgeChange} type="number" /><p>Age: {age}</p><textarea value={description} onChange={handleDescriptionChange} placeholder="please input desscription" /><p>Description: {description}</p><select value={gender} onChange={handleGenderChange}><option value="">choose your gender</option><option value="Male">Male</option><option value="Female">Female</option></select><p>Gender: {gender}</p><label><input value="Student" checked={job === "Student"} onChange={handleJobChange} type="radio" />Student</label><br /><label><input value="Worker" checked={job === "Worker"} onChange={handleJobChange} type="radio" />Worker</label><p>Job: {job}</p></div>)
}export default Change

11updater机制

// Updater.jsx
import { useState } from "react";function Updater() {const [count, setCount] = useState(0)function increment() {setCount(count + 1)setCount(count + 1)setCount(count + 1)}function decrement() {setCount(c => c - 1)setCount(preCount => preCount - 1)}function reset() {setCount(0)}return (<div><p>Count: {count}</p><button onClick={increment}>increment</button><button onClick={reset}>reset</button><button onClick={decrement}>decrement</button></div>)
}export default Updater

在某一个状态变量更新函数中,多次执行 set 函数的效果与一次执行的效果相同,这是因为状态变量的更新依赖于当前的状态变量,多次执行 set 函数时都依赖于相同的状态变量,所以得到的效果与一次执行相同,如 increment 函数
但是,decrement 函数将状态变量更新写为函数形式,这就会将每次更新进行传递,保持状态变量的一致性,实现多次执行的效果,并且更新函数中状态变量有两种建议方式:状态变量开头字母、pre状态变量
注意,reset 函数中并不需要写作函数形式,因为直接传递值,而不依赖于当前状态变量

12对象更新

对象更新的原理在于将原有变量所有属性进行更新并添加一属性的新值,而 JavaScript 会采用相同属性的最新值,从而实现对象更新

// ObjectUpdate.jsx
import { useState } from "react";function ObjectUpdate() {const [student, setStudent] = useState({ id: 1, name: "Jack", age: 18 })function handleIdChange(event) {setStudent({ id: 1, name: "Jack", age: 18, id: event.target.value })}function handleNameChange(event) {setStudent(c => ({ ...c, name: event.target.value }))}function handleAgeChange(event) {setStudent(c => ({ ...c, age: event.target.value }))}return (<div><p>{student.name} is {student.age} years old, this student's id is {student.id}</p><input type="number" value={student.id} onChange={handleIdChange} /><input type="text" value={student.name} onChange={handleNameChange} /><input type="number" value={student.age} onChange={handleAgeChange} /></div>)
}export default ObjectUpdate

注意,当你直接更新对象的某一属性,而忽略其他属性时,其他属性会消失,因为相当于新对象覆盖旧对象,并且新对象只包含更新的属性

13数组更新

数组类型状态变量的更新与对象类型类似

// ArrayUpdate.jsx
import { useState } from "react";function ArrayUpdate() {const [fruits, setFruits] = useState(["apple", "orange", "pineapple"])function handleAddFruit() {const newFruit = document.getElementById("fruitInput").valuedocument.getElementById("fruitInput").value = ""setFruits([...fruits, newFruit])}function handleRemoveFruit(index) {setFruits(fruits.filter((_, i) => i !== index))}return (<div><h2>List of fruit</h2><ul>{fruits.map((fruit, index) => <li key={index} onClick={() => handleRemoveFruit(index)}>{fruit}</li>)}</ul><input type="text" id="fruitInput" placeholder="input new fruit" /><button onClick={handleAddFruit}>add fruit</button></div>)
}export default ArrayUpdate

14对象数组更新

// AOOUpdate.jsx
import { useState } from "react";function AOOUpdate() {const [students, setStudent] = useState([])const [studentId, setStudentId] = useState(0)const [studentName, setStudentName] = useState("")const [studentAge, setStudentAge] = useState(0)function handleAddStudent() {const newStudent = { id: studentId, name: studentName, age: studentAge }setStudent(s => [...s, newStudent])setStudentId(0)setStudentName("")setStudentAge(0)}function handleRemoveStudent(index) {setStudent(s => s.filter((_, i) => i !== index))}function handleStudentIdChange(event) {setStudentId(event.target.value)}function handleStudentNameChange(event) {setStudentName(event.target.value)}function handleStudentAgeChange(event) {setStudentAge(event.target.value)}return (<div><h2>List of student</h2><ul>{students.map((student, index) => <li key={index} onClick={() => handleRemoveStudent(index)}>{student.name} is {student.age} years old, this student's id is {student.id}</li>)}</ul><input type="number" value={studentId} onChange={handleStudentIdChange} /><br /><input type="text" value={studentName} placeholder="input student name" onChange={handleStudentNameChange} /><br /><input type="number" value={studentAge} onChange={handleStudentAgeChange} /><br /><button onClick={handleAddStudent}>Add student</button></div>)
}export default AOOUpdate

15useEffect

useEffect 实现组件与系统之间的交互,能够在某些情况下执行特定的函数,通常包含以下三种情况:

  1. useEffect(() => {})(重新渲染则执行一次函数)

  2. useEffect(() => {}, [])(仅初始渲染时执行一次函数)

  3. useEffect(() => {}, [value])(value 数组中变量值改变时执行一次函数)

// UseEffect.jsx
import { useState, useEffect } from "react";function UseEffect() {const [count, setCount] = useState(0)const [color, setColor] = useState("black")useEffect(function () {console.log("Hello")}, [])useEffect(() => {document.title = `${count} ${color}`}, [count])function addCount() {setCount(c => c + 1)}function changeColor() {setColor(c => c === "black" ? "white" : "black")}return (<div><p>Count: {count}</p><button onClick={addCount}>add</button><button onClick={changeColor}>change color</button></div>)
}export default UseEffect

注意,在 value 数组同时包含 count 以及 color 时,其效果与重新渲染相同,因为 value 数组包含所有情况,但这并不意味着两者没有区别,当 value 数组仅包含部分情况时可以看出两者的不同

useEffect 中的执行函数可以设置 return 值,一般情况下,会对事件进行解绑操作

// UseEffectReturn.jsx
import { useState, useEffect } from "react";function UseEffectReturn() {const [width, setWidth] = useState(window.innerWidth)const [height, setHeight] = useState(window.innerHeight)useEffect(function () {window.addEventListener("resize", handleResize)return () => {window.removeEventListener("resize", handleResize)}}, [])function handleResize() {setWidth(window.innerWidth)setHeight(window.innerHeight)}return (<div><p>Width: {width}px</p><p>Height: {height}px</p></div>)
}export default UseEffectReturn

注意,上述解绑事件并不会导致事件无法正确绑定,因为只有当组件被移除时才会执行 return 操作,这样能够有效防止事件的重复绑定

16useContext

useContext 机制实现多层次组件间的通信,并且并不依赖于 React Props

// ComponentA.jsx
import { useState, createContext } from "react"
import ComponentB from "./ComponentB"export const userContext = createContext()function ComponentA() {const [user, setUser] = useState("Jack")return (<div className="box" style={{ border: "2px solid black", padding: "25px" }}><h1>Component A</h1><h2>{`Hello ${user}`}</h2><userContext.Provider value={user}><ComponentB /></userContext.Provider></div>)
}export default ComponentA
// ComponentB.jsx
import ComponentC from "./ComponentC"function ComponentB() {return (<div className="box" style={{ border: "2px solid black", padding: "25px" }}><h1>Component B</h1><ComponentC /></div>)
}export default ComponentB
// ComponentC.jsx
import ComponentD from "./ComponentD"function ComponentC() {return (<div className="box" style={{ border: "2px solid black", padding: "25px" }}><h1>Component C</h1><ComponentD /></div>)
}export default ComponentC
// ComponentD.jsx
import { useContext } from "react"
import { userContext } from "./ComponentA"function ComponentD() {const user = useContext(userContext)return (<div className="box" style={{ border: "2px solid black", padding: "25px" }}><h1>Component D</h1><h2>{`Bye ${user}`}</h2></div>)
}export default ComponentD

上述通过 useContext 机制实现多层次组件间的通信,同样可以利用 React Props 机制实现,但显然后者会更加繁琐

17useRef

useRef 机制类似于 useState 机制,但是 useState 机制中状态变量更新是需要重新渲染的,而 useRef 机制更加“纯粹”,仅更新,不重新渲染
并且 useRef 有一个特殊的作用,即指向 DOM 元素,useRef 的返回值是一个对象,并且对象中仅有一个属性 current,用以存储,而 DOM 元素同样可以被存储,只需要在某一 DOM 元素(标签)中添加 ref 属性

// UseRef.jsx
import { useState, useEffect, useRef } from "react";function UseRef() {const [stateNumber, setStateNumber] = useState(0)const refNumber = useRef(0)const inputRef = useRef(null)function addStateNumber() {setStateNumber(s => s + 1)}function addRefNumber() {refNumber.current = refNumber.current + 1console.log(`refNumber: ${refNumber.current}`)}function focusInputRef() {inputRef.current.focus()}useEffect(() => {console.log("stateNumber add one")})return (<><p>stateNumber: {stateNumber}</p><button onClick={addStateNumber}>click one</button><br /><button onClick={addRefNumber}>click two</button><br /><input ref={inputRef} /><button onClick={focusInputRef}>focus</button></>)
}export default UseRef
http://www.wuyegushi.com/news/59.html

相关文章:

  • tinymce富文本编辑器使用
  • 微软C语言编译器‘strcpy‘: This function or variable may be unsafe. Consider using strcpy_s instead
  • Java学习Day26
  • 线性基(个人学习笔记)
  • 花菖蒲 2025.7.26 模拟赛题解
  • 信任的意外反射:深入解析LLVM循环向量化器中的罕见编译错误
  • P1429 平面最近点对(加强版)[骗分解法]
  • 7.26 - GENGAR
  • P4565 [CTSC2018] 暴力写挂 题解
  • 第十二篇
  • 计算机网络——应用层 - 浪矢
  • 《第一节:跟着符映维学C语言---配置c语言开发环境》
  • 再见,大连
  • 影视软件集合分享
  • 7.26总结
  • geogebra 2 进阶
  • 20250726GF模拟赛
  • java学习
  • 深入解析Passkeys背后的密码学原理
  • CCF中学生计算机程序设计-基础篇-第一章-函数练习答案
  • 第二次总结——关系中的魔法语言
  • 2025.7 Solar应急响应-
  • 【计算几何】Largest Quadrilateral
  • 2025暑假qbxtNOIP实战梳理营Day1题目
  • 请求类型绑定响应类型
  • Untitled-1
  • AI代理性能提升实战:LangChain+LangGraph内存管理与上下文优化完整指南
  • GAIA基准测试介绍
  • 多项式全家桶(wjc)
  • 暑假qbxtNOIP实战梳理营Day1题目