From c783bce8c1d29480ad0c80e9f2ab98d0f5e41990 Mon Sep 17 00:00:00 2001 From: hanyuxinting Date: Wed, 3 Jun 2026 23:54:06 +0800 Subject: [PATCH 1/2] fix(swipe): resolve infinite re-render loop causing page freeze - Fix useCallback refs with unstable deps (props.leftAction/rightAction) that triggered setActionWidth on every render, causing an infinite loop - Add width equality check before updating state to break the cycle - Fix `opened` ref accessed without `.current` in both H5 and Taro versions Closes #3433 Co-Authored-By: Claude Opus 4.6 --- src/packages/swipe/swipe.taro.tsx | 5 +++-- src/packages/swipe/swipe.tsx | 35 +++++++++++++++++-------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/packages/swipe/swipe.taro.tsx b/src/packages/swipe/swipe.taro.tsx index 8ff06cabc1..e2ad4fd88b 100644 --- a/src/packages/swipe/swipe.taro.tsx +++ b/src/packages/swipe/swipe.taro.tsx @@ -142,7 +142,8 @@ export const Swipe = forwardRef< if (touch.isHorizontal()) { lockClick.current = true const newState = { ...state, dragging: true } - const isEdge = !opened || touch.deltaX.current * startOffset.current < 0 + const isEdge = + !opened.current || touch.deltaX.current * startOffset.current < 0 if (isEdge) { preventDefault(event, true) } @@ -170,7 +171,7 @@ export const Swipe = forwardRef< const toggle = (side: PositionX) => { const offset = Math.abs(state.offset) const base = 0.3 - const baseNum = opened ? 1 - base : base + const baseNum = opened.current ? 1 - base : base const width = side === 'left' ? actionWidth.current.left : actionWidth.current.right if (width && offset > Number(width) * baseNum) { diff --git a/src/packages/swipe/swipe.tsx b/src/packages/swipe/swipe.tsx index df5ee1f38f..991a664ed7 100644 --- a/src/packages/swipe/swipe.tsx +++ b/src/packages/swipe/swipe.tsx @@ -52,6 +52,8 @@ export const Swipe = forwardRef< left: 0, right: 0, }) + const actionWidthRef = useRef(actionWidth) + actionWidthRef.current = actionWidth const wrapperStyle = { transform: `translate3d(${state.offset}px, 0, 0)`, transitionDuration: state.dragging ? '0s' : '.6s', @@ -78,7 +80,8 @@ export const Swipe = forwardRef< if (touch.isHorizontal()) { lockClick.current = true const newState = { ...state, dragging: true } - const isEdge = !opened || touch.deltaX.current * startOffset.current < 0 + const isEdge = + !opened.current || touch.deltaX.current * startOffset.current < 0 if (isEdge) { preventDefault(event, true) } @@ -108,7 +111,7 @@ export const Swipe = forwardRef< const toggle = (side: PositionX) => { const offset = Math.abs(state.offset) const base = 0.3 - const baseNum = opened ? 1 - base : base + const baseNum = opened.current ? 1 - base : base const width = side === 'left' ? leftWidth : rightWidth if (width && offset > Number(width) * baseNum) { @@ -151,22 +154,22 @@ export const Swipe = forwardRef< } return 0 } - const leftRef = useCallback( - (node: Element | null) => { - if (node !== null) { - setActionWidth((v) => ({ ...v, left: getNodeWidth(node) })) + const leftRef = useCallback((node: Element | null) => { + if (node !== null) { + const width = getNodeWidth(node) + if (width !== actionWidthRef.current.left) { + setActionWidth((v) => ({ ...v, left: width })) } - }, - [props.leftAction] - ) - const rightRef = useCallback( - (node: Element | null) => { - if (node !== null) { - setActionWidth((v) => ({ ...v, right: getNodeWidth(node) })) + } + }, []) + const rightRef = useCallback((node: Element | null) => { + if (node !== null) { + const width = getNodeWidth(node) + if (width !== actionWidthRef.current.right) { + setActionWidth((v) => ({ ...v, right: width })) } - }, - [props.rightAction] - ) + } + }, []) const renderActionContent = (side: PositionX, measuredRef: any) => { if (props[`${side}Action`]) { return ( From 9a54f3f981127faf0ae2bfe1e971da0513ca5b39 Mon Sep 17 00:00:00 2001 From: hanyuxinting Date: Thu, 4 Jun 2026 00:35:13 +0800 Subject: [PATCH 2/2] fix(build): migrate Sass @import to @use in Taro build output The Taro build script's PostCSS plugin only handled @import rules but the source scss files had already been migrated to @use syntax. This caused the output dist scss files to retain deprecated @import rules, triggering Dart Sass deprecation warnings for downstream consumers. Align the Taro build (buildCSS, buildHarmonyCSS) with the H5 build by handling both @import and @use at-rules and ensuring @use with 'as *' in the output. Also fix sass compilation to place @use statements before variable definitions as required by the Sass spec. Closes #3430 Co-Authored-By: Claude Opus 4.6 --- scripts/build-taro.mjs | 71 ++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/scripts/build-taro.mjs b/scripts/build-taro.mjs index 4380c57b82..3dff5f4692 100644 --- a/scripts/build-taro.mjs +++ b/scripts/build-taro.mjs @@ -253,14 +253,14 @@ async function buildAllCSS(themeName = '') { const themeStylePath = themeName ? `style-${themeName}` : 'style' async function generateAllStyles() { const content = [ - `@import './styles/variables${themeName ? `-${themeName}` : ''}.scss';`, - `@import './styles/mixins/index.scss';`, - `@import './styles/animation/index.scss';`, + `@use './styles/variables${themeName ? `-${themeName}` : ''}.scss' as *;`, + `@use './styles/mixins/index.scss' as *;`, + `@use './styles/animation/index.scss' as *;`, ] const scssFiles = await glob([`${dist}/es/packages/**/${themeStylePath}/*.scss`]) scssFiles.forEach((file) => { content.push( - `@import '${relativePath('/' + file, `/${dist}/${themeStylePath}.scss`)}';`, + `@use '${relativePath('/' + file, `/${dist}/${themeStylePath}.scss`)}';`, ) }) await dest(`${dist}/${themeStylePath}.scss`, content.join('\n')) @@ -367,21 +367,24 @@ async function buildCSS(themeName = '') { base.replace('.scss', ''), ) const cssPath = relative('src', loadPath) - // 删除 import + // 删除 import/use // 写入 style.scss const atRules = [] const postcssRes = await postcss([ { postcssPlugin: 'remove-atrule', AtRule(root) { - if (root.name === 'import') { - if (root.params.indexOf('\'../../styles') > -1) { - atRules.push(root.params) - root.params = root.params.replace('../../', '../../../../') + if (root.name === 'import' || root.name === 'use') { + const rawParams = root.params + const params = rawParams.replace(/\s+as\s+\*$/, '') + if (params.indexOf('\'../../styles') > -1) { + atRules.push(params) + root.params = params.replace('../../', '../../../../') + ' as *' + root.name = 'use' return } - if (root.params.indexOf('styles') === -1) { - atRules.push(root.params) + if (params.indexOf('styles') === -1) { + atRules.push(params) root.remove() } } @@ -396,7 +399,18 @@ async function buildCSS(themeName = '') { await dest(join(`${dist}/es`, cssPath, `${themeDir}/${base}`), postcssRes.css) await dest(join(`${dist}/cjs`, cssPath, `${themeDir}/${base}`), postcssRes.css) - const code = sass.compileString(variables + '\n' + postcssRes.css.replaceAll('../../../../', '../../'), { + const compileCss = postcssRes.css.replaceAll('../../../../', '../../') + const useStatements = [] + const restLines = [] + compileCss.split('\n').forEach((line) => { + if (line.startsWith('@use ')) { + useStatements.push(line) + } else { + restLines.push(line) + } + }) + const sassInput = [...useStatements, variables, ...restLines].join('\n') + const code = sass.compileString(sassInput, { loadPaths: [loadPath], }) await dest(join(`${dist}/es`, cssPath, `${themeDir}/style.css`), code.css) @@ -460,17 +474,20 @@ async function buildHarmonyCSS(themeName = '') { base.replace('.scss', ''), ) const cssPath = relative('src', loadPath) - // 删除 import + // 删除 import/use // 写入 style.scss const atRules = [] const postcssRes = await postcss([ { postcssPlugin: 'remove-atrule', AtRule(root) { - if (root.name === 'import') { - if (root.params.indexOf('\'../../styles') > -1) { - atRules.push(root.params) - root.params = root.params.replace('../../', '../../../../') + if (root.name === 'import' || root.name === 'use') { + const rawParams = root.params + const params = rawParams.replace(/\s+as\s+\*$/, '') + if (params.indexOf('\'../../styles') > -1) { + atRules.push(params) + root.params = params.replace('../../', '../../../../') + ' as *' + root.name = 'use' return } } @@ -482,12 +499,20 @@ async function buildHarmonyCSS(themeName = '') { return result }) const themeDir = themeName ? `style-${themeName}` : 'style' - const code = sass.compileString( - variables + '\n' + postcssRes.css.replaceAll('../../../../', '../../'), - { - loadPaths: [loadPath], - }, - ) + const compileCss = postcssRes.css.replaceAll('../../../../', '../../') + const useStatements = [] + const restLines = [] + compileCss.split('\n').forEach((line) => { + if (line.startsWith('@use ')) { + useStatements.push(line) + } else { + restLines.push(line) + } + }) + const sassInput = [...useStatements, variables, ...restLines].join('\n') + const code = sass.compileString(sassInput, { + loadPaths: [loadPath], + }) if (file.indexOf('countup') === -1) { await dest( join(`${dist}/es`, cssPath, `${themeDir}/style.harmony.css`),