Vite+React+TypeScript+VsCode框架实战
引言
本文将开始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 对象副本。
- 当组件状态发生变化时,React 会先在内存中创建一个新的虚拟 DOM 树。
- 然后,通过高效的Diffing 算法对比新旧虚拟 DOM 树的差异。
- 最后,只将变化的部分精确地更新到真实的 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 + '">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 AppVUE
<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 路由集成
8 状态管理
react Props存在Props Drilling问题,也就是层层传递有几种非常成熟的方案可以完美避开这个Props Drilling问题:
- Context API(官方原生方案)
React 自带的“任意门”。你可以把数据放在顶层的Context里,深层组件直接通过useContext获取,完全不需要中间组件参与传递。非常适合传递主题、用户登录状态等全局数据。 - 组件组合(Component Composition)
利用children属性。有时候不需要传数据,直接把需要数据的深层组件作为children传给顶层,由顶层包一层渲染出来,也能绕过中间层级。 - 状态管理库(如 Zustand, Redux, Jotai 等)
当应用变得非常复杂时,可以使用专门的状态管理库。它们相当于在组件树之外建了一个“公共仓库”,任何组件都可以随时去仓库里存取数据,彻底摆脱层层传递的束缚。
