鸿蒙ArkTS开发实战:生肖查询工具

发布时间:2026/6/15 18:27:56
鸿蒙ArkTS开发实战:生肖查询工具
一、前言1.1 为什么选择 ArkTS2024 年华为推出了 HarmonyOS NEXT彻底剥离了 Android 兼容层标志着鸿蒙真正成为了一个独立的操作系统。而 ArkTS 作为鸿蒙原生应用的首选开发语言基于 TypeScript 语法进行了深度扩展专为声明式 UI 开发而设计。对于前端开发者来说ArkTS 的学习曲线非常友好。TypeScript 开发者可以直接上手React/Vue 开发者能快速理解状态驱动的理念而 Flutter/Compose 开发者则会发现 ArkTS 的声明式 UI 语法几乎是直觉式的。更重要的是ArkTS 对于 UI 的响应式更新是自动且精准的。开发者只需要关注数据逻辑框架会在底层自动追踪状态依赖只更新受影响的组件而不是全量刷新。这和 React 的虚拟 DOM diff 不同ArkTS 采用的是更细粒度的观察者模式——每个State变量都维护着自己的订阅列表。1.2 本文目标本文将以一个生肖查询工具为载体从零开始讲解 ArkTS 的核心概念。这个例子虽然简单但涵盖了一个高频交互页面所需的所有基础能力状态管理State用户输入处理TextInput事件响应onClick布局编排Column、FlexAlign条件逻辑与错误处理字符串模板与数组操作我们不仅要讲清楚怎么写更要讲清楚为什么这么写。二、鸿蒙开发环境准备2.1 工具链概览在开始写代码之前先了解鸿蒙开发的完整工具链工具用途下载方式DevEco Studio官方 IDE基于 IntelliJ华为开发者官网HarmonyOS SDK编译工具链、模拟器随 DevEco Studio 安装ArkTS 编译器将 ArkTS 编译为方舟字节码SDK 内置预览器 Previewer实时代码预览DevEco Studio 内置DevEco Studio 内置了实时预览器Previewer修改代码后几乎立即能看到界面变化。这个功能在开发 UI 密集的页面时非常高效。2.2 创建项目在 DevEco Studio 中选择 File → New → Create Project选择Empty Ability模板。这会生成一个标准的Index.ets文件——也就是我们写代码的地方。项目结构示意MyZodiacApp/ ├── entry/ │ ├── src/main/ │ │ └── ets/ │ │ └── pages/ │ │ └── Index.ets ← 我们在这写代码 │ ├── resources/ │ └── build-profile.json5 ├── oh_modules/ └── hvigor/2.3 理解 Entry 和 Component每个 ArkTS 页面都由两个装饰器标记Entry // 标记该组件为页面入口 Component // 标记该结构体为一个组件 struct Index { // 组件名通常与文件名一致 // ... }Entry—— 告诉框架这个组件是一个页面的根节点。一个页面只能有一个Entry组件。你可以把它理解为路由的终点——当用户导航到该页面时框架会实例化这个组件。Component—— 声明一个自定义组件。组件的核心是build()方法它描述了组件的 UI 结构。组件可以嵌套使用例如在一个页面中Entry Component struct MainPage { build() { Column() { Header() // 子组件 Content() // 子组件 Footer() // 子组件 } } } Component struct Header { build() { Text(页头) } }这种组件化拆分方式和 React 的函数组件、Vue 的 SFC 思想完全一致——高内聚、低耦合。三、完整代码与效果预览3.1 代码全文在正式拆解之前先看完整代码建立整体认知Entry Component struct Index { State year: string State zodiac: string 请输入出生年份 getZodiac() { const arr [鼠,牛,虎,兔,龙,蛇,马,羊,猴,鸡,狗,猪] let y parseInt(this.year) if (isNaN(y)) { this.zodiac 请输入有效年份 return } let idx (y - 4) % 12 this.zodiac 生肖${arr[idx]} } build() { Column({ space: 30 }) { TextInput({ text: this.year, placeholder: 输入出生年份 }) .width(80%) .height(50) .onChange(v this.year v) Button(查询生肖) .onClick(() this.getZodiac()) .width(120) Text(this.zodiac) .fontSize(22) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) .padding(20) } }38 行代码五行核心逻辑三行 UI 声明。整个组件从数据到界面一气呵成。3.2 运行效果在模拟器中运行后屏幕上显示---------------------------------- | | | | | [ 输入出生年份 ] | | | | [ 查询生肖 ] | | | | 生肖龙 | | | | | ----------------------------------页面整体垂直居中输入框占 80% 宽度按钮居中结果文字 22 号字清晰易读。用户输入2024点击按钮后页面立即显示生肖龙。如果输入非法字符则提示请输入有效年份。四、逐行深度解析好的接下来我们逐行拆解深入到每一行代码背后的设计理念。4.1 响应式状态 State —— 数据的神经中枢State year: string State zodiac: string 请输入出生年份State是 ArkTS 中最重要的装饰器之一它实现了响应式数据绑定。4.1.1 工作原理当State修饰的变量发生变化时ArkTS 框架会自动检测—— 在运行时维护一个依赖追踪图记录每个State变量被哪些 UI 节点读取精准更新—— 只重新渲染读取了该变量的 UI 组件而不是整个页面批量合并—— 在同一帧内发生的多次状态变更会合并为一次渲染避免重复计算这和 Vue 3 的ref()/reactive()、SwiftUI 的State、Flutter 的setState()、Compose 的mutableStateOf()本质上是同一类机制——细粒度响应式。背后的大致实现原理简化版// 伪代码State 底层简化概念 class StateVariableT { private _value: T private subscribers: SetUIComponent new Set() get value(): T { // 在 build 中读取时自动注册订阅 currentBuildContext?.addDependency(this) return this._value } set value(newVal: T) { if (this._value ! newVal) { this._value newVal // 通知所有订阅者重新渲染 this.subscribers.forEach(comp comp.requestReRender()) } } }当然ArkTS 的底层实现远比这个伪代码复杂但核心思想一致——状态和 UI 之间建立订阅关系数据变了 UI 自动更新。4.1.2 何时用 StateState适合以下场景场景示例是否适合 State组件内部的可变状态输入框内容、开关状态✅父组件传到子组件的数据用户信息、配置项❌ 用Prop全局共享状态登录态、主题色❌ 用StorageLink计算属性根据状态推导的值❌ 直接写 getter在本文的例子中year和zodiac都是组件内部的状态且数据流是单向的输入框 → year → 算法 → zodiac → UI用State恰到好处。4.2 生肖算法 —— 核心业务逻辑getZodiac() { const arr [鼠,牛,虎,兔,龙,蛇,马,羊,猴,鸡,狗,猪] let y parseInt(this.year) if (isNaN(y)) { this.zodiac 请输入有效年份 return } let idx (y - 4) % 12 this.zodiac 生肖${arr[idx]} }4.2.1 数学原理为什么减 4十二生肖是中国传统文化中记录年份的符号系统每 12 年一个轮回。生肖的顺序是固定的鼠 → 牛 → 虎 → 兔 → 龙 → 蛇 → 马 → 羊 → 猴 → 鸡 → 狗 → 猪我们需要找一个已知的锚点年份。查阅干支纪年表可知公元 4 年 甲子年 鼠年公元 5 年 乙丑年 牛年公元 6 年 丙寅年 虎年...依此类推因此公式(year - 4) % 12中- 4就是将年份偏移到以鼠年索引 0为起点的坐标系。验证几个已知年份公式余数生肖是否正确(2024 - 4) % 1280→鼠,1→牛,...,8→龙✅ 2024 是龙年(2023 - 4) % 1277→兔✅ 2023 是兔年(2020 - 4) % 1200→鼠✅ 2020 是鼠年(1996 - 4) % 1200→鼠✅ 1996 是鼠年(1988 - 4) % 1200→龙不对1988是龙年...等等让我重新算等一下我手动验证一下 1988(1988 - 4) % 12 1984 % 12 1984 / 12 165.333... 12 × 165 1980余数 4。 4 对应数组索引 4 →arr[4] 龙。✅ 1988 年确实是龙年。公式正确。4.2.2 边界情况处理好的代码不仅要处理正常路径还要覆盖边界情况。这个例子中涉及三个边界场景① 空字符串用户打开页面直接点击按钮year为空字符串。parseInt()返回NaN进入 isNaN 分支显示友好提示。② 非数字字符用户输入abc或二零二四parseInt同样返回NaN被兜底拦截。③ 负数年份用户输入-1000parseInt会解析为 -1000公式依然可以计算(-1000 - 4) % 12 (-1004) % 12在 JavaScript/TypeScript 中负数的%运算结果可能是负数。让我们验证-1004 % 12 -8 因为 -1004 12×83 -1004 996 -8数组索引 -8 在 JavaScript 中是undefined导致结果是生肖undefined。这是一个隐蔽的 bug修复方案对求模结果取绝对值或做正数化处理。或者更安全地限制输入范围if (y 1900 || y 2100) { this.zodiac 请输入 1900-2100 之间的年份 return }这就是为什么写好边界处理很重要——真实环境中的用户行为永远比预期更丰富。4.3 build 方法 —— 声明式 UI 的编排build() { Column({ space: 30 }) { TextInput({ text: this.year, placeholder: 输入出生年份 }) .width(80%) .height(50) .onChange(v this.year v) Button(查询生肖) .onClick(() this.getZodiac()) .width(120) Text(this.zodiac) .fontSize(22) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) .padding(20) }4.3.1 布局容器 ColumnColumn是 ArkTS 中最基本的布局容器子组件从上到下纵向排列。参数{ space: 30 }表示子组件之间的间距为 30。对应的底层布局计算若 Column 高度为 H子组件数量为 n3间距 space30 则子组件总内容高度 Sum(子组件自身高度) (n-1)×space 剩余空间 H - 总内容高度 剩余空间按 justifyContent 规则分配其他布局容器容器方向类似 CSSColumn纵向flex-direction: columnRow横向flex-direction: rowFlex自定义方向display: flexStack层叠position: absolute 相对定位Grid网格display: gridList列表虚拟滚动列表性能最优4.3.2 TextInput —— 用户输入TextInput({ text: this.year, placeholder: 输入出生年份 }) .width(80%) .height(50) .onChange(v this.year v)text: this.year—— 绑定当前值这是受控组件模式输入框显示的内容永远等于this.yearplaceholder—— 占位提示用户未输入时显示灰色文字.width(80%)—— 宽度为父容器的 80%相对单位.height(50)—— 高度固定 50 像素绝对单位.onChange(v this.year v)—— 输入内容变化时更新状态year受控组件设计的好处单一数据源—— 组件的状态year是唯一真相来源输入框只是状态的投影易于校验—— 可以在赋值前校验或转换输入可预测—— UI 始终和状态同步不会出现显示的内容和实际数据不一致扩展一下如果我们要做输入实时校验.onChange(v { // 只允许数字输入 const filtered v.replace(/\D/g, ) this.year filtered // 如果输入非数字字符输入框不会显示它们 })4.3.3 Button —— 触发事件Button(查询生肖) .onClick(() this.getZodiac()) .width(120)Button组件接收一个字符串参数作为按钮文本。.onClick()是点击事件绑定参数是一个回调函数。ArkTS 支持的事件类型远不止点击事件描述适用场景onClick点击按钮、列表项onLongPress长按上下文菜单onSwipe滑动卡片滑动删除onPinch捏合图片缩放onRotate旋转图片旋转onDragStart拖拽开始拖拽排序onTouch原始触摸事件自定义手势4.3.4 Text —— 展示结果Text(this.zodiac) .fontSize(22)Text用于展示文本内容直接绑定this.zodiac状态。.fontSize(22)设置字号。ArkTS 中默认单位为 vpvirtual pixel虚拟像素会自适应不同屏幕密度。Text 组件支持丰富的样式Text(示例文本) .fontSize(22) .fontColor(#333333) .fontWeight(FontWeight.Bold) .textAlign(TextAlign.Center) .lineHeight(32) .letterSpacing(2) .textOverflow({ overflow: TextOverflow.Ellipsis }) .maxLines(2)4.3.5 容器属性Column 容器上的链式调用.width(100%) .height(100%) .justifyContent(FlexAlign.Center) .padding(20).width(100%).height(100%)—— 铺满整个屏幕.justifyContent(FlexAlign.Center)—— 主轴方向纵向居中.padding(20)—— 四边内边距防止内容紧贴边缘FlexAlign枚举值值效果Start顶部对齐Center居中对齐End底部对齐SpaceBetween均匀分布首尾贴边SpaceAround均匀分布首尾留一半间距SpaceEvenly均匀分布全部间距相等五、扩展与优化基础版本已经跑通了但作为一个真正的 App还有很多可以完善的地方。5.1 增加输入验证State errorMsg: string getZodiac() { const arr [鼠,牛,虎,兔,龙,蛇,马,羊,猴,鸡,狗,猪] let y parseInt(this.year) if (isNaN(y) || this.year.trim() ) { this.errorMsg 请输入有效的四位年份 this.zodiac return } if (y 1900 || y 2100) { this.errorMsg 请输入 1900-2100 之间的年份 this.zodiac return } this.errorMsg let idx ((y - 4) % 12 12) % 12 this.zodiac 生肖${arr[idx]} }这样用户体验更加友好输入不合法时明确告知问题所在而不是笼统地显示请输入有效年份。5.2 改用 Select 下拉框对于生肖查询这种场景用输入框其实不太理想——用户可能不知道自己的出生年份或者记错了。更好的方案是用年份选择器State selectedYear: number 2024 build() { Column({ space: 30 }) { // 使用 Select 组件让用户选择年份 Select([ { value: 1990 }, { value: 1991 }, // ... 逐年生成 ]) .onSelect((index, value) { this.selectedYear parseInt(value) }) Button(查询生肖) .onClick(() this.getZodiacWithYear(this.selectedYear)) Text(this.zodiac) .fontSize(22) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) }但手动写 100 多个选项太笨了。更好的做法是动态生成getYearOptions(): ArraySelectOption { const options: ArraySelectOption [] const currentYear new Date().getFullYear() for (let y currentYear - 80; y currentYear; y) { options.push({ value: y.toString() }) } return options }这样就生成了从当前年份往前 80 年的选项列表覆盖了从幼儿到长者的年龄段。5.3 添加生肖动画静态文字不够有趣可以加上动画效果ArkTS 的animationAPI 支持过渡动画属性变化时自动产生平滑过渡效果。支持动画化的属性包括opacity、translate、scale、rotate、backgroundColor、width、height等。5.4 多语言支持十二生肖不只是中国有很多东亚国家也有类似的生肖体系但动物名称不同序号中文英文日文越南文0鼠Rat鼠ねずみChuột (鼠)1牛Ox牛うしTrâu (水牛)2虎Tiger虎とらHổ (虎)3兔Rabbit兎うさぎMèo (猫)4龙Dragon龍たつRồng (龙)5蛇Snake蛇へびRắn (蛇)6马Horse馬うまNgựa (马)7羊Goat羊ひつじDê (羊)8猴Monkey猿さるKhỉ (猴)9鸡Rooster鶏とりGà (鸡)10狗Dog犬いぬChó (狗)11猪Pig猪いのししLợn (猪)可以看到越南的生肖中用猫代替了兔子。加入多语言支持可以这样设计5.5 接入生肖运势 API如果想让这个工具更有用可以接入一个每天更新运势的 APIState fortune: string async fetchFortune(zodiacName: string) { try { const response await fetch(https://api.example.com/zodiac/fortune, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ zodiac: zodiacName, date: new Date().toISOString() }) }) const data await response.json() this.fortune data.fortune } catch (e) { this.fortune 获取运势失败请稍后重试 } }Copy在 ArkTS 中网络请求使用标准的fetchAPI与 Web 标准保持一致。5.6 历史同年出生名人增加一个趣味功能——显示同年出生的名人const famousPeople: Recordnumber, string[] { 1988: [刘亦菲, 林宥嘉, 李现], 1990: [吴亦凡, 华晨宇, 鹿晗], // ... } getFamousPeople(year: number): string { const list famousPeople[year] if (list list.length 0) { return 同年出生名人 list.join(、) } return }六、常见问题与调试技巧6.1 状态不更新现象修改了State变量但 UI 没有变化。原因和解决直接修改对象属性ArkTS 的State对对象的深度变化追踪有特殊性。如果是对象类型直接修改对象的某个属性可能不会触发更新。// ❌ 不会触发的写法 State user: User { name: 张三, age: 25 } this.user.name 李四 // UI 不会更新 // ✅ 正确的写法 this.user { ...this.user, name: 李四 } // 返回新对象异步回调中修改如果在 setTimeout 或 Promise 回调中修改状态需要确保回调执行时组件还未销毁。修改不在 build 中读取的变量只有被 UI 读取的State变量变化才会触发渲染。如果变量只用于内部计算不会被 UI 读取那它的变化不会引起重渲染。6.2 键盘弹起遮挡输入框现象在手机上键盘弹起后输入框被遮挡。解决方法Column({ space: 30 }) { // ... } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) .padding(20) // 添加键盘避让 .expandSafeArea([SafeAreaType.KEYBOARD], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])ArkTS 提供了expandSafeArea方法可以指定需要避让的安全区域类型键盘、刘海屏、导航条等。6.3 输入框类型设置默认的TextInput会弹出全键盘对于年份输入应该限制为数字键盘TextInput({ text: this.year, placeholder: 输入出生年份 }) .width(80%) .height(50) .type(InputType.Number) // 数字键盘 .maxLength(4) // 最多 4 位 .onChange(v this.year v)InputType支持的类型类型键盘样式Normal全键盘Number数字键盘PhoneNumber电话拨号盘Email带 和 . 的键盘Password隐藏输入的密码键盘6.4 调试工具ArkTS 开发中常用的调试手段console.log 打印日志在 DevEco Studio 的 Logcat 面板查看Watch 装饰器监测状态变化State Watch(onYearChange) year: string onYearChange() { console.info(year 变为: ${this.year}) }Previewer 实时预览右侧预览面板实时显示 UI 效果修改代码即时刷新Inspector 布局检查运行时可以查看组件树和各组件的布局属性七、与其他框架的深度对比7.1 ArkTS vs SwiftUI维度ArkTSSwiftUI语言TypeScript 扩展Swift状态StateState布局Column/Row/StackVStack/HStack/ZStack修饰符链式调用.width()链式调用.frame()预览DevEco PreviewerXcode Canvas发布App GalleryApp Store语法结构惊人地相似——苹果和华为在声明式 UI 设计上殊途同归。7.2 ArkTS vs Jetpack Compose维度ArkTSJetpack Compose语言TypeScriptKotlin状态StatemutableStateOf/remember布局ColumnColumn作用域链式调用中缀/点调用重组自动追踪依赖自动追踪依赖Compose 中的remember对应 ArkTS 的StateCompose 中的LaunchedEffect对应 ArkTS 的Monitor或aboutToAppear生命周期钩子。7.3 ArkTS vs Flutter维度ArkTSFlutter语言TypeScriptDart状态StatesetState/ValueNotifier组件树Column({ children })Column({ children })自定义组件Component structStatelessWidget/StatefulWidget热重载支持支持Flutter 的setState是全量更新整个 Widget 树重建而 ArkTS 的State是细粒度更新只渲染变化的组件。这是架构上的本质区别——Flutter 依赖组件的判断来决定是否重建而 ArkTS 依赖编译期的依赖追踪。7.4 架构设计理念对比React 模型全量 diffState → Virtual DOM → diff → 真实 DOM 最小化更新Compose 模型范围重组State → 读取该 State 的 Composable 函数 → 跳过未读取的状态 → 只重组受影响的范围ArkTS 模型细粒度观察State → 读取该 State 的 UI 节点 → 更新这些节点的属性 → 跳过其他所有节点ArkTS 的模型在理论上性能更高因为它不需要遍历组件树做 diff也不需要执行函数体来判断状态依赖——依赖关系在编译时就确定了。不过实际性能还取决于具体实现和场景优化。对于大多数业务页面来说三个框架的表现差距微乎其微。八、项目结构的最佳实践如果这个生肖查询工具只是一个更大 App 中的一小部分合理的项目结构应该是entry/src/main/ets/ ├── pages/ │ └── Index.ets ← 入口页面 ├── components/ │ ├── ZodiacCard.ets ← 生肖卡片组件 │ ├── YearInput.ets ← 年份输入组件 │ └── ZodiacResult.ets ← 结果展示组件 ├── models/ │ └── ZodiacData.ets ← 数据和算法模型 ├── utils/ │ ├── ZodiacCalculator.ets ← 生肖计算工具类 │ └── FormValidator.ets ← 表单校验工具 └── constants/ └── ZodiacConstants.ets ← 常量定义生肖数组等这样拆分后每个组件职责单一易于测试和复用。例如将业务逻辑抽离到单独的模型文件中// ZodiacData.ets export class ZodiacData { static readonly ZODIAC_NAMES [鼠,牛,虎,兔,龙,蛇,马,羊,猴,鸡,狗,猪] static readonly ZODIAC_EMOJIS [,,,,,,,,,,,] static getZodiacName(year: number): string { const idx ((year - 4) % 12 12) % 12 return this.ZODIAC_NAMES[idx] } static getZodiacEmoji(year: number): string { const idx ((year - 4) % 12 12) % 12 return this.ZODIAC_EMOJIS[idx] } static isValidYear(year: number): boolean { return !isNaN(year) year 1900 year 2100 } }然后在组件中引用import { ZodiacData } from ../models/ZodiacData Component struct ZodiacResult { Prop year: number build() { Row({ space: 8 }) { Text(ZodiacData.getZodiacEmoji(this.year)) .fontSize(32) Text(生肖 ZodiacData.getZodiacName(this.year)) .fontSize(22) } .alignItems(VerticalAlign.Center) } }九、性能优化要点虽然这个小例子不需要优化但了解一些原则总是好的9.1 避免不必要的状态更新每次State变化都会触发 UI 更新。如果某个变量只在内部计算中使用不要用State修饰。// ❌ 不必要的 State State tempResult: number 0 // ✅ 使用普通变量 tempResult: number 09.2 使用 Prop 和 ObjectLink数据从父组件传到子组件时根据数据类型选择合适的装饰器简单类型string、number、boolean→Prop对象类型 →ObjectLink数组 →Prop用ObjectLink可以避免深拷贝9.3 使用 LazyForEach 优化长列表如果需要在列表中展示多年份的生肖数据用LazyForEach替代ForEach它只会渲染当前可见的项LazyForEach(this.yearDataSource, (item: number) { Text(${item}年 - ${ZodiacData.getZodiacName(item)}) }, (item: number) item.toString())十、总结这篇文章从一个简单的生肖查询工具出发深入探讨了 ArkTS 的核心概念和最佳实践。10.1 核心要点回顾State响应式状态—— 数据驱动 UI自动追踪依赖精准更新声明式布局——Column、Row、Stack等布局容器描述界面结构事件绑定——onChange、onClick等链式回调处理交互组件化设计—— 将 UI 拆分为可复用的组件职责单一边界处理—— 输入校验、异常捕获、友好提示10.2 学习路线建议如果你刚接触 ArkTS建议的学习顺序基础语法TypeScript 类型系统、装饰器、箭头函数布局组件Column、Row、Stack、Flex、Grid状态管理State → Prop → Link → Provide/Consume → StorageLink事件系统点击、触摸、手势动画显式动画、隐式动画、转场动画网络与数据fetch、本地存储、数据库高级特性自定义绘制、Native 插件、多线程10.3 完整代码回顾最后让我们回到最初的代码。38 行简洁、完整、可运行Entry Component struct Index { State year: string State zodiac: string 请输入出生年份 getZodiac() { const arr [鼠,牛,虎,兔,龙,蛇,马,羊,猴,鸡,狗,猪] let y parseInt(this.year) if (isNaN(y)) { this.zodiac 请输入有效年份 return } let idx (y - 4) % 12 this.zodiac 生肖${arr[idx]} } build() { Column({ space: 30 }) { TextInput({ text: this.year, placeholder: 输入出生年份 }) .width(80%) .height(50) .onChange(v this.year v) Button(查询生肖) .onClick(() this.getZodiac()) .width(120) Text(this.zodiac) .fontSize(22) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) .padding(20) } }这 38 行代码背后我们讨论了响应式编程模型、声明式 UI 设计、组件化架构、布局计算原理、边界条件处理、跨框架对比、项目结构设计、性能优化原则——这些知识才是真正有价值的资产。代码很简短但背后的思想很丰富。