0
点赞
收藏
分享

微信扫一扫

react + zarm 实现底部导航栏


需要实现的效果

需要实现下面栏目固定,并且点击时切换到不同页面路由

react + zarm 实现底部导航栏_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,) {
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,) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};

安装依赖:

npm

react + zarm 实现底部导航栏_ico_02

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

我们发现报错了:​​Uncaught Error: You cannot render a <Router> inside another <Router>. You should never have more than one in your app.​

react + zarm 实现底部导航栏_hooks_03


这是因为想要在函数组件内执行 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

​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 文件夹里添加下面三个模块的页面

react + zarm 实现底部导航栏_zarm_04

// 账单
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

然后在 ​​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

6.效果

react + zarm 实现底部导航栏_zarm_05

我们可以切换到统计,然后刷新,发现也是没有问题。

react + zarm 实现底部导航栏_zarm_06

参考资料

  • ​​React-Router v6 新特性解读及迁移指南​​
  • ​​系好安全带,带你遨游 React Router v6 源码​​
  • ​​https://zarm.design/#/components/tab-bar​​
  • ​​使用 PropTypes 进行类型检查​​



举报

相关推荐

0 条评论