🧳 我的 React Trip 之旅(1):从空文件夹到“这 App 真像国外大厂做的”

56 阅读4分钟

先别急着写业务逻辑!打好地基 + 精准还原设计稿,才是专业前端的第一步


🌍 开头:为什么我的旅游 App 一启动就“赢了”?

那天,我刷着 Airbnb 的丝滑轮播、小红书的瀑布流、Google Travel 的极简 UI,突然冒出一个念头:

“如果我能用 React 从零复刻一款高保真旅游 App,再悄悄加上 AI 聊天、智能推荐……那简历上是不是能多一行‘亮点项目’?”

于是,TripApp 诞生了。

但和大多数人不同,我没有一上来就写 <div>首页</div>,而是只做两件事:

  1. 搭一个让面试官眼前一亮的工程骨架
  2. 确保设计师甩来的 750px 稿,1px 都不差地还原

因为我知道——细节,才是专业和业余的分水岭。


🛠️ 第一步:克制地选择技术栈,拒绝“全家桶焦虑”

很多人一建项目就狂装库:Redux、Antd Mobile、Tailwind、Webpack、Sass……结果项目跑起来慢如蜗牛,自己都搞不清哪个是核心依赖。

而我,只选了这几样“趁手兵器”:

- React + Hooks + Router(SPA 核心)
- Zustand(轻量状态管理,API 简洁到哭)
- react-vant(70% UI 组件开箱即用)
- Vite(快如闪电的开发体验)
- lib-flexible + postcss-pxtorem(精准适配移动端)
- Axios + Mock(前后端并行开发)
- stylus(CSS 预处理器,支持嵌套 & 变量)

为什么不用 Redux?Zustand 一行 create 就搞定状态,还支持 TypeScript 推导。
为什么选 react-vant?它的组件风格接近 iOS/Android 原生,且体积小、文档全。

技术不是越多越好,而是“刚好够用”。


🗂️ 项目结构:让代码自己会说话

我的 src 目录长这样:

src/
├── components/   // 通用组件(Waterfall, Loading, SearchBox...)
├── pages/        // 页面(Home, Search, Detail, Account...)
├── stores/       // Zustand 状态(useImageStore, useSearchStore...)
├── hooks/        // 自定义 Hooks(useTitle, useDebounce...)
├── api/          // 接口封装(getDetail, getImages...)
├── mock/         // 模拟数据(开发无需后端)
├── llm/          // LLM 封装(chat, kimiChat, generateAvatar...)
├── styles/       // 全局样式(可选)
└── App.css       // 全局样式 + 原子类

这种分层不是炫技,而是为了让三个月后的我(或接手的同事)一眼看懂:“哦,搜索逻辑在 stores/useSearchStore.js,改这里就行。”


⚙️ Vite 配置:藏在 .env 里的小心机

我在 .env.local 里放了这些(已加入 .gitignore):

VITE_DEEPSEEK_API_KEY=sk-xxxx
VITE_KIM_API_KEY=sk-yyyy

然后在 llm/chat.js 中安全调用:

const api_key = import.meta.env.VITE_DEEPSEEK_API_KEY;

面试官问:“你怎么管理敏感密钥?”
我微微一笑:“Vite 的 import.meta.env 在构建时自动替换,根本不会打包进前端代码。”

同时,vite.config.js 里配置了 alias、mock、CSS 预处理:

// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import pxtorem from 'postcss-pxtorem'

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: { '@': path.resolve(__dirname, 'src') }
  },
  css: {
    preprocessorOptions: {
      stylus: { /* ... */ }
    },
    postcss: {
      plugins: [
        pxtorem({ rootValue: 37.5, propList: ['*'] })
      ]
    }
  }
})

📱 移动端适配:设计师说“1px都不能差”,我笑了

设计师甩来一张 750px 宽度 的 Figma 稿(iPhone 6/7/8 标准),要求“像素级还原”。

我知道,直接写 px 是死路一条——在 iPhone 14 Pro Max 上会小得看不见,在小米低端机上又会撑爆屏幕。

于是,我祭出阿里开源的 lib-flexible + postcss-pxtorem 组合拳:

Step 1:安装并引入 lib-flexible

npm install lib-flexible

main.js 顶部引入:

import 'lib-flexible'

它会在页面加载时动态设置:

// 例如:屏幕宽度 375pxhtml { font-size: 37.5px }
// 因为 375 / 10 = 37.5

Step 2:配置 postcss-pxtorem

// postcss.config.js
module.exports = {
  plugins: {
    'postcss-pxtorem': {
      rootValue: 37.5, // 750 设计稿 / 2 (dpr) / 10 = 37.5
      propList: ['*'],
      exclude: /node_modules/
    }
  }
}

现在,我在 CSS 里写:

.title {
  font-size: 32px; /* 设计稿上是 32px */
  margin: 20px 0;
}

Vite 构建时自动转成:

.title {
  font-size: 0.85333rem;
  margin: 0.53333rem 0;
}

在任何设备上,1rem = 屏幕宽度 / 10,所以比例永远一致。
从此告别手动除以 75 的痛苦!


🎨 原子 CSS:让样式复用率提升 80%

除了模块化 CSS(.module.css),我在 App.css 里还定义了一些原子类:

/* App.css */
.mt4 { margin-top: 0.4rem; }
.flex { display: flex; }
.flex-col { flex-direction: column; }
.flex-1 { flex: 1; }
.h-all { height: 100vh; }
.text-center { text-align: center; }

于是,在组件里可以这样写:

<div className="flex flex-col h-all mt4">
  <Header />
  <Main />
</div>

不用重复写 display: flex,也不怕样式污染。
原子类 + module.css,是我目前最舒服的 CSS 组合。


🧭 结尾:地基已打,旅程刚开始

现在,我的 TripApp 还没有用户登录、没有真实行程、没有 AI 生成头像……但它已经有了:

  • 清晰的工程架构
  • 专业的移动端适配方案
  • 对设计稿的极致尊重
  • 安全的密钥管理
  • 高复用的样式体系

这就像旅行前收拾行李:护照、充电器、转换插头、防晒霜都齐了,只等买机票出发。

下一站,我会带你深入“路由与 TabBar”的实现——如何让底部导航高亮精准匹配当前路径?如何用 Layout + Outlet 实现多模板 SPA?