React初体验:从零搭建第一个应用的实战思考

谢雨佳_码农
2025-12-24 11:57
阅读 427

上周五晚上十点半,我正戴着耳机听着Lo-fi beats敲代码,突然收到产品经理发来的钉钉消息:“咱们新医疗问诊模块要上H5页面了,下周三上线,后端用Django REST Framework,前端你看着搞个React吧。”
我当时差点把咖啡喷到键盘上——咱可是正儿八经的Python后端开发啊!不过转念一想,在深圳这片腾讯、字节扎堆的地界儿,前后端边界早就模糊了。再说了,去年双11我们医疗SaaS系统崩了一次,就是因为前端没做懒加载,用户刷病历列表直接卡死,运维半夜打电话骂我“你们后端怎么不控制数据量”,其实锅在前端……这回干脆自己上手,至少能保证交互流畅、用户体验在线。

于是周末两天,我硬着头皮啃React文档,边学边搭。今天这篇技术分享,就是给和我一样“被逼转型”的后端兄弟们准备的——不讲虚的,直接上手,顺便聊聊我在搭建过程中踩的坑和架构上的小思考。


为什么选React?不只是因为面试题多

说实话,一开始我也纠结过Vue还是React。但翻了翻最近半年深圳大厂的前端面试题,发现React占比明显更高,尤其是中高级岗位,几乎必问Hooks、虚拟DOM diff策略、Context vs Redux这些。更重要的是,我们团队未来要对接腾讯云的微前端方案,人家官方推荐的就是React生态。

再加上React的组件化思想和我们后端熟悉的“高内聚低耦合”理念高度一致——每个组件只管自己的状态和渲染,通过props通信,简直像写Django的Model-View-Template,只不过View变成了JSX。


环境搭建:别再用create-react-app裸奔了!

很多入门教程一上来就让你:

npx create-react-app my-medical-app

确实快,但生产项目里这么干,后期会哭。比如我们医疗系统要求:

  • 所有静态资源必须走CDN
  • 必须支持IE11(医院老电脑太多)
  • 需要集成Sentry做错误监控

这些create-react-app默认都不支持,得 eject 出配置文件再改,结果搞得 webpack.config.js 足足800行,维护起来像看天书。

我的建议是:初期用Vite,后期按需定制。Vite启动快、热更新秒级响应,对TS支持也原生友好。命令如下:

npm create vite@latest my-medical-app -- --template react-ts
cd my-medical-app
npm install
npm run dev

两分钟,一个带TypeScript的React项目就跑起来了。而且Vite的配置文件 vite.config.ts 清晰明了,加CDN、配代理、注入环境变量都一目了然。


写第一个组件:别只写Hello World

大多数教程到这儿就停了,但真实业务哪有那么简单?我做的第一个组件是患者信息卡片,包含头像、姓名、就诊状态、最后就诊时间。看起来简单,但要考虑:

  • 头像加载失败怎么办?
  • 时间格式要不要国际化?
  • 状态字段(如“待复诊”、“已结束”)要不要做成可配置?

于是我抽象出一个 PatientCard 组件:

// components/PatientCard.tsx
import React, { useState } from 'react';
import fallbackAvatar from '@/assets/fallback-avatar.png';

interface Patient {
  id: string;
  name: string;
  lastVisitAt: string; // ISO 8601格式
  status: 'pending' | 'completed' | 'cancelled';
}

const STATUS_LABELS = {
  pending: '待复诊',
  completed: '已结束',
  cancelled: '已取消'
};

const PatientCard: React.FC<{ patient: Patient }> = ({ patient }) => {
  const [avatarError, setAvatarError] = useState(false);

  const handleAvatarError = () => setAvatarError(true);

  // 简单的时间格式化,实际项目建议用dayjs或date-fns
  const formatDate = (isoString: string) => {
    const date = new Date(isoString);
    return date.toLocaleDateString('zh-CN');
  };

  return (
    <div className="patient-card" style={{ padding: '16px', border: '1px solid #eee', borderRadius: '8px' }}>
      <img
        src={avatarError ? fallbackAvatar : `/api/avatars/${patient.id}`}
        onError={handleAvatarError}
        alt={patient.name}
        style={{ width: '48px', height: '48px', borderRadius: '50%' }}
      />
      <h3>{patient.name}</h3>
      <p>最后就诊:{formatDate(patient.lastVisitAt)}</p>
      <span className={`status ${patient.status}`}>
        {STATUS_LABELS[patient.status]}
      </span>
    </div>
  );
};

export default PatientCard;

这里有几个细节值得说:

  • 错误兜底:图片加载失败自动切默认图,避免白块影响UI
  • 状态映射:用常量对象管理文案,方便后期国际化
  • 样式内联+class混合:简单布局用style快速调试,复杂样式抽到CSS Module

状态管理:别一上来就上Redux

新手最容易犯的错就是:只要有两个组件要通信,就引入Redux。结果项目才三个页面,store目录比业务代码还大。

React 16.8之后,Hooks + Context 已经能解决80%的状态共享问题。比如我们的问诊流程需要跨步骤共享患者ID和症状描述,我这样设计:

// context/ConsultationContext.tsx
import React, { createContext, useContext, useState } from 'react';

interface ConsultationState {
  patientId?: string;
  symptoms: string;
  setPatientId: (id: string) => void;
  setSymptoms: (text: string) => void;
}

const ConsultationContext = createContext<ConsultationState | undefined>(undefined);

export const ConsultationProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [patientId, _setPatientId] = useState<string | undefined>();
  const [symptoms, _setSymptoms] = useState('');

  const setPatientId = (id: string) => _setPatientId(id);
  const setSymptoms = (text: string) => _setSymptoms(text);

  return (
    <ConsultationContext.Provider value={{
      patientId,
      symptoms,
      setPatientId,
      setSymptoms
    }}>
      {children}
    </ConsultationContext.Provider>
  );
};

export const useConsultation = () => {
  const context = useContext(ConsultationContext);
  if (!context) {
    throw new Error('useConsultation must be used within ConsultationProvider');
  }
  return context;
};

然后在App.tsx里包一层:

// App.tsx
import { ConsultationProvider } from './context/ConsultationContext';
import Step1 from './steps/Step1';
import Step2 from './steps/Step2';

function App() {
  return (
    <ConsultationProvider>
      <Step1 />
      <Step2 />
    </ConsultationProvider>
  );
}

任何子组件都能通过 useConsultation() 拿到状态,清爽又类型安全。只有当状态逻辑复杂到需要中间件(比如日志、持久化)、或者性能成为瓶颈时,再考虑Zustand或Redux Toolkit。


调试与性能:前端不是“能跑就行”

作为后端出身,我对性能特别敏感。React DevTools 是必备神器,装上之后能在Components面板看到每个组件的props和state,还能录屏分析重渲染。

有一次我发现患者列表滑动卡顿,打开Performance面板一看,原来每次滚动都在触发 PatientCard 重渲染——因为父组件传了个新对象 { ...patient } 当props。解决方案很简单:React.memo包裹组件,并确保props引用稳定

const PatientCard = React.memo(({ patient }: { patient: Patient }) => {
  // ...
});

同时,列表用 React.Fragmentdiv 包裹,加上 key={patient.id},避免React diff时搞混节点。

另外,懒加载一定要做!尤其医疗系统常有长列表:

import { lazy, Suspense } from 'react';

const PatientList = lazy(() => import('./PatientList'));

function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <PatientList />
    </Suspense>
  );
}

学习资源推荐:少走弯路

最后分享几个我觉得真正有用的资源,不是那些“7天精通React”的毒鸡汤:

类型 推荐内容 说明
官方文档 React Beta Docs 新版文档以Hooks为中心,例子贴近实战
视频教程 EpicReact.dev(付费) Kent C. Dodds讲的,深入原理
社区 Reactiflux Discord 国外活跃社区,提问响应快
中文博客 掘金“React进阶”专栏 国内实战经验多,适合查漏补缺

总结:后端视角下的前端思维转变

折腾完这个小项目,最大的感悟是:前端不仅是“画页面”,更是用户体验的守门人。一个按钮的loading状态、一个表单的校验反馈、一次网络错误的友好提示——这些细节决定了医生愿不愿意用你的系统。

现在我已经能和前端同事对线“这个交互能不能优化”了(笑)。如果你也是后端被迫学前端,别怕,React的函数式思维其实和写Python装饰器、上下文管理器异曲同工。关键是动手做,哪怕只是个小demo。

对了,下周三上线顺利的话,请我喝喜茶——毕竟在深圳,程序员的快乐就这么朴实无华。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝