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 都会立即重新渲染,效率低
相关推荐
用户461169657283 分钟前
网站 HTTPS 不稳定?证书排查指南
前端
高晶10 分钟前
Android 通讯录应用开发实战:从零搭建 SQLite 数据持久化完整项目
android·设计模式·前端框架
n876515 分钟前
Playwright Java WEB UI 自动化实现框架与逻辑
java·前端·ui
keyipatience17 分钟前
TypeScript学习(1)
前端·javascript·学习·typescript·html·css3
xifangge20251 小时前
基于 JavaScript 实现 CNC 数控切削参数与螺纹公差纯前端核算引擎
开发语言·前端·javascript
wangruofeng1 小时前
2026 Web 技术选型指南,AI 时代如何选择适合项目的技术栈
前端·aigc·ai编程
honkun61 小时前
vue 表格组件 vxe-table 修改滚动条样式,修改滚动条样式,统一宽度、颜色与自动隐藏
前端·javascript·vue.js
爱勇宝2 小时前
研究数字孪生的这半年,做了个 Demo,也有了一些新想法
前端·程序员
不可能片场2 小时前
工程实践 一个缓存把选片从十分钟压到一分钟
前端·electron
前端小万2 小时前
探索副业五个月,最终以放弃告终
前端·后端·面试