引言

本文将开始React最新框架研究。

React 是由 Facebook(现 Meta)开发并于 2013 年开源的 JavaScript 库,专门用于构建用户界面,特别是单页应用程序(SPA)。React 允许开发者使用声明式的方式来构建可复用的 UI 组件。网上有个菜鸟教程,无基础的可以 看看,这个 文章示例比较混乱,可以找个更好的看。

React 的定位

  • 不是框架,而是库:React 专注于视图层(MVC 中的 V),不像 Angular 那样提供完整的框架解决方案
  • 组件化思想:将 UI 拆分成独立、可复用的组件
  • JavaScript 为中心:一切皆 JavaScript,包括结构、样式和逻辑

1 基本特点

主要归功于以下几个核心特点:

1.1 组件化 

React 鼓励将整个用户界面拆分成一个个独立、可复用的小部件,即“组件”。每个组件管理自身的逻辑和状态,就像搭积木一样,通过组合这些组件来构建复杂的页面。

  • 可复用性: 一个按钮、一个卡片都可以是组件,在应用的不同地方甚至不同项目中复用。
  • 可维护性: 组件职责单一,修改某个功能时只需关注对应的组件,不影响其他部分。

1.2 声明式编程 

使用 React,你只需要描述 UI “应该是什么样子”(即 UI 与数据状态的对应关系),而不用关心“如何一步步更新 UI”。当数据状态改变时,React 会自动高效地更新并渲染正确的组件。

  • 更直观: 代码更简洁、更易读,让开发者专注于业务逻辑。
  • 更易调试: 因为每个状态都对应一个明确的 UI 视图,使得追踪问题变得更容易。

1.3 虚拟 DOM

虚拟 DOM 是 React 高性能的关键。它是一个存在于内存中的、轻量级的真实 DOM 的 JavaScript 对象副本。

  1. 当组件状态发生变化时,React 会先在内存中创建一个新的虚拟 DOM 树。
  2. 然后,通过高效的 Diffing 算法 对比新旧虚拟 DOM 树的差异。
  3. 最后,只将变化的部分精确地更新到真实的 DOM 上。
    这种方式最大限度地减少了对真实 DOM 的直接操作(真实 DOM 操作非常消耗性能),从而实现了高效的渲染。

1.3 JSX 语法

JSX 是 JavaScript 的一种语法扩展,它允许你在 JavaScript 代码中直接编写类似 HTML 的标记。

  • 更直观: 将 UI 结构和逻辑代码紧密结合在一起,使组件的结构一目了然。
  • 功能强大: JSX 最终会被编译成标准的 JavaScript 对象(React 元素),你可以在其中使用 JavaScript 的全部能力。

所以JSX是HTML和JavaScript杂交,主要用于 React 应用中。代码看起了自然不那么清爽,因为html的属性可能是js的关键子,所以一些属性发放到JSX中时需要改变名字,有点考记忆。

注释要写在大括号中,对大括号是情有独钟啊!

 {/*注释...*/}

1.3.1 Vue 3 中的支持

虽然 Vue 最经典的用法是“模板语法”(Template Syntax,即 HTML 风格的写法),但 Vue 对 JSX 有着极好的支持,特别是在 Vue 3 中。

  • 使用场景:当你需要编写逻辑非常复杂的组件,或者需要利用 JavaScript 的全功能来处理渲染逻辑时,Vue 开发者往往会切换到 JSX/TSX。
  • 现状:Vue 3 的生态系统中,TSX(TypeScript + JSX)的使用非常普遍。Vue 官方甚至专门优化了对 JSX 的支持,移除了全局命名空间以避免与 React 冲突。

文件名从js->jsx->tsx 这种变化对代码规范和工程框架都影响很大。不过现在都关心代码是不是优雅了,加上AI生成,估计更没有人去管了。

       这里提这个的原因时Vue组件文件以.vue结尾,和相当于JSX,如果按上面的说法直接改成JSX,不知到和react有啥区别了。真实情况时几乎没有人改,不是一个简单的rename那么简单,特别时大型项目。就像那些想用typescript,把.js改成改成.ts文件一样,哪有那么简单的事情,都是站起说话不腰疼的。这些演变其实兼容性都很差。你做几年了,发现有点项目用的时js,有的时ts,有的是tsx,其实是非常混乱的。一项技术都迭代得很快,所以要想做全栈再加上手机端,估计得天天加班学习。

      如果你在vue中使用JSX,你得问问自己不直接用React理由是啥。

1.3.2 SolidJS

这是一个近年来非常火爆的高性能框架。

  • 特点:SolidJS 默认就是使用 JSX 的。它的语法看起来和 React 的 JSX 几乎一模一样,但底层原理完全不同(SolidJS 没有虚拟 DOM,它是直接编译成原生 DOM 操作)。
  • 体验:如果你习惯了 React 的 JSX 写法,上手 SolidJS 会非常快,但能享受到更高的运行时性能。

这里就不得理解一下好奇的方式理解一下它为什么高性能了。vue,react本质上就是通过jssctipt动态生成和修改dom结构,自己在内存中维护了一份虚拟的dom树,如果状态变了,要生成新树,然后计算新树和老树差异,最后在dom上应用变更。这地方对比过程vue或者react做了优化,据说很高效。现在SolidJS来了,直接回归原始,没有虚拟dom,直接生成dom,傻眼了吧。虚拟dom的高性能一下被打脸了。这个机制其实就是类似原生dom的监听机制和局部刷新,只不过SolidJS 自动帮你做了。

       因为一直做后端,所以长时间没有精力跟踪前端框架。所以一直好奇为啥虚拟Dom会比我局部刷新更快?局部刷新那个数据变了,我就刷哪里,虚拟dom怎么做到更快的?经常用到react或者vue写的页面,发现还没有以前jquery写的流畅。因为没有输入研究,也不知道式写的人没做好。还是框架的问题。

       大道至简,最有效的方法通常都是最简单直白的。

1.4 单向数据流 

在 React 中,数据通常是从父组件通过属性(props)单向地流向子组件。子组件不能直接修改父组件传来的数据,如果需要改变状态,必须通过父组件传递下来的回调函数来通知父组件进行更新。

  • 数据清晰: 这种设计让数据流向变得清晰、可预测,便于理解和调试大型应用的状态。

1.5 钩子

Hooks 是 React 16.8 版本引入的一项革命性特性,它让你在不编写类组件的情况下,也能在函数组件中使用状态(state)和其他 React 特性(如生命周期)。

  • 简化组件: 让函数组件成为主流,代码更简洁。
  • 逻辑复用: 通过自定义 Hooks,可以非常方便地在不同组件间复用有状态逻辑,避免了类组件时代复杂的模式(如高阶组件、render props)。

1.6 丰富的生态系统与跨平台能力

React 本身专注于 UI 视图层,但拥有极其庞大和活跃的社区生态。

  • 灵活组合: 你可以根据项目需求,自由选择并搭配路由(如 React Router)、状态管理(如 Redux, Zustand)等库。
  • 跨平台开发: 基于 React 的核心思想,衍生出了 React Native,让你可以用几乎相同的语法来开发原生的 iOS 和 Android 应用,实现“一次学习,随处编写”。

1.7 JSX与VUE模板风格对比

js是动态类型语言,其语言本身可读性就查。用jquery的年代,偶尔还会遇到动态添加html的场景,就会用js拼接html代码

html += 
    '<div class="user-card ' + activeClass + '" data-id="' + user.id + '">' +
      '<h3>' + 
        user.name + 
        ' <small class="age">' + ageWarning + '</small>' +
      '</h3>' +
      // 5. 事件绑定的噩梦:需要在字符串里拼接函数调用,还得处理引号
      '<button onclick="alert(\'确定要删除用户:' + user.name + '\')">删除</button>' +
      // 6. 内容转义问题:如果 bio 里包含单引号,HTML 结构直接断裂
      '<p class="bio">' + user.bio + '</p>' + 
    '</div>';

还有比这更长的,简直就是噩梦,这种可读性和维护性都很不好。所以都是禁止使用,要求请求使用后端模板。

现在我们来对比一下JSX和vue模板写法

JSX

import HelloWorld from './components/HelloWorld.jsx'

function App() {
  const items = [
    { id: 1, name: '苹果' },
    { id: 2, name: '香蕉' },
  ]
   {/*不用写引号和+号连接了,不然要疯,这注释要带一个大阔号!...*/}
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>
          {item.name}
        </li>
      ))}
    </ul>
  )
}

export default App

VUE

<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
</template>

<script setup>
import { ref } from 'vue'
import HelloWorld from './components/HelloWorld.vue'

const items = ref([
  { id: 1, name: '苹果' },
  { id: 2, name: '香蕉' },
])
</script>

其他的地方都一样,把关键地方放在一起来对比一下

return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>
          {item.name}
        </li>
      ))}
    </ul>
  )

# 上面这个夹了几个括号,看起来有点蛋疼,欣赏不来这种艺术感
# 所以vue也支持JSX,不太懂
# 只喜欢干净直接一目录了然的代码

<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
</template>

1.8 JSX组件名字

Reat要求组件首字母大写,以区别自定义组件的名字和原始html组件。实际上你会发现有很多HTML标签都是纯大写的。这个形同虚设,关于Vue组件命名规范也有类似问题。所有这种脚本感觉在规范性这方面做得都很差,工程化程度不友好。

2 基础框架搭建

项目架构都使用vite了,依赖管理使用pnpm ,如果不太了解的,可以看我前面的文章。

2.1 无ts

pnpm create vite@latest test-react-app1 -- --template react


2.2 有ts

pnpm create vite@latest my-react-app -- --template react-ts

创建的时候还自动给你跑起来了,不像vue需要你选组件。

2.3 导入

创建后用vs code导入项目就行了。

3 框架结构分析

3.1 无ts结构

我们先看看无生成的工程结构

3.1.1 index.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>test-react-app1</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

入口文件,引入了main.jsx。<div id="root"></div>是渲染容器.这可以说是个模板文件,内容基本需要动。

3.1.2 main.jsx

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
/* 将虚拟的dom渲染到html的root元素上 */
createRoot(document.getElementById('root')).render(
  /*(严格模式)是 React 提供的一个开发辅助工具*/
  <StrictMode>
    /* 入口 */
    <App />
  </StrictMode>,
)

导入了index.css全局样式文件和App.jsx组件,这个可以说是根组件,应用入口。这个也可以不用动,是个模板文件。

3.1.3 App.jsx

import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css' /*应用的样式*/

/*返回了一个app组件*/
function App() {
  const [count, setCount] = useState(0)

  return (
    <>
      <div>
        <a href="https://vite.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.jsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}

export default App

这个就是你需要根据需求,开始编写代码的地方了。

3.1.4 package.json

这个是个工程的核心配置文件,一般不需要人工改动,依赖主要包括生产和开发(devDependencies),安装或者卸载依赖时是自动维护的,这里我们关注的是scripts部分

3.1.5 运行

vite dev

什么都没干,一秒多,有点吓人

3.1.6 查看运行结果

看看,跑起来了,牛逼不😊。搓它几下,计数器变了

3.2 有ts结构

讲到这里,我又忘记ts语法了,那玩意不一直用,过不了多久就全忘记了,实在是语法过于烧脑。

3.2.1 index.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>test-react-app2</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

注意文件后缀变成了tsx

3.2.2 main.tsx

这个文件感觉命名成index.tsx会更恰当。但是默认是这个名字就是算了

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx' /*文件后缀变成tsx*/

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

3.2.3 App.tsx

import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from './assets/vite.svg'
import heroImg from './assets/hero.png'
import './App.css'

function App() {
  const [count, setCount] = useState(0)

  return (
    <>
      <section id="center">
        <div className="hero">
          <img src={heroImg} className="base" width="170" height="179" alt="" />
          <img src={reactLogo} className="framework" alt="React logo" />
          <img src={viteLogo} className="vite" alt="Vite logo" />
        </div>
        <div>
          <h1>Get started</h1>
          <p>
            Edit <code>src/App.tsx</code> and save to test <code>HMR</code>
          </p>
        </div>
        <button
          type="button"
          className="counter"
          onClick={() => setCount((count) => count + 1)}
        >
          Count is {count}
        </button>
      </section>

      <div className="ticks"></div>

      <section id="next-steps">
        <div id="docs">
          <svg className="icon" role="presentation" aria-hidden="true">
            <use href="/icons.svg#documentation-icon"></use>
          </svg>
          <h2>Documentation</h2>
          <p>Your questions, answered</p>
          <ul>
            <li>
              <a href="https://vite.dev/" target="_blank">
                <img className="logo" src={viteLogo} alt="" />
                Explore Vite
              </a>
            </li>
            <li>
              <a href="https://react.dev/" target="_blank">
                <img className="button-icon" src={reactLogo} alt="" />
                Learn more
              </a>
            </li>
          </ul>
        </div>
        <div id="social">
          <svg className="icon" role="presentation" aria-hidden="true">
            <use href="/icons.svg#social-icon"></use>
          </svg>
          <h2>Connect with us</h2>
          <p>Join the Vite community</p>
          <ul>
            <li>
              <a href="https://github.com/vitejs/vite" target="_blank">
                <svg
                  className="button-icon"
                  role="presentation"
                  aria-hidden="true"
                >
                  <use href="/icons.svg#github-icon"></use>
                </svg>
                GitHub
              </a>
            </li>
            <li>
              <a href="https://chat.vite.dev/" target="_blank">
                <svg
                  className="button-icon"
                  role="presentation"
                  aria-hidden="true"
                >
                  <use href="/icons.svg#discord-icon"></use>
                </svg>
                Discord
              </a>
            </li>
            <li>
              <a href="https://x.com/vite_js" target="_blank">
                <svg
                  className="button-icon"
                  role="presentation"
                  aria-hidden="true"
                >
                  <use href="/icons.svg#x-icon"></use>
                </svg>
                X.com
              </a>
            </li>
            <li>
              <a href="https://bsky.app/profile/vite.dev" target="_blank">
                <svg
                  className="button-icon"
                  role="presentation"
                  aria-hidden="true"
                >
                  <use href="/icons.svg#bluesky-icon"></use>
                </svg>
                Bluesky
              </a>
            </li>
          </ul>
        </div>
      </section>

      <div className="ticks"></div>
      <section id="spacer"></section>
    </>
  )
}

export default App

上面这个内容有点多,不用太关注。本质和无ts是一样的

3.2.4 package.json

多了一些typescript的开发依赖

3.2.5 运行

3.2.6 查看运行结果

这个图标看起来有点酷炫

3.2.7 tsconfig.json

这是 TypeScript 项目的根配置文件,也是编译器首先寻找的文件。

  • 核心作用:它通常不直接写具体的编译选项,而是作为整个项目的“解决方案”入口。它的主要任务是通过 references 字段来引用其他配置文件(也就是下面的两个文件),从而将不同环境的配置分离开来。
  • 好处:这种结构让项目结构更清晰,避免了所有配置都堆在一个文件里,也方便了构建工具进行增量编译
{
  "files": [],
  "references": [
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.node.json" }
  ]
}

引用了其他两个文件

3.2.8 tsconfig.app.json

这个文件专门用于配置前端应用代码的编译选项。适用代码:你项目里所有的 React/Vue 组件、页面、工具函数等最终会在浏览器中运行的代码。

典型配置:

  • target: 设置编译后的 JavaScript 版本,例如 ESNext 或 ES2015,以兼容现代浏览器。
  • lib: 指定项目需要包含的库定义文件,如 ["DOM", "ESNext"],告诉 TypeScript 代码会运行在浏览器环境中。
  • jsx: 配置如何处理 JSX 语法,例如 react-jsx。

3.2.9 tsconfig.node.json

这个文件专门用于配置Node.js 环境下代码的编译选项。

  • 适用代码:你的项目构建脚本和配置文件,最典型的就是 vite.config.ts。这些文件不是给浏览器运行的,而是给 Node.js 执行的。
  • 典型配置
    • target: 通常设置为 ESNext 或一个较高的 Node.js 版本,因为现代 Node.js 支持很多新特性。
    • module: 设置模块系统,如 ESNext 或 CommonJS,以适应 Node.js 的模块加载方式。
    • types: 可能会包含 ["node"],以引入 Node.js 的全局类型定义(如 process__dirname 等)。

文件名 角色定位 负责范围
tsconfig.json 总指挥 引用其他配置,管理整个项目
tsconfig.app.json 前端专员 浏览器环境代码 (React/Vue 组件等)
tsconfig.node.json 后端/工具专员 Node.js 环境代码 (vite.config.ts 等)

3.3 .vscode目录

这个目录其实是一个重要目录,因为里面一些配置在开发中通常会用到。创建项目时并不会创建该

这个目录作用和产生机制可以参看文章

总结

加了TypeScript的之后,整个框架会复杂很多,越复杂的东西以后迁移维护成本越高。TypeScript从目前趋势来看,都是要集成的。所有弱类型变成强类型之后AI和代码提示等工具就会智能化很多,包括Python(类型提示)。动态类型、非面向对象语言严重缺点也暴露了出来。没有类型提示,光看函数签名,估计什么信息也看不出来。

现在前端框架演变的越来越复杂了,光各种插件和配置文件就够得搞半天。那些要求全栈、PC、手机、还要懂硬件的公司比周扒皮还强上十倍不止。

4 框架重构

框架布局估计会按vue的调整的目录结构重构,默认的目录结果分类不太好。

5 开发调试

开发调试和vue差不多的,请参看前面写过文章

6 格式化插件

 可以参看前面写过文章。使用Prettier。最好不要配置成保存就自动格式化,使用手动选择格式化保存,不然遇到幺蛾子很麻烦。

7 路由集成

React Router 其实有几个不同的包:
react-router:提供路由的核心逻辑,不依赖特定平台。
react-router-dom:基于核心包,专门为 Web 浏览器环境增加了 DOM 相关的功能(如 <BrowserRouter>、<Link> 等),是我们日常写网页最常用的。
react-router-native:专门为 React Native 移动端应用提供的路由支持。
所以,如果你在开发 React 网页应用,放心使用 react-router-dom,它就是目前最正统的官方路由选择!

7.1 安装包

pnpm install react-router-dom

7.2 一级路由

import React from 'react';
import ReactDOM from 'react-dom/client';
import {BrowserRouter as Router,
  Routes,Route,Link
} from 'react-router-dom';

import Home from '@/components/route_test/Home.jsx';
import About from '@/components/route_test/About.jsx';

const App = () => {
  return (
    <Router>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/about">About</Link>
            </li>
          </ul>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
        </Routes>
    </Router>
  );
};

export default App;

7.3 嵌套路由

import React from 'react';
import ReactDOM from 'react-dom/client';
import {
  BrowserRouter as Router,
  Routes,
  Route,
  Link
} from 'react-router-dom';

import Home from '@/components/route_test/Home.jsx';
import About from '@/components/route_test/About.jsx';
import Profile from '@/components/route_test/Profile.jsx';
import Dashboard from '@/components/route_test/Dashboard.jsx';

const App = () => {
  return (
    <Router>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
             <li>
              <Link to="/dashboard">Dashboard</Link>
            </li>
          </ul>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />}>
            <Route path="profile" element={<Profile />} />
          </Route>
        </Routes>
    </Router>
  );
};

export default App;

7.3 路由传参

// User.js
import React from 'react';
import { useParams } from 'react-router-dom';

const User = () => {
  const { userId } = useParams();
  return <h2>User ID: {userId}</h2>;
};

export default User;


#App.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import {
  BrowserRouter as Router,
  Routes,
  Route,
  Link
} from 'react-router-dom';

import Home from '@/components/route_test/Home.jsx';
import About from '@/components/route_test/About.jsx';
import Profile from '@/components/route_test/Profile.jsx';
import Dashboard from '@/components/route_test/Dashboard.jsx';
import User from '@/components/route_test/User.jsx';

const App = () => {
  return (
    <Router>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
             <li>
              <Link to="/dashboard">Dashboard</Link>
            </li>
            <li>
              <Link to="/user/3">User</Link>
            </li>
          </ul>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />}>
            <Route path="profile" element={<Profile />} />
          </Route>
          <Route path="/user/:userId" element={<User />} />
        </Routes>
    </Router>
  );
};

export default App;

基本就是这样,也类似vue一样,可以独立出一个路由配置文件,方便工程化。

8 状态管理

react Props存在Props Drilling问题,也就是层层传递有几种非常成熟的方案可以完美避开这个Props Drilling问题:

  • Context API(官方原生方案)
    React 自带的“任意门”。你可以把数据放在顶层的 Context 里,深层组件直接通过 useContext 获取,完全不需要中间组件参与传递。非常适合传递主题、用户登录状态等全局数据。
  • 组件组合(Component Composition)
    利用 children 属性。有时候不需要传数据,直接把需要数据的深层组件作为 children 传给顶层,由顶层包一层渲染出来,也能绕过中间层级。
  • 状态管理库(如 Zustand, Redux, Jotai 等)
    当应用变得非常复杂时,可以使用专门的状态管理库。它们相当于在组件树之外建了一个“公共仓库”,任何组件都可以随时去仓库里存取数据,彻底摆脱层层传递的束缚。

9 请求组件

请求组件用axios,可以参看中文网和npm英文网。除了做请求之外,用的得最多的还有拦截器功能。

10 PC UI组件

11 移动端UI 组件

总结

更多推荐