react(一)

命令式编程,

声明式编程

react核心思想:UI = f(State)。状态驱动视图更新

事件驱动:通过事件驱动(改变)视图内容的变化

搭建项目

开发环境:vite

安装pnpm:

c 复制代码
npm install -g pnpm

创建项目的命令:

c 复制代码
npm create vite@latest

常见的 "Variant" 选项解析

不同框架对应的变体选项不同,最常见的是关于语言(JavaScript 或 TypeScript)和编译工具(如 Babel 或 SWC)的选择。

以下是几种典型场景:

  1. react 框架:
  • React:使用 Babel 进行代码转译,生态成熟稳定。
    使用场景:项目较小、团队熟悉 Babel 生态、或需要兼容性较好的场景。
  • React + TypeScript:在 Babel 转译的基础上,加入 TypeScript 类型系统。
    使用场景:希望使用 TypeScript 但偏好传统 Babel 工具链的 React 项目。
  • React + SWC:使用 SWC (Speedy Web Compiler) 进行转译,构建和热重载速度极快。
    使用场景:中大型项目、对构建性能有高要求、追求极致开发体验的新项目。
  • React + TypeScript + SWC:结合 TypeScript 类型检查与 SWC 的高速编译能力。
    使用场景:强烈推荐给大多数希望同时使用 TypeScript 和追求高性能的现代 React 项目。
  1. Vue 框架:
  • Vue:标准的 Vue 3 项目,使用 JavaScript。
    使用场景:使用 JavaScript 的通用 Vue 项目。
  • Vue + TypeScript 在 Vue 项目的基础上集成了 TypeScript。
    使用场景:需要使用 TypeScript 进行类型校验的 Vue 项目。
  1. 其他框架
  • JavaScript / TypeScript 对于 Preact、Svelte、Solid 等框架,变体通常是在基础框架和集成了 TypeScript 的版本之间选择。
    使用场景:根据项目是否需要静态类型检查来决定。

项目启动成功:

复制地址 http://localhost:5173/,在网页上打开

文件介绍

根目录关键文件

  • package.json
    项目配置文件,定义了项目名称、版本、依赖包、运行脚本(如 dev、build)等。
  • vite.config.ts
    Vite 构建工具的配置文件,用于优化开发服务器和打包流程。
  • tsconfig.json & tsconfig...json*
    TypeScript 编译配置,分别针对项目整体、应用代码和 Node 环境。
文件 用途 配置目标 包含文件
tsconfig.json 基础配置 提供通用配置,供其他文件继承 不直接编译任何文件
tsconfig.app.json 前端应用配置 浏览器环境 (React/Vue 等) src/目录下的所有文件
tsconfig.node.json 构建工具配置 Node.js 环境 (Vite/构建脚本) vite.config.ts等配置文件和脚本
  • .gitignore
    指定哪些文件/文件夹不应被 Git 版本控制跟踪(如 node_modules)。
  • eslint.config.js
    ESLint 静态代码检查工具的配置文件,用于统一代码风格。
  • pnpm-lock.yaml
    锁定依赖版本,确保团队协作或部署时依赖一致性。

核心文件夹

  • node_modules/
    存放项目所有依赖的第三方库(安装 npm 包后自动生成)。
  • public/
    存放静态公共资源(如图标、全局字体),该目录下的文件会被直接复制到构建产物的根目录。
    favicon.svg:网站标签页图标
    icons.svg:可能存放多个 SVG 图标集合
  • src/
    项目源码目录,开发者主要工作区域:
    • assets/:存放图片、字体等模块化静态资源
    • App.tsx:React 应用的根组件
    • App.css:根组件的样式文件
    • main.tsx:项目入口文件,负责渲染根组件到 DOM
      -i ndex.css:全局样式文件
  • 其他配置文件
    • index.html:项目主页面模板,Vite 会自动注入打包后的脚本
    • README.md:项目说明文档(通常包含启动方式、功能简介等)

认识 jsx

1. jsx 是什么

JSX 是 JavaScript XML​ 的缩写。它是一种 JavaScript 的语法扩展,允许你在 JavaScript 代码中编写类似 HTML 的结构。

简单说,JSX 让你能在 JavaScript 里写 HTML。

js 复制代码
// 变量值是 html 的语法
const element = <h1 className="greeting">Hello, world!</h1>;

2. JSX 用来干嘛?

JSX 的核心目的只有一个:用来描述 UI 应该长什么样。

在 React 中,UI 是通过"组件"来构建的。JSX 让你能以一种 声明式、结构化、视觉上直观 ​ 的方式来描述组件的结构,而不是用一堆函数调用来"拼凑"它。这极大地提升了代码的可读性和开发效率。

你可以把它理解为 React 组件的"模板语法",但它比传统模板更强大,因为它完全是 JavaScript 的一部分。

3. JSX 怎么编译?

浏览器不认识 JSX!

所以,JSX 代码在最终交给浏览器运行之前,必须被编译成标准的 JavaScript 代码 (即 React.createElement(...)函数调用)。

这个过程通常由 Babel​ 这个工具来完成。

编译过程:

JSX-> (通过 Babel 编译) -> 普通的 JavaScript 函数调用-> (React 处理) -> React 元素-> (React DOM 渲染) -> 真实的 DOM

编译示例:

js 复制代码
// 用 JSX 创建元素
const element = <h1 className="greeting">Hello, world!</h1>;

// Babel 帮你编译成的 JS
const element = React.createElement(
  'h1',
  { className: 'greeting' },
  'Hello, world!'
);

现代 React 项目(如用 Create React App, Vite 创建的)通常都内置配置好了 Babel,所以你无需手动操作,写 JSX 就行,工具链会自动完成编译。

4. JSX 的写法

核心规则:​ JSX 看起来像 HTML,但本质是 JavaScript 对象。遵循以下几个关键规则:

  1. 只能有一个根元素
    JSX 表达式必须有一个顶层标签包裹。
js 复制代码
// ❌ 错误!返回了两个同级元素
const element = (
  <h1>标题</h1>
  <p>段落</p>
);

// ✅ 正确!用一个 div 或空标签 <> 包裹
const element = (
  <div>
    <h1>标题</h1>
    <p>段落</p>
  </div>
);

// ✅ 也可以用 Fragment(幽灵标签),不会产生实际 DOM
const element = (
  <>
    <h1>标题</h1>
    <p>段落</p>
  </>
);
  1. 标签必须闭合
    无论是单标签还是双标签,都必须闭合。
js 复制代码
// 单标签
<img src="..." alt="..." />  // 单标签必须用 `/` 闭合
<br />
<input type="text" />

// 双标签
<div>内容</div>  // 双标签正常闭合
  1. 使用 className代替 class,htmlFor代替 for
    因为 class和 for是 JavaScript 的保留字。
js 复制代码
// ❌
<div class="box">...</div>
<label for="name">Name:</label>

// ✅
<div className="box">...</div>
<label htmlFor="name">Name:</label>
  1. 在 JSX 中嵌入 JavaScript 表达式:用大括号 {}
    这是 JSX 最强大的特性。在大括号里,你可以写任何有效的 JavaScript 表达式(变量、函数调用、三元运算符等)。
js 复制代码
const name = '吃西瓜的年年';
const isLoggedIn = true;
const user = { avatarUrl: '...' };
const hasName = true;

const element = (
  <div>
    <h1>Hello, {name}!</h1> // 输出:Hello, 吃西瓜的年年! 
    <p>1 + 2 等于 {1 + 2}</p> //输出:1 + 2 等于 3
    <img src={user.avatarUrl} />
    // 条件渲染,三元表达式
    {isLoggedIn ? <button>退出</button> : <button>登录</button>}
    // 逻辑与 (&&) hasName 为true时就渲染 后面的 h1标签内容
    { hasName && <h1>Hello, {name}!</h1> }
    // 调用函数
    <p>时间:{new Date().toLocaleTimeString()}</p>
  </div>
);
  1. 行内样式:用对象传递
    样式属性名要写成小驼峰形式。
js 复制代码
// ❌
<div style="color: red; font-size: 20px;">...</div>

// ✅
const divStyle = {
  color: 'red',
  fontSize: '20px', // font-size -> fontSize
  backgroundColor: '#fff' // background-color -> backgroundColor
};
<div style={divStyle}>...</div>

// 或者直接写
<div style={{ color: 'red', marginTop: '10px' }}>...</div>
  1. 事件处理:属性名用小驼峰
    事件处理属性名都必须以 on开头 ,后面跟随事件名称,并且采用小驼峰命 名法。
    写法on+ 事件名(首字母大写)={方法名}
    如:onClick={handleClick},onMouseOver={handleMouseOver}
js 复制代码
<button onClick={handleClick}>点击</button>
<input onChange={handleInputChange} />
// 传递自定义参数:事件绑定的位置改造成箭头函数的写法,在执行clickHandler实际处理业务函数的时候传递实参
<button onClick={()=>clickHandler('jack')}>click me</button>

const clickHandler = (name)=>{
   console.log('button按钮点击了', name)
}

// 使用事件参数:在事件回调函数中设置形参e即可
<button onClick={clickHandler}>click me</button>
const clickHandler = (e)=>{
   console.log('button按钮点击了', e)
}

// 同时传递事件对象和自定义参数:在事件绑定的位置传递事件实参e和自定义参数,clickHandler中声明形参,注意顺序对应
<button onClick={(e)=>clickHandler('jack',e)}>click me</button>
const clickHandler = (name,e)=>{
   console.log('button按钮点击了', name,e)
}
  1. 列表渲染 (使用 map)
    为每个项返回 JSX,并给一个唯一的 key属性(通常是数据ID),帮助 React 高效更新。
js 复制代码
const items = ['React', 'Vue', 'Angular'];

const list = (
  <ul>
    {items.map((item, index) => (
      <li key={index}>{item}</li> // 尽量用稳定 ID,而非 index
    ))}
  </ul>
);

组件化开发

1. React组件的基本概念

React组件类似于JavaScript函数,可以接受任意属性(Props),并返回页面显示的内容。

在 react 应用中,页面上所有的内容都是由一个个组件构成的。

组件可以并列,可以嵌套一个又一个。

组件的特点:

  • 独立性:每个组件管理自己的状态和UI
  • 可复用性:同一组件可在多处使用
  • 可组合性:组件可嵌套形成复杂UI
  • 单向数据流:数据从父组件流向子组件

2. 组件的基本结构

定义组件有两种方式:一种是函数式组件(function component),另一种是类组件(class component)

函数式组件(推荐)

js 复制代码
function Badge() {
  return <h1>Hello, my name is 吃西瓜的年年</h1>;
}

类组件 React 16.8之前的主流

js 复制代码
class Badge extends React.Component {
  render() {
    return <h1>Hello, my name is 吃西瓜的年年</h1>;
  }
}

父子组件的使用

子组件:

在 src 下建立 components 文件夹,再建立 helloWorld.tsx 文件


子组件:

1.helloWorld.tsx

js 复制代码
// 具名导出
export const HelloWorld = () => {
  return <div>Hello, World</div>
}

父组件

根目录的 app.tsx,改为以下内容

js 复制代码
import './App.css'
// 导入子组件 组件名首字母要大写
import { HelloWorld } from './components/1.helloWorld'

function App() {
	// 引用子组件
	// 返回值就是组件的实体内容
	// 页面上展示的是子组件的内容
  return <HelloWorld />
}
export default App

打开页面就能看到

3. 组件通信的基础

父传子:Props

props 是 properties 的简写形式,它们仅指 React 组件的内部数据。

  • 父组件 → 子组件:通过props传递数据
  • 只读属性:子组件不能直接修改props
  • 它们使用与 HTML 属性相同的语法,例如 <div id="name"></div>里的id="name"

注意:

  • props可以传递任意的合法数据,比如数字、字符串、布尔值、数组、对象、函数、JSX
  • props是只读对象
    子组件只能读取props中的数据,不能直接进行修改, 父组件的数据只能由父组件修改

子组件:

1.helloWorld.tsx

js 复制代码
// 定义 props 类型
interface HelloWorldProps {
	title: string,
	num: number
}
// 使用父组件传进来的数据
export const HelloWorld = (props: HelloWorldProps) => {
  // 解构数据
  const { title, num } = props
  // 使用数据
  return <div>Hello, World {title} { num }</div>
}

父组件

根目录的 app.tsx

js 复制代码
import './App.css'
import { HelloWorld } from './components/1.helloWorld'

function App() {
  // 父组件以属性的形式给子组件传数据
  return <HelloWorld title='hello, world' num={10} />
}

export default App

子传父:事件处理

基础概念:父组件通过 props 传递一个回调函数给子组件,子组件调用这个函数并传递数据。

  • 事件名为驼峰命名
  • 传递一个函数作为事件处理程序

子组件:

1.helloWorld.tsx

js 复制代码
// 定义 props 类型
interface HelloWorldProps {
  title: string,
  // 定义一个函数的 props,类型为不接受参数,无返回值的函数
  onHandleClick: () => void
}
// 使用父组件传进来的数据
export const HelloWorld = (props: HelloWorldProps) => {
  // 解构数据
  const { title, onHandleClick } = props
  // 使用父组件传进来的函数
  return <div onClick={onHandleClick}>{title}</div>
}

父组件

根目录的 app.tsx

js 复制代码
import './App.css'
import { HelloWorld } from './components/1.helloWorld'

function App() {
  const handleClick = () => { // 传递给子组件的函数
    console.log('click')
  }
  return (
    <>
      {/* 事件名采用驼峰命名方式 */}
      <HelloWorld title='hello, world' onHandleClick={handleClick} />
    </>
  )
  
  
}

export default App

函数带有带有参数,能把子组件的数据传给父组件
子组件:

1.helloWorld.tsx

js 复制代码
import { useState } from "react"

// 定义 props 类型
interface HelloWorldProps {
  title: string;
  onChange?: (count: number) => void; // 带参数
}
export const HelloWorld = (props: HelloWorldProps) => {
  // 解构数据
  const { title, onChange } = props
  const [count, setCount] = useState(0)

  const handleAddd = () => {
  // count + 1是异步,count还没 + 1,所以下面还是count + 1。下面会讲解到
    setCount(count + 1)
    // 传参数
    onChange?.(count + 1)
  }
  // 使用父组件传进来的函数
  return (
    <div>
      {title} --- {count}
      <button onClick={handleAddd}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
    </div>
  )
}

父组件

根目录的 app.tsx

js 复制代码
import './App.css'
import { HelloWorld } from './components/1.helloWorld'

function App() {
  const handleChange = (count: number) => {
    // 接受子组件传过来的参数
    console.log('change:', count);
  }
  return (
    <>
      <HelloWorld title='hello, world' onChange={handleChange} />
    </>
  )
  
  
}

export default App

Render Props

Render Props 是一种在 React 组件之间共享代码的技术,通过一个值为函数的 prop 来实现。这个函数返回一个 React 元素,父组件调用这个函数来渲染内容。

简单来说就是用来传递函数,类似Vue的插槽

子组件:

1.helloWorld.tsx

js 复制代码
// 定义 props 类型
interface HelloWorldProps {
	title: string,
  // 父组件的 <div>你好</div> 属于 React.ReactNode 类型
	render?: () => React.ReactNode // // "?"表示这是一个可选的prop,不是必填,父组件不用也不会报错
}
// 使用父组件传进来的数据
export const HelloWorld = (props: HelloWorldProps) => {
  // 结构数据
  const { title, render } = props
  // render 为函数,需要加括号调用。内容是根据父组件的数据渲染的
  return <div>Hello, World {title} { render?.() }</div>
}

父组件

根目录的 app.tsx

js 复制代码
import './App.css'
import { HelloWorld } from './components/1.helloWorld'

function App() {
  // 父组件以属性的形式给子组件传数据
  return <HelloWorld title='hello, world' render={() => <div>你好</div>} />
}

export default App

状态提升

当多个子组件需要反映相同的数据变化时,我们通常将共享的状态提升到它们最近的共同父组件中去。这就叫状态提升 。在 React 中,状态提升是一种常见的模式,用于在多个组件之间共享状态

子组件:

1.helloWorld.tsx

js 复制代码
import { useState } from "react"

// 定义 props 类型
interface HelloWorldProps {
  title: string,
}
// 使用父组件传进来的数据
export const HelloWorld = (props: HelloWorldProps) => {
  // 解构数据
  const { title } = props
  // 使用 useState 存储组件的数据,被多次调用时数据有各自的状态,不会混在一起
  // 下面有关于useState的详解
  const [count, setCount] = useState(0)
  // 使用父组件传进来的函数
  return (
    <div>
      {title} --- {count}
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
    </div>
  )
}

父组件

根目录的 app.tsx

js 复制代码
import './App.css'
import { HelloWorld } from './components/1.helloWorld'

function App() {
  return (
    <>
      {/* 多次使用同一组件 */}
      <HelloWorld title='hello, world' />
      <HelloWorld title='hello, world' />
    </>
  )
}

export default App

4. 状态管理:useState

React Hook 是 React 16.8 版本引入的一项重要特性,它让你在函数组件中也能使用 state(暂存组件的数据) 以及其他 React 特性(如生命周期方法、上下文等),而无需编写 class 组件。

1. 什么是 useState?**

1.1 理解"状态"

  • 想象一下,你正在做一个记数器应用:
  • 用户每点一次按钮,数字就增加 1
  • 这个"数字"就是状态
  • 它会在程序运行过程中变化

在 React 中,状态就是组件内部可以变化的数据。

1.2 为什么需要 useState?

在没有 Hooks 之前,函数组件被称为"无状态组件",因为它们无法存储变化的数据。比如:

js 复制代码
// ❌ 这样不行!普通变量改变不会让页面更新
function Counter() {
  let count = 0;  // 普通变量
  
  const handleClick = () => {
    count = count + 1;  // 变量变了,但页面不会重新显示
    console.log(count); // 控制台能看到变化,但屏幕上还是 0
  };
  
  return (
    <div>
      <p>计数: {count}</p>  {/* 永远显示 0 */}
      <button onClick={handleClick}>增加</button>
    </div>
  );
}

问题:普通变量改变后,React 不知道要重新渲染页面。

解决方案:使用 useState,它能让 React "记住"数据,并在数据改变时自动更新页面。

js 复制代码
// ✅ 使用 useState
function Counter() {
  const [count, setCount] = useState(0);  // 状态变量
  
  const handleClick = () => {
    setCount(count + 1);  // 改变状态,页面自动更新
  };
  
  return (
    <div>
      <p>计数: {count}</p>  {/* 每次点击都会显示新数字 */}
      <button onClick={handleClick}>增加</button>
    </div>
  );
}

1.3 useState 的本质

useState 就像一个"记忆盒子":

  • 它可以存储一个值
  • 当这个值改变时,React 会自动重新渲染组件
  • 每次渲染,你都能拿到最新的值

2. useState 怎么用?

2.1 基本语法

js 复制代码
import React, { useState } from 'react';

function MyComponent() {
  // 解构赋值: [当前值, 修改值的函数] = useState(初始值)
  const [value, setValue] = useState(初始值);
  
  return (
    // JSX 代码
  );
}

语法解释:

  • useState 是一个函数,调用它会返回一个数组
  • 数组的第一个元素是当前的状态值
  • 数组的第二个元素是修改状态的函数
  • 括号里的参数是初始值

2.2 理解解构赋值

我们来拆解一下const [count, setCount] = useState(0) 这种写法:

js 复制代码
// useState(0) 返回一个数组,比如: [0, function]
const result = useState(0);
console.log(result);  // [0, ƒ]

// 传统的写法
const count = result[0];      // 状态值
const setCount = result[1];   // 修改函数

// 解构赋值的写法(更简洁)
const [count, setCount] = useState(0);

2.3 命名规范

状态名 :用描述性的名词
修改函数名:通常用 set + 状态名(驼峰命名)

js 复制代码
const [count, setCount] = useState(0);
const [userName, setUserName] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [items, setItems] = useState([]);

3. 示例

js 复制代码
import React, { useState } from 'react';

function Counter() {
  // 1. 声明状态:count 初始值为 0
  const [count, setCount] = useState(0);
  
  // 2. 定义事件处理函数
  const addOne = () => {
    setCount(count + 1);  // 把 count 加 1
  };
  
  const subtractOne = () => {
    setCount(count - 1);  // 把 count 减 1
  };
  
  const reset = () => {
    setCount(0);  // 重置为 0
  };
  
  // 3. 渲染页面
  return (
    <div style={{ textAlign: 'center', marginTop: '50px' }}>
      <h1>计数器示例</h1>
      <h2 style={{ fontSize: '48px' }}>{count}</h2>
      <button onClick={addOne} style={{ margin: '5px' }}>➕ 增加</button>
      <button onClick={subtractOne} style={{ margin: '5px' }}>➖ 减少</button>
      <button onClick={reset} style={{ margin: '5px' }}>🔄 重置</button>
    </div>
  );
}

export default Counter;

对象状态

js 复制代码
import { useState } from "react"

export const BasicState = () => {
  const [info, setInfo]  = useState({
    num: 0
  })
  const handleAdd = () => {
    // 响应式修改:
    // 1.创建新对象,覆盖旧对象
    setInfo({
      ...info,
      num: info.num + 1
    })
    // 2.回调函数式写法:(pre) => (); pre就是原始的info(推荐)
    setInfo((pre) => ({
      ...pre,
      num: pre.num + 1
    }))
  }
  return (
    <>
      <div>
        {info.num}
        <button onClick={handleAdd}>+</button>
      </div>
    </>
  )
}

4. 注意事项

1. 状态是不可变的!

错误示范:

js 复制代码
// ❌ 直接修改状态
const [user, setUser] = useState({ name: '小明', age: 18 });

// 这样不行!
user.age = 19;
setUser(user);  // React 认为没有变化,不会重新渲染

// ❌ 数组也不能直接修改
const [list, setList] = useState(['a', 'b']);

list.push('c');  // 直接修改数组
setList(list);    // 不会触发更新

正确做法:

js 复制代码
// ✅ 创建新的对象
setUser({ ...user, age: 19 });  // 展开原对象,覆盖 age

// ✅ 创建新的数组
setList([...list, 'c']);        // 展开原数组,添加新元素
setList(list.filter(item => item !== 'a'));  // 过滤后返回新数组
  • React 使用 Object.is() 比较状态是否改变
  • 如果对象引用相同,React 认为没变化,不会重新渲染
  • 所以必须创建新的对象或数组
  1. 多次更新合并
js 复制代码
function MergeExample() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    // setCount参数是 数字
    // ❌ 这样只会加 1,而不是 3
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
    // 因为 count 始终是旧值,相当于:0+1, 0+1, 0+1
    
    // setCount参数是 函数
    // ✅ 这样会加 3
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
    // prev 依次是:0→1→2
  };
  
  return <button onClick={handleClick}>计数: {count}</button>;
}

为什么要异步?

  • 性能优化:避免多次更新多次渲染
  • 如果同步更新,每次 setCount 都会立即重新渲染,效率低
相关推荐
sunxunyong5 小时前
集群增加用户&权限
前端·javascript·vue.js
小年糕是糕手5 小时前
【35天从0开始备战蓝桥杯 -- Day6】
开发语言·前端·网络·数据库·c++·蓝桥杯
console.log('npc')5 小时前
2026前端进阶学习路线
前端·学习
每天都要进步哦5 小时前
React入门和快速上手
前端·vue.js·react.js·react
wuhen_n5 小时前
组件测试策略:测试 Props、事件和插槽
前端·javascript·vue.js
Jiude5 小时前
Skill + MCP + Linear 自动化工作流:让 AI 包揽变更日志工作
前端·架构·cursor
zhensherlock5 小时前
Protocol Launcher 系列:Pika 取色器的协议控制(上篇)
前端·javascript·macos·typescript·github·mac·view design
蚂蚁家的砖5 小时前
基于 Vue 3 + Cesium 的 DJI 无人机航线规划系统技术实践
前端·无人机
inksci5 小时前
推荐动态群聊二维码制作工具
前端·javascript·微信小程序