Skip to content

refactor: 重构引入的css transition 库 #3236

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: feat_v3.x
Choose a base branch
from
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
"async-validator": "^4.2.5",
"classnames": "^2.5.1",
"codesandbox": "^2.2.3",
"dom-helpers": "5.2.1",
"lodash.kebabcase": "^4.1.1",
"lottie-react": "^2.4.0",
"react-fast-compare": "^3.2.2",
Expand Down
2 changes: 1 addition & 1 deletion src/packages/overlay/overlay.taro.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { FunctionComponent, useEffect, useState } from 'react'
import { CSSTransition } from 'react-transition-group'
import classNames from 'classnames'
import { ITouchEvent, View } from '@tarojs/components'
import CSSTransition from '@/utils/css-transition/CSSTransition'
import { ComponentDefaults } from '@/utils/typings'
import { useLockScrollTaro } from '@/hooks/taro/use-lock-scoll'
import { TaroOverlayProps } from '@/types'
Expand Down
2 changes: 1 addition & 1 deletion src/packages/overlay/overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import React, {
useRef,
useState,
} from 'react'
import { CSSTransition } from 'react-transition-group'
import classNames from 'classnames'
import CSSTransition from '@/utils/css-transition/CSSTransition'
import { ComponentDefaults } from '@/utils/typings'
import { useLockScroll } from '@/hooks/use-lock-scroll'
import { WebOverlayProps } from '@/types'
Expand Down
201 changes: 201 additions & 0 deletions src/utils/css-transition/CSSTransition.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import addOneClass from 'dom-helpers/addClass'

import removeOneClass from 'dom-helpers/removeClass'
import React, { FunctionComponent } from 'react'

import { Transition, TransitionProps } from './Transition'
import { forceReflow } from './utils/reflow'

const addClassCommon = (node: HTMLElement | null, classes: string) =>
node && classes && classes.split(' ').forEach((c) => addOneClass(node, c))
const removeClassCommon = (node: HTMLElement | null, classes: string) =>
node && classes && classes.split(' ').forEach((c) => removeOneClass(node, c))

type ClassNamesShape =
| string
| {
appear: string
appearActive: string
appearDone: string
enter: string
enterActive: string
enterDone: string
exit: string
exitActive: string
exitDone: string
}

interface CSSTransitionProps extends TransitionProps {
classNames: ClassNamesShape
children: React.ReactNode
}

const defaultProps = {
classNames: '',
}

const CSSTransition: FunctionComponent<Partial<CSSTransitionProps>> = (
props
) => {
const {
classNames,
onEnter: _onEnter,
onEntering: _onEntering,
onEntered: _onEntered,
onExit: _onExit,
onExiting: _onExiting,
onExited: _onExited,
nodeRef: _nodeRef,
...childProps
} = { ...defaultProps, ...props }

const appliedClasses = {
appear: {},
enter: {},
exit: {},
}

const onEnter = (maybeNode: any, maybeAppearing: boolean) => {
const [node, appearing] = resolveArguments(maybeNode, maybeAppearing)
removeClasses(node, 'exit')
addClass(node, appearing ? 'appear' : 'enter', 'base')

if (_onEnter) {
_onEnter(maybeNode, maybeAppearing)
}
}

const onEntering = (maybeNode: any, maybeAppearing: boolean) => {
const [node, appearing] = resolveArguments(maybeNode, maybeAppearing)
const type = appearing ? 'appear' : 'enter'
addClass(node, type, 'active')

if (_onEntering) {
_onEntering(maybeNode, maybeAppearing)
}
}

const onEntered = (maybeNode: any, maybeAppearing: boolean) => {
const [node, appearing] = resolveArguments(maybeNode, maybeAppearing)
const type = appearing ? 'appear' : 'enter'
removeClasses(node, type)
addClass(node, type, 'done')

if (_onEntered) {
_onEntered(maybeNode, maybeAppearing)
}
}

const onExit = (maybeNode: any) => {
const [node] = resolveArguments(maybeNode)
removeClasses(node, 'appear')
removeClasses(node, 'enter')
addClass(node, 'exit', 'base')

if (_onExit) {
_onExit(maybeNode)
}
}

const onExiting = (maybeNode: any) => {
const [node] = resolveArguments(maybeNode)
addClass(node, 'exit', 'active')

if (_onExiting) {
_onExiting(maybeNode)
}
}

const onExited = (maybeNode: any) => {
const [node] = resolveArguments(maybeNode)
removeClasses(node, 'exit')
addClass(node, 'exit', 'done')

if (_onExited) {
_onExited(maybeNode)
}
}

// when prop `nodeRef` is provided `node` is excluded
const resolveArguments = (maybeNode: any, maybeAppearing: boolean) =>
_nodeRef
? [_nodeRef.current, maybeNode] // here `maybeNode` is actually `appearing`
: [maybeNode, maybeAppearing] // `findDOMNode` was used

const getClassNames = (type: string) => {
const isStringClassNames = typeof classNames === 'string'
const prefix = isStringClassNames && classNames ? `${classNames}-` : ''

const baseClassName = isStringClassNames
? `${prefix}${type}`
: classNames[type]

const activeClassName = isStringClassNames
? `${baseClassName}-active`
: classNames[`${type}Active`]

const doneClassName = isStringClassNames
? `${baseClassName}-done`
: classNames[`${type}Done`]

return {
baseClassName,
activeClassName,
doneClassName,
}
}

const addClass = (node: HTMLElement | null, type: string, phase: string) => {
let className = getClassNames(type)[`${phase}ClassName`]
const { doneClassName } = getClassNames('enter')

if (type === 'appear' && phase === 'done' && doneClassName) {
className += ` ${doneClassName}`
}

// This is to force a repaint,
// which is necessary in order to transition styles when adding a class name.
if (phase === 'active') {
if (node) forceReflow(node)
}

if (className) {
appliedClasses[type][phase] = className
addClassCommon(node, className)
}
}
Comment on lines +148 to +166
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

类名管理逻辑实现良好,但存在小问题

addClass 函数正确处理了各个阶段的类名,包括强制重排以触发过渡效果。但第156行的实现有潜在问题:

let className = getClassNames(type)[`${phase}ClassName`]

这里 phase 的值是 "base"、"active" 或 "done",但 getClassNames 返回的对象键是 baseClassNameactiveClassNamedoneClassName。建议修正:

-    let className = getClassNames(type)[`${phase}ClassName`]
+    const classNameKey = `${phase}ClassName` as keyof ReturnType<typeof getClassNames>
+    let className = getClassNames(type)[classNameKey]
🤖 Prompt for AI Agents
In src/utils/css-transition/CSSTransition.tsx around lines 155 to 173, the
variable className is assigned using getClassNames(type)[`${phase}ClassName`],
but phase values like "base", "active", or "done" need to be concatenated with
"ClassName" to match keys such as "baseClassName", "activeClassName", and
"doneClassName". Fix this by constructing the key string properly, for example
by using `${phase}ClassName` as a single string key to access the correct
property from the object returned by getClassNames.


const removeClasses = (node: HTMLElement | null, type: string) => {
const {
base: baseClassName,
active: activeClassName,
done: doneClassName,
} = appliedClasses[type]

appliedClasses[type] = {}

if (baseClassName) {
removeClassCommon(node, baseClassName)
}
if (activeClassName) {
removeClassCommon(node, activeClassName)
}
if (doneClassName) {
removeClassCommon(node, doneClassName)
}
}

return (
<Transition
{...childProps}
onEnter={onEnter}
onEntered={onEntered}
onEntering={onEntering}
onExit={onExit}
onExiting={onExiting}
onExited={onExited}
/>
)
}

export default CSSTransition
Loading
Loading