✨家人们记得点个账号关注,会持续发布大前端领域技术文章💕 🍃
组件导航(Navigation)
Navigation是路由容器组件,一般作为首页的根容器 ,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式。Navigation组件适用于模块内和跨模块的路由切换,一次开发,多端部署场景。通过组件级路由能力实现更加自然流畅的转场体验,并提供多种标题栏样式来呈现更好的标题和内容联动效果。在不同尺寸的设备上,Navigation组件能够自适应显示大小,自动切换分栏展示效果。
- 模块内、和跨模块实现路由切换
- 一次开发,多端部署场景
- 通过组件级路由能力实现更加自然流畅的转场体验
- 更好的标题、tabBar等等联动效果
- 等等
2.1 Navigation属性
plain
@Entry
@Component
struct Index {
build() {
Navigation() {
}
.title("主标题")
.mode()
.toolbarConfiguration()
.menus()
}
}

title 设置标题
显示在这个主标题的位置
titleMode 标题栏模式
标题栏在界面顶部,用于呈现界面名称和操作入口,Navigation组件通过titleMode属性设置标题栏模式
- Mini模式:普通型标题栏,用于一级页面不需要突出标题的场景

- Full模式:强调型标题栏,用于一级页面需要突出标题的场景。

mode 设置模式
Navigation是路由容器组件,一般作为首页的根容器,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式.Navigation组件适用于模块内和跨模块的路由切换,一次开发,多端部署场景
Navigation组件通过mode属性设置页面的显示模式。自适应模式下,当页面宽度大于等于一定阈值( API version 9及以前:520vp,API version 10及以后:600vp )时,Navigation组件采用分栏模式,反之采用单栏模式。

将mode属性设置为NavigationMode.Stack,Navigation组件即可设置为单页面显示模式。

将mode属性设置为NavigationMode.Split,Navigation组件即可设置为分页面显示模式。
toolbarConfiguration 底层标题栏
.toolbarConfiguration() 里面设置的是一个数组,数组里面每个元素ToolbarItem类型,数据的结构如下
plain
{
value: "我的",
icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=",
action: () => {
},
activeIcon: "",
status: ToolbarItemStatus.DISABLED,
}
value 就是显示的文字
icon 显示的图标
action 当点击时触发做某些事情,例如跳转路由
activeIcon 激活时的图标
status 当前图标的状态,例如不可用等等

menus 菜单栏
菜单栏位于Navigation组件的右上角,开发者可以通过menus属性进行设置。menus支持Array和CustomBuilder两种参数类型。使用Array类型时,竖屏最多支持显示3个图标,横屏最多支持显示5个图标,多余的图标会被放入自动生成的更多图标。

plain
{
value: "我的",
icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=",
action: () => {
},
isEnabled:false
}
value 就是显示的文字
icon 显示的图标
action 当点击时触发做某些事情,例如跳转路由
isEnabled 是否可用
- 示例代码(全部)
arkts
@Entry
@Component
struct Index {
build() {
Navigation() {
}
.title("主标题")
.mode(NavigationMode.Stack)
.titleMode(NavigationTitleMode.Free)
.toolbarConfiguration([
{
value: "我的",
icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=",
action: () => {
},
},
{
value: "购物车",
icon: "https://s1.aigei.com/src/img/png/5d/5dc2f07ba8224cd4883648c9ea939ac5.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:Q5dD_6ZS_ry4kaVXHgv6gnKw2IQ=",
action: () => {
}
},
{
value: "更多",
icon: "https://s1.aigei.com/src/img/png/03/03d71ede7b404e70838f3d575b31a930.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:ABookBJ6PVBeSxtM2hEuQHPlGLE=",
action: () => {
}
}
])
.menus([
{
value: "设置",
icon: "https://s1.4sai.com/src/img/png/6c/6ca77e26c31548e680b784ab33f72900.png?e=1735488000&token=1srnZGLKZ0Aqlz6dk7yF4SkiYf4eP-YrEOdM1sob:9x9bgI9kDs15UsWPzGN7ZuaICXM=",
action: () => {
},
},
{
value: "小红书",
icon: "https://s1.aigei.com/src/img/png/3e/3e3bda0ac48046b08e560e8764942bd8.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:JPuRl9bkD4UfIc8fGN4ApLuS_rE=",
action: () => {
}
},
{
value: "微信",
icon: "https://s1.aigei.com/src/img/png/ef/efd714270fdd44f485156053901ecb51.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:GfcdaqAj6Uie-yCw4Wt1pPqAGwg=",
action: () => {
}
},
{
value: "抖音",
icon: "https://s1.aigei.com/src/img/png/5b/5b26e982f0b34c47817d3b40c9bf2d1f.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:YCO6IvUtFIqf6x1hmy82VctIElo=",
action: () => {
}
}
])
}
}
2.2 NavRouter
导航组件,默认提供点击响应处理,不需要开发者自定义点击事件逻辑。
必须包含两个子组件,其中第二个子组件必须为NavDestination。
其中第一个组件是需要点击的元素
第二个必须是NavDestination
plain
@Entry
@Component
struct Index {
build() {
Navigation() {
// NavRouter() {
// Text('菜单1')
// NavDestination() {
// Text('内容1')
// }
// }
//
//
// NavRouter() {
// Text('菜单2')
// NavDestination() {
// Text('内容2')
// }
// }
ForEach('123'.split(''), (item: number) => {
NavRouter() {
// 这里是点击的内容
Column() {
Text("文本" + item)
.width("85%")
.height(60)
.backgroundColor("#ccc")
.borderRadius(25)
.margin({ top: 10 })
.textAlign(TextAlign.Center)
}
// 这里是跳转显示的页面
NavDestination() {
Text("文本" + item).fontSize(30).fontColor("red")
}.title("标题" + item).hideTitleBar(false)
}.mode(NavRouteMode.PUSH)
})
}.mode(NavigationMode.Split)
}
}
这里有一个属性
mode(mode: NavRouteMode)
设置指定点击NavRouter跳转到NavDestination页面时,使用的路由模式
PUSH_WITH_RECREATE 跳转到新的NavDestination页面时,替换当前显示的NavDestination页面,页面销毁,但该页面信息仍保留在路由栈中。
PUSH 跳转到新的NavDestination页面时,覆盖当前显示的NavDestination页面,该页面不销毁,且页面信息保留在路由栈中。
REPLACE 跳转到新的NavDestination页面时,替换当前显示的NavDestination页面,页面销毁,且该页面信息从路由栈中清除。
还有一个事件
onStateChange(callback: (isActivated: boolean) => void)
组件激活状态切换时触发该回调。开发者点击激活NavRouter,加载对应的NavDestination子组件时,回调onStateChange(true)。NavRouter对应的NavDestination子组件不再显示时,回调onStateChange(false)。
最后效果
plain
@Entry
@Component
struct Index {
pageStack: NavPathStack = new NavPathStack();
@State list: Array<number> = [1, 2, 3]
build() {
Navigation(this.pageStack) {
ForEach(this.list, (item: number) => {
NavRouter() {
Column() {
Text("文本" + item)
.width("85%")
.height(60)
.backgroundColor("#ccc")
.borderRadius(25)
.margin({ top: 10 })
.textAlign(TextAlign.Center)
}
NavDestination() {
Text("文本" + item)
.fontSize(30)
.fontColor("red")
}.title("标题" + item)
}.mode(NavRouteMode.PUSH)
.onStateChange((isActivated: boolean) => {
console.log(item + "激活:" + isActivated)
})
})
}
.title("主标题")
.mode(NavigationMode.Stack)
.titleMode(NavigationTitleMode.Free)
.toolbarConfiguration([
{
value: "我的",
icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=",
action: () => {
this.pageStack.pushPath({ name: "pageOne", param: "aaa" })
},
},
{
value: "购物车",
icon: "https://s1.aigei.com/src/img/png/5d/5dc2f07ba8224cd4883648c9ea939ac5.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:Q5dD_6ZS_ry4kaVXHgv6gnKw2IQ=",
action: () => {
}
},
{
value: "更多",
icon: "https://s1.aigei.com/src/img/png/03/03d71ede7b404e70838f3d575b31a930.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:ABookBJ6PVBeSxtM2hEuQHPlGLE=",
action: () => {
}
}
])
.menus([
{
value: "设置",
icon: "https://s1.4sai.com/src/img/png/6c/6ca77e26c31548e680b784ab33f72900.png?e=1735488000&token=1srnZGLKZ0Aqlz6dk7yF4SkiYf4eP-YrEOdM1sob:9x9bgI9kDs15UsWPzGN7ZuaICXM=",
action: () => {
},
},
{
value: "小红书",
icon: "https://s1.aigei.com/src/img/png/3e/3e3bda0ac48046b08e560e8764942bd8.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:JPuRl9bkD4UfIc8fGN4ApLuS_rE=",
action: () => {
}
},
{
value: "微信",
icon: "https://s1.aigei.com/src/img/png/ef/efd714270fdd44f485156053901ecb51.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:GfcdaqAj6Uie-yCw4Wt1pPqAGwg=",
action: () => {
}
},
{
value: "抖音",
icon: "https://s1.aigei.com/src/img/png/5b/5b26e982f0b34c47817d3b40c9bf2d1f.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:YCO6IvUtFIqf6x1hmy82VctIElo=",
action: () => {
}
}
])
}
}
2.3 独立的NavDestination
切记独立的NavDestination不能通过NavRouter跳转 它也被弃用了,只能通过 NavPathStack创建一个路由栈对象,
然后通过路由栈对象控制页面跳转
具体实现
1、Profile.ets 把页面内容NavDestination独立出去
plain
@Component
export struct Profile {
build() {
NavDestination() { // 根元素必须是NavDestination
Column() {
Text("实际的内容")
}
}
.title("标题")
}
}
2、Navigation通过属性关联Profile页面
plain
import {Profile} from './Profile'
@Entry
@Component
export struct Index {
@Builder PagesMap(name: string) {
if (name === "Profile") {
Profile()
}
}
pageStack: NavPathStack = new NavPathStack() // 页面栈对象 管理页面栈里面的网页
build() {
Navigation(this.pageStack) {
Button('跳转到Cart页面').onClick(() => {
this.pageStack.pushPathByName('Profile', null)
// this.pageStack.pushPath({ name: "Profile", param: "实际需要显示的内容" })
})
}
.title("主标题")
.mode(NavigationMode.Stack)
.navDestination(this.PagesMap) // 通过属性的方式动态设置
}
}
3、控制跳转(之前没有独立NavRouter 独立之后必须通过Navigation提供的页面栈对象来操作
plain
3.1 创建页面栈
pageStack: NavPathStack = new NavPathStack(); // 创建页面栈实例化对象 控制页面栈路由增删改等
3.2 注册关联
Navigation(this.pageStack) {
3.3 跳转
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-navigation-navigation-V5#%E8%B7%AF%E7%94%B1%E6%93%8D%E4%BD%9C
NavDestination的模式
NavDestination的mode属性有两种值
标准类型 NavDestinationMode.STANDARD
标准类型的NavDestination的生命周期跟随其在NavPathStack页面栈中的位置变化而改变。
弹窗类型 NavDestinationMode.DIALOG
整个NavDestination默认透明显示。弹窗类型的NavDestination显示和消失时不会影响下层标准类型的NavDestination的显示和生命周期,两者可以同时显示。
下面处理一个弹窗问题
plain
@Component
export struct DialogPage {
@Consume pageStack: NavPathStack;
param: string = ""
build() {
NavDestination() {
Column() {
Stack({ alignContent: Alignment.TopEnd }) {
Text(this.param)
.fontSize(30)
.fontColor("red")
.textAlign(TextAlign.Center)
.width("100%")
.margin({ top: 80 })
Button("×").onClick(() => {
this.pageStack.pop();
})
}.width("70%")
.height("30%")
.backgroundColor("white")
.borderRadius(30)
}.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.width("100%")
.height("100%")
}
.backgroundColor("rgba(0,0,0,0.5)")
.mode(NavDestinationMode.DIALOG)
.title("标题")
.hideTitleBar(true)
}
}
import { DialogPage } from '../components/DialogPage';
@Entry
@Component
struct Index {
@Provide pageStack: NavPathStack = new NavPathStack();
@Builder
PagesMap(name: string, param: string) {
if (name === "dialogPage") {
DialogPage({ param: param })
}
}
build() {
Navigation(this.pageStack) {
}
.title("主标题")
.mode(NavigationMode.Stack)
.titleMode(NavigationTitleMode.Free)
.navDestination(this.PagesMap)
.toolbarConfiguration([
{
value: "我的",
icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=",
action: () => {
this.pageStack.pushPath({ name: "dialogPage", param: "aaa" })
},
},
{
value: "购物车",
icon: "https://s1.aigei.com/src/img/png/5d/5dc2f07ba8224cd4883648c9ea939ac5.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:Q5dD_6ZS_ry4kaVXHgv6gnKw2IQ=",
action: () => {
}
},
{
value: "更多",
icon: "https://s1.aigei.com/src/img/png/03/03d71ede7b404e70838f3d575b31a930.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:ABookBJ6PVBeSxtM2hEuQHPlGLE=",
action: () => {
}
}
])
}
}

2.4 NavPathStack
NavPathStrack是页面栈为路由跳转提供的方法对象,每个Navigation都需要创建并传入一个NavPathStack对象,用于管理页面。主要涉及页面跳转、页面返回、页面替换、页面删除、参数获取、路由拦截等功能。
pageStack: NavPathStack = new NavPathStack()
普通跳转
plain
this.pageStack.pushPath({ name: "DialogPage", param: "aaa" })
this.pageStack.pushPathByName("DialogPage", "aaa")
这两种方法都可以使用
带返回回调的跳转,跳转时添加onPop回调,能在页面出栈时获取返回信息
plain
this.pageStack.pushPath({
name: "DialogPage", param: "aaa", onPop: (popInfo) => {
Logger.log(popInfo.result)
}
})
关闭退回时通过pop传回的数据
plain
Button("×").onClick(() => {
this.pageStack.pop("返回的内容")
})
页面返回
plain
// 返回到上一页
this.pageStack.pop()
// 返回到上一个PageOne页面
this.pageStack.popToName("PageOne")
// 返回到索引为1的页面
this.pageStack.popToIndex(1)
// 返回到根首页(清除栈中所有页面)
this.pageStack.clear()
页面替换
plain
// 将栈顶页面替换为PageOne
this.pageStack.replacePath({ name: "PageOne", param: "PageOne Param" })
this.pageStack.replacePathByName("PageOne", "PageOne Param")
页面删除
plain
// 删除栈中name为PageOne的所有页面
this.pageStack.removeByName("PageOne")
// 删除指定索引的页面
this.pageStack.removeByIndexes([1,3,5])
参数获取
plain
// 获取栈中所有页面name集合
this.pageStack.getAllPathName()
// 获取索引为1的页面参数
this.pageStack.getParamByIndex(1)
// 获取PageOne页面的参数
this.pageStack.getParamByName("PageOne")
// 获取PageOne页面的索引集合
this.pageStack.getIndexByName("PageOne")
路由拦截
在页面的初始生命周期中设置
plain
aboutToAppear(): void {
this.pageStack.setInterception({
willShow: (from: NavDestinationContext | NavBar, to: NavDestinationContext | NavBar,
operation: NavigationOperation, isAnimated: boolean) => {
if (typeof to === "string") return;
if (to.pathInfo.name === "dialogPage") {
to.pathStack.pop();
to.pathStack.pushPath({ name: "page1", param: "bbbb" })
}
}
})
}
2.5 跨包动态路由-系统路由表
准备生成Navigation的route_map路由
a)给模块创建系统路由表 (也就是针对于Navigation以后还是通过配置文件生成路由)
创建resources/base/profile/route_map.json(切记route不加r)路由配置文件,填写下述代码
plain
{
"routerMap": [
{
# 路由名 也就是跳转名
"name": "Home或者Profile",
# 改名字对应的页面
"pageSourceFile": "src/main/ets/pages/Profile.ets",
# 切记MainPage.ets这个文件必须用MainPageBuilder当做入口(比较抽象跟着写 写完反过来看才能理解)
"buildFunction": "MainPageBuilder",
"data": {
"description" : "this is PageOne"
}
}
]
}
b)让创建route_map.json生效 也就是跟模块绑定
修改模块的module.json5文件 "routerMap": "$profile:route_map",
plain
{
"module": {
"routerMap": "$profile:route_map",
"name": "cartHar",
"type": "har",
"deviceTypes": [
"default",
"tablet",
"2in1"
]
}
}
后续创建页面跳转
a)必须按照下述格式创建页面
scss
按照系统路由表需要的组件格式修改组件 `src/main/ets/components/MainPage.ets`
```plain
// 跳转页面入口函数
@Builder
export function MainPageBuilder() { // 细节2:按照这个格式配置
MainPage()
}
@Component
struct MainPage { // 细节1.1:不用导出 但是必须按照上述配置
build() {
NavDestination() { // 细节1.2:必须通过NavDestination写
Text('内容').fontSize(30)
}
.title('CartHar/MainPage')
.hideTitleBar(false)
}
}
```
b)通过NavPathStack跳转
2.6 跨包动态路由-自定义路由表 HMRouter
千锋生鲜项目
2.7 企业级面试题
-
Navigation 和 Router 区别
1 路由有闪屏的问题、Navigation更流畅
2 Navigation更适合一次开发、多端适配
3 Navigation页面转场动画更方便
4 跨模块开发Navigation更方便
等等 -
如何用的
diff
传统:1-创建页面、2-配置路由main_pages、3-router跳转
现在:
- 准备:Navigation动态路由-系统路由表配置:1-创建route_map.json写配置、2-注册把1的文件跟项目关联module.json5、3-入口文件Navigation配置
- 后续1:创建页面 必须按照系统路由规则去创建 (必须写NavDestination 然后不写导出但是必须被@Builder调用)
- 后续2:配置该页面路由就可以跳转
✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃
✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃
✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃
^_^ 点关注、不迷路、主播带你学技术 (๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤