ASCF form 的使用场景很典型:设置页面上有一堆 switch,用户一次性改完点保存,开发者需要把这些值集中提交。如果不借助 form,就得逐个拿 switch 的 checked、slider 的 value、input 的内容,再拼成一个对象。form 的价值就在这一步——组件上写好 name,submit 时一次性全部收走。
ASCF form 自身只有两个事件:bindsubmit 和 bindreset。它不在页面上渲染任何可见内容,纯粹是一个数据收集容器。官方文档列出了能配合 form 使用的组件,但有一个关键前提:组件必须设置 name,没有 name 的组件在 submit 时不会出现在结果里,这是最常见的翻车原因。textarea 官方没列,但实测设置 name 后也能提交;editor 不行,它的内容只能通过 EditorContext.getContents 手动获取。
基础用法如下,注意按钮上的 form-type:submit 触发提交,reset 触发重置。form-type 忘写的话,点按钮不会发生任何事,用的时候容易误以为事件绑定错了。
- <form bindsubmit="formSubmit" bindreset="formReset">
- <switch name="switch" checked="{{true}}" />
- <radio-group name="radio">
- <label><radio value="option1" />选项一</label>
- <label><radio value="option2" />选项二</label>
- </radio-group>
- <slider name="slider" value="50" show-value />
- <button form-type="submit" type="primary">提交</button>
- <button form-type="reset">重置</button>
- </form>
复制代码- Page({
- formSubmit(e) {
- console.info('表单数据:', e.detail.value);
- },
- formReset() {
- console.info('表单已重置');
- },
- });
复制代码
提交后,e.detail.value 里每个字段的类型由组件本身决定。switch 返回布尔值,radio 返回选项 value,checkbox-group 永远返回数组——哪怕只选了一个也是 ["item1"],不是 "item1"。slider 返回数字,input 返回字符串,picker 返回选中下标或日期字符串。这个数组问题在联调时特别容易踩:把单选数组直接传给后端,后端说类型不对,排查半天才发现少了一层。
实际使用中,注册和个人信息表单很适合用 form 分区域收集。基本信息里放 input、radio-group、picker,偏好设置里放 switch、checkbox-group、slider。注意 picker 与 form 配合时,提交的是选中的下标或日期字符串,不是显示文本。比如生日提交 "2000-01-01",性别提交 "male" 而不是 "男",需要显示名就自己维护映射。
电商筛选页也是典型场景:关键词、分类、价格区间、库存状态一次性组合提交。这种场景我通常不加 reset 按钮,因为用户可能已经填了很多条件,误点重置会全部清空,体验不好。注册表单加 reset,搜索筛选不加。
设置页面最常见的是一堆 switch,每个开关控制一个功能。switch 提交的是 boolean,但如果后端接口要求传 "1"/"0",就得手动转换一遍。字段多的时候这个转换很繁琐,建议抽成工具函数统一处理。
ASCF form 没有内置验证功能,必填、邮箱格式、密码长度之类都得自己写。我习惯在 submit 回调里做校验:
- Page({
- formSubmit(e) {
- const data = e.detail.value;
- if (!data.nickname || !data.nickname.trim()) {
- has.showToast({ title: '请输入昵称', icon: 'none' });
- return;
- }
- if (!data.gender) {
- has.showToast({ title: '请选择性别', icon: 'none' });
- return;
- }
- if (data.hobbies && data.hobbies.length < 2) {
- has.showToast({ title: '至少选择 2 个兴趣', icon: 'none' });
- return;
- }
- // 验证通过后再提交
- },
- });
复制代码
验证失败用 has.showToast 弹出提示,然后 return。代码多了就抽函数,别都堆在 formSubmit 里。
form 的 bindsubmit 不会像 HTML 那样刷新页面,它只是触发一个 JS 事件。数据怎么传、传到哪,完全自己控制。常见做法是在 formSubmit 里调 has.request:
- Page({
- formSubmit(e) {
- has.request({
- url: 'https://api.example.com/submit',
- method: 'POST',
- data: e.detail.value,
- success() { has.showToast({ title: '提交成功' }); },
- fail() { has.showToast({ title: '提交失败', icon: 'none' }); },
- });
- },
- });
复制代码
form 只负责收集,传输交给网络层。两者不冲突。
最后总结一下踩过的坑:
第一,name 漏了就不提交。组件上没写 name,e.detail.value 里就没有这个字段,而且不报错。排查时先看每个要提交的组件有没有 name。
第二,checkbox 永远是数组。即使只选一个,拿到的也是数组,和后端对接口时确认字段类型。
第三,switch 是 boolean 不是字符串。后端要求 "1"/"0" 时自己转换。
第四,reset 后 switch 回到 checked 初始值,不是关掉。很多人以为 reset 会清空所有组件,结果 switch 还是选中状态,因为它的初始 checked 是 true。官方语义是恢复到初始状态,不是清空。
第五,picker 提交的是下标而不是文本。mode="selector" 时,拿到的是数字下标,要显示文本还得 categories[index]。
第六,form 不能嵌套。form 里再放 form,子 form 的提交行为不确定,外层 bindsubmit 也可能被干扰。不同区域要分开提交,用两个独立 form 或全手动收集。
总的来说,form 不复杂,就 bindsubmit 和 bindreset 两个事件。它的价值在于把一堆组件值一次性收齐,省去逐个 getData。但 form 不是必须的,只有一个 input 加一个按钮时,直接 bindinput 加 bindtap 手动拿值就行。字段数量超过五个再用 form,字段少了反而多此一举。 |