react + zarm 实现底部导航栏
Posted 凯小默:树上的男爵
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react + zarm 实现底部导航栏相关的知识,希望对你有一定的参考价值。
需要实现的效果
需要实现下面栏目固定,并且点击时切换到不同页面路由
实现过程
1.使用 prop-types 库进行类型检查
注意:自 React v15.5 起,React.PropTypes 已移入另一个包中。请使用 prop-types 库 代替。
PropTypes 提供了使用不同验证器的例子:
import PropTypes from 'prop-types';
MyComponent.propTypes =
// 你可以将属性声明为 JS 原生类型,默认情况下
// 这些属性都是可选的。
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// 任何可被渲染的元素(包括数字、字符串、元素或数组)
// (或 Fragment) 也包含这些类型。
optionalNode: PropTypes.node,
// 一个 React 元素。
optionalElement: PropTypes.element,
// 一个 React 元素类型(即,MyComponent)。
optionalElementType: PropTypes.elementType,
// 你也可以声明 prop 为类的实例,这里使用
// JS 的 instanceof 操作符。
optionalMessage: PropTypes.instanceOf(Message),
// 你可以让你的 prop 只能是特定的值,指定它为
// 枚举类型。
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// 一个对象可以是几种类型中的任意一个类型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 可以指定一个数组由某一类型的元素组成
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// 可以指定一个对象由某一类型的值组成
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// 可以指定一个对象由特定的类型值组成
optionalObjectWithShape: PropTypes.shape(
color: PropTypes.string,
fontSize: PropTypes.number
),
// An object with warnings on extra properties
optionalObjectWithStrictShape: PropTypes.exact(
name: PropTypes.string,
quantity: PropTypes.number
),
// 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
// 这个 prop 没有被提供时,会打印警告信息。
requiredFunc: PropTypes.func.isRequired,
// 任意类型的必需数据
requiredAny: PropTypes.any.isRequired,
// 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
// 请不要使用 `console.warn` 或抛出异常,因为这在 `oneOfType` 中不会起作用。
customProp: function(props, propName, componentName)
if (!/matchme/.test(props[propName]))
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
,
// 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
// 它应该在验证失败时返回一个 Error 对象。
// 验证器将验证数组或对象中的每个值。验证器的前两个参数
// 第一个是数组或对象本身
// 第二个是他们当前的键。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName)
if (!/matchme/.test(propValue[key]))
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
)
;
安装依赖:
npm i prop-types -S
2.使用 useNavigate
v6 用 useNavigate
替代了 useHistory
,其返回了一个 navigate
(点击查看用法) 的方法,实现比较简单:
- 从
NavigationContext
拿到navigator
,也就是history
实例。 - 然后根据
to、matches
的每项pathnameBase
以及当前URL pathname
生成最终的路径path(pathname, search, hash)
- 根据是否指定
replace
来判断是调用replace
还是push
方法
// v5
import useHistory from 'react-router-dom';
function MyButton()
let history = useHistory();
function handleClick()
history.push('/home');
;
return <button onClick=handleClick>Submit</button>;
;
现在,history.push()
将替换为 navigation()
:
// v6
import useNavigate from 'react-router-dom';
function MyButton()
let navigate = useNavigate();
function handleClick()
navigate('/home');
;
return <button onClick=handleClick>Submit</button>;
;
3.编写标签栏组件
新建 components/NavBar/index.jsx
文件,用于编写底部导航栏,代码如下所示:
import React, useState from 'react';
import PropTypes from 'prop-types'
import TabBar from 'zarm';
import useNavigate, useLocation from 'react-router-dom';
import CustomIcon from '../CustomIcon'
import s from './style.module.less';
const NavBar = ( showNav ) =>
const location = useLocation() // 拿到 location 实例
const pathname = location // 获取当前路径
console.log('navbar pathname', pathname)
const [activeKey, setActiveKey] = useState(pathname);
const navigate = useNavigate()
const changeTab = (path) =>
setActiveKey(path)
navigate(path)
return (
<TabBar visible=showNav className=s.tab activeKey=activeKey onChange=changeTab>
<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="user" />
/>
</TabBar>
);
;
NavBar.propTypes =
showNav: PropTypes.bool
export default NavBar;
新建 components/NavBar/style.module.less
文件,用于编写底部导航栏样式,代码如下所示:
.tab
border-top: 1px solid #e9e9e9;
4.使用标签栏组件
打开 App.jsx
,添加如下代码:
import React, useState, useEffect from 'react'
import NavBar from '@/components/NavBar';
import Routes, Route, useLocation, BrowserRouter from 'react-router-dom'
import ConfigProvider from 'zarm';
import routes from '../src/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 <BrowserRouter>
<ConfigProvider primaryColor='#007fff'>
<Routes>
routes.map(route => <Route key=route.path path=route.path element=<route.component />></Route>)
</Routes>
</ConfigProvider>
<NavBar showNav=showNav />
</BrowserRouter>
export default App
我们发现报错了:Uncaught Error: You cannot render a <Router> inside another <Router>. You should never have more than one in your app.
这是因为想要在函数组件内执行 useLocation,该组件必须被 Router 高阶组件包裹,我们做如下改动,将 App.jsx
的 BrowserRouter 组件,前移到 main.jsx
内,如下:
App.jsx
里面
import React, useState, useEffect from 'react'
import NavBar from '@/components/NavBar';
import Routes, Route, useLocation from 'react-router-dom'
import ConfigProvider from 'zarm';
import routes from '../src/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 <>
<ConfigProvider primaryColor='#007fff'>
<Routes>
routes.map(route => <Route key=route.path path=route.path element=<route.component />></Route>)
</Routes>
</ConfigProvider>
<NavBar showNav=showNav />
</>
export default App
main.jsx
:
import React from 'react'
import ReactDOM from 'react-dom'
import BrowserRouter from 'react-router-dom'
import 'lib-flexible/flexible'
import './index.css'
import App from './App'
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
)
5.添加对应的页面路由
在 container 文件夹里添加下面三个模块的页面
// 账单
import React from 'react'
const Home = () =>
return <div>账单</div>
export default Home
// 统计
import React from 'react'
const Data = () =>
return <div>统计</div>
export default Data
// 个人中心
import React from 'react'
const User = () =>
return <div>个人中心</div>
export default User
然后在 router/index.js
添加路由:
import Login from '@/container/Login'
import Home from '@/container/Home'
import Data from '@/container/Data'
import User from '@/container/User'
const routes = [
path: "/login",
component: Login
,
path: "/",
component: Home
,
path: "/data",
component: Data
,
path: "/user",
component: User
];
export default routes
6.效果
我们可以切换到统计,然后刷新,发现也是没有问题。
参考资料
- React-Router v6 新特性解读及迁移指南
- 系好安全带,带你遨游 React Router v6 源码
- https://zarm.design/#/components/tab-bar
- 使用 PropTypes 进行类型检查
以上是关于react + zarm 实现底部导航栏的主要内容,如果未能解决你的问题,请参考以下文章
react + zarm 实现账单列表类型以及时间条件弹窗封装
react + zarm + antV F2 实现账单数据统计饼图效果