移动端React拉布布旅游应用开发实践:从路由设计到移动适配

115 阅读7分钟

本文记录了本犬在构建一个移动端React应用框架过程中的学习心得,重点分享了路由管理、布局设计和移动端适配的核心实现方案,之后会继续跟进项目,分享一些项目亮点,技能点

项目概述

最近我正在开发一个移动端旅游应用,核心功能包括首页、特惠专区、收藏、行程和个人账户等模块。项目基于Vite构建,使用React作为前端框架,并采用了React Router进行路由管理。特别值得一提的是,我选择React Vant作为UI组件库,它提供了丰富的移动端组件,极大提升了开发效率。

路由架构设计

双层布局方案

在路由设计上,我采用了双层布局策略,根据页面需求灵活切换不同的布局容器:

import {
  lazy,
  Suspense
} from 'react'
import {
  Routes,
  Route,
  Navigate
} from 'react-router-dom'
import MainLayout from '@/components/MainLayout'
import BlankLayout from '@/components/BlankLayout'

// 页面组件懒加载
const Home = lazy(() => import('@/pages/Home'))
const Search = lazy(() => import('@/pages/Search'))
const Discount = lazy(() => import('@/pages/Discount'))
const Collection = lazy(() => import('@/pages/Collection'))
const Trip = lazy(() => import('@/pages/Trip'))
const Acount = lazy(() => import('@/pages/Acount'))

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        {/* 带有Tabbar的主布局 */}
        <Route path='/' element={<MainLayout />}>
          <Route path='/' element={<Navigate to='/home' />} />
          <Route path='/home' element={<Home />} />
          <Route path='/discount' element={<Discount />} />
          <Route path='/collection' element={<Collection />} />
          <Route path='/trip' element={<Trip />} />
          <Route path='/acount' element={<Acount />} />
        </Route>
        
        {/* 空白布局(无Tabbar) */}
        <Route path='/' element={<BlankLayout />}>
          <Route path='/search' element={<Search />} />
        </Route>
      </Routes>
    </Suspense>
  )
}

这种设计的优势在于:

  1. 布局复用:共享的UI元素(如底部导航栏)只需在一个地方维护
  2. 灵活扩展:新增页面时只需选择合适的布局容器
  3. 代码解耦:布局逻辑与页面内容分离,便于维护

布局组件实现

主布局(带底部导航栏)

import { useEffect, useState } from 'react';
import { Tabbar } from 'react-vant';
import { HomeO, Search, FriendsO, SettingO, UserO } from '@react-vant/icons';
import { Outlet, useNavigate, useLocation } from 'react-router-dom'

const tabs = [
  { icon: <HomeO />, title: '首页', path: '/home' },
  { icon: <Search />, title: '特惠专区', path: '/discount' },
  { icon: <FriendsO />, title: '我的收藏', path: '/collection' },
  { icon: <SettingO />, title: '行程', path: '/trip' },
  { icon: <UserO />, title: '我的账户', path: '/acount' }
]

const MainLayout = () => {
  const [active, setActive] = useState(0)
  const navigate = useNavigate();
  const location = useLocation();
  
  useEffect(() => {
    // 根据当前路径高亮对应的Tab
    const index = tabs.findIndex(tab => 
      location.pathname.startsWith(tab.path)
    )
    setActive(index)
  }, [location.pathname])
  
  return (
    <>
      <Outlet />
      <Tabbar value={active} onChange={(key) => {
        setActive(key);
        navigate(tabs[key].path)
      }}>
        {tabs.map((tab, index) => (
          <Tabbar.Item key={index} icon={tab.icon}>
            {tab.title}
          </Tabbar.Item>
        ))}
      </Tabbar>
    </>
  )
}

在这个组件中,我使用了几个关键技术点:

  1. 动态Tab高亮:通过useLocation获取当前路径,使用findIndexstartsWith方法确定激活的Tab项
  2. 路由跳转:点击Tab时使用useNavigate进行路由切换
  3. Outlet组件:作为子路由的渲染出口

使用useLocation + findIndexstartsWith方法是为了防止刷新后,状态丢失,我们利用es6新特性,相当于炫技了,就是就和location.pathname===tab.path效果一致,可以看到效果,刷新后页面高亮不再回到默认的第一个tab栏,这里的tabbar组件是利用的react-van这个第三方组件库,图标也是用的@react-vant/icons,非常方便,不需要我们去做额外的工作,这是 react-van的操作文档

1.gif

空白布局(无导航栏)

import { Outlet } from "react-router-dom";

const BlankLayout = () => {
  return (
    <>
      <Outlet />
    </>
  )
}

空白布局非常简洁,仅作为内容容器使用,适合不需要底部导航的页面(如搜索页)。现在只是搭建了一个框架,可以实现路由的跳转,具体的页面内容还得后续实现

页面组件实现

页面组件保持简洁,专注于内容呈现:

// Home.jsx
import useTitle from "@/hooks/useTitle";

const Home = () => {
  useTitle('奶龙首页'); // 自定义Hook设置页面标题
  
  return <>Home</>
}

export default Home;
// Discount.jsx
const Discount = () => {
  return <>特惠专区</>
}

export default Discount;
// Collection.jsx
const Collection = () => {
  return <>我的收藏</>
}

export default Collection;
// Trip.jsx
const Trip = () => {
  return <>行程</>
}

export default Trip;
// Acount.jsx
const Acount = () => {
  return <>我的账户</>
}

export default Acount;
// Search.jsx
const Search = () => {
  return <>搜索</>
}

export default Search;

还是上述说的,现在是项目刚刚开始,页面还待完善,现在只是实现了路由的成功跳转,页面并没有什么内容

移动端适配方案

移动端适配是项目的重要环节,我采用了阿里成熟的lib-flexible方案:

// main.jsx
import { createRoot } from 'react-dom/client'
import 'lib-flexible' // 引入移动端适配方案
import './index.css'
import App from './App.jsx'
import { BrowserRouter as Router } from 'react-router-dom'

createRoot(document.getElementById('root')).render(
  <Router>
    <App />
  </Router>
)

适配原理详解

lib-flexible的核心思想是动态设置根元素字体大小,使页面元素能够根据屏幕尺寸自适应:

  1. 基准设置:1rem = 屏幕宽度 / 10

    • 在750px宽的设计稿上,1rem = 75px
    • 在375px宽的设备上,1rem = 37.5px
  2. 设计稿换算

    • 设计稿尺寸:750px(iPhone标准尺寸)
    • 设计稿元素宽度260px → 实际开发中应设置为:260 / 75 = 3.4667rem

自动化转换方案

为了避免繁琐的手动计算,我们可以在Vite中配置PostCSS插件自动完成px到rem的转换:

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

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: { '@': path.resolve(__dirname, './src') }
  },
  css: {
    postcss: {
      plugins: [
        require('postcss-pxtorem')({
          rootValue: 75, // 设计稿宽度/10
          propList: ['*'], // 转换所有属性
          selectorBlackList: ['.norem'] // 忽略带有norem类的元素
        })
      ]
    }
  }
})

这样配置后,在CSS中直接使用设计稿的像素值,构建时会自动转换为rem单位:

/* 设计稿尺寸:260px */
.element {
  width: 260px; /* 构建后自动转换为 260/75 = 3.4667rem */
}

我们只需要按照设计稿的要求来写,看到什么写什么,不需要自己去频繁地转换单位,非常方便

技术亮点总结

1. ES6特性的应用

在路由高亮逻辑中,我充分利用了ES6的数组和字符串方法:

// 查找当前激活的Tab项
const index = tabs.findIndex(tab => 
  location.pathname.startsWith(tab.path)
)
  • findIndex:高效查找匹配条件的数组元素
  • startsWith:检查路径是否以指定字符串开头
  • 箭头函数:简化回调函数写法

这些现代JavaScript特性让代码更简洁、可读性更强。我们上述也说了其实location.pathname.startsWith(tab.path)的作用就相当于location.pathname===tab.path,而findIdex是返回满足条件的索引

2. React Vant组件库的优势

选择React Vant作为UI组件库带来了显著优势:

  • 丰富的移动端组件:如Tabbar组件开箱即用
  • 良好的视觉体验:遵循移动端设计规范
  • 主题定制能力:支持灵活的风格调整
  • 社区支持:活跃的社区和持续更新

3. 路由懒加载优化

通过React的lazy函数和Suspense组件实现路由懒加载:

const Home = lazy(() => import('@/pages/Home'))
// ...
<Suspense fallback={<div>Loading...</div>}>
  {/* 路由配置 */}
</Suspense>

这种方式可以:

  • 减少首屏加载时间:只加载当前需要的资源
  • 优化用户体验:显示加载状态避免空白页面
  • 提高应用性能:按需加载代码块

开发经验分享

布局设计的思考过程

在设计双层布局时,我经历了以下思考:

  1. 分析页面需求:哪些页面需要底部导航?哪些不需要?
  2. 确定布局类型:主布局(带导航)和空白布局(纯内容)
  3. 设计路由结构:嵌套路由实现布局继承
  4. 实现布局组件:使用Outlet作为内容插槽
  5. 处理边界情况:如默认重定向、未匹配路由处理

移动端适配的挑战

在适配不同设备时,遇到了几个关键问题:

  1. 单位选择:px固定不变,rem/vw相对灵活
  2. 设计稿转换:手动计算容易出错
  3. 第三方组件适配:确保组件库也遵循适配方案

解决方案:

  • 采用rem作为主要单位
  • 使用PostCSS插件自动化转换
  • 检查组件库是否支持rem或提供尺寸配置

性能优化实践

在开发过程中,我实施了以下性能优化措施:

  • 路由懒加载:减少首屏资源大小
  • 组件复用:避免重复代码
  • 图标按需引入:减小打包体积
  • 生产环境构建:Vite自动优化生产包

移动端开发是一个持续学习的过程,随着项目的深入,本犬将继续探索更多优化方案和最佳实践,分享一些项目的亮点和难点

参考文档:

react-vant

react中文文档

Github项目上传仓库地址