Open toFrankie opened 4 months ago
最近在做 Taro 项目,有微信小程序和 H5 两端,因导航栏样式无法满足要求,需实现自定义 TabBar。
目前 Taro for H5 未默认支持自定义 TabBar,详见 #10049。
相关链接:
注意事项:
custom-tab-bar
src
tabBar
custom: true
实现思路:
对于微信小程序来说,Taro 已经支持了,这个不多说,按要求写就能正常显示。而 H5 则在 TabBar 页面中引入 custom-tab-bar 即可。有两个问题,一是内置的 TabBar 仍然存在,通过样式将其屏蔽掉。二是,在 TabBar 页面切换时,由于组件不会重新挂载,可能不会触发重新渲染,为避免 Tab 的高亮状态不正确,需在 onShow 时机进行设置。
onShow
引入 custom-tab-bar 页面的方式有两种,一是在每个 TabBar 页面中手动引入(一般不会很多),二是通过插件(比如 taro-inject-component-loader)自动插入到每个页面。后者,还需要在组件内进一步判断,若是 TabBar 的路由则显示自定义 TabBar 的内容,否则不显示。
下文以 React 为例,并选择第二种方式导入自定义 TabBar。
小程序配置 app.config.js,记得完整配置 tabBar。
app.config.js
export default defineAppConfig({ tabBar: { + custom: true, // ... }, })
安装 taro-inject-component-loader,如果手动引入 custom-tab-bar 可以忽略。
taro-inject-component-loader
$ pnpm add taro-inject-component-loader -D
编译配置 config/index.js 如下:
config/index.js
import path from 'path' export default { // ... alias: { + '@': path.resolve('./src'), }, h5: { // ... webpackChain(chain) { chain.merge({ module: { rule: { injectBaseComponentLoader: { test: /\.jsx$/, use: [ { + loader: 'taro-inject-component-loader', + options: { + importPath: '@/custom-tab-bar', + }, }, ], }, }, }, }) }, }, }
可关注下 taro-inject-component-loader 里 isPage 的默认配置是否满足要求,特别是有分包时,它只会插入到页面组件里,详见。
isPage
编写 custom-tab-bar 组件,几个注意点:
injectBaseComponentLoader.use[].options.isPage
Taro.eventCenter
import
Taro.getTabBar()
src/constants/index.js 👇
src/constants/index.js
export const IS_H5 = process.env.TARO_ENV === 'h5' export const ROUTE = { INDEX: '/pages/index/index', MINE: '/pages/mine/index', } export const TAB_BAR_ROUTES = [ROUTE.INDEX, ROUTE.MINE] export const EVENT_NAME = { TAB_BAR_PAGE_VISIBLE: 'tab_bar_page_visible', }
src/custom-tab-bar/index.jsx 👇
src/custom-tab-bar/index.jsx
抽成 Hook src/hooks/use-tab-bar.js 👇
src/hooks/use-tab-bar.js
import Taro, { useDidShow } from '@tarojs/taro' import { EVENT_NAME } from '@/constants' export default function useTabBar(selectedIndex) { useDidShow(() => { Taro.eventCenter.trigger(EVENT_NAME.TAB_BAR_PAGE_VISIBLE, selectedIndex) }) }
页面使用 src/pages/index/index.jsx 👇
src/pages/index/index.jsx
import { View, Text } from '@tarojs/components' import useTabBar from '@/hooks/use-tab-bar' import './index.scss' export default function Index() { useTabBar(0) return ( <View className="index"> <Text>首页</Text> </View> ) }
H5 端内置的 TabBar 组件还是会渲染的,要通过样式来隐藏。src/app.scss 👇
src/app.scss
.taro-tabbar__tabbar { display: none !important; }
完整示例 👉 taro-custom-tab-bar。
有个体验上的问题,自定义 TabBar 在各个 TabBar 页面初始化时都会创建一个新的组件实例导致,导致切换 Tab 时图片闪烁,可以关注 #7302。
The end.
RN 可以兼容么
嗯...没写过 RN,你可以试试~
背景
最近在做 Taro 项目,有微信小程序和 H5 两端,因导航栏样式无法满足要求,需实现自定义 TabBar。
目前 Taro for H5 未默认支持自定义 TabBar,详见 #10049。
开始之前
相关链接:
注意事项:
custom-tab-bar
,且放在src
目录下。tabBar
字段,一是为了向下兼容,二是不配置的情况下 H5 端切换页面的体验很差。后者不确定是否因为 Taro 不支持所以没做兼容。custom: true
编译为 H5 时是无效的。实现思路:
对于微信小程序来说,Taro 已经支持了,这个不多说,按要求写就能正常显示。而 H5 则在 TabBar 页面中引入
custom-tab-bar
即可。有两个问题,一是内置的 TabBar 仍然存在,通过样式将其屏蔽掉。二是,在 TabBar 页面切换时,由于组件不会重新挂载,可能不会触发重新渲染,为避免 Tab 的高亮状态不正确,需在onShow
时机进行设置。引入
custom-tab-bar
页面的方式有两种,一是在每个 TabBar 页面中手动引入(一般不会很多),二是通过插件(比如 taro-inject-component-loader)自动插入到每个页面。后者,还需要在组件内进一步判断,若是 TabBar 的路由则显示自定义 TabBar 的内容,否则不显示。实现
小程序配置
app.config.js
,记得完整配置tabBar
。安装
taro-inject-component-loader
,如果手动引入custom-tab-bar
可以忽略。编译配置
config/index.js
如下:编写
custom-tab-bar
组件,几个注意点:custom-tab-bar
组件内容。这个可以在injectBaseComponentLoader.use[].options.isPage
通过自定义正则限制只在 TabBar 页注入组件。custom-tab-bar
隐藏 → 显示 → 隐藏的过程,影响用户体验,要使用一定的缓存处理。onShow
生命周期设置 Tab 的高亮状态,以确保 Tab 正确显示。onShow
生命周期(详见),因此这里使用Taro.eventCenter
来监听页面组件的onShow
生命周期。import
导入的方式,Taro 会自动根据 TabBar 配置处理(详见)。而 H5 由于官方未支持,则需要手动import
导入。Taro.getTabBar()
获取组件实例,进而更新组件状态。但在 H5 端并未提供Taro.getTabBar()
方法,因此它无法兼容小程序和 H5 两端。下面我用 Functional Component 并统一用 Taro 的消息机制来更新组件的状态。src/constants/index.js
👇src/custom-tab-bar/index.jsx
👇展开
```jsx import { useMemo, useState } from 'react' import { View, Image } from '@tarojs/components' import Taro, { eventCenter } from '@tarojs/taro' import { IS_H5, EVENT_NAME, TAB_BAR_ROUTES } from '@/constants' import indexIcon from '@/images/icon-index.png' import indexIconActive from '@/images/icon-index-active.png' import mineIcon from '@/images/icon-mine.png' import mineIconActive from '@/images/icon-mine-active.png' // 样式文件碍于篇幅原因,就不贴出来了,请看文末完整示例 import './index.scss' const tabBarConfig = { color: '#7A7E83', selectedColor: '#3CC51F', backgroundColor: '#F7F7F7', borderStyle: 'black', list: [ { iconPath: IS_H5 ? indexIcon : '../images/icon-index.png', selectedIconPath: IS_H5 ? indexIconActive : '../images/icon-index-active.png', pagePath: '/pages/index/index', text: '首页', }, { iconPath: IS_H5 ? mineIcon : '../images/icon-mine.png', selectedIconPath: IS_H5 ? mineIconActive : '../images/icon-mine-active.png', pagePath: '/pages/mine/index', text: '我的', }, ], } export default function CustomTabBar() { const [selected, setSelected] = useState(-1) const onChange = (index, url) => { setSelected(index) Taro.switchTab({ url }) } const currentRoute = useMemo(() => { const pages = Taro.getCurrentPages() const currentPage = pages[pages.length - 1] const route = currentPage.route?.split('?')[0] return IS_H5 ? route : `/${route}` }, []) const isTabBarPage = useMemo(() => { return tabBarConfig.list.some(item => { // 如有做路由映射,此处可能要调整判断条件 const matched = TAB_BAR_ROUTES.find(route => route === currentRoute) return matched && item.pagePath === matched }) }, [currentRoute]) // 以避免多余的监听,特别是 rerender 时 useState(() => { if (!isTabBarPage) return eventCenter.on(EVENT_NAME.TAB_BAR_PAGE_VISIBLE, index => setSelected(index)) }) const element = useMemo(() => { if (IS_H5 && !isTabBarPage) return null return (抽成 Hook
src/hooks/use-tab-bar.js
👇页面使用
src/pages/index/index.jsx
👇H5 端内置的 TabBar 组件还是会渲染的,要通过样式来隐藏。
src/app.scss
👇最后
完整示例 👉 taro-custom-tab-bar。
有个体验上的问题,自定义 TabBar 在各个 TabBar 页面初始化时都会创建一个新的组件实例导致,导致切换 Tab 时图片闪烁,可以关注 #7302。
The end.