Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions demo/pages/SwipeAction/index.axml
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,54 @@
</block>
</view>
</view>

<view class="t-swipe">
<view class="t-swipe-item">
<view class="t-swipe-item-title">默认展开-使用 defaultSwiped</view>
<view class="t-swipe-item-con">
<ant-swipe-action
rightButtons="{{ rightBtns }}"
elasticity="{{ true }}"
defaultSwiped="right"
onSwipeEnd="onSwipeEnd"
onSwipeStart="onSwipeStart"
onButtonTap="onButtonTap">
<view class="t-swipe-item-con-view">默认展开右侧按钮(defaultSwiped="right")</view>
</ant-swipe-action>
</view>
<view class="t-swipe-item-con">
<ant-swipe-action
leftButtons="{{ leftBtns }}"
elasticity="{{ true }}"
defaultSwiped="left"
onSwipeEnd="onSwipeEnd"
onSwipeStart="onSwipeStart"
onButtonTap="onButtonTap">
<view class="t-swipe-item-con-view">默认展开左侧按钮(defaultSwiped="left")</view>
</ant-swipe-action>
</view>
</view>
</view>

<view class="t-swipe">
<view class="t-swipe-item">
<view class="t-swipe-item-title">受控模式-通过按钮控制展开</view>
<view class="control-buttons">
<button size="mini" onTap="onControlSwipe" data-index="0" data-direction="">关闭</button>
<button size="mini" onTap="onControlSwipe" data-index="0" data-direction="right">展开右侧</button>
<button size="mini" onTap="onControlSwipe" data-index="0" data-direction="left">展开左侧</button>
</view>
<view class="t-swipe-item-con">
<ant-swipe-action
rightButtons="{{ rightBtns }}"
leftButtons="{{ leftBtns }}"
elasticity="{{ true }}"
swiped="{{ controlledSwipe }}"
onSwipeEnd="onSwipeEnd"
onSwipeStart="onSwipeStart"
onButtonTap="onButtonTap">
<view class="t-swipe-item-con-view">受控组件示例1 - 当前状态: {{ controlledSwipe || '关闭' }}</view>
</ant-swipe-action>
</view>
</view>
</view>
11 changes: 11 additions & 0 deletions demo/pages/SwipeAction/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,14 @@
justify-content: center;
width: 100%;
}

.control-buttons {
display: flex;
gap: 16 * @rpx;
padding: 16 * @rpx;
background-color: var(--color-background);
button {
flex: 1;
font-size: 24 * @rpx;
}
}
7 changes: 7 additions & 0 deletions demo/pages/SwipeAction/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Page({
data: {
controlledSwipe: '',
rightBtns: [
{
text: '取消关注',
Expand Down Expand Up @@ -99,4 +100,10 @@ Page({
onButtonTap(data, e) {
console.log(data, e);
},
onControlSwipe(e) {
const { direction } = e.currentTarget.dataset;
this.setData({
controlledSwipe: direction,
});
},
});
69 changes: 56 additions & 13 deletions src/SwipeAction/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,27 +95,69 @@ Component(
transformOptions({
props: SwipeActionDefaultProps,
didMount() {
const { defaultSwiped, elasticity } = this.getProps();
this.setButtonItemWidth();
const { defaultSwiped, swiped, elasticity } = this.getProps();
this.setData({ inertiaWidth: !isOldVersion && elasticity ? 20 : 0 });
if (defaultSwiped) {
this.initWidth((maxSwipe: any) => {
maxSwipe &&
this.setData({
swipeX: (maxSwipe + 0.01) * (defaultSwiped === 'right' ? -1 : 1),
swipedR: defaultSwiped === 'right',
swipedL: defaultSwiped === 'left',
});
});

// 优先使用 swiped 属性(受控),其次使用 defaultSwiped(非受控)
const initialSwiped = swiped || defaultSwiped;

// 先设置按钮宽度,然后在回调中处理初始展开状态
this.setButtonItemWidth();

if (initialSwiped) {
// 需要等待按钮宽度设置完成后再初始化展开状态
setTimeout(() => {
this.initWidth((maxSwipe: any) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The maxSwipe parameter is typed as any. For better type safety and code clarity, it should be explicitly typed as number, since it represents a width value obtained from initWidth.

Suggested change
this.initWidth((maxSwipe: any) => {
this.initWidth((maxSwipe: number) => {

if (maxSwipe) {
const direction = initialSwiped === 'left' ? 'left' : 'right';
this.setData({
swipeX: (maxSwipe + 0.01) * (direction === 'right' ? -1 : 1),
swipedR: direction === 'right',
swipedL: direction === 'left',
});
}
});
Comment on lines +110 to +119
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

重复的初始化逻辑应该提取为独立方法

Lines 110-119 (在 didMount 中) 和 Lines 145-156 (在 didUpdate 中) 包含几乎完全相同的初始化逻辑。这违反了 DRY 原则,增加了维护成本和出错风险。

建议提取为独立的辅助方法:

+    applySwipedState(swiped, delay = 50) {
+      setTimeout(() => {
+        this.initWidth((maxSwipe: any) => {
+          if (maxSwipe) {
+            const direction = swiped === 'left' ? 'left' : 'right';
+            this.setData({
+              swipeX: (maxSwipe + 0.01) * (direction === 'right' ? -1 : 1),
+              swipedR: direction === 'right',
+              swipedL: direction === 'left',
+              tapTypeL: '',
+              tapTypeR: '',
+            });
+          }
+        });
+      }, delay);
+    },
     didMount() {
       const { defaultSwiped, swiped, elasticity } = this.getProps();
       this.setData({ inertiaWidth: !isOldVersion && elasticity ? 20 : 0 });
       
       const initialSwiped = swiped || defaultSwiped;
       this.setButtonItemWidth();
       
       if (initialSwiped) {
-        setTimeout(() => {
-          this.initWidth((maxSwipe: any) => {
-            if (maxSwipe) {
-              const direction = initialSwiped === 'left' ? 'left' : 'right';
-              this.setData({
-                swipeX: (maxSwipe + 0.01) * (direction === 'right' ? -1 : 1),
-                swipedR: direction === 'right',
-                swipedL: direction === 'left',
-              });
-            }
-          });
-        }, 50);
+        this.applySwipedState(initialSwiped);
       }
     },
     didUpdate(prevProp) {
       // ... 省略其他代码
       if (swipedChanged) {
         if (!swiped) {
           // ... 重置逻辑
         } else {
-          setTimeout(() => {
-            this.initWidth((maxSwipe: any) => {
-              if (maxSwipe) {
-                const direction = swiped === 'left' ? 'left' : 'right';
-                this.setData({
-                  swipeX: (maxSwipe + 0.01) * (direction === 'right' ? -1 : 1),
-                  swipedR: direction === 'right',
-                  swipedL: direction === 'left',
-                  tapTypeL: '',
-                  tapTypeR: '',
-                });
-              }
-            });
-          }, 50);
+          this.applySwipedState(swiped);
         }
       }
     },

Also applies to: 145-156

🤖 Prompt for AI Agents
In src/SwipeAction/index.ts around lines 110-119 and 145-156, the same
initialization code that reads maxSwipe and sets swipeX, swipedR, swipedL is
duplicated; extract that logic into a single private/helper method (e.g.,
initSwipeState(initialSwiped?: string)) that calls this.initWidth and in its
callback computes direction, swipeX and sets the data, then replace the
duplicated blocks in didMount and didUpdate with calls to this new helper
(ensure correct this binding and preserve the same behavior when initialSwiped
is undefined).

}, 50);
Comment on lines +109 to +120
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The use of a fixed setTimeout(..., 50) to wait for component rendering before calculating widths is fragile and can lead to race conditions on slower devices or under heavy load. This could result in the swipe action not initializing to the correct defaultSwiped state. Please consider a more robust approach, such as using my.nextTick (if available on your target platform) or another mechanism that guarantees execution after the render cycle is complete.

}
},
Comment on lines 97 to 122
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

缺少定时器清理可能导致状态更新异常

didMount 中的 setTimeout (Line 109) 没有保存返回的 timer ID,也没有在组件卸载或更新时清理。如果组件在 50ms 内被销毁或 swiped 属性再次变化,可能导致过期的状态更新。

建议将 timer ID 保存到实例变量,并在 didUnmount 和相关更新逻辑中清理:

+let initTimer = null;
+
 Component(
   transformOptions({
     props: SwipeActionDefaultProps,
     didMount() {
       const { defaultSwiped, swiped, elasticity } = this.getProps();
       this.setData({ inertiaWidth: !isOldVersion && elasticity ? 20 : 0 });
       
       const initialSwiped = swiped || defaultSwiped;
       this.setButtonItemWidth();
       
       if (initialSwiped) {
-        setTimeout(() => {
+        initTimer = setTimeout(() => {
           this.initWidth((maxSwipe: any) => {
             if (maxSwipe) {
               const direction = initialSwiped === 'left' ? 'left' : 'right';
               this.setData({
                 swipeX: (maxSwipe + 0.01) * (direction === 'right' ? -1 : 1),
                 swipedR: direction === 'right',
                 swipedL: direction === 'left',
               });
             }
           });
         }, 50);
       }
     },
+    didUnmount() {
+      if (initTimer) {
+        clearTimeout(initTimer);
+        initTimer = null;
+      }
+    },

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/SwipeAction/index.ts around lines 97 to 122, the setTimeout started at
line 109 isn’t storing its timer ID and therefore isn’t cleared on unmount or
when props change; save the timeout ID to an instance property (e.g.,
this._initTimeout), clear it with clearTimeout(this._initTimeout) and null it in
didUnmount, and also clear any existing timeout before creating a new one when
reacting to prop updates (like swiped changes) to prevent stale callbacks from
updating state after the component is destroyed or reconfigured.

didUpdate(prevProp) {
const { swiped, damping, elasticity } = this.getProps();
// 设置不同的滑动位置时需要重置
const rs = prevProp.swiped !== swiped && !swiped;
const swipedChanged = prevProp.swiped !== swiped;
const is = prevProp.elasticity !== elasticity;
const ds = prevProp.damping !== damping;
if (rs || is || ds) {

// 处理 swiped 变化
if (swipedChanged) {
if (!swiped) {
// swiped 变为 false/空,需要关闭
this.setData({
swipeX: 0,
swipedR: false,
swipedL: false,
tapTypeL: '',
tapTypeR: '',
});
} else {
// swiped 变为 true/'left'/'right',需要打开
// 需要等待下一个事件循环,确保按钮宽度已经渲染
setTimeout(() => {
this.initWidth((maxSwipe: any) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The maxSwipe parameter is typed as any. It should be typed as number to improve type safety and make the code easier to understand, as it represents a width value.

Suggested change
this.initWidth((maxSwipe: any) => {
this.initWidth((maxSwipe: number) => {

if (maxSwipe) {
const direction = swiped === 'left' ? 'left' : 'right';
this.setData({
swipeX: (maxSwipe + 0.01) * (direction === 'right' ? -1 : 1),
swipedR: direction === 'right',
swipedL: direction === 'left',
tapTypeL: '',
tapTypeR: '',
});
}
});
}, 50);
Comment on lines +144 to +157
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to the implementation in didMount, using a fixed setTimeout(..., 50) here is fragile and can lead to race conditions when the swiped prop is updated. This could cause the component to not open correctly. A more robust post-render callback mechanism like my.nextTick should be preferred to ensure reliability across different devices and load conditions.

}
} else if (is || ds) {
// 只有 elasticity 或 damping 变化时,才需要重置
this.setData({
swipeX: 0,
swipedR: false,
Expand All @@ -124,6 +166,7 @@ Component(
tapTypeR: '',
});
}

if (is) {
this.setData({ inertiaWidth: elasticity ? 20 : 0 });
}
Expand Down