React学习
React组件与jsx
使用jsx编写,最纯粹的js开发
React组件可以是一个方法,也可以是一个class
可以直接在js文件里写一段html来作为一个组件,也可以写成一个单独的jsx或者js文件
import logo from './logo.svg'; // 导入一个 SVG 格式的 logo 图片
import './App.css' // 导入组件的样式文件,为当前组件添加视觉样式
function App() { // App组件
return ( // JSX 结构,描述页面 UI
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code> src/App.js**</code>** and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App; // 是 ES6 模块系统中的语法,作用是将 App 这个组件设置为当前模块(即 App.js 文件)的默认导出项,方便其他模块导入使用
这是一个 函数式组件 (React 中定义组件的方式之一),组件的 UI 由返回的 JSX(JavaScript XML,一种类似 HTML 的语法,用于描述 React 组件的结构)描述
function App() {
return (
// JSX 结构,描述页面 UI
);
}

React组件分类
- 函数组件(配合Hook使用)
- Hello() {
return (
<div>
<p> hello**</p>**
</div>
);
}
- Class组件
- Hello extends React.Component { // 这个类继承自 React 内置的 Component 类
render() {
return ( // 返回组件要渲染的 UI 结构(通过 JSX 描述),这里返回的 JSX 结构是:一个 <div> 容器,里面包含一个 <p> 标签,标签内的文本是 hello
<div>
<p> hello**</p>**
</div>
)
}
}
React 组件的命名惯例是首字母大写
jsx的特点
- 可以直接写在js文件中
- 项目利用babel做了对js的编译,所以可以直接在js里写jsx的
- 写法接近js
- 几乎和js一样,不同点就在于,可以更方便地写html在js里
初始化react项目
创建react项目
npx create-react-app <projectName>
npx 是 npm 5.2版本后自带的一个工具,让我们可以不用安装脚手架直接去使用的一些包
npx之前:如果你想使用某个 Node 包的可执行命令(比如 create-react-app 来创建 React 项目),通常有两种麻烦的方式:
- 全局安装 :执行 npm install -g create-react-app,然后再运行 create-react-app my-app。但这样会导致:全局空间被占用,时间久了容易堆积大量 "只偶尔用一次" 的工具;不同项目可能需要不同版本的工具,全局安装无法灵活切换。
- 本地安装 + 手动执行:先在项目里 npm install create-react-app,再去 node_modules/.bin 目录里找可执行文件运行,步骤繁琐。
npx 的核心是 "临时执行,用完即走":
- 运行 npx <命令> 时,它会先在本地项目的 node_modules/.bin 目录中查找对应的可执行文件;
- 如果本地没有,就会去远程 npm 仓库下载对应的包(临时下载,执行完不会残留全局安装的包);
- 执行命令后,临时下载的包会被自动清理,不会占用本地空间。
创建完成后会得到如下内容:

默认目录结构:
- node_modules :存放项目的所有依赖包(通过 npm install 安装的第三方库都会存放在这里),是项目运行的依赖基础
- public :存放静态资源 ,这些资源不会被 Webpack 编译,直接以原始形式被浏览器访问。常见内容包括:
- index.html:React 应用的HTML 模板(应用最终会被注入到这个文件的 <div id="root"></div> 中);
- favicon.ico:浏览器标签页的图标;
- 其他静态文件(如图片、第三方脚本等)
- src:存放项目源码,是开发的核心区域,包含组件、样式、入口文件等。典型内容有:
- index.js:应用的入口文件,负责将根组件渲染到 public/index.html 的 root 节点中;
- 各种 React 组件(如 App.js);
- 样式文件(如 index.css、App.css);
- 资源文件(如图片、SVG 等)。
- .gitignore :配置 Git 版本控制的 "忽略规则",指定哪些文件 / 目录不需要被 Git 追踪
- package-lock.json :锁定依赖版本的文件,确保在不同环境(如开发机、测试机、生产机)中安装的依赖版本完全一致,避免 "依赖版本不一致导致的 Bug"
- package.json :项目的核心配置文件 ,包含:
- 项目信息(名称、版本、描述等);
- 依赖包列表(dependencies 是生产依赖,devDependencies 是开发依赖);
- 脚本命令(如 start、build、test 等,可通过 npm run <脚本名> 执行)'
- README.md :项目的说明文档,通常包含项目介绍、安装步骤、运行方式、功能说明等信息,方便其他开发者了解和使用项目
index.html 和 index.js 是启动应用的核心文件
- public/index.html:HTML 模板载体:位置 :位于 public 目录下。作用:是 React 应用在浏览器中渲染的 "容器页面" 。
- 它包含一个关键的 <div id="root"></div>,React 应用的所有组件最终都会注入到这个 div 中,形成完整的页面 UI。
- 还可用于配置页面元信息(如 <meta> 标签、标题)、静态资源(如 favicon.ico、第三方脚本)等
- <!DOCTYPE html> // 声明文档类型为 HTML5,用于规范浏览器的解析行为
<html >
<head > // 用于定义页面的元信息(不直接显示在页面主体的内容)
<title >React App</title > // 设置浏览器标签页的标题
</head >
<body > // 包含页面的可见内容
<!-- React 应用的注入点 -->
<div id="root"></div > // React 应用的挂载点,React 通过 JavaScript 代码生成的所有组件、交互内容,最终都会被 "注入" 到这个 div 中,成为页面的实际内容。
</body >
</html >
- src/index.js:应用入口与渲染桥梁:位置 :位于 src 目录下。作用:是 React 应用的 "启动入口" ,负责将 React 组件渲染到index.html中。
-
它通过 ReactDOM.render() 方法,把根组件(通常是 App.js)"挂载" 到 index.html 的 root 节点上。
-
是连接 "React 组件逻辑" 和 "HTML 页面" 的桥梁,应用的执行流程从这里开始。
-
import React from 'react'; // 导入 react 库的核心模块
import ReactDOM from 'react-dom/client'; // 导入 react-dom/client 模块,它提供了将 React 组件渲染到浏览器 DOM 中的工具
import App from './App'; // 导入根组件const root = ReactDOM.createRoot(document.getElementById('root')); // 获取 HTML 页面中 id="root" 的 DOM 元素,基于这个 DOM 元素创建一个 React 根节点(root),后续所有 React 组件都会通过这个根节点渲染到页面上
root.render( // 调用根节点的 render 方法,将组件渲染到 DOM 中
<React.StrictMode> // React 的 "严格模式"
<App /> // 被渲染的根组件,App 组件的内容会被解析为 DOM 元素,并最终显示在 id="root" 的 div 中
</React.StrictMode>
);
-
当启动 React 项目时:
- Webpack 会从 src/index.js 开始打包所有源码;
- index.js 会把根组件 <App /> 渲染到 public/index.html 的 root 节点中;
- 最终在浏览器中,你看到的页面就是 index.html 承载的、由 React 组件渲染出的完整 UI。
Webpack 是一个前端模块打包工具 ,核心作用是将项目中分散的JS、CSS、图片、字体 等各类资源(都视为 "模块"),经过处理后打包成一个或多个静态文件 ,从而优化前端项目的加载性能、管理模块依赖,并支持各种高级特性。它会从一个 "入口文件"开始,递归分析所有依赖的模块,然后将这些模块合并、转换、优化 **,最终输出少数几个(甚至一个)"打包后的文件",让浏览器可以高效加载。
像 Create React App、Vue CLI 这类脚手架工具,底层都是基于 Webpack 封装的。它们把 Webpack 的复杂配置隐藏起来,让开发者可以 "开箱即用",但核心的打包、资源处理逻辑还是由 Webpack 完成的。
- 在App.js中创建App组件:

- 在index.js中引入App组件

- 将App组件渲染到index.html中

package.json文件:
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
}
package.json 中用于定义npm 脚本命令的配置
- start :执行 react-scripts start启动本地开发服务器,开启热更新功能。开发时运行 npm run start,可以在浏览器中实时预览代码修改的效果。
- build :执行 react-scripts build构建生产环境代码。它会对项目进行压缩、优化、打包,最终生成可部署的静态文件(存放在 build 目录),这些文件可直接部署到服务器上运行。
- test :执行 react-scripts test启动测试运行器(默认是 Jest),用于执行项目中的单元测试和集成测试,确保代码逻辑的正确性。
- eject :执行 react-scripts eject这是一个不可逆操作 。Create React App 封装了 Webpack、Babel 等工具的配置,执行 npm run eject 会将所有隐藏的配置文件暴露出来,让你可以完全自定义构建流程。但执行后无法再回到 "封装状态",需谨慎使用。

运行react项目
npm run start
启动本地开发服务器,跑开发模式

在App.js中定义的内容
React的state
state是组件要用的数据
前端框架(React、Vue)实现 "数据响应式展示 :前端的核心任务之一是把从后端(或其他渠道)获取的数据呈现到页面上,且当数据更新时,页面要自动响应变化(无需手动操作 DOM)
这类框架的价值就在于 "让数据和页面联动"------ 把请求到的数据渲染到页面,且数据更新时,页面能自动同步新数据。
React 中的state是组件的 "响应式数据容器",当state里的数据变化时,组件会重新渲染,页面随之更新。
Vue 中的data是实例的 "响应式数据对象",当data里的数据变化时,Vue 会自动更新对应的 DOM。
两者作用一致:让数据变化驱动页面自动更新,实现 "响应式" 的交互体验。
src/components文件夹放组件
src/components/test1.jsx文件放test1组件;src/components/test2.jsx文件放test2组件(组件写成js也是OK的)
test1.jsx文件写函数组件
- 组件首字母必须大写
- App.js文件中引入test1组件
-
'./App.css';
import Test1 from "./components/test1";function App() {
return (
<div className="App">
<Test1></Test1>
</div>
);
}export default App;
- state的编写需要结合Hook,使用 useState Hook 管理组件状态;useState 是 React 提供的状态 Hook ,用于在函数组件中声明 "响应式状态";objA:状态变量,初始值是一个对象{ a: 123 };setA:更新状态的函数,当调用 setA(newValue) 时,会修改 objA 的值,并触发组件重新渲染;普通变量的修改不会触发组件重新渲染,页面上显示的 b 值不会更新;而状态变量通过 setA 修改后,会自动重新渲染组件,页面同步更新
- React, { useState } from "react" // 导入 React 核心库和 useState Hook
//函数组件
function Test1() {
let [objA, setA] = useState({
a: 123 // objA:状态变量
})
let b = "i am b"; // 普通的 JavaScript 变量
return ( // 组件返回的 JSX 结构,定义了页面要显示的内容
<div>
<p> {objA.a}</p>
<p> {b}</p>
</div>
)
}
export default Test1 // 将 Test1 组件设置为默认导出
test,jsx文件写class组件
- 继承React的Component,所以要引入React
- 写一个构造函数,构造函数是类组件初始化时第一个被调用的方法 ,用于初始化组件的状态(state)、绑定事件处理函数等;接收父组件传递给当前组件的属性(props),用于组件间的数据传递;super() 用于调用父类(React.Component)的构造函数,这是子类构造函数中必须执行的步骤 ,传递 props 作为参数,是为了在构造函数中能通过 this.props 访问父组件传递的属性;this.state 是类组件用于存储内部可变数据的对象,当 state 中的数据发生变化时,组件会自动重新渲染(响应式更新)。
- React 类组件必须实现 render() 方法(用于返回要渲染的 JSX 结构),否则组件无法正常渲染到页面
- App.js文件中也引入test2组件
-
'./App.css';
import Test1 from "./components/test1";
import Test2 from "./components/test2";function App() {
return (
<div className="App">
<Test1></Test1>
<Test2></Test2>
</div>
);
}export default App;
-
React from "react";
class Test2 extends React.Component {
constructor(props) {
super (props);
this .state = {
a: 111,
arr: [1, 23, 3]
}
}render() {
return (
<div>
<p> {this .state.a}</p>
</div>
);
}
}export default Test2
state的特点:
- 直接修改数据是无效的,必须通过对应的方法修改 state 才能触发视图更新;在 React 中,无论是类组件的 this.state 还是函数组件的 useState 状态 ,直接修改状态变量本身不会触发组件重新渲染。
- useState 返回的 "更新函数"(如 setCount);类组件:使用 this.setState() 方法;
- 是通过 "状态更新方法" 来感知状态变化的,只有通过这些方法,React 才会触发组件重新渲染,进而更新页面视图。
- State 的修改是一个浅合并的过程,State 的修改是与原 state 对象合并的过程;当修改 state 时,React 会将新状态与原状态进行 "浅层次合并"(只更新状态中指定的属性,未被指定的属性会保留原状态,且仅合并第一层属性)。
错误的:
import React, { useState } from "react"
//函数组件
function Test1() {
let [objA, setA] = useState({
a: 123
})
let b = "i am b";
setTimeout(()=> { // 错误的状态更新方式,必须通过 useState 返回的更新函数(如 setA)修改状态,直接修改状态变量 objA 不会触发组件重新渲染,页面也不会更新
objA.a=999
},1000)
return (
<div>
<p> {objA.a}</p>
<p> {b}</p>
</div>
)
}
export default Test1
import React from "react";
//class组件
class Test2 extends React.Component {
constructor(prop) {
super (prop);
this .state = {
a: 111,
arr: [1, 23, 3]
}
}
render() {
setTimeout(()=> { // 错误的状态更新方式,在 React 类组件中,不能直接修改 this.state,这种操作不会触发组件重新渲染,页面也不会同步更新
this .state.a = 888
}, 1000)
return (
<div>
<p> {this .state.a}</p>
</div>
)
}
}
export default Test2
正确的:
import React, { useState } from "react"
function Test1() {
const [objA, setA] = useState({
a: 123
})
const b = "i am b";
setTimeout(()=> {
// 通过 setA 函数更新状态,触发重新渲染;浅合并:prev 代表原状态对象,...prev 是 ES6 展开运算符,用于复制原状态的所有第一层属性;然后用 a: 999 覆盖原 a 属性
setA(prev => ({ ...prev, a: 999 }))
},1000)
return (
<div>
<p> {objA.a}</p>
<p> {b}</p>
</div>
)
}
export default Test1
import React from "react";
//class组件
class Test2 extends React.Component {
constructor(prop) {
super (prop);
this .state = {
a: 111, // 第一层属性,值
arr: [1, 23, 3], // 另一个第一层属性,数组
user: { name: "张三", age: 18 }, // 嵌套对象(深层属性),对象
}
}
render() {
setTimeout(()=> {
this .setState({ a: 888 }); // 通过 setState 修改状态;浅合并:只更新了 a 这个第一层属性,其他第一层属性(arr、user)完全保留原状态,没有被清空或替换
}, 1000)
return (
<div>
<p> {this .state.a}</p>
</div>
)
}
}
export default Test2
// 原状态:info: { city: "北京", zip: "100000" }
setA(prev => ({
info: { zip: "200000" } // 直接替换 info 对象(city 丢失),同样需要手动复制深层属性:info: { ...prev.info, zip: "200000" }
}));
// 原状态:user: { name: "张三", age: 18 }
this .setState({
user: { age: 19 } // 直接替换整个 user 对象(而非仅修改 age);合并后 user 会变成 { age: 19 }(name 属性丢失),因为 setState 只浅合并第一层,不会自动保留深层属性。必须手动复制深层属性:user: { ...prev.user, age: 19 }
});
数组的state修改:
render() {
setTimeout(() => {
let _arr = this .state.arr; // 创建新数组引用
_arr.push(1);
this .setState({
arr: _arr
})
}, 1000)
return (
<div>
<p> {this .state.a}</p>
<p> {this .state.arr[0]}</p>
<p> {this .state.arr[1]}</p>
<p> {this .state.arr[2]}</p>
<p> {this .state.arr[3]}</p>
</div>
)
}
对象的state修改:
render() {
setTimeout(() => {
let _obj = this .state.obj; // 创建新对象引用
_obj.obj1 = 777;
this .setState({
obj: _obj
})
}, 1000)
return (
<div>
<p> {this .state.a}</p>
<p> {this .state.obj.obj1}</p>
</div>
)
}
循环渲染和条件渲染
数组的所有值都渲染,需要用到循环渲染,jsx赋予html使用js结合的能力,可以直接在返回的jsx结构中使用循环,循环需要给一个key值,会让循环性能变高
// class组件的循环渲染
return (
<div>
<p> {this .state.a}</p>
<p> {this .state.obj.obj1}</p>
{
this .state.arr.map((item) => { // map可以将所有东西变成一个数组
return (
<div>
<h1> {item.title}</h1>
<p> {item.content}</p>
</div>
)
})
}
</div>
)
// 或者
render() {
let arr = []; // 创建新数组
this .state.arr.forEach((item) => {
arr.push(
<div key={item.title}>
<h1> {item.title}</h1>
<p> {item.content}</p>
</div>
);
});
}
// 或者
render() {
let arr = [];
this .state.arr.forEach((item) => { // 为每个 item 创建包含 title 和 content 的 JSX 结构
let _itemText = <div key={item.title}>
<h1> {item.title}</h1>
<p> {item.content}</p>
</div> ;
arr.push(_itemText);
});
}
// 或者
function createArrList() { // 定义一个新函数
let arr = [];
this .state.arr.forEach((item) => {
let _itemText = (
<div key={item.title}>
<h1> {item.title}</h1>
<p> {item.content}</p>
</div>
);
arr.push(_itemText);
});
return arr;
}
// 函数组件的条件渲染
import { react, useState } from "react"
function Test1() {
let [objA, setA] = useState({
a: 123
})
let [show, changeShow] = useState(true );
function showDiv(){ // 写一个函数
if (show){
return <div> 123**</div>**
}
}
let b = "i am b";
setTimeout(() => {
setA({ a: 999 })
}, 1000)
return (
<div>
<p> {objA.a}</p>
<p> {b}</p>
{showDiv()}
</div>
)
}
export default Test1
// 或者
return (
<div>
{show?<div> 123**</div>** :""};
</div>
)
// 或者
return ( // && 第一个成立了才会执行后面的操作
<div>
{show && <div> 123**</div>** };
</div>
)
// let [show, changeShow] = useState(0);面板的切换选择
function showTab(){ // 写一个函数
if (show == 0){
return <div> i am 0**</div>**
}else if (show == 1){
return <div> i am 1**</div>**
}else {
return <div> i am 2**</div>**
}
}
setTimeout(() => {
changeShow(1) // 1秒钟后0会变成1
},1000)
事件绑定
给用户操作界面,需要给元素绑定一些事件让用户进行操作时会有一些对应的响应
设置一个按钮,点击按钮,会将false变成true
import React, { useState } from "react"
function Test1() {
let [objA, setA] = useState({
a: 123
})
let [show, changeShow] = useState(false );
function showTab() {
if (show) return <div> 123**</div>**
}
function changeDiv(){ // 写一个函数,切换show的值
let _show = !show
changeShow(!show);
}
return ( // 绑定事件onClick与函数changeDiv
<div>
<p> {objA.a}</p>
{showTab()}
<button onClick={changeDiv}> {show ? '隐藏' : '显示'}</button>
</div>
)
}
export default Test1
// 绑定事件时如何传参
import { react, useState } from "react"
function Test1() {
let [objA, setA] = useState({
a: 123
})
let [show, changeShow] = useState(false );
function showTab() {
if (show) return <div> 123**</div>**
}
function changeDiv(num) { // 事件处理函数,传参
console.log(num); // 打印传入的参数
let _show = !show;
changeShow(_show);
}
return ( // 通过 bind(this, 123) 传递参数 123 并绑定函数 changeDiv
<div>
<p> {objA.a}</p>
{showTab()}
<button onClick={changeDiv.bind(this , 123)}> {show ? '隐藏' : '显示'}</button>
</div>
)
}
export default Test1
//
function changeDiv(num, num2, e) { // 函数接收 num、num2和事件对象 e(会自动加上e)
e.stopPropagation(); // 阻止事件冒泡,阻止当前事件向父元素传播
let show = !show;
changeShow(show);
}