前言

# 前言

上一章节,我们从 0 开始搭建出一套以 React 技术栈为基础的前端开发环境,过程中肯定会遇到各种奇奇怪怪的问题,比如 Node 版本问题,工具包的版本问题,插件下载完之后,无法得到自己想要的效果等等,为了大家能顺畅地进行开发,请大家尽量将项目中用到的 node_module 包版本和我提供给大家的项目中的版本,保持一致。

倘若你学会了这一套搭建流程,我希望你能举一反三,根据项目需求,灵活的切换组件库、PC 版、甚至是主框架。这对提升自己的知识广度很有帮助,因为相比每次都看教程,自己手动实现一遍,印象会更深刻,遇到问题也能通过自己的认知,去解决它。

扯得有点远了,本章节我们将正式进入前端实战环节。

本教程已有线上地址在线地址 (opens new window),同学们可以在实战部分,对照着线上页面。

# 知识点

  • 编写底部导航栏
  • 创建图标公用组件
  • 路由控制底部导航栏的显隐

# 编写底部导航栏

我们先观察我们今天要实现的底部导航长啥样,如下所示:

img

上图红框中的底部导航栏,在很多业务场景下都是需要的,三个导航栏对应着三个不同的三个页面组件,分别是「账单」、「统计」、「我的」。这三个页面组件是需要导航栏的。如果我们点击内页如账单详情页,则底部的导航栏会被隐藏,这就需要我们在导航栏的控制上,下一些功夫。

话不多说,我们在上一章的代码基础上添加导航栏组件,在 src 目录下新建 components 目录,专门用于放置一些公用组件,我们再在 components 目录下新建 NavBar 目录,用于编写底部导航栏,代码如下所示:

Nav/index.jsx

import React, { useState } from 'react';
import PropTypes from 'prop-types'
import { TabBar } from 'zarm';
import { useNavigate } from 'react-router-dom';
import s from './style.module.less';

const NavBar = ({ showNav }) => {
  const [activeKey, setActiveKey] = useState('/');
  const navigateTo = useNavigate()

  const changeTab = (path) => {
    setActiveKey(path)
    navigateTo(path)
  }

  return (
    <TabBar visible={showNav} className={s.tab} activeKey={activeKey} onChange={changeTab}>
      <TabBar.Item
        itemKey="/"
        title="账单"
      />
      <TabBar.Item
        itemKey="/data"
        title="统计"
      />
      <TabBar.Item
        itemKey="/user"
        title="我的"
      />
    </TabBar>
  );
};

NavBar.propTypes = {
  showNav: PropTypes.bool
}

export default NavBar;

代码解析:

首先是声明 NavBar 函数组件,它接收一个外部传入的 showNav 属性,用于控制导航栏的显示隐藏。

通过 useNavigate 钩子方法,拿到路由实例 navigateTo,它内部含有很多路由的方法,在上述代码中,我们使用到的是 navigateTo 进行路由跳转。

在页面中,引入 TabBar 组件,它接受几个属性:

  • visible:用于控制导航栏的显示隐藏。
  • activeKey:当前被点击的导航栏。
  • onChange:点击导航栏之后的回调方法,path 参数为 TabBar.ItemitemKey 属性。

TabBar 官方文档:zarm.gitee.io/#/component… (opens new window)

所以当你点击导航栏的时候,changeTab 方法便会被触发,执行内部的 setActiveKeynavigateTo,他们的作用分别是设置当前点击的高亮和让页面跳转到对应的页面组件。

说到跳转到对应的组件,'/'、'/data'、'/user' 这三个路由对应的三个组件我们还未编写,这里我们在 container 目录下新建这三个页面组件,作为占位。

// Home/index.jsx
import React from 'react'

const Home = () => {
  return <div>首页</div>
}

export default Home

// Data/index.jsx
import React from 'react'

const Data = () => {
  return <div>数据</div>
}

export default Data

// User/index.jsx
import React from 'react'

const User = () => {
  return <div>个人中心</div>
}

export default User

别忘了,前往 router/index.js 添加路由配置,如果不添加这个配置,调用 navigateTo 这个方法,就无法匹配到对应的页面组件,代码如下:

// router/index.js
import Home from '@/container/Home'
import Data from '@/container/Data'
import User from '@/container/User'

const routes = [
  {
    path: "/",
    component: Home
  },
  {
    path: "/data",
    component: Data
  },
  {
    path: "/user",
    component: User
  }
];

export default routes

这时,我们还缺少一步,将导航栏组件引入 App.jsx 入口页面,如下所示:

// App.jsx
...
import NavBar from '@/components/NavBar';
...
function App() {
  return <Router>
    <ConfigProvider primaryColor={'#007fff'}>
      <>
       <Routes>
        {routes.map(route => <Route exact key={route.path} path={route.path} element={<route.component />} />)}
       </Routes>
      <NavBar showNav={showNav} />
     </>
    </ConfigProvider>
    <NavBar showNav={true} />
  </Router>
}

通过 npm run dev 启动项目,浏览器展示效果如下所示:

img

上图效果所示,注意地址栏的变化,点击相应的 Tab,调用的 navigateTo 方法,将地址栏的 pathname 改变,随之而来的是页面组件的改变。这个就应证了我们第 10 章给大家解释的单页面路由控制的原理。navigateTo 做的事情就是改变地址栏,地址栏一旦改变,就会触发地址所对应的组件渲染,如 /data,渲染的就是 Data 页面组件。

你会问为什么导航栏会一直显示在底部,我们来分析以下代码:

img

红色框是组件展示的区域,每个路径对应着一个组件,这个在 router/index.js 文件中也有所体现。

绿色框则代表导航栏的位置,也就是说,无论上面的组件怎么变化,底部的导航栏一直都是存在的。

# 添加底部导航图标

我们将图标写成公共组件,这样便于后面各个页面方便引入,我们新建 components/CustomIcon/index.jsx,添加如下代码:

import { Icon } from 'zarm';

export default Icon.createFromIconfont('//at.alicdn.com/t/font_2236655_w1mpqp7n1ni.js');

上述代码,我们引入 Icon,执行它的自定义图标方法 createFromIconfont,它接收一个参数,为 iconfont 生产的静态脚本路径,你可以自己去 官网 (opens new window) 配置,也可以直接用我提供的:

img

这里我已经为大家添加好了各个图标,地址就是上述代码的地址。

接着我们将其引入到代码中使用,打开 components/NavBar/index.jsx ,添加如下属性:

import CustomIcon from '../CustomIcon';
...
<TabBar.Item
  itemKey="/"
  title="账单"
  icon={<CustomIcon type="zhangdan" />}
/>
<TabBar.Item
  itemKey="/data"
  title="统计"
  icon={<CustomIcon type="tongji" />}
/>
<TabBar.Item
  itemKey="/user"
  title="我的"
  icon={<CustomIcon type="wode" />}
/>

查看浏览器展示效果如下:

img

# 底部导航栏的显示隐藏

我们在之前引入 NavBar 的代码中,将 showNav 属性写死为 true。此时,我们需要将其盘活,打开 App.jsx,添加如下代码:

import React, { useEffect, useState } from 'react'
import {
  BrowserRouter as Router,
  Routes,
  Route,
  useLocation
} from "react-router-dom"

import NavBar from '@/components/NavBar';

import { ConfigProvider } from 'zarm'

import routes from '@/router'
function App() {
  const location = useLocation() // 拿到 location 实例
  const { pathname } = location // 获取当前路径
  const needNav = ['/', '/data', '/user'] // 需要底部导航栏的路径
  const [showNav, setShowNav] = useState(false) // 是否展示 Nav
  useEffect(() => {
    setShowNav(needNav.includes(pathname))
  }, [pathname]) // [] 内的参数若是变化,便会执行上述回调函数=
  return <Router>
    <ConfigProvider primaryColor={'#007fff'}>
      <Switch>
        {
          routes.map(route => <Route exact key={route.path} path={route.path}>
            <route.component />
          </Route>)
        }
      </Switch>
    </ConfigProvider>
    <NavBar showNav={true} />
  </Router>
}

export default App

当你刷新浏览器,控制台应该会报下面的错误:

img

执行 useLocation 时,报错 location of undefined。这是因为想要在函数组件内执行 useLocation,该组件必须被 Router 高阶组件包裹,我们做如下改动,将 App.jsxRouter 组件,前移到 main.jsx 内,如下:

img

逻辑分析:

我们拿到 pathname,将其设置为 useEffect 钩子函数的第二个参数,监听它的变化,一旦 pathname 变化,便会触发回调函数执行 setShowNav(needNav.includes(pathname)),结果会传递给 NavBar 组件,从而控制组件的显示隐藏。needNav 为需要底部导航的路径值。

我们不妨做个测试,在 container 目录下新建一个测试页面组件 Detail,并且添加路由配置。

别忘记把组件属性修改成动态变量:

<NavBar showNav={showNav} />

查看浏览器的展示效果:

img

# 总结

导航栏可以用在很多地方,映射到 PC 网页就是左侧侧边导航,道理都是相通的。移动端放在下面控制,PC 端放在左边或者右边控制罢了。所以再次强调不要学完了一个知识点,就思维定势地认为只能用在某一个需求上,能做到融会贯通,才是判断一个好程序员的标准。