@doubao-apps/ai 0.0.19 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,200 @@
1
+ # Component Mapping
2
+
3
+ > **Mapping Version**: 1.1.0
4
+ > **Source Framework**: 抖音小程序 (Douyin Mini Program)
5
+ > **Target Framework**: Doubao Apps (React Lynx)
6
+ > **Last Updated**: 2026-04-02
7
+
8
+ ## 官方事实依据
9
+
10
+ - 组件概述: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/component/overview/`
11
+ - TTML 数据绑定: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/miniapp-framework/view/ttml-data-bind`
12
+ - TTML 列表渲染: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/miniapp-framework/view/ttml-list-draw`
13
+ - `Component()` 构造器: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/custom-component/component-constructor`
14
+
15
+ ---
16
+
17
+ ## 当前项目组件现状
18
+
19
+ 基于 `packages/components/src` 与 `packages/kit/src/builder/loader/components-loader/index.ts`,当前工程已支持以下小写标签直接写入 TSX,并由 loader 自动从 `@doubao-apps/framework/components` 注入组件 import:
20
+
21
+ - 基础标签:`<view>`、`<text>`、`<image>`、`<input>`、`<textarea>`、`<scroll-view>`、`<list>`、`<list-item>`、`<svg>`
22
+ - 扩展标签:`<button>`、`<switch>`、`<slider>`、`<swiper>`、`<swiper-item>`、`<map>`、`<map-marker>`、`<movable-area>`、`<movable-view>`、`<popup>`、`<radio>`、`<radio-group>`、`<picker-view>`、`<picker-column>`、`<web-view>`
23
+
24
+ 迁移抖音小程序时,能保留这些小写标签就优先保留;需要显式 ref、类型或更高层封装时,再使用命名导入。
25
+
26
+ ---
27
+
28
+ ## 文件结构转换
29
+
30
+ 抖音小程序页面/组件通常由 4 个文件组成,迁移后统一收敛为 `index.tsx` + `index.scss`:
31
+
32
+ | 抖音小程序 | Doubao Apps | 内容 |
33
+ |-----------|-------------|------|
34
+ | `page.ttml` | `index.tsx` | 模板结构 |
35
+ | `page.ttss` | `index.scss` | 样式 |
36
+ | `page.js` / `page.ts` | `index.tsx` | 页面逻辑 |
37
+ | `page.json` | 合并到工程配置 | 页面/组件配置 |
38
+
39
+ ---
40
+
41
+ ## TTML 指令映射
42
+
43
+ | 抖音小程序 | Doubao Apps | 说明 |
44
+ |-----------|-------------|------|
45
+ | `{{expr}}` | `{expr}` | 表达式输出 |
46
+ | `tt:if="{{cond}}"` | `{cond && ...}` 或三元 | 条件渲染 |
47
+ | `tt:for="{{list}}"` | `{list.map(...)}` | 列表渲染 |
48
+ | `tt:key="id"` | `key={item.id}` | 唯一键 |
49
+ | `<block>` | `<>...</>` | 结构块 |
50
+
51
+ ### TTML 转 TSX 示例
52
+
53
+ ```tsx
54
+ // Before (抖音小程序)
55
+ <view class="list">
56
+ <block tt:for="{{items}}" tt:key="id">
57
+ <view class="item" bind:tap="handleTap" data-id="{{item.id}}">
58
+ <text>{{item.title}}</text>
59
+ </view>
60
+ </block>
61
+ </view>
62
+
63
+ // After (Doubao Apps)
64
+ <view className="list">
65
+ {items.map((item) => (
66
+ <view key={item.id} className="item" onClick={() => handleTap(item.id)}>
67
+ <text>{item.title}</text>
68
+ </view>
69
+ ))}
70
+ </view>
71
+ ```
72
+
73
+ ---
74
+
75
+ ## 页面参数与 `Component()` 页面
76
+
77
+ 官方 `Component()` 构造器文档说明,页面也可以通过 `Component()` 构造,并让 `properties` 接收页面参数。
78
+
79
+ **迁移规则**:
80
+
81
+ - 发现页面使用 `Component({ properties: { id: String } })` 时,仍归类为页面
82
+ - 不要假设 `definePage.render(query)` 这类参数签名存在;当前仓库优先通过 `getViewData()/getViewContext()` 或页面容器显式注入页面输入
83
+ - 不保留“页面像组件一样靠 properties 接参”的模式
84
+
85
+ ---
86
+
87
+ ## 布局容器
88
+
89
+ | 抖音小程序 | Doubao Apps | 说明 |
90
+ |-----------|-------------|------|
91
+ | `<view>` | `<view>` | 通用容器 |
92
+ | `<block>` | `<>` 或移除 | 不渲染实体节点 |
93
+ | `<scroll-view scroll-y>` | `<scroll-view scrollOrientation="vertical">` | 垂直滚动 |
94
+ | `<scroll-view scroll-x>` | `<scroll-view scrollOrientation="horizontal">` | 水平滚动 |
95
+ | `<movable-area>` / `<movable-view>` | `<movable-area>` / `<movable-view>` | 当前项目已有对应组件,优先保留拖拽语义;复杂缩放细节需逐项校对 |
96
+
97
+ ---
98
+
99
+ ## 文本
100
+
101
+ | 抖音小程序 | Doubao Apps | 说明 |
102
+ |-----------|-------------|------|
103
+ | `<text>` | `<text>` | 文本组件 |
104
+ | 直接文本节点 | `<text>` 包裹 | 统一文本容器语义 |
105
+
106
+ ```tsx
107
+ // Before
108
+ <view>{{title}}</view>
109
+
110
+ // After
111
+ <view>
112
+ <text>{title}</text>
113
+ </view>
114
+ ```
115
+
116
+ ---
117
+
118
+ ## 媒体
119
+
120
+ | 抖音小程序 | Doubao Apps | 说明 |
121
+ |-----------|-------------|------|
122
+ | `<image src mode>` | `<image src mode>` | 图片组件 |
123
+ | `<video src>` | `<video src>` 或对应组件 | 纯播放可迁移 |
124
+ | `<camera>` | 需专项适配 | 宿主能力相关 |
125
+
126
+ ---
127
+
128
+ ## 视图与列表
129
+
130
+ ### `scroll-view`
131
+
132
+ ```tsx
133
+ // Before
134
+ <scroll-view scroll-y style="height: 400rpx;" bindscrolltolower="loadMore">
135
+ <view tt:for="{{list}}" tt:key="id">{{item.name}}</view>
136
+ </scroll-view>
137
+
138
+ // After
139
+ <scroll-view className="feed-scroll" scrollOrientation="vertical" onScrollToLower={loadMore}>
140
+ {list.map((item) => (
141
+ <view key={item.id} className="feed-item">
142
+ <text>{item.name}</text>
143
+ </view>
144
+ ))}
145
+ </scroll-view>
146
+ ```
147
+
148
+ ### `swiper`
149
+
150
+ 源端 `swiper` / `swiper-item` 优先保留为小写 `<swiper>` / `<swiper-item>` 标签。当前项目已支持这些标签,并会自动注入 import;不要手写低质量横向滚动替代。
151
+
152
+ ---
153
+
154
+ ## 表单组件
155
+
156
+ | 抖音小程序 | Doubao Apps | 说明 |
157
+ |-----------|-------------|------|
158
+ | `<button>` | `<button>` | 普通点击可直迁,保留小写标签即可 |
159
+ | `<input>` | `<input>` | 输入框 |
160
+ | `<textarea>` | `<textarea>` | 多行输入 |
161
+ | `<switch>` | `<switch>` | 回调改为 `checked` |
162
+ | `<slider>` | `<slider>` | 回调改为数值 |
163
+ | `<picker>` | `<popup>` + `<picker-view>` / `<picker-column>` | 按需重写弹出式选择器 |
164
+ | `<picker-view>` | `<picker-view>` + `<picker-column>` | 当前项目已有滚动选择器 |
165
+ | `<checkbox-group>` | 受控多选组件 | 当前项目暂无现成组件,按需重写 |
166
+ | `<radio-group>` | `<radio-group>` + `<radio>` | 当前项目已有单选组件,优先直接映射 |
167
+
168
+ ---
169
+
170
+ ## 导航组件
171
+
172
+ | 抖音小程序 | Doubao Apps | 说明 |
173
+ |-----------|-------------|------|
174
+ | `<navigator url="...">` | `onClick={() => navigateTo({ url })}` | 显式路由 |
175
+
176
+ ```tsx
177
+ // Before
178
+ <navigator url="/pages/detail/index?id={{item.id}}">
179
+ <view class="link">查看详情</view>
180
+ </navigator>
181
+
182
+ // After
183
+ import { navigateTo } from '@doubao-apps/framework/api';
184
+
185
+ <view className="link" onClick={() => navigateTo({ url: `/pages/detail/index?id=${item.id}` })}>
186
+ <text>查看详情</text>
187
+ </view>
188
+ ```
189
+
190
+ ---
191
+
192
+ ## 平台强绑定组件能力
193
+
194
+ 以下场景不要做“同名替换”:
195
+
196
+ - `button open-type="share"`
197
+ - 依赖抖音视频发布链路的组件
198
+ - 依赖直播挂载或直播预览流的组件
199
+
200
+ 这类能力要在迁移计划里单列处理。
@@ -0,0 +1,231 @@
1
+ # Core API Mapping
2
+
3
+ > **Mapping Version**: 1.0.0
4
+ > **Source Framework**: 抖音小程序 (Douyin Mini Program)
5
+ > **Target Framework**: Doubao Apps (React Lynx)
6
+ > **Last Updated**: 2026-04-01
7
+
8
+ ## 官方事实依据
9
+
10
+ - 框架概述: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/miniapp-framework/introduction`
11
+ - `Component()` 构造器: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/custom-component/component-constructor`
12
+ - 网络连接 / `tt.request`: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/basic-ability/net-connection`
13
+ - `tt.login`: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/api/open-interface/log-in/tt-login`
14
+ - `tt.authorize`: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/api/open-interface/authorization/tt-authorize`
15
+
16
+ ---
17
+
18
+ ## 状态管理
19
+
20
+ 抖音小程序使用 `data` + `setData`;Doubao Apps 使用 React Hooks。
21
+
22
+ | 抖音小程序 | Doubao Apps | 转换规则 |
23
+ |-----------|-------------|----------|
24
+ | `this.data.foo` | `foo` | 状态变量直接读取 |
25
+ | `this.setData({ foo: val })` | `setFoo(val)` | 单状态更新 |
26
+ | `this.setData({ foo: val, bar: val2 })` | `setFoo(val); setBar(val2)` | 拆分状态 |
27
+ | `this.setData({ 'list[0].name': val })` | `setList(prev => ...)` | 不做字符串路径更新 |
28
+
29
+ ### 状态转换示例
30
+
31
+ ```tsx
32
+ // Before (抖音小程序)
33
+ Page({
34
+ data: {
35
+ count: 0,
36
+ keyword: '',
37
+ },
38
+ increase() {
39
+ this.setData({ count: this.data.count + 1 });
40
+ },
41
+ onInput(e) {
42
+ this.setData({ keyword: e.detail.value });
43
+ },
44
+ });
45
+
46
+ // After (Doubao Apps)
47
+ import { FC, useState, definePage } from '@doubao-apps/framework';
48
+
49
+ const IndexPage: FC = () => {
50
+ const [count, setCount] = useState(0);
51
+ const [keyword, setKeyword] = useState('');
52
+
53
+ return (
54
+ <view className="page">
55
+ <text>{count}</text>
56
+ <input value={keyword} onInput={(e) => setKeyword(e.detail.value)} />
57
+ <view onClick={() => setCount((prev) => prev + 1)}>
58
+ <text>+1</text>
59
+ </view>
60
+ </view>
61
+ );
62
+ };
63
+
64
+ export default definePage({
65
+ render() {
66
+ return <IndexPage />;
67
+ },
68
+ });
69
+ ```
70
+
71
+ ---
72
+
73
+ ## 派生值与副作用
74
+
75
+ | 抖音小程序 | Doubao Apps | 说明 |
76
+ |-----------|-------------|------|
77
+ | 在 `setData` 前手动算派生值 | `useMemo()` | 派生值显式声明 |
78
+ | `onLoad` / `attached` 中做初始化请求 | `useEffect(() => {...}, [])` | 初始化副作用 |
79
+ | `observer` / 人工比较旧值 | `useEffect(() => {...}, [dep])` | 响应依赖变化 |
80
+
81
+ ---
82
+
83
+ ## 页面 / 组件 / 应用定义
84
+
85
+ | 抖音小程序 | Doubao Apps | 说明 |
86
+ |-----------|-------------|------|
87
+ | `Page({ ... })` | `definePage({ ..., render })` | 页面定义 |
88
+ | `Component({ properties, methods })` | `FC<Props>` | 组件定义 |
89
+ | `Component({ ... })` 构造页面 | `definePage({ ... })` | 页面仍按页面迁移 |
90
+ | `App({ ... })` | `defineApp({ ... })` | 应用定义 |
91
+ | `this.properties.foo` | `props.foo` | 属性访问 |
92
+ | `this.triggerEvent('tap', detail)` | `props.onTap?.(detail)` | 自定义事件 |
93
+
94
+ ### 组件定义示例
95
+
96
+ ```tsx
97
+ // Before (抖音小程序)
98
+ Component({
99
+ properties: {
100
+ title: {
101
+ type: String,
102
+ value: '',
103
+ },
104
+ },
105
+ methods: {
106
+ handleTap() {
107
+ this.triggerEvent('tap', { title: this.properties.title });
108
+ },
109
+ },
110
+ });
111
+
112
+ // After (Doubao Apps)
113
+ import { FC } from '@doubao-apps/framework';
114
+
115
+ interface CardProps {
116
+ title?: string;
117
+ onTap?: (detail: { title: string }) => void;
118
+ }
119
+
120
+ export const Card: FC<CardProps> = ({ title = '', onTap }) => {
121
+ return (
122
+ <view className="card" onClick={() => onTap?.({ title })}>
123
+ <text>{title}</text>
124
+ </view>
125
+ );
126
+ };
127
+ ```
128
+
129
+ ---
130
+
131
+ ## `tt.*` API 映射
132
+
133
+ ### 网络
134
+
135
+ | 抖音小程序 | Doubao Apps | 说明 |
136
+ |-----------|-------------|------|
137
+ | `tt.request({ url, method, data, success, fail })` | `request({ url, method, data, header })` | Promise 化 |
138
+ | `tt.uploadFile(...)` | `uploadFile(...)` | 视目标能力而定 |
139
+ | `tt.downloadFile(...)` | `downloadFile(...)` | 视目标能力而定 |
140
+
141
+ ```tsx
142
+ // Before
143
+ tt.request({
144
+ url: 'https://api.example.com/list',
145
+ success: (res) => {
146
+ this.setData({ list: res.data.list });
147
+ },
148
+ });
149
+
150
+ // After
151
+ import { request } from '@doubao-apps/framework/api';
152
+
153
+ const res = await request({ url: 'https://api.example.com/list' });
154
+ setList(res.data.list);
155
+ ```
156
+
157
+ ### 路由导航
158
+
159
+ | 抖音小程序 | Doubao Apps | 说明 |
160
+ |-----------|-------------|------|
161
+ | `tt.navigateTo({ url })` | `navigateTo({ url })` | 保留当前页跳转 |
162
+ | `tt.navigateBack({ delta })` | `navigateBack({ delta })` | 返回 |
163
+ | `tt.redirectTo({ url })` | `redirectTo({ url })` | 关闭当前页后跳转 |
164
+ | `tt.switchTab({ url })` | 需先核对当前项目是否暴露 `switchTab` | 当前仓库未检索到公开实现,不要机械替换 |
165
+ | `tt.reLaunch({ url })` | `reLaunch({ url })` | 重启路由栈 |
166
+
167
+ ### UI 交互
168
+
169
+ | 抖音小程序 | Doubao Apps | 说明 |
170
+ |-----------|-------------|------|
171
+ | `tt.showToast({ title, icon })` | `showToast({ message: title, icon })` | 当前仓库参数名是 `message`,不是 `title` |
172
+ | `tt.showLoading({ title })` | 需先核对当前项目是否暴露对应 API | 当前仓库未在 `packages/open-api/src` 中检索到直接实现 |
173
+ | `tt.hideLoading()` | 需先核对当前项目是否暴露对应 API | 当前仓库未在 `packages/open-api/src` 中检索到直接实现 |
174
+ | `tt.showModal({ ... })` | 需先核对当前项目是否暴露对应 API | `showModal` 曾被标记为临时移除,不要假设稳定可用 |
175
+
176
+ ### 缓存
177
+
178
+ | 抖音小程序 | Doubao Apps | 说明 |
179
+ |-----------|-------------|------|
180
+ | `tt.setStorageSync(key, value)` | `setStorageSync({ key, data: value })` | 当前仓库是对象参数签名 |
181
+ | `tt.getStorageSync(key)` | `getStorageSync({ key })` | 当前仓库是对象参数签名 |
182
+ | `tt.setStorage({ key, data })` | `setStorage({ key, data })` | 异步写入 |
183
+ | `tt.getStorage({ key })` | `getStorage({ key })` | 异步读取 |
184
+
185
+ > 当前仓库源码确认 `setStorageSync/getStorageSync` 使用对象参数签名;若迁移目标不是当前仓库模板,再额外核对目标项目封装。
186
+
187
+ ---
188
+
189
+ ## 必须人工判断的 API
190
+
191
+ ### 登录
192
+
193
+ | 抖音小程序 | Doubao Apps | 迁移判断 |
194
+ |-----------|-------------|----------|
195
+ | `tt.login()` | 无一键等价物 | 需要服务端登录链路改造 |
196
+ | `tt.checkSession()` | 视目标登录态设计 | 不要直接删掉 session 语义 |
197
+
198
+ ### 授权
199
+
200
+ | 抖音小程序 | Doubao Apps | 迁移判断 |
201
+ |-----------|-------------|----------|
202
+ | `tt.authorize()` | 不建议保留 | 官方已标记逐步废弃 |
203
+
204
+ ### 分享 / 平台挂载
205
+
206
+ | 抖音小程序 | Doubao Apps | 迁移判断 |
207
+ |-----------|-------------|----------|
208
+ | `onShareAppMessage` | 视目标宿主能力决定 | 不是普通函数替换 |
209
+ | `button open-type="share"` | 无机械等价物 | 需重写交互 |
210
+ | 抖音视频/直播挂载能力 | 无机械等价物 | 平台强绑定,单列迁移项 |
211
+
212
+ ---
213
+
214
+ ## 推荐导入模板
215
+
216
+ ```tsx
217
+ import { FC, definePage, useEffect, useMemo, useState } from '@doubao-apps/framework';
218
+ import {
219
+ navigateBack,
220
+ navigateTo,
221
+ redirectTo,
222
+ reLaunch,
223
+ request,
224
+ showToast,
225
+ getStorage,
226
+ getStorageSync,
227
+ setStorage,
228
+ setStorageSync,
229
+ } from '@doubao-apps/framework/api';
230
+ import './index.scss';
231
+ ```
@@ -0,0 +1,196 @@
1
+ # Event Mapping
2
+
3
+ > **Mapping Version**: 1.0.0
4
+ > **Source Framework**: 抖音小程序 (Douyin Mini Program)
5
+ > **Target Framework**: Doubao Apps (React Lynx)
6
+ > **Last Updated**: 2026-04-01
7
+
8
+ ## 官方事实依据
9
+
10
+ - TTML 事件: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/miniapp-framework/view/ttml-event`
11
+ - 组件概述: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/component/overview/`
12
+
13
+ ---
14
+
15
+ ## 最关键的迁移差异
16
+
17
+ | 特性 | 抖音小程序 | Doubao Apps |
18
+ |------|-----------|-------------|
19
+ | 事件处理器 | 字符串方法名 `"handleTap"` | 函数引用 `{handleTap}` |
20
+ | 事件语法 | `bindtap` / `bind:tap` / `bind:tap.stop` | `onClick` / `onCatchTap` / 显式逻辑 |
21
+ | 捕获阶段 | `capture-bind:*` / `bind:*.capture` | 无机械等价,需重构 |
22
+ | dataset 取值 | `event.currentTarget.dataset.xxx` | 优先闭包传值 |
23
+
24
+ ```tsx
25
+ // ❌ 源端写法
26
+ <view bind:tap="handleTap"></view>
27
+
28
+ // ✅ 目标写法
29
+ <view onClick={handleTap}></view>
30
+ ```
31
+
32
+ ---
33
+
34
+ ## 触摸 / 点击事件
35
+
36
+ | 抖音小程序 | Doubao Apps | 说明 |
37
+ |-----------|-------------|------|
38
+ | `bindtap` / `bind:tap` | `onClick` | 点击 |
39
+ | `catchtap` / `catch:tap` / `bind:tap.stop` | `onCatchTap` | 阻止冒泡 |
40
+ | `bindlongpress` / `bind:longpress` | `onLongPress` | 长按 |
41
+ | `bindtouchstart` / `bind:touchstart` | `onTouchStart` | 触摸开始 |
42
+ | `bindtouchmove` / `bind:touchmove` | `onTouchMove` | 触摸移动 |
43
+ | `bindtouchend` / `bind:touchend` | `onTouchEnd` | 触摸结束 |
44
+
45
+ ### 基础示例
46
+
47
+ ```tsx
48
+ // Before (抖音小程序)
49
+ <view bind:tap="handleTap" bind:longpress="handleLongPress">
50
+ <text>点击我</text>
51
+ </view>
52
+
53
+ // After (Doubao Apps)
54
+ <view onClick={handleTap} onLongPress={handleLongPress}>
55
+ <text>点击我</text>
56
+ </view>
57
+ ```
58
+
59
+ ---
60
+
61
+ ## 捕获阶段与 modifier
62
+
63
+ 官方文档说明,自基础库 `2.66.0` 起,支持:
64
+
65
+ - `capture-bind:event`
66
+ - `bind:event.capture`
67
+ - `bind:event.stop`
68
+ - `bind:event.prevent`
69
+ - `bind:event.stop.prevent`
70
+
71
+ ### 迁移原则
72
+
73
+ | 源写法 | 迁移策略 |
74
+ |------|----------|
75
+ | `bind:tap.capture` | 重构事件流,不做字符串级替换 |
76
+ | `bind:tap.stop` | 优先改为 `onCatchTap` |
77
+ | `bind:tap.prevent` | 显式重写默认行为,不机械映射 |
78
+ | `bind:tap.stop.prevent` | 通常需要重写交互结构 |
79
+
80
+ > `prevent` 与捕获语义在目标侧通常不是同名属性就能保留,必须结合组件行为重写。
81
+
82
+ ---
83
+
84
+ ## 输入类事件
85
+
86
+ | 抖音小程序 | Doubao Apps | 说明 |
87
+ |-----------|-------------|------|
88
+ | `bindinput` / `bind:input` | `onInput` | 输入变化 |
89
+ | `bindfocus` / `bind:focus` | `onFocus` | 获取焦点 |
90
+ | `bindblur` / `bind:blur` | `onBlur` | 失去焦点 |
91
+ | `bindconfirm` / `bind:confirm` | `onConfirm` | 键盘确认 |
92
+
93
+ ```tsx
94
+ // Before
95
+ <input value="{{keyword}}" bind:input="handleInput" bind:confirm="handleConfirm" />
96
+
97
+ // After
98
+ <input value={keyword} onInput={handleInput} onConfirm={handleConfirm} />
99
+ ```
100
+
101
+ ---
102
+
103
+ ## 滚动事件
104
+
105
+ | 抖音小程序 | Doubao Apps | 说明 |
106
+ |-----------|-------------|------|
107
+ | `bindscroll` | `onScroll` | 滚动中 |
108
+ | `bindscrolltoupper` | `onScrollToUpper` | 到顶 |
109
+ | `bindscrolltolower` | `onScrollToLower` | 到底 |
110
+
111
+ ```tsx
112
+ <scroll-view className="feed-scroll" scrollOrientation="vertical" onScrollToLower={loadMore}>
113
+ ...
114
+ </scroll-view>
115
+ ```
116
+
117
+ ---
118
+
119
+ ## 事件对象与 dataset
120
+
121
+ 官方 TTML 事件文档说明:
122
+
123
+ - 事件对象可包含 `type`、`timeStamp`、`target`、`currentTarget`、`detail`
124
+ - `data-element-type` 会映射为 `dataset.elementType`
125
+
126
+ ### 推荐迁移方式
127
+
128
+ ```tsx
129
+ // Before
130
+ <view data-id="{{item.id}}" bind:tap="handleTap"></view>
131
+
132
+ // After
133
+ <view onClick={() => handleTap(item.id)}></view>
134
+ ```
135
+
136
+ 只有在需要通用事件代理或复用组件协议时,才继续保留 `data-*`。
137
+
138
+ ---
139
+
140
+ ## switch / slider 事件
141
+
142
+ Doubao 侧常见组件回调更偏“受控组件”风格:
143
+
144
+ | 源端 | 目标端 |
145
+ |------|--------|
146
+ | `switch bindchange="onSwitchChange"` | `onChange={(checked) => ...}` |
147
+ | `slider bindchange="onSliderChange"` | `onChange={(value) => ...}` |
148
+
149
+ ```tsx
150
+ <switch checked={checked} onChange={setChecked} />
151
+ ```
152
+
153
+ ```tsx
154
+ <slider value={progress} onChange={(value) => setProgress(value)} />
155
+ ```
156
+
157
+ ---
158
+
159
+ ## 自定义事件
160
+
161
+ | 抖音小程序 | Doubao Apps |
162
+ |-----------|-------------|
163
+ | `this.triggerEvent('select', detail)` | `props.onSelect?.(detail)` |
164
+
165
+ ### 自定义事件示例
166
+
167
+ ```tsx
168
+ // Before
169
+ Component({
170
+ methods: {
171
+ handleTap() {
172
+ this.triggerEvent('select', { id: 1 });
173
+ },
174
+ },
175
+ });
176
+
177
+ // After
178
+ interface Props {
179
+ onSelect?: (detail: { id: number }) => void;
180
+ }
181
+
182
+ const Item = ({ onSelect }: Props) => {
183
+ return <view onClick={() => onSelect?.({ id: 1 })}></view>;
184
+ };
185
+ ```
186
+
187
+ ---
188
+
189
+ ## 不要机械迁移的事件
190
+
191
+ - `bind:tap.capture`
192
+ - `bind:tap.prevent`
193
+ - 抖音分享、视频挂载、直播挂载相关回调
194
+ - 依赖 `dataset` 大量透传对象的旧代码
195
+
196
+ 这些事件必须基于目标交互模型重写。