Flutter for OpenHarmony Web开发助手App实战:JavaScript参考
下面我按真实项目里常见的拆分方式来写,不把所有东西都堆在一个build()里。

JavaScript参考手册本质上就是一个“随手查”的工具:
- 能快速搜到(不然列表一长就很难用)
- 能点进去看详情(常见参数、返回值、例子)
- 最好能一键复制(开发时真的省事)
下面我按真实项目里常见的拆分方式来写,不把所有东西都堆在一个 build() 里。
1. 数据结构:别一上来就 Map
class JsMethod {
final String name;
final String desc;
final String example;
const JsMethod({
required this.name,
required this.desc,
required this.example,
});
}
说明
实际写页面时我不太建议用 Map<String, String> 直接硬塞,因为后面你大概率会加字段(比如 category、mdnUrl、tags)。用一个小的 model,改动成本会低很多,而且 IDE 自动补全也舒服。
2. 方法清单:先放在本地常量,后面再接配置/接口
const kJsMethods = <JsMethod>[
JsMethod(
name: 'console.log()',
desc: '输出日志到控制台',
example: "console.log('hello');",
),
JsMethod(
name: 'setTimeout()',
desc: '延迟执行函数',
example: "setTimeout(() => console.log('later'), 300);",
),
JsMethod(
name: 'JSON.parse()',
desc: '解析 JSON 字符串',
example: "const obj = JSON.parse('{\"a\":1}');",
),
JsMethod(
name: 'Array.filter()',
desc: '数组过滤',
example: 'const even = [1,2,3,4].filter(x => x % 2 === 0);',
),
];
说明
这里我刻意把例子写成“能直接复制就能跑”的那种,不写伪代码。后面如果你要做多语言(比如 JS/TS)或者分类筛选,也可以把这些数据挪到 JSON 文件里,启动时加载。
3. 列表页骨架:用 StatefulWidget 做搜索过滤
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class JsReferencePage extends StatefulWidget {
const JsReferencePage({super.key});
State<JsReferencePage> createState() => _JsReferencePageState();
}
class _JsReferencePageState extends State<JsReferencePage> {
final _controller = TextEditingController();
String _keyword = '';
void dispose() {
_controller.dispose();
super.dispose();
}
}
说明
搜索这种交互,不管你最后用不用状态管理(Provider / Riverpod / Bloc),最起码需要一个输入源和一个过滤条件。我习惯先从最简单的 TextEditingController 起步,后面再升级。
4. 搜索框 + 过滤逻辑:放在 build() 里,但别写太挤
Widget build(BuildContext context) {
final shown = kJsMethods.where((m) {
final k = _keyword.trim().toLowerCase();
if (k.isEmpty) return true;
return m.name.toLowerCase().contains(k) || m.desc.toLowerCase().contains(k);
}).toList();
return Scaffold(
appBar: AppBar(title: const Text('JavaScript参考')),
body: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
children: [
TextField(
controller: _controller,
decoration: const InputDecoration(
hintText: '输入关键字,比如 parse / timeout',
prefixIcon: Icon(Icons.search),
),
onChanged: (v) => setState(() => _keyword = v),
),
SizedBox(height: 12.h),
Expanded(
child: ListView.builder(
itemCount: shown.length,
itemBuilder: (context, index) => _MethodTile(method: shown[index]),
),
),
],
),
),
);
}
说明
这里的过滤我用的是最“朴素”的 contains。真实项目里如果数据量大、还要模糊匹配,你可以换成分词或拼音库,但一开始别过度设计。
另外 ScreenUtil 这类适配库,一定要确保在 main() 里已经初始化,不然 16.w 这种调用会直接不正常(这个坑我踩过一次)。
5. 列表项组件:把点击行为收口到一个地方
class _MethodTile extends StatelessWidget {
final JsMethod method;
const _MethodTile({required this.method});
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.only(bottom: 12.h),
child: ListTile(
leading: Icon(Icons.code, color: Colors.yellow[700]),
title: Text(
method.name,
style: const TextStyle(fontFamily: 'monospace', fontWeight: FontWeight.bold),
),
subtitle: Text(method.desc),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => JsMethodDetailPage(method: method)),
),
),
);
}
}
说明
列表项组件抽出来之后,页面主结构就不会被 Card/ListTile 这些 UI 细节淹没。点进去的行为也集中管理,后面你要换成命名路由或者加埋点,改一个地方就够。
6. 详情页:展示示例 + 复制按钮(使用系统剪贴板)
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class JsMethodDetailPage extends StatelessWidget {
final JsMethod method;
const JsMethodDetailPage({super.key, required this.method});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(method.name)),
body: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(method.desc, style: Theme.of(context).textTheme.titleMedium),
SizedBox(height: 12.h),
_ExampleBlock(example: method.example),
],
),
),
);
}
}
说明
详情页我就做三件事:
- 标题:方法名,一眼知道自己在看什么
- 描述:一句话说明用途
- 例子块:能复制,能直接拿去用
不要一上来把“参数解释、更多示例、链接跳转”全塞进来,先把核心体验打通。
7. 例子块:看起来像代码、但不追求做成复杂编辑器
class _ExampleBlock extends StatelessWidget {
final String example;
const _ExampleBlock({required this.example});
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.06),
borderRadius: BorderRadius.circular(12.r),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
example,
style: const TextStyle(fontFamily: 'monospace'),
),
),
IconButton(
tooltip: '复制',
icon: const Icon(Icons.copy),
onPressed: () async {
await Clipboard.setData(ClipboardData(text: example));
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已复制到剪贴板')),
);
},
),
],
),
);
}
}
说明
我这里没上第三方高亮库(比如 code editor),原因很简单:这个页面是“参考”,不是“IDE”。做成一个干净的等宽字体块,再给一个复制按钮,基本就够用了。
顺手提一句:context.mounted 这个判断是为了避免页面已经被 pop 掉时还去弹 SnackBar,一些机型/场景下确实能碰到。
8. 这一页在项目里怎么接
如果你的首页是一个工具列表,一般就是点进去 push 这页。比如:
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const JsReferencePage()),
);
说明
我个人更喜欢先用 MaterialPageRoute 把流程跑通;等页面多了、路由复杂了,再统一改成命名路由或路由库,不然一开始容易“为了架构而架构”。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)