前言
只要应用会联网,URL 就无处不在:发起 HTTP 请求要拼 URL、处理深链接(Deep Link)要解析 URL、做分享要在 URL 上追加追踪参数、跳转 WebView 要读取查询串。看似简单的一个字符串,里面其实塞满了协议、账号、主机、端口、路径、查询参数、锚点等一大堆结构化信息。手写字符串切割去解析这些字段,既容易出错(想想?和#同时出现、参数值里含&、中文需要百分号编码这些边界情况),又难以维护。
HarmonyOS NEXT 的标准库提供了@ohos.url模块,把 URL 的解析与构建标准化成两个核心类:url.URL(整条 URL 的结构化视图)和url.URLParams(查询参数的增删改查)。它们零权限、纯本地、无网络依赖,是处理 URL 的正确姿势。
本文用一个可交互的"URL 解析与构建实验室"页面,把这两个类讲透:输入任意 URL 实时拆解出全部组成部分,可视化编辑查询参数并观察URLParams如何自动做百分号编码。全文含完整可运行代码,适合中级开发者掌握这套基础但高频的工具。
一、URL 的结构
在动手之前,先看清楚一条完整 URL 的解剖图。以本文示例 URL 为例:
https://user:pass@shop.example.com:8443/api/v2/items?category=book&sort=price&page=2#reviews └─┬─┘ └─┬─┘└┬─┘ └──────┬───────┘└┬─┘└────┬─────┘ └──────────┬──────────┘└──┬──┘ protocol username pass hostname port pathname search hash各部分含义:
| 组成 | 示例值 | 说明 |
|---|---|---|
| protocol | https: | 协议(含冒号) |
| username | user | 认证用户名 |
| password | pass | 认证密码 |
| hostname | shop.example.com | 域名(不含端口) |
| port | 8443 | 端口 |
| host | shop.example.com:8443 | 域名+端口 |
| origin | https://shop.example.com:8443 | 源(协议+主机) |
| pathname | /api/v2/items | 路径 |
| search | ?category=book&sort=price&page=2 | 查询串(含问号) |
| hash | #reviews | 锚点/片段 |
@ohos.url的URL类会把这些字段全部解析成对象属性,你不用再跟正则和字符串切割较劲。
二、url.URL:解析整条 URL
引入方式:
import{url}from'@kit.ArkTS';2.1 创建与解析
推荐用静态方法url.URL.parseURL()解析:
constu=url.URL.parseURL('https://user:pass@shop.example.com:8443/api/v2/items?category=book&page=2#reviews');它还支持第二个参数base,用于把相对路径拼到基地址上:
constu2=url.URL.parseURL('/detail?id=10','https://shop.example.com');// → https://shop.example.com/detail?id=10这在处理服务端返回的相对链接时非常方便。
2.2 读取各个字段
解析出来的URL对象,属性一目了然:
u.protocol// 'https:'u.username// 'user'u.password// 'pass'u.hostname// 'shop.example.com'u.port// '8443'u.host// 'shop.example.com:8443'u.origin// 'https://shop.example.com:8443'u.pathname// '/api/v2/items'u.search// '?category=book&page=2'u.hash// '#reviews'u.href// 完整 URL 字符串u.params// URLParams 对象(查询参数)2.3 实验室里的解析逻辑
页面里把解析结果组织成一个键值列表逐条展示。注意parseURL遇到非法 URL 会抛异常,必须用 try-catch 兜住:
interfaceUrlPart{label:string;value:string;}// 解析 URL 的各个组成部分privateparse():void{this.parseError='';try{constu=url.URL.parseURL(this.inputUrl);constlist:UrlPart[]=[{label:'origin 源',value:u.origin},{label:'protocol 协议',value:u.protocol},{label:'username 用户名',value:u.username.length>0?u.username:'(无)'},{label:'password 密码',value:u.password.length>0?u.password:'(无)'},{label:'host 主机',value:u.host},{label:'hostname 域名',value:u.hostname},{label:'port 端口',value:u.port.length>0?u.port:'(默认)'},{label:'pathname 路径',value:u.pathname},{label:'search 查询串',value:u.search.length>0?u.search:'(无)'},{label:'hash 锚点',value:u.hash.length>0?u.hash:'(无)'}];this.parts=list;// 同步提取查询参数供第二个 Tab 编辑constps:ParamItem[]=[];u.params.forEach((value:string,name:string)=>{ps.push({name:name,value:value});});this.params=ps;this.rebuildQuery();}catch(e){this.parseError='解析失败:请检查 URL 格式是否合法';this.parts=[];}}几个要点:
- 异常处理不可少。用户输入的 URL 可能缺协议、含非法字符,
parseURL会直接抛错,不 catch 会导致页面崩溃。 - 端口/用户名等可能为空,用三元表达式给出友好占位(“默认”“无”),避免界面出现空白项。
u.params是一个URLParams对象,可以直接forEach遍历所有查询参数,我们借此把参数同步到第二个 Tab 做可视化编辑。
在aboutToAppear里首次自动解析一次,让页面打开即有内容:
aboutToAppear():void{this.parse();}三、url.URLParams:查询参数增删改查
URLParams(在部分文档里旧称URLSearchParams)专门管理查询串,把?a=1&b=2这种扁平字符串抽象成可编程的键值集合。
3.1 核心 API
constp=newurl.URLParams('a=1&b=2');p.append('c','3');// 追加(同名 key 可重复)p.set('a','10');// 设置(覆盖同名 key)p.get('a');// '10'p.getAll('a');// ['10'] —— 取同名的全部值p.has('b');// truep.delete('b');// 删除p.toString();// 'a=10&c=3' —— 序列化,自动百分号编码p.sort();// 按 key 排序append和set的区别值得注意:append允许同名 key 共存(比如tag=a&tag=b),set则会把同名 key 全部替换成一个。做多选筛选参数时用append,做唯一配置项时用set。
3.2 实验室里的参数编辑
页面第二个 Tab 提供参数的可视化增删。我们用一个ParamItem[]数组承载参数列表做展示,每次变更后用URLParams重新序列化出查询串:
interfaceParamItem{name:string;value:string;}// 用 URLParams 重建查询串(自动做百分号编码)privaterebuildQuery():void{constp=newurl.URLParams();this.params.forEach((it:ParamItem)=>{p.append(it.name,it.value);});this.queryString=p.toString();}privateaddParam():void{if(this.newKey.length===0){return;}constnext:ParamItem[]=this.params.slice();next.push({name:this.newKey,value:this.newValue});this.params=next;this.rebuildQuery();}privateremoveParam(index:number):void{constnext:ParamItem[]=this.params.slice();next.splice(index,1);this.params=next;this.rebuildQuery();}这里有个 ArkTS 状态管理的细节:修改数组要用slice()生成新数组再赋值,而不是直接push/splice原数组。因为@State依赖引用变化来触发刷新,原地修改数组不会被侦测到。这是 ArkUI 里操作@State数组的通用做法。
3.3 观察百分号编码
URLParams.toString()最实用的特性是自动百分号编码。当参数值里含有空格、中文、&、=等特殊字符时,它会自动转义,保证生成的查询串合法。
在实验室里输入参数keyword = 鸿蒙 开发(值里有中文和空格),点"append 追加参数",底部会显示:
?keyword=%E9%B8%BF%E8%92%99%20%E5%BC%80%E5%8F%91- 中文"鸿蒙"被编码成 UTF-8 字节的百分号形式(
%E9%B8%BF%E8%92%99); - 空格被编码成
%20。
如果你手写字符串拼接查询参数,极容易漏掉这一步,导致请求参数被服务端错误解析,甚至触发安全问题。URLParams帮你把编码这件"必须做但容易忘"的事自动化了。
对应的展示 UI:
Column(){Text('URLParams.toString() 重建结果(自动百分号编码)').fontColor('#7C3AED').fontWeight(FontWeight.Bold)Text(this.queryString.length>0?'?'+this.queryString:'(无参数)').fontFamily('monospace')}.backgroundColor('#EDE9FE').borderRadius(10)用monospace等宽字体展示,让编码后的百分号序列对齐更清晰。
四、页面整体结构
整个实验室分为"输入区 + 双 Tab"结构:
@Entry@Componentstruct UrlLabPage{@StateactiveTab:number=0;@StateinputUrl:string='https://user:pass@shop.example.com:8443/...';@Stateparts:UrlPart[]=[];@StateparseError:string='';@Stateparams:ParamItem[]=[];@StatenewKey:string='keyword';@StatenewValue:string='鸿蒙 开发';@StatequeryString:string='';build(){Column(){// 标题栏Row(){/* 返回 + 标题 + @ohos.url 角标 */}// 输入区:TextArea + 解析按钮Column(){/* ... */}// Tab 切换Row(){this.tabBtn('结构解析',0)this.tabBtn('查询参数',1)}// 内容区Scroll(){Column(){if(this.parseError.length>0){/* 错误提示 */}if(this.activeTab===0){this.parseTab()}else{this.paramsTab()}}}.layoutWeight(1)}}}第一个 TabparseTab()用ForEach把parts逐条渲染成"标签 + 值"的行卡片;第二个 TabparamsTab()包含参数输入框、追加按钮、带删除按钮的参数列表,以及底部重建的查询串。两个 Tab 各自封装成@Builder,主体逻辑清爽。
4.1 参数列表项
每个参数渲染成一行,右侧带删除按钮:
ForEach(this.params,(item:ParamItem,index:number)=>{Row(){Column(){Text(item.name).fontWeight(FontWeight.Bold)Text(item.value).fontColor('#6B7280')}.layoutWeight(1)Text('删除').fontColor('#DC2626').backgroundColor('#FEE2E2').onClick(()=>{this.removeParam(index);})}.backgroundColor('#FFFFFF').borderRadius(10)})ForEach的第二个回调参数index用于定位要删除的项。删除后rebuildQuery()会立即刷新底部的查询串,形成"编辑即所见"的闭环。
五、典型应用场景
掌握@ohos.url后,以下场景都能优雅解决:
5.1 深链接(Deep Link)解析
外部通过myapp://detail?id=100&from=push唤起应用时,用url.URL.parseURL解析出pathname决定跳转哪个页面,用params.get('id')取出业务参数,比手写切割可靠得多。
5.2 给 URL 追加追踪参数
分享链接时统一追加来源、渠道等追踪参数:
constu=url.URL.parseURL(shareUrl);u.params.append('utm_source','app');u.params.append('utm_medium','share');constfinalUrl=u.href;// 自动带上编码后的参数5.3 安全地拼接请求 URL
构建 API 请求时,用URLParams组织参数而非手写拼接,天然避免编码遗漏和特殊字符注入:
constp=newurl.URLParams();p.append('q',userInput);// 用户输入自动被编码p.append('page','1');constapi='https://api.example.com/search?'+p.toString();5.4 解析回调地址
OAuth 登录、支付回调等场景,服务端会把结果拼在回调 URL 的查询串或 hash 里,用params/hash精确读取,不必担心参数顺序和编码。
六、几个实战要点
6.1 parseURL 会抛异常
非法 URL(缺协议、含非法字符)会让parseURL抛错。凡是解析用户输入或外部来源的 URL,都要用 try-catch 包裹,给出友好的错误提示,而不是让应用崩溃。
6.2 @State 数组要用新引用
修改@State数组时,先slice()复制再修改再赋值,依靠引用变化触发 UI 刷新。原地push/splice不会触发重渲染,这是 ArkUI 状态管理的常见坑。
6.3 append 与 set 的语义差异
append保留同名 key(适合多值参数),set覆盖同名 key(适合唯一参数)。选错会导致参数丢失或重复,按业务语义选择。
6.4 toString 自动编码,别手动再编一遍
URLParams.toString()已经做了百分号编码,如果你在传入前又用encodeURIComponent编一次,会出现"双重编码"(%变成%25),导致服务端解析错误。记住:交给URLParams就别自己再编。
七、小结
本文以"URL 解析与构建实验室"为载体,系统讲解了 HarmonyOS NEXT 标准库@ohos.url的两个核心类:
url.URL:用parseURL()把整条 URL 结构化,读取 protocol / host / port / pathname / search / hash / origin 等全部字段,支持 base 拼接相对路径;url.URLParams:用 append / set / get / getAll / has / delete / toString 管理查询参数,自动完成百分号编码。
同时覆盖了工程实践要点:parseURL 的异常处理、@State 数组的新引用刷新、append 与 set 的语义差异、以及避免双重编码。这些是处理深链接、请求拼接、分享追踪等联网场景的基础能力。把@ohos.url用熟,你就能彻底告别手写字符串切割解析 URL 的脆弱代码。