零基础鸿蒙应用开发第三十三节:正则表达式基础与应用

零基础鸿蒙应用开发学习计划表

【学习目标】

  1. 掌握正则表达式的核心语法(元字符、量词、边界匹配),能独立构建手机号、密码、日期等常见场景的正则模式。
  2. 熟练使用ArkTS的RegExp类实现字符串校验、提取与替换操作,理解test()exec()的核心差异。
  3. 实战鸿蒙应用中JSON数据字段的正则校验,整合JsonUtil工具类完成“读取JSON→反序列化→字段校验”的完整链路。
  4. 规避正则表达式开发中的常见陷阱(转义字符、贪婪匹配、边界符遗漏)。
  5. 理解工程化开发规范:将校验逻辑封装为静态工具类,实现代码复用与职责分离。

【学习重点】

  1. 正则核心语法:元字符(^/$/\d/[])、量词({n}/{n,m}/+/*)的组合使用,重点掌握手机号/密码/日期的正则构建逻辑。
  2. ArkTS实战:RegExp类的test()(格式校验)和exec()(内容提取)方法,全局匹配(g修饰符)的实现方式。
  3. 工程化落地:将校验逻辑封装为ValidationUtil静态工具类,结合JsonUtil完成本地JSON文件的字段合法性校验。
  4. 避坑指南:转义字符双重处理、贪婪匹配优化、鸿蒙预览器/真机运行的环境适配。

一、工程准备

本节我们将创建名为RegexJsonDemo的工程,基于鸿蒙5.0(API12)开发,使用DevEco Studio 6.0+工具,项目结构目录如下:

entry/
├── src/
│   ├── main/
│   │   ├── ets/
│   │   │   ├── model/                  // 数据模型层:仅定义类型接口
│   │   │   │   ├── UserInfo.ets        // 用户信息字段接口(与JSON文件对齐)
│   │   │   │   └── RegexModel.ets      // 正则校验规则的类型约束接口
│   │   │   ├── constant/               // 常量层:存储固定正则规则
│   │   │   │   └── RegexValidator.ets  // 手机号/密码/日期等正则常量
│   │   │   ├── utils/                  // 工具层:封装可复用方法
│   │   │   │   ├── JsonUtil.ets        // 复用JSON读写工具类
│   │   │   │   └── ValidationUtil.ets  // 静态工具类:用户信息正则校验
│   │   │   └── pages/                  // 页面层:UI渲染+工具调用
│   │   │       └── Index.ets           // 主页面:演示正则+JSON校验流程
│   │   └── resources/                  // 资源目录
│   │       └── rawfile/                // 本地静态资源
│   │           └── user_info.json      // 测试用JSON文件

二、正则表达式核心基础

正则表达式是通过模式字符串匹配字符串的工具,ArkTS完全兼容ECMAScript规范,核心由「元字符+量词+修饰符」组成。

1. 正则创建方式

ArkTS支持两种创建方式,适用于不同场景:

// 演示方法:可在Index.ets中调用测试
testRegexCreate() {
  // 方式1:字面量形式(推荐)- 语法简洁、性能高,适用于固定模式
  const phoneRegex = /^1[3-9]\d{9}$/;

  // 方式2:构造函数形式 - 适用于动态拼接模式,需注意转义符\\
  const phonePrefix = '1[3-9]';
  const phoneSuffix = '\\d{9}'; // 字符串中\需双重转义
  const phoneRegex2 = new RegExp(`^${phonePrefix}${phoneSuffix}$`);

  // 测试结果(两者等价)
  console.log(`${phoneRegex.test("13800138000")}`); // true
  console.log(`${phoneRegex2.test("13800138000")}`); // true
}

2. 核心元字符(必掌握)

元字符定义匹配规则,是正则的基础,常用元字符如下:

元字符 含义 示例 匹配结果
. 匹配任意单个字符(除换行) a.b "acb"、"a2b"、"a@b"
\d 匹配数字(0-9) \d{3} "123"、"456"
\D 匹配非数字字符 \D+ "abc"、"x_y"
\w 匹配字母/数字/下划线 \w+ "user123"、"name_01"
\W 匹配非单词字符 \W "@"、"#"、空格
^ 匹配字符串开头 ^hello "hello world" 的开头"hello"
$ 匹配字符串结尾 world$ "hello world" 的结尾"world"
[] 字符集(匹配任意一个) [abc] "a"、"b"、"c"
[^] 否定字符集 [^0-9] 非数字字符(如"a"、"@")
() 分组捕获 (\d{4})-(\d{2}) 捕获"2024-06"中的"2024"和"06"

3. 量词(控制匹配次数)

量词指定元字符/分组的匹配次数,核心量词如下:

量词 含义 示例 匹配结果
{n} 精确匹配n次 \d{2} "12"、"99"
{n,} 至少匹配n次 a{2,} "aa"、"aaa"
{n,m} 匹配n-m次 a{1,3} "a"、"aa"、"aaa"
+ 匹配1次及以上(等价于{1,}) \d+ "1"、"123"
* 匹配0次及以上(贪婪) a* ""、"a"、"aaa"
? 匹配0次或1次 https? "http"、"https"

4. 修饰符(修改匹配行为)

修饰符用于调整正则的匹配规则,常用修饰符:

修饰符 含义 示例 效果
i 忽略大小写 /hello/i 匹配"Hello"、"HELLO"
g 全局匹配 /\d+/g 匹配字符串中所有数字组(如"123abc456"中的"123"和"456")
m 多行匹配 /^hello/m 匹配"hello\nworld"中第一行开头的"hello"

5. 经典正则实战

案例1:中国大陆手机号

// 规则:11位数字,第一位为1,第二位为3-9,后9位任意数字
testRegexTel() {
  const phoneRegex = /^1[3-9]\d{9}$/;
  console.log(`${phoneRegex.test("13800138000")}`); // true
  console.log(`${phoneRegex.test("12800138000")}`); // false(第二位非法)
  console.log(`${phoneRegex.test("138001380")}`);   // false(长度不足)
}

案例2:强密码(8-20位,含数字+字母+特殊字符)

testStrongPassword() {
  const pwdRegex = /^(?=.*\d)(?=.*[a-zA-Z])(?=.*[!@#$%^&*]).{8,20}$/;
  // 测试
  console.log(`${pwdRegex.test("Test123!")}`); // true
  console.log(`${pwdRegex.test("12345678")}`); // false(无字母/特殊字符)
}

案例3:日期(yyyy-mm-dd,限制月份/日期范围)

testRegexDate() {
  const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
  console.log(`${dateRegex.test("2024-06-01")}`); // true
  console.log(`${dateRegex.test("2024/06/01")}`); // false
  console.log(`${dateRegex.test("2024-6-1")}`);   // false
}

三、ArkTS中RegExp类的核心方法

RegExp类提供两个核心方法,覆盖绝大多数字符串处理场景:

1. test():格式校验(返回布尔值)

用于快速判断字符串是否符合正则规则,适用于表单校验、字段合法性检查:

testRegexTest() {
  const emailRegex = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
  const emailList = ["user@example.com", "invalid-email", "user@.com"];
  
  emailList.forEach((email, idx) => {
    console.log(`邮箱${idx+1} [${email}]:${emailRegex.test(email)}`);
  });
  // 输出:
  // 邮箱1 [user@example.com]:true
  // 邮箱2 [invalid-email]:false
  // 邮箱3 [user@.com]:false
}

2. exec():内容提取(返回匹配数组/null)

用于提取字符串中符合规则的内容,结合g修饰符可实现全局匹配:

testRegexExec() {
  // 案例1:提取URL中的域名
  const url = "https://www.example.com/path?name=test";
  const urlRegex = /https?:\/\/([\w.]+)/;
  const urlResult = urlRegex.exec(url);
  if (urlResult) {
    console.log("完整匹配:", urlResult[0]); // https://www.example.com
    console.log("提取域名:", urlResult[1]); // www.example.com
  }

  // 案例2:全局匹配提取所有数字
  const str = "hello123 world456! 789";
  const numRegex = /\d+/g; // g修饰符开启全局匹配
  let numMatch;
  
  while ((numMatch = numRegex.exec(str)) !== null) {
    console.log(`数字:${numMatch[0]},位置:${numMatch.index}`);
  }
  // 输出:
  // 数字:123,位置:5
  // 数字:456,位置:14
  // 数字:789,位置:20
}

四、JSON字段正则校验实战

整合JsonUtil(JSON读取)和ValidationUtil(静态校验工具),完成本地JSON文件的字段合法性校验。

1. 定义数据模型

model/UserInfo.ets

/**
 * 用户信息接口:与user_info.json字段一一对应
 */
export interface UserInfo {
  id: number;
  name: string;
  phone: string;
  email: string;
  password: string;
  birthday: string;
}

model/RegexModel.ets

/**
 * 正则校验规则的类型约束:统一规则结构
 */
export interface RegexValidatorType {
  phone: RegExp;       // 手机号正则
  email: RegExp;       // 邮箱正则
  password: RegExp;    // 密码正则
  date: RegExp;        // 基础日期正则
  dateAdvanced: RegExp;// 进阶日期正则(限制月/日范围)
}

2. 定义正则常量

constant/RegexValidator.ets

import { RegexValidatorType } from "../model/RegexModel";

/**
 * 正则校验规则常量:集中管理,便于维护
 */
export const RegexValidator: RegexValidatorType = {
  phone: /^1[3-9]\d{9}$/,
  email: /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/,
  password: /^(?=.*\d)(?=.*[a-zA-Z])(?=.*[!@#$%^&*]).{8,20}$/,
  date: /^\d{4}-\d{2}-\d{2}$/,
  dateAdvanced: /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/
};

3. JSON读写工具

utils/JsonUtil.ets

import { Context } from '@kit.AbilityKit';

/**
 * JSON工具类:读取rawfile目录下的JSON文件(适配API12)
 */
export class JsonUtil {
  /**
   * 异步读取rawfile中的JSON文件
   * @param context 应用上下文(需通过getUIContext().getHostContext()获取)
   * @param fileName JSON文件名(如user_info.json)
   * @returns JSON字符串
   */
  public static async readRawFileJson(context: Context, fileName: string): Promise<string> {
    try {
      const fileContent = await context.resourceManager.getRawFileContent(fileName);
      return String.fromCharCode.apply(null, new Uint8Array(fileContent));
    } catch (error) {
      console.error(`读取JSON失败:${(error as Error).message}`);
      throw new Error(`文件${fileName}不存在或格式错误`);
    }
  }
}

4. 静态校验工具类(核心)

utils/ValidationUtil.ets

import { UserInfo } from '../model/UserInfo';
import { RegexValidator } from '../constant/RegexValidator';
import { JsonUtil } from './JsonUtil';
import { Context } from '@kit.AbilityKit';

/**
 * 静态校验工具类:无需实例化,直接通过类名调用
 */
export class ValidationUtil {
  /**
   * 校验用户信息字段合法性
   * @param user 用户信息对象
   * @returns 错误信息数组(空数组=校验通过)
   */
  public static validateUser(user: UserInfo): string[] {
    const errors: string[] = [];

    // 空值前置校验(私有方法复用)
    const checkEmpty = (value: string, field: string) => {
      if (!value?.trim()) errors.push(`${field}不能为空`);
    };
    checkEmpty(user.phone, "手机号");
    checkEmpty(user.email, "邮箱");
    checkEmpty(user.password, "密码");
    checkEmpty(user.birthday, "生日");

    // 格式校验(仅当非空时执行)
    if (user.phone?.trim() && !RegexValidator.phone.test(user.phone)) {
      errors.push("手机号格式错误(11位有效数字,如13800138000)");
    }
    if (user.email?.trim() && !RegexValidator.email.test(user.email)) {
      errors.push("邮箱格式错误(示例:user@example.com)");
    }
    if (user.password?.trim() && !RegexValidator.password.test(user.password)) {
      errors.push("密码需8-20位,含数字+字母+特殊字符(!@#$%^&*)");
    }
    if (user.birthday?.trim() && !RegexValidator.dateAdvanced.test(user.birthday)) {
      errors.push("生日格式错误(yyyy-mm-dd,如1999-01-01)");
    }

    return errors;
  }

  /**
   * 一体化流程:读取JSON → 反序列化 → 正则校验
   * @param context 应用上下文
   */
  public static async processUserJson(context: Context) {
    try {
      // 1. 读取JSON文件
      const jsonStr = await JsonUtil.readRawFileJson(context, "user_info.json");
      console.log("读取的JSON:", jsonStr);

      // 2. 反序列化为UserInfo对象
      const user: UserInfo = JSON.parse(jsonStr);

      // 3. 执行正则校验
      const errors = ValidationUtil.validateUser(user);

      // 4. 输出校验结果
      if (errors.length === 0) {
        console.log("✅ 用户信息全部合法!");
      } else {
        console.log("❌ 校验失败:");
        errors.forEach((err, idx) => console.log(`  ${idx+1}. ${err}`));
      }
    } catch (error) {
      console.error("处理JSON失败:", error);
    }
  }
}

5. 测试用JSON文件

resources/rawfile/user_info.json

{
  "id": 1001,
  "name": "散修",
  "phone": "13800138000",
  "email": "sanxiu@example.com",
  "password": "Test123!",
  "birthday": "1999-01-01"
}

6. 页面调用与演示

pages/Index.ets

import { ValidationUtil } from '../utils/ValidationUtil';
import { Context } from '@kit.AbilityKit';

@Entry
@Component
struct Index {
  // 获取API12兼容的应用上下文
  private context: Context = this.getUIContext().getHostContext() as Context;

  // 正则创建方式测试
  testRegexCreate() {
    // 方式1:字面量形式(推荐)- 语法简洁、性能高,适用于固定模式
    const phoneRegex = /^1[3-9]\d{9}$/;
    // 方式2:构造函数形式 - 适用于动态拼接模式,需注意转义符\\
    const phonePrefix = '1[3-9]';
    const phoneSuffix = '\\d{9}'; // 字符串中\需双重转义
    const phoneRegex2 = new RegExp(`^${phonePrefix}${phoneSuffix}$`);
    // 测试结果(两者等价)
    console.log(`${phoneRegex.test("13800138000")}`); // true
    console.log(`${phoneRegex2.test("13800138000")}`); // true
  }

  // 手机号正则测试
  testRegexTel() {
    const phoneRegex = /^1[3-9]\d{9}$/;
    console.log(`${phoneRegex.test("13800138000")}`); // true
    console.log(`${phoneRegex.test("12800138000")}`); // false(第二位非法)
    console.log(`${phoneRegex.test("138001380")}`);   // false(长度不足)
  }

  // 强密码正则测试
  testStrongPassword() {
    const pwdRegex = /^(?=.*\d)(?=.*[a-zA-Z])(?=.*[!@#$%^&*]).{8,20}$/;
    console.log(`${pwdRegex.test("Test123!")}`); // true
    console.log(`${pwdRegex.test("12345678")}`); // false(无字母/特殊字符)
  }

  // 日期正则测试
  testRegexDate() {
    const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
    console.log(`${dateRegex.test("2024-06-01")}`); // true
    console.log(`${dateRegex.test("2024/06/01")}`); // false
    console.log(`${dateRegex.test("2024-6-1")}`);   // false
  }

  // test()方法测试
  testRegexTest() {
    const emailRegex = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
    const emailList = ["user@example.com", "invalid-email", "user@.com"];
    emailList.forEach((email, idx) => {
      console.log(`邮箱${idx+1} [${email}]:${emailRegex.test(email)}`);
    });
  }

  // exec()方法测试
  testRegexExec() {
    // 案例1:提取URL中的域名
    const url = "https://www.example.com/path?name=test";
    const urlRegex = /https?:\/\/([\w.]+)/;
    const urlResult = urlRegex.exec(url);
    if (urlResult) {
      console.log("完整匹配:", urlResult[0]); // https://www.example.com
      console.log("提取域名:", urlResult[1]); // www.example.com
    }
    // 案例2:全局匹配提取所有数字
    const str = "hello123 world456! 789";
    const numRegex = /\d+/g; // g修饰符开启全局匹配
    let numMatch:RegExpExecArray|null;
    while ((numMatch = numRegex.exec(str)) !== null) {
      console.log(`数字:${numMatch[0]},位置:${numMatch.index}`);
    }
  }

  // 测试正则基础语法
  testRegexBasic() {
    this.testRegexCreate();
    this.testRegexTel();
    this.testStrongPassword();
    this.testRegexDate();
    this.testRegexTest();
    this.testRegexExec();
  }

  // 页面加载时执行基础测试
  aboutToAppear(): void {
    this.testRegexBasic();
  }

  build() {
    Column({ space: 20 }) {
      Text("正则+JSON校验实战")
        .fontSize(24)
        .fontWeight(FontWeight.Bold);

      Button("执行用户信息校验")
        .width('80%')
        .height(50)
        .backgroundColor('#007DFF')
        .onClick(async () => {
          // 调用静态工具类方法
          await ValidationUtil.processUserJson(this.context);
        });

      Text("请打开Logcat查看输出结果")
        .fontSize(14)
        .fontColor(Color.Grey);
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center);
  }
}

7. 运行结果说明

  1. 运行环境:必须使用真机/模拟器(预览器不支持resourceManager接口);
  2. 预期日志:
    true
    true
    true
    false
    false
    true
    false
    false
    邮箱1 [user@example.com]:true
    邮箱2 [invalid-email]:false
    邮箱3 [user@.com]:false
    完整匹配: https://www.example.com
    提取域名: www.example.com
    数字:123,位置:5
    数字:456,位置:14
    数字:789,位置:20
    读取的JSON: {"id":1001,"name":"散修","phone":"13800138000","email":"sanxiu@example.com","password":"Test123!","birthday":"1999-01-01"}
    ✅ 用户信息全部合法!
    
  3. 异常测试:修改user_info.json中手机号为123456,日志会输出:
    ❌ 校验失败:
      1. 手机号格式错误(11位有效数字,如13800138000)
    

五、常见错误与避坑指南

1. 转义字符错误(高频)

  • 问题:构造函数创建正则时写\d导致匹配失效;
  • 原因:字符串中\是转义符,需双重转义;
  • 解决:字面量/\d{4}/ → 构造函数new RegExp("\\d{4}")

2. 贪婪匹配陷阱

  • 问题/<.*>/匹配<div>内容</div>时会匹配整个字符串;
  • 原因*是贪婪量词,尽可能匹配更多字符;
  • 解决:使用非贪婪量词*?,正则改为/<.*?>/

3. 边界符遗漏

  • 问题/1[3-9]\d{9}/会匹配13800138000abc为合法手机号;
  • 原因:未加^/$,仅匹配子串而非整串;
  • 解决:严格匹配/^1[3-9]\d{9}$/

4. 预览器环境限制

  • 问题:调用JsonUtil提示resourceManager未定义;
  • 原因:预览器不支持设备相关API;
  • 解决:始终用真机/模拟器运行文件读写相关代码。

5. 复杂正则性能问题

  • 问题:嵌套量词(如((a+)+)+)匹配长字符串时超时;
  • 解决:简化正则、使用非捕获分组(?:)、分步校验。

六、实用工具推荐

  1. 在线正则测试:Regex101(https://regex101.com/)→ 语法高亮、实时预览、分组提取;
  2. 编辑器插件:VS Code「Regex Previewer」→ 实时预览匹配结果;
  3. 模板库:GitHub「Regex Patterns」→ 现成的手机号/身份证/邮箱正则模板。

七、内容总结

  1. 正则核心:由元字符(匹配规则)、量词(匹配次数)、修饰符(匹配行为)组成,^/$是严格匹配的关键。
  2. ArkTS方法test()用于格式校验(返回布尔值),exec()用于内容提取(结合g实现全局匹配)。
  3. 工程化落地:将校验逻辑封装为ValidationUtil静态工具类,结合JsonUtil完成“读取JSON→反序列化→正则校验”的完整链路。
  4. 避坑关键:构造函数正则需双重转义、避免贪婪匹配、添加边界符、适配鸿蒙真机运行环境。

八、代码仓库

九、下节预告

下一节将整合正则校验、JSON处理、MVVM架构等知识点,完善商品管理项目

  1. 商品数据模型重构:正则校验商品编号、价格、生产日期等字段;
  2. 用户登录模块开发:正则校验账号密码,实现“登录验证→商品数据加载”的完整业务;
  3. 异常处理体系:全流程添加try/catch,统一错误提示与日志输出。
posted @ 2026-01-25 12:11  鸿蒙-散修  阅读(0)  评论(0)    收藏  举报