Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Walkthrough新增针对字节跳动(tt)小程序的可选 TT Dom 渲染支持:添加共享常量与检测工具、编译时注入 enableTTDom 配置、运行时通过 isEnableTTDom 分流为 TT Dom 的 document/element/event/innerHTML/样式处理逻辑,并暴露相关运行时接口。 Changes
Sequence Diagram(s)sequenceDiagram
participant Webpack as Webpack (编译)
participant MiniPlugin as MiniPlugin
participant TaroLoadChunks as TaroLoadChunksPlugin
participant AppSource as 生成源码 (webpack util)
participant Runtime as Taro Runtime
participant TTEnv as tt (运行时 / appDocument)
Webpack->>MiniPlugin: 传入 options(包含 appConfig)
MiniPlugin->>TaroLoadChunks: 传递 appConfig
TaroLoadChunks->>AppSource: addRequireToSource(..., appConfig)
alt TARO_ENV === 'tt' 且 appConfig.enableTTDom
AppSource->>AppSource: 注入 tt.__$enableTTDom$__ = appConfig.enableTTDom
end
Runtime->>Runtime: 调用 isEnableTTDom()
alt isEnableTTDom() 为 true
Runtime->>TTEnv: 使用 tt.appDocument 创建 TT Dom(createTTDomDocument)
Runtime->>TTEnv: 使用 eventHandlerTTDom 派发事件(bind*/catch* 适配)
Runtime->>TTEnv: styleObjectToCss -> setAttribute('style', ...)
Runtime->>TTEnv: setInnerHTML 使用 tt.appDocument 解析并设置内容
else
Runtime->>Runtime: 使用常规 createDocument()/performUpdate 流程
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@packages/taro-react/src/props.ts`:
- Around line 243-249: In the TT DOM branch inside the block guarded by
isEnableTTDom() handle the case where value is null/undefined (currently neither
isString nor isObject) and clear the old styles; update the logic around the
checks for isString(value)/isObject(value) in that branch (the code using
dom.setAttribute('style'...) and styleObjectToCss) to call the DOM clearing API
(e.g., dom.removeAttribute('style') or set an empty style) when value == null ||
value === undefined so old styles are removed in the TT path.
In `@packages/taro-runtime/src/bom/document.ts`:
- Around line 50-51: createTTDomDocument currently accesses tt.appDocument
without null/undefined checks; add a defensive guard (use optional chaining or
an explicit null check) around tt and tt.appDocument in createTTDomDocument and
either return a safe fallback or throw a clear, descriptive error so failures
are informative (align style with usage at tt?.getBuiltInComponents). Locate the
createTTDomDocument function and update its access to tt.appDocument to validate
tt and tt.appDocument before use, and ensure any thrown error message mentions
"tt.appDocument" for easier debugging.
- Line 69: The value returned from tt?.getBuiltInComponents?.() may not be a
Set, so initialize builtInComponents defensively: call
tt?.getBuiltInComponents?.(), check with instanceof Set (or Array.isArray) and
if it's not a Set convert it to one (e.g., new Set(returnedValue) or fallback to
new Set([...DEFAULT_Components, ...TT_SPECIFIC_COMPONENTS])); update the code
around the builtInComponents initialization and ensure later uses like
builtInComponents.has(type) are safe.
- Around line 106-107: The function createTTDomDocument currently returns
tt.appDocument typed as any, which lets an any flow into taroDocumentProvider
typed TaroDocument; update createTTDomDocument to declare an explicit return
type compatible with TaroDocument (or a narrower interface) and ensure its
implementation returns a value matching that type (replace the implicit any on
tt.appDocument with a typed wrapper or cast within createTTDomDocument) so the
compiler can validate that taroDocumentProvider (and env.document) adhere to
TaroDocument.
In `@packages/taro-runtime/src/interface/hydrate.ts`:
- Line 15: The __webviewId__ property on the MpInstance interface is currently
required but only used for the TT DOM path; make it optional (like the existing
route? pattern) so non-TT platforms don't need to provide it—locate the
MpInstance declaration in hydrate.ts and change the __webviewId__ member to an
optional property (__webviewId__?: number) so TypeScript won't force it for all
implementations.
🧹 Nitpick comments (8)
packages/taro-webpack5-runner/src/utils/webpack.ts (1)
17-22: 当enableTTDom未设置时,注入了不必要的代码。当用户没有在配置中设置
enableTTDom时,appConfig.enableTTDom为undefined,生成的代码将会是tt.__$enableTTDom$__ = undefined;。虽然运行时不会出错(undefined是 falsy),但这段代码对不使用 TT DOM 的抖音小程序来说是多余的。建议仅在
enableTTDom为true时注入,或至少确保输出的是布尔值:♻️ 建议的修改
- if (process.env.TARO_ENV === 'tt' && appConfig) { + if (process.env.TARO_ENV === 'tt' && appConfig?.enableTTDom) { source.add(` if (typeof tt !== 'undefined') { - tt.__$enableTTDom$__ = ${appConfig.enableTTDom}; + tt.__$enableTTDom$__ = true; }\n`) }packages/shared/src/utils.ts (1)
275-283:styleObjectToCss的类型签名中ArrayLike<unknown>不太合理函数接受
ArrayLike<unknown>类型,但内部使用Object.entries(style)处理,这会将 ArrayLike 的索引作为 CSS 属性名(如"0","1"),产生无效的 CSS。如果调用方始终传入对象,建议收窄类型。♻️ 建议收窄类型签名
-export function styleObjectToCss(style: { [s: string]: unknown } | ArrayLike<unknown>) { +export function styleObjectToCss(style: Record<string, string | number>) {packages/taro-runtime/src/dom-external/inner-html/html.ts (1)
30-34:typeof tt !== 'undefined'检查冗余
isEnableTTDom()内部已经检查了typeof tt === 'undefined'并在该情况下返回false。因此当代码执行到 Line 31 时,tt必定已定义,该检查可以简化。♻️ 简化冗余检查
if (process.env.TARO_ENV === 'tt' && isEnableTTDom()) { - if (typeof tt !== 'undefined' && 'appDocument' in tt) { + if ('appDocument' in tt) { ownerDocument = tt.appDocument } }packages/taro-runtime/src/dom/event.ts (1)
206-213:event.mpEvent = event产生循环引用Line 208 将
mpEvent设置为event自身,产生循环引用。虽然这可能是为了让 TT DOM 事件对象兼容TaroEvent的mpEvent访问模式,但循环引用可能导致序列化(如JSON.stringify)抛异常,或在调试时带来困扰。建议添加注释说明此设计意图。📝 建议添加注释说明
export function eventHandlerTTDom(ele: any, listener: (event: MpEvent, element: any) => void, event: MpEvent) { + // Note: mpEvent 指向 event 自身以兼容 TaroEvent.mpEvent 的访问模式 Object.assign(event, { mpEvent: event, bubbles: true, cancelable: true, }) listener(event, ele) }packages/taro-react/src/props.ts (1)
129-138:eventHandlerTTDom.bind(this, dom, value)中this的值不确定
setEvent是一个普通函数而非方法,this在严格模式下为undefined。虽然eventHandlerTTDom内部未使用this,但为避免歧义,建议使用null替代。♻️ 建议修复
- dom[`__${eventName}__`] = eventHandlerTTDom.bind(this, dom, value) + dom[`__${eventName}__`] = eventHandlerTTDom.bind(null, dom, value)packages/shared/src/constants.ts (1)
41-41:DEFAULT_Components的命名不符合该文件的常量命名约定该文件中所有其他常量均使用
UPPER_SNAKE_CASE(如COMPILE_MODE_IDENTIFIER_PREFIX、PLATFORM_CONFIG_MAP、TT_SPECIFIC_COMPONENTS),此处应保持一致性。♻️ 建议重命名为 DEFAULT_COMPONENTS
-export const DEFAULT_Components = new Set<string>([ +export const DEFAULT_COMPONENTS = new Set<string>([需要同步更新
packages/taro-runner-utils/src/constant.ts中的定义以及所有引用处。packages/taro-runtime/src/bom/document.ts (2)
71-78: 使用__proto__访问原型方法是已弃用的模式。虽然已有 eslint-disable 注释,但
__proto__在严格模式下行为不可靠,且属于已弃用特性。建议使用Object.getPrototypeOf(this)替代。建议:使用 Object.getPrototypeOf 替代 __proto__
document.getElementById = function getElementById(id: string) { if (id === 'app') { return app } else { - // eslint-disable-next-line no-proto - return this.__proto__.getElementById.call(this, id) + return Object.getPrototypeOf(this).getElementById.call(this, id) } }
createElement内部同理。Also applies to: 84-96
80-82:getLastPage的实现将整个 Map 展开为数组,仅为取最后一个元素。可以使用迭代器避免 O(n) 的数组分配,虽然对于小 Map 影响有限。
更高效的替代方案
document.getLastPage = function getLastPage() { - return [...this._pageDocumentMap.values()][this._pageDocumentMap.size - 1] + let last + for (const v of this._pageDocumentMap.values()) last = v + return last }
7b36ec6 to
f3a082f
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/taro-runtime/src/dsl/common.ts (1)
374-391:⚠️ Potential issue | 🟡 MinorTTDom 模式下
customWrapperCache的不一致性问题。
ATTACHED和DETACHED生命周期在 TTDom 启用时直接返回,导致customWrapperCache不被填充和清理。但在root.ts的数据更新路径中(performUpdate()中的findCustomWrapper()调用),仍会尝试从这个空缓存中读取自定义包裹组件,此代码路径缺少 TTDom 守卫。当 TTDom 启用时,指向自定义包裹组件的数据更新将无法从缓存中找到对应组件,导致这些更新被误认为是页面级别的更新而不是被正确路由到
customWrapperMap。请确认 TTDom 是否不支持自定义包裹组件的数据更新,或在数据更新路径中添加相应的 TTDom 守卫。
🤖 Fix all issues with AI agents
In `@packages/shared/src/utils.ts`:
- Around line 275-283: In styleObjectToCss, filter out entries whose value is
null or undefined before mapping to CSS so you don't emit "key: undefined|null;"
strings; update the code that iterates Object.entries(style) to skip entries
where value === null || value === undefined, then continue using kebabCaseKey
and isUnitlessProperty to produce the final cssValue and string.
In `@packages/taro-react/src/props.ts`:
- Around line 129-138: 在 TT DOM 分支里将 eventHandlerTTDom 的绑定上下文由 this 显式改为 null:把对
eventHandlerTTDom.bind 的调用(目前为 eventHandlerTTDom.bind(this, dom, value))改为
eventHandlerTTDom.bind(null, dom, value),同时保持对 dom[`__${eventName}__`] 的赋值与
addEventListener 调用不变;参考相关符号:setEvent(由 setProperty
普通调用),eventHandlerTTDom,dom[`__${eventName}__`],addEventListener,以确保意图明确且不依赖
this 上下文。
In `@packages/taro-runtime/src/dsl/common.ts`:
- Around line 164-168: The TTDom branch is dropping the callback `cb` (passed
from ONLOAD) by calling `(pageElement as any).sync()` without invoking `cb`,
which can break lifecycle signaling; update the TTDom path so that after calling
`pageElement.sync()` you invoke the same `cb` (guarding for existence) just like
the non-TTDom path uses `pageElement.performUpdate(true, cb)`; ensure you call
`cb()` after sync completes (synchronously if sync is sync) or schedule it
appropriately if `sync()` returns/accepts async completion.
🧹 Nitpick comments (7)
packages/taro-webpack5-runner/src/utils/webpack.ts (1)
5-5:AppConfig仅作为类型使用,应使用import type。
AppConfig在第 15 行仅用作参数类型注解,而非运行时值。当前使用值导入(value import)可能导致打包时引入不必要的运行时代码,且与第 8 行 webpack 类型的import type风格不一致。♻️ 建议修改
-import { AppConfig } from '@tarojs/taro' +import type { AppConfig } from '@tarojs/taro'packages/taro-runtime/src/dsl/common.ts (2)
153-157: TTDom 路径下pageElement的类型安全性较弱。
(env.document as any).getPageDocumentById(this.__webviewId__)返回的对象被赋给pageElement: TaroRootElement | null,但实际类型可能与TaroRootElement不同。后续代码(如 Line 159 的ensure检查、Line 163 的ctx赋值)都依赖于它是TaroRootElement。建议为 TTDom 的 page document 补充类型定义或至少加一个接口声明,避免在后续维护中出现隐式类型不匹配的问题。
153-157: 可选优化:提取重复的 TTDom 判断条件。
process.env.TARO_ENV === 'tt' && isEnableTTDom()在本文件中出现了 5 次。虽然外层的TARO_ENV检查对编译时 dead-code elimination 有意义(process.env.TARO_ENV会在构建时被替换),但建议在首次出现时加一行注释说明这一设计意图,方便后续维护者理解为什么isEnableTTDom()内部已有相同检查仍要在外部重复。Also applies to: 164-168, 335-337, 374-377, 388-391
packages/shared/src/utils.ts (2)
268-272: 三元赋值可简化为布尔表达式。♻️ 建议简化
const ttMode = tt.getRenderMode ? tt.getRenderMode() : TTRenderType.V1 - - ttMode === TTRenderType.V2 && tt.__$enableTTDom$__ ? (ttUseV2TTDom = true) : (ttUseV2TTDom = false) + ttUseV2TTDom = ttMode === TTRenderType.V2 && !!tt.__$enableTTDom$__ return ttUseV2TTDom
261-265:isEnableTTDom在非 TT 环境下每次调用都会重复检查环境变量。当
TARO_ENV !== 'tt'时,函数每次都会走到 Line 263 的return false,而不会利用ttUseV2TTDom缓存。虽然性能影响很小,但若此函数在热路径中被频繁调用(如setProperty、setEvent等),可以考虑也缓存非 TT 环境的结果。packages/taro-runtime/src/bom/document.ts (2)
88-90:getLastPage将整个 Map 展开为数组只为取最后一个元素,效率较低。
[...this._pageDocumentMap.values()][this._pageDocumentMap.size - 1]会创建一个临时数组,当页面栈较深时有不必要的内存分配。可以改用迭代器直接获取最后一个值。♻️ 建议优化
document.getLastPage = function getLastPage() { - return [...this._pageDocumentMap.values()][this._pageDocumentMap.size - 1] + let last + for (const v of this._pageDocumentMap.values()) last = v + return last }
79-86: 使用__proto__访问原型方法已被废弃,建议使用Object.getPrototypeOf。Line 84、98、100 处使用了
this.__proto__来调用原型方法。虽然已有 eslint-disable 注释,但__proto__是非标准的遗留特性,推荐使用Object.getPrototypeOf(this)替代。♻️ 建议替换示例(getElementById 为例)
document.getElementById = function getElementById(id: string) { if (id === 'app') { return app } else { - // eslint-disable-next-line no-proto - return this.__proto__.getElementById.call(this, id) + return Object.getPrototypeOf(this).getElementById.call(this, id) } }
createElement中的__proto__引用也应同样替换。Also applies to: 92-109
90f0c50 to
31503d4
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (3)
packages/taro-react/src/props.ts (2)
243-248:⚠️ Potential issue | 🟠 MajorTT DOM 分支移除
style时不会清空旧样式。
value变成null/undefined时,这里既不setAttribute也不removeAttribute,旧的style会一直残留。非 TT 分支已经会清空样式,两条路径现在行为不一致。🔧 建议修复
if (process.env.TARO_ENV === 'tt' && isEnableTTDom()) { if (isString(value)) { dom.setAttribute('style', value) } else if (isObject(value)) { dom.setAttribute('style', styleObjectToCss(value as StyleValue)) + } else if (value == null) { + dom.removeAttribute('style') } } else {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/taro-react/src/props.ts` around lines 243 - 248, The TT DOM branch in props.ts currently only sets style when value is string/object and never clears it when value is null/undefined; update the branch inside the isEnableTTDom() check (the block using isString(value), isObject(value), dom.setAttribute and styleObjectToCss) to call dom.removeAttribute('style') when value is nullish so old styles are cleared and behavior matches the non-TT branch.
328-335:⚠️ Potential issue | 🟡 Minor
styleObjectToCss仍会把undefined序列化成无效 CSS。Line 247 把任意对象强转成
StyleValue,运行时{ color: undefined }仍然可能进来;当前会生成color: undefined;。🔧 建议修复
function styleObjectToCss(style: StyleValue) { return Object.entries(style) + .filter(([, value]) => value != null) .map(([key, value]) => { const kebabCaseKey = key.replace(/([A-Z])/g, '-$1').toLowerCase() const cssValue = typeof value === 'number' && !UNITLESS_PROPERTIES_SET.has(kebabCaseKey) ? `${value}px` : value === null ? '' : value return `${kebabCaseKey}: ${cssValue};` })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/taro-react/src/props.ts` around lines 328 - 335, The styleObjectToCss function currently serializes undefined (and null) values into invalid CSS; update styleObjectToCss to skip entries whose value is undefined or null before mapping (i.e., filter Object.entries(style) to remove v === undefined || v === null), then apply the existing kebab-case and UNITLESS_PROPERTIES_SET logic so only valid values are emitted.packages/taro-runtime/src/bom/document.ts (1)
166-186:⚠️ Potential issue | 🔴 Critical
__eventWrappers的清理方式对WeakMap无效,而且同一 listener 会覆盖不同事件的包装器。
__eventWrappers在这里是WeakMap,delete el.__eventWrappers[listener]不会删除WeakMap条目。再加上当前只按listener建索引,同一个函数复用到多个事件时,后一次set(listener, wrapper)会覆盖前一个包装器,removeEventListener很容易取错引用并留下悬挂监听。🔧 建议修复
if (!el.__eventWrappers) { el.__eventWrappers = new WeakMap() } - el.__eventWrappers.set(listener, wrapper) + const wrappers = el.__eventWrappers.get(listener) ?? new Map() + wrappers.set(bindEventName, wrapper) + el.__eventWrappers.set(listener, wrappers) @@ - const wrapper = el.__eventWrappers?.get(listener) + const wrappers = el.__eventWrappers?.get(listener) + const wrapper = wrappers?.get(bindEventName) if (wrapper) { ttRemoveEventListener(bindEventName, wrapper) - delete el.__eventWrappers[listener] + wrappers.delete(bindEventName) + if (wrappers.size === 0) { + el.__eventWrappers.delete(listener) + } }可用下面的脚本直接确认这里先创建了
WeakMap,后面却用delete obj[key]清理,并且缓存键只有listener:#!/bin/bash rg -n -C3 '__eventWrappers\s*=\s*new WeakMap|__eventWrappers\.set\(listener,\s*wrapper\)|__eventWrappers\?\.get\(listener\)|delete\s+el\.__eventWrappers\[listener\]' packages/taro-runtime/src/bom/document.ts🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/taro-runtime/src/bom/document.ts` around lines 166 - 186, The code creates el.__eventWrappers as a WeakMap but later tries to delete entries with delete el.__eventWrappers[listener] and also only keys by listener (so one wrapper overwrites another for different event types). Change the structure to a WeakMap keyed by the listener that maps to a Map of bindEventName→wrapper: initialize el.__eventWrappers = new WeakMap<Function, Map<string, Function>>(), when adding do let m = el.__eventWrappers.get(listener) || new Map(); m.set(bindEventName, wrapper); el.__eventWrappers.set(listener, m); when removing in removeEventListener, look up const m = el.__eventWrappers.get(listener), get the wrapper via m.get(bindEventName), call ttRemoveEventListener(bindEventName, wrapper) if found, m.delete(bindEventName) and if m.size === 0 call el.__eventWrappers.delete(listener); this ensures correct per-event wrappers and uses WeakMap.delete correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/taro-react/src/props.ts`:
- Around line 129-136: The TT DOM handler cache currently reuses
dom[`__${eventName}__`] for both capture and bubble handlers, causing onClick
and onClickCapture to override each other and incorrect removals; change the
cache key to include the capture flag (e.g. `__${eventName}__capture` vs
`__${eventName}__bubble` or append isCapture) so eventHandlerTTDom bindings are
stored separately, and ensure removeEventListener reads the same capture-aware
key before removing so addEventListener/removeEventListener use the same stored
function per (eventName,isCapture) pair.
In `@packages/taro-runtime/src/bom/document.ts`:
- Around line 114-135: The current setAttribute interceptor (function
setAttribute) only adds the emptyFunction listener when catchMove is truthy but
never removes it when catchMove is set to false, causing stale catchtouchmove
listeners; update setAttribute so when name === 'catchMove' and the new value is
falsy it calls el.removeEventListener('catchtouchmove', emptyFunction), and when
truthy it calls el.addEventListener('catchtouchmove', emptyFunction); keep
calling originalSetAttribute(name, value) and preserve the existing
removeAttribute behavior that removes the listener when the attribute is
deleted.
---
Duplicate comments:
In `@packages/taro-react/src/props.ts`:
- Around line 243-248: The TT DOM branch in props.ts currently only sets style
when value is string/object and never clears it when value is null/undefined;
update the branch inside the isEnableTTDom() check (the block using
isString(value), isObject(value), dom.setAttribute and styleObjectToCss) to call
dom.removeAttribute('style') when value is nullish so old styles are cleared and
behavior matches the non-TT branch.
- Around line 328-335: The styleObjectToCss function currently serializes
undefined (and null) values into invalid CSS; update styleObjectToCss to skip
entries whose value is undefined or null before mapping (i.e., filter
Object.entries(style) to remove v === undefined || v === null), then apply the
existing kebab-case and UNITLESS_PROPERTIES_SET logic so only valid values are
emitted.
In `@packages/taro-runtime/src/bom/document.ts`:
- Around line 166-186: The code creates el.__eventWrappers as a WeakMap but
later tries to delete entries with delete el.__eventWrappers[listener] and also
only keys by listener (so one wrapper overwrites another for different event
types). Change the structure to a WeakMap keyed by the listener that maps to a
Map of bindEventName→wrapper: initialize el.__eventWrappers = new
WeakMap<Function, Map<string, Function>>(), when adding do let m =
el.__eventWrappers.get(listener) || new Map(); m.set(bindEventName, wrapper);
el.__eventWrappers.set(listener, m); when removing in removeEventListener, look
up const m = el.__eventWrappers.get(listener), get the wrapper via
m.get(bindEventName), call ttRemoveEventListener(bindEventName, wrapper) if
found, m.delete(bindEventName) and if m.size === 0 call
el.__eventWrappers.delete(listener); this ensures correct per-event wrappers and
uses WeakMap.delete correctly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e8ad01ec-6f0d-4911-a24a-ed1fd6500eb7
📒 Files selected for processing (13)
packages/shared/src/constants.tspackages/shared/src/utils.tspackages/taro-react/src/props.tspackages/taro-runtime/src/bom/document.tspackages/taro-runtime/src/dom-external/inner-html/html.tspackages/taro-runtime/src/dom/event.tspackages/taro-runtime/src/dsl/common.tspackages/taro-runtime/src/index.tspackages/taro-runtime/src/interface/hydrate.tspackages/taro-webpack5-runner/src/plugins/MiniPlugin.tspackages/taro-webpack5-runner/src/plugins/TaroLoadChunksPlugin.tspackages/taro-webpack5-runner/src/utils/webpack.tspackages/taro/types/taro.config.d.ts
✅ Files skipped from review due to trivial changes (3)
- packages/taro-runtime/src/interface/hydrate.ts
- packages/shared/src/constants.ts
- packages/taro/types/taro.config.d.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- packages/taro-runtime/src/dom-external/inner-html/html.ts
- packages/taro-runtime/src/dom/event.ts
- packages/taro-runtime/src/index.ts
- packages/shared/src/utils.ts
- packages/taro-webpack5-runner/src/plugins/MiniPlugin.ts
- packages/taro-runtime/src/dsl/common.ts
| if (process.env.TARO_ENV === 'tt' && isEnableTTDom()) { | ||
| if (isFunction(oldValue)) { | ||
| (dom as any).removeEventListener(`bind${eventName}`, dom[`__${eventName}__`]) | ||
| } | ||
| if (isFunction(value)) { | ||
| dom[`__${eventName}__`] = eventHandlerTTDom.bind(null, dom, value) | ||
| dom.addEventListener(`bind${eventName}`, dom[`__${eventName}__`], isCapture) | ||
| } |
There was a problem hiding this comment.
TT DOM 事件缓存键会让冒泡/捕获监听互相覆盖。
这里把缓存固定在 dom[\${eventName}`]。onClick和onClickCapture` 会落到同一个键上,后注册的会覆盖前一个;后续更新或卸载其中一个 prop 时,也会把另一个监听误删/遗留。
🔧 建议修复
function setEvent (dom: TaroElement, name: string, value: unknown, oldValue?: unknown) {
const isCapture = name.endsWith('Capture')
+ const handlerKey = `__${name}__`
let eventName = name.toLowerCase().slice(2)
if (isCapture) {
eventName = eventName.slice(0, -7)
}
@@
if (process.env.TARO_ENV === 'tt' && isEnableTTDom()) {
if (isFunction(oldValue)) {
- (dom as any).removeEventListener(`bind${eventName}`, dom[`__${eventName}__`])
+ (dom as any).removeEventListener(`bind${eventName}`, dom[handlerKey], isCapture)
}
if (isFunction(value)) {
- dom[`__${eventName}__`] = eventHandlerTTDom.bind(null, dom, value)
- dom.addEventListener(`bind${eventName}`, dom[`__${eventName}__`], isCapture)
+ dom[handlerKey] = eventHandlerTTDom.bind(null, dom, value)
+ dom.addEventListener(`bind${eventName}`, dom[handlerKey], isCapture)
}
return
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/taro-react/src/props.ts` around lines 129 - 136, The TT DOM handler
cache currently reuses dom[`__${eventName}__`] for both capture and bubble
handlers, causing onClick and onClickCapture to override each other and
incorrect removals; change the cache key to include the capture flag (e.g.
`__${eventName}__capture` vs `__${eventName}__bubble` or append isCapture) so
eventHandlerTTDom bindings are stored separately, and ensure removeEventListener
reads the same capture-aware key before removing so
addEventListener/removeEventListener use the same stored function per
(eventName,isCapture) pair.
| el.setAttribute = function (name: string, value: any) { | ||
| const result = originalSetAttribute(name, value) | ||
|
|
||
| // 处理 catchMove 属性 | ||
| if (name === 'catchMove' && value) { | ||
| el.addEventListener('catchtouchmove', emptyFunction) | ||
| } | ||
|
|
||
| return result | ||
| } | ||
|
|
||
| // 拦截 removeAttribute 来处理 catchMove | ||
| el.removeAttribute = function (name: string) { | ||
| const oldValue = el.getAttribute(name) | ||
|
|
||
| // 处理 catchMove 属性 | ||
| if (name === 'catchMove' && oldValue) { | ||
| el.removeEventListener('catchtouchmove', emptyFunction) | ||
| } | ||
|
|
||
| return originalRemoveAttribute(name) | ||
| } |
There was a problem hiding this comment.
catchMove={false} 时旧的阻止滚动监听不会被移除。
这里只在 removeAttribute 时调用 removeEventListener,但 packages/taro-react/src/props.ts 在 Line 309 只有 value == null 才会走 removeAttribute;catchMove={false} 仍然会调用 setAttribute。从 true 切到 false 后,catchtouchmove 空监听会残留,滚动拦截无法关闭。
🔧 建议修复
el.setAttribute = function (name: string, value: any) {
const result = originalSetAttribute(name, value)
// 处理 catchMove 属性
- if (name === 'catchMove' && value) {
- el.addEventListener('catchtouchmove', emptyFunction)
+ if (name === 'catchMove') {
+ el.removeEventListener('catchtouchmove', emptyFunction)
+ if (value) {
+ el.addEventListener('catchtouchmove', emptyFunction)
+ }
}
return result
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/taro-runtime/src/bom/document.ts` around lines 114 - 135, The
current setAttribute interceptor (function setAttribute) only adds the
emptyFunction listener when catchMove is truthy but never removes it when
catchMove is set to false, causing stale catchtouchmove listeners; update
setAttribute so when name === 'catchMove' and the new value is falsy it calls
el.removeEventListener('catchtouchmove', emptyFunction), and when truthy it
calls el.addEventListener('catchtouchmove', emptyFunction); keep calling
originalSetAttribute(name, value) and preserve the existing removeAttribute
behavior that removes the listener when the attribute is deleted.
|
@xzdadada 大佬测试没过,辛苦看一下 |
这个 PR 做了什么? (简要描述所做更改)
一、概述
官方提供 tt-dom 接口,让 taro 视图创建/更新更加高效。
通过 tt-dom 接口,tarojs 可以不依赖于 Template 进行递归渲染节点。
二、技术方案
TT-DOM 架构
整体架构将从 react -> taro dom -> setData -> template render 变成 react -> tt-dom -> 内置组件 render。

接入 TT-DOM
抖音小程序用户可通过在 appConfig 中配置$enableTTDom$ = true,在运行时提供 isEnableTTDom 判断,却别处理逻辑
enableTTDom: true开启 tt-dom架构内,判断开启 enableTTDom 并且使用抖音小程序时,在入口文件中注入 tt.
三、tt-dom 使用示例
四、收益
测试数据:
基准 Benchmark:借鉴 https://github.com/krausest/js-framework-benchmark 中对于框架的性能测试方案,对于创建、更新、交换、删除四个场景在 ios 真机环境下进行对比测试,得到如下数据结果
这个 PR 是什么类型? (至少选择一个)
这个 PR 涉及以下平台:
Summary by CodeRabbit
新功能
配置