From f96f841fe06089c5a36378895c511f09a7b3fbd9 Mon Sep 17 00:00:00 2001 From: eternalsky Date: Sat, 13 Jan 2024 15:06:24 +0800 Subject: [PATCH] chore(Animate): ts improve --- .../animate/__docs__/demo/basic/index.tsx | 4 +- .../animate/__docs__/demo/expand/index.tsx | 21 ++-- .../animate/__docs__/demo/multiple/index.tsx | 8 +- components/animate/animate.tsx | 29 +++--- components/animate/child.tsx | 67 +++++++------ components/animate/expand.tsx | 53 ++++++---- components/animate/index.tsx | 5 +- components/animate/mobile/index.tsx | 1 + components/animate/overlay-animate.tsx | 28 +++--- components/animate/types.ts | 99 ++++++++++++++++--- 10 files changed, 200 insertions(+), 115 deletions(-) diff --git a/components/animate/__docs__/demo/basic/index.tsx b/components/animate/__docs__/demo/basic/index.tsx index 1baf60b691..0eee5e4a59 100644 --- a/components/animate/__docs__/demo/basic/index.tsx +++ b/components/animate/__docs__/demo/basic/index.tsx @@ -2,8 +2,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Animate } from '@alifd/next'; -class Demo extends React.Component { - constructor(props) { +class Demo extends React.Component { + constructor(props: any) { super(props); this.state = { visible: true }; this.handleToggle = this.handleToggle.bind(this); diff --git a/components/animate/__docs__/demo/expand/index.tsx b/components/animate/__docs__/demo/expand/index.tsx index 67a46ffc88..7e34370166 100644 --- a/components/animate/__docs__/demo/expand/index.tsx +++ b/components/animate/__docs__/demo/expand/index.tsx @@ -2,8 +2,9 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Animate } from '@alifd/next'; -class Demo extends React.Component { - constructor(props) { +class Demo extends React.Component { + [key: string]: any; + constructor(props: any) { super(props); this.state = { expand: true }; [ @@ -25,30 +26,30 @@ class Demo extends React.Component { }); } - beforeEnter(node) { + beforeEnter(node: HTMLElement) { this.height = node.offsetHeight; node.style.height = '0px'; } - onEnter(node) { + onEnter(node: HTMLElement) { node.style.height = `${this.height}px`; } - afterEnter(node) { + afterEnter(node: HTMLElement) { this.height = null; - node.style.height = null; + node.style.setProperty('height', null); } - beforeLeave(node) { + beforeLeave(node: HTMLElement) { node.style.height = `${this.height}px`; } - onLeave(node) { + onLeave(node: HTMLElement) { node.style.height = '0px'; } - afterLeave(node) { - node.style.height = null; + afterLeave(node: HTMLElement) { + node.style.setProperty('height', null); } render() { diff --git a/components/animate/__docs__/demo/multiple/index.tsx b/components/animate/__docs__/demo/multiple/index.tsx index a0a05a8342..02a005d5e5 100644 --- a/components/animate/__docs__/demo/multiple/index.tsx +++ b/components/animate/__docs__/demo/multiple/index.tsx @@ -2,8 +2,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Animate } from '@alifd/next'; -class TodoList extends React.Component { - constructor(props) { +class TodoList extends React.Component { + constructor(props: any) { super(props); this.state = { items: ['hello', 'world', 'click', 'me'] }; } @@ -18,7 +18,7 @@ class TodoList extends React.Component { }); } - handleRemove(i) { + handleRemove(i: number) { const newItems = this.state.items.slice(); newItems.splice(i, 1); this.setState({ items: newItems }); @@ -42,7 +42,7 @@ class TodoList extends React.Component { onLeave={() => console.log('leave')} afterLeave={() => console.log('after leave')} > - {this.state.items.map((item, i) => ( + {this.state.items.map((item: string, i: number) => (
{item} diff --git a/components/animate/animate.tsx b/components/animate/animate.tsx index aeaa05d6fa..7b84364eee 100644 --- a/components/animate/animate.tsx +++ b/components/animate/animate.tsx @@ -1,10 +1,13 @@ -import React, { Component, Children } from 'react'; +import React, { Component, Children, type ReactNode, ReactElement } from 'react'; import PropTypes from 'prop-types'; import { TransitionGroup } from 'react-transition-group'; import AnimateChild from './child'; +import type { AnimateProps } from './types'; +import Expand from './expand'; +import OverlayAnimate from './overlay-animate'; const noop = () => {}; -const FirstChild = props => { +const FirstChild = (props: { children: ReactNode }) => { const childrenArray = React.Children.toArray(props.children); return childrenArray[0] || null; }; @@ -12,7 +15,10 @@ const FirstChild = props => { /** * Animate */ -class Animate extends Component { +class Animate extends Component { + static Expand = Expand; + static OverlayAnimate = OverlayAnimate; + static displayName = 'Animate'; static propTypes = { /** * 动画 className @@ -36,47 +42,38 @@ class Animate extends Component { children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]), /** * 执行第一次挂载动画前触发的回调函数 - * @param {HTMLElement} node 执行动画的 dom 元素 */ beforeAppear: PropTypes.func, /** * 执行第一次挂载动画,添加 xxx-appear-active 类名后触发的回调函数 - * @param {HTMLElement} node 执行动画的 dom 元素 */ onAppear: PropTypes.func, /** * 执行完第一次挂载动画后触发的函数 - * @param {HTMLElement} node 执行动画的 dom 元素 */ afterAppear: PropTypes.func, /** * 执行进场动画前触发的回调函数 - * @param {HTMLElement} node 执行动画的 dom 元素 */ beforeEnter: PropTypes.func, /** * 执行进场动画,添加 xxx-enter-active 类名后触发的回调函数 - * @param {HTMLElement} node 执行动画的 dom 元素 */ onEnter: PropTypes.func, /** * 执行完进场动画后触发的回调函数 - * @param {HTMLElement} node 执行动画的 dom 元素 */ afterEnter: PropTypes.func, /** * 执行离场动画前触发的回调函数 - * @param {HTMLElement} node 执行动画的 dom 元素 */ beforeLeave: PropTypes.func, /** * 执行离场动画,添加 xxx-leave-active 类名后触发的回调函数 - * @param {HTMLElement} node 执行动画的 dom 元素 */ onLeave: PropTypes.func, /** * 执行完离场动画后触发的回调函数 - * @param {HTMLElement} node 执行动画的 dom 元素 */ afterLeave: PropTypes.func, }; @@ -96,7 +93,7 @@ class Animate extends Component { afterLeave: noop, }; - normalizeNames(names) { + normalizeNames(names: AnimateProps['animation']) { if (typeof names === 'string') { return { appear: `${names}-appear`, @@ -120,7 +117,6 @@ class Animate extends Component { } render() { - /* eslint-disable no-unused-vars */ const { animation, children, @@ -138,13 +134,12 @@ class Animate extends Component { afterLeave, ...others } = this.props; - /* eslint-enable no-unused-vars */ const animateChildren = Children.map(children, child => { return ( {}; const { on, off } = events; const { addClass, removeClass } = dom; const prefixes = ['-webkit-', '-moz-', '-o-', 'ms-', '']; -function getStyleProperty(node, name) { +function getStyleProperty(node: HTMLElement, name: string) { const style = window.getComputedStyle(node); let ret = ''; for (let i = 0; i < prefixes.length; i++) { @@ -20,7 +21,8 @@ function getStyleProperty(node, name) { return ret; } -export default class AnimateChild extends Component { +export default class AnimateChild extends Component { + static displayName = 'AnimateChild'; static propTypes = { names: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), onAppear: PropTypes.func, @@ -45,8 +47,13 @@ export default class AnimateChild extends Component { onExiting: noop, onExited: noop, }; + endListeners: Record void>>; + timeoutMap: Record; + node: HTMLElement; + transitionOff: () => void; + animationOff: () => void; - constructor(props) { + constructor(props: AnimateChildProps) { super(props); func.bindCtx(this, [ 'handleEnter', @@ -76,9 +83,10 @@ export default class AnimateChild extends Component { }; } - generateEndListener(node, done, eventName, id) { + generateEndListener(node: HTMLElement, done: () => void, eventName: string, id: string) { + // eslint-disable-next-line @typescript-eslint/no-this-alias const _this = this; - return function endListener(e) { + return function endListener(e: UIEvent) { if (e && e.target === node) { if (_this.timeoutMap[id]) { clearTimeout(_this.timeoutMap[id]); @@ -94,7 +102,7 @@ export default class AnimateChild extends Component { }; } - addEndListener(node, done) { + addEndListener(node: HTMLElement, done: () => void) { if (support.transition || support.animation) { const id = guid(); @@ -132,7 +140,7 @@ export default class AnimateChild extends Component { animationDuration + animationDelay ); if (time) { - this.timeoutMap[id] = setTimeout( + this.timeoutMap[id] = window.setTimeout( () => { done(); }, @@ -150,85 +158,84 @@ export default class AnimateChild extends Component { this.animationOff && this.animationOff(); } - removeClassNames(node, names) { - Object.keys(names).forEach(key => { - removeClass(node, names[key]); + removeClassNames(node: HTMLElement, names: NonNullable) { + Object.keys(names).forEach((key: keyof typeof names) => { + removeClass(node, names[key]!); }); } - handleEnter(node, isAppearing) { + handleEnter(node: HTMLElement, isAppearing: boolean) { const { names } = this.props; if (names) { this.removeClassNames(node, names); const className = isAppearing ? 'appear' : 'enter'; - addClass(node, names[className]); + addClass(node, names[className]!); } const hook = isAppearing ? this.props.onAppear : this.props.onEnter; - hook(node); + hook!(node); } - handleEntering(node, isAppearing) { + handleEntering(node: HTMLElement, isAppearing: boolean) { setTimeout(() => { const { names } = this.props; if (names) { const className = isAppearing ? 'appearActive' : 'enterActive'; - addClass(node, names[className]); + addClass(node, names[className]!); } const hook = isAppearing ? this.props.onAppearing : this.props.onEntering; - hook(node); + hook!(node); }, 10); } - handleEntered(node, isAppearing) { + handleEntered(node: HTMLElement, isAppearing: boolean) { const { names } = this.props; if (names) { const classNames = isAppearing ? [names.appear, names.appearActive] : [names.enter, names.enterActive]; classNames.forEach(className => { - removeClass(node, className); + removeClass(node, className!); }); } const hook = isAppearing ? this.props.onAppeared : this.props.onEntered; - hook(node); + hook!(node); } - handleExit(node) { + handleExit(node: HTMLElement) { const { names } = this.props; if (names) { this.removeClassNames(node, names); - addClass(node, names.leave); + addClass(node, names.leave!); } - this.props.onExit(node); + this.props.onExit!(node); } - handleExiting(node) { + handleExiting(node: HTMLElement) { setTimeout(() => { const { names } = this.props; if (names) { - addClass(node, names.leaveActive); + addClass(node, names.leaveActive!); } - this.props.onExiting(node); + this.props.onExiting!(node); }, 10); } - handleExited(node) { + handleExited(node: HTMLElement) { const { names } = this.props; if (names) { [names.leave, names.leaveActive].forEach(className => { - removeClass(node, className); + removeClass(node, className!); }); } - this.props.onExited(node); + this.props.onExited!(node); } render() { - /* eslint-disable no-unused-vars */ const { names, onAppear, @@ -242,8 +249,6 @@ export default class AnimateChild extends Component { onExited, ...others } = this.props; - /* eslint-enable no-unused-vars */ - return ( {}; const { getStyle } = dom; -export default class Expand extends Component { +export default class Expand extends Component { + static displayName = 'Expand'; static propTypes = { animation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), beforeEnter: PropTypes.func, @@ -25,8 +27,19 @@ export default class Expand extends Component { onLeave: noop, afterLeave: noop, }; - - constructor(props) { + leaving: boolean; + styleBorderTopWidth: string; + stylePaddingTop: string; + styleHeight: string; + stylePaddingBottom: string; + styleBorderBottomWidth: string; + borderTopWidth: string | number; + paddingTop: string | number; + height: number; + paddingBottom: string | number; + borderBottomWidth: string | number; + + constructor(props: ExpandProps) { super(props); func.bindCtx(this, [ 'beforeEnter', @@ -38,7 +51,7 @@ export default class Expand extends Component { ]); } - beforeEnter(node) { + beforeEnter(node: HTMLElement) { if (this.leaving) { this.afterLeave(node); } @@ -47,46 +60,46 @@ export default class Expand extends Component { this.cacheComputedStyle(node); this.setCurrentStyleToZero(node); - this.props.beforeEnter(node); + this.props.beforeEnter!(node); } - onEnter(node) { + onEnter(node: HTMLElement) { this.setCurrentStyleToComputedStyle(node); - this.props.onEnter(node); + this.props.onEnter!(node); } - afterEnter(node) { + afterEnter(node: HTMLElement) { this.restoreCurrentStyle(node); - this.props.afterEnter(node); + this.props.afterEnter!(node); } - beforeLeave(node) { + beforeLeave(node: HTMLElement) { this.leaving = true; this.cacheCurrentStyle(node); this.cacheComputedStyle(node); this.setCurrentStyleToComputedStyle(node); - this.props.beforeLeave(node); + this.props.beforeLeave!(node); } - onLeave(node) { + onLeave(node: HTMLElement) { this.setCurrentStyleToZero(node); - this.props.onLeave(node); + this.props.onLeave!(node); } - afterLeave(node) { + afterLeave(node: HTMLElement) { this.leaving = false; this.restoreCurrentStyle(node); - this.props.afterLeave(node); + this.props.afterLeave!(node); } - cacheCurrentStyle(node) { + cacheCurrentStyle(node: HTMLElement) { this.styleBorderTopWidth = node.style.borderTopWidth; this.stylePaddingTop = node.style.paddingTop; this.styleHeight = node.style.height; @@ -94,7 +107,7 @@ export default class Expand extends Component { this.styleBorderBottomWidth = node.style.borderBottomWidth; } - cacheComputedStyle(node) { + cacheComputedStyle(node: HTMLElement) { this.borderTopWidth = getStyle(node, 'borderTopWidth'); this.paddingTop = getStyle(node, 'paddingTop'); this.height = node.offsetHeight; @@ -102,7 +115,7 @@ export default class Expand extends Component { this.borderBottomWidth = getStyle(node, 'borderBottomWidth'); } - setCurrentStyleToZero(node) { + setCurrentStyleToZero(node: HTMLElement) { node.style.borderTopWidth = '0px'; node.style.paddingTop = '0px'; node.style.height = '0px'; @@ -110,7 +123,7 @@ export default class Expand extends Component { node.style.borderBottomWidth = '0px'; } - setCurrentStyleToComputedStyle(node) { + setCurrentStyleToComputedStyle(node: HTMLElement) { node.style.borderTopWidth = `${this.borderTopWidth}px`; node.style.paddingTop = `${this.paddingTop}px`; node.style.height = `${this.height}px`; @@ -118,7 +131,7 @@ export default class Expand extends Component { node.style.borderBottomWidth = `${this.borderBottomWidth}px`; } - restoreCurrentStyle(node) { + restoreCurrentStyle(node: HTMLElement) { node.style.borderTopWidth = this.styleBorderTopWidth; node.style.paddingTop = this.stylePaddingTop; node.style.height = this.styleHeight; diff --git a/components/animate/index.tsx b/components/animate/index.tsx index 2fa9027e2e..48d362009c 100644 --- a/components/animate/index.tsx +++ b/components/animate/index.tsx @@ -1,8 +1,5 @@ import Animate from './animate'; -import Expand from './expand'; -import OverlayAnimate from './overlay-animate'; -Animate.Expand = Expand; -Animate.OverlayAnimate = OverlayAnimate; +export type { AnimateProps, ExpandProps, OverlayAnimateProps } from './types'; export default Animate; diff --git a/components/animate/mobile/index.tsx b/components/animate/mobile/index.tsx index b1550c5d77..7ee3ec5157 100644 --- a/components/animate/mobile/index.tsx +++ b/components/animate/mobile/index.tsx @@ -1,3 +1,4 @@ +// @ts-expect-error Animate 在 meet-react 中没有导出 import { Animate as MeetAnimate } from '@alifd/meet-react'; import NextAnimate from '../index'; diff --git a/components/animate/overlay-animate.tsx b/components/animate/overlay-animate.tsx index 06b4f8e16b..576c123a20 100644 --- a/components/animate/overlay-animate.tsx +++ b/components/animate/overlay-animate.tsx @@ -1,9 +1,9 @@ -/* istanbul ignore file */ -import React, { useCallback, useEffect, useState } from 'react'; -import { Transition } from 'react-transition-group'; +import React from 'react'; +import { Transition, type TransitionStatus } from 'react-transition-group'; import classNames from 'classnames'; +import type { OverlayAnimateProps } from './types'; -const OverlayAnimate = props => { +const OverlayAnimate = (props: OverlayAnimateProps) => { const { animation, visible, @@ -40,7 +40,7 @@ const OverlayAnimate = props => { onExited, }; - Object.keys(animateProps).forEach(k => { + Object.keys(animateProps).forEach((k: keyof typeof animateProps) => { if (!(k in props) || typeof props[k] === 'undefined') { delete animateProps[k]; } @@ -49,10 +49,10 @@ const OverlayAnimate = props => { const animationMap = typeof animation === 'string' ? { in: animation, out: animation } : animation; - const animateClsMap = animation + const animateClsMap: Partial> = animation ? { - entering: animationMap.in, - exiting: animationMap.out, + entering: (animationMap as Record<'in' | 'out', string>).in, + exiting: (animationMap as Record<'in' | 'out', string>).out, } : {}; @@ -65,20 +65,20 @@ const OverlayAnimate = props => { {state => { const cls = classNames({ - [children.props.className]: !!children.props.className, - [animateClsMap[state]]: state in animateClsMap && animateClsMap[state], + [children!.props.className]: !!children!.props.className, + [animateClsMap[state]!]: state in animateClsMap && animateClsMap[state], }); - const childProps = { + const childProps: Record = { ...others, className: cls, }; - if (style && children.props && children.props.style) { - childProps.style = Object.assign({}, children.props.style, style); + if (style && children!.props && children!.props.style) { + childProps.style = Object.assign({}, children!.props.style, style); } - return React.cloneElement(children, childProps); + return React.cloneElement(children!, childProps); }} ); diff --git a/components/animate/types.ts b/components/animate/types.ts index c12acf50de..929a7287d7 100644 --- a/components/animate/types.ts +++ b/components/animate/types.ts @@ -1,15 +1,36 @@ -/// - -import React from 'react'; -import { CommonProps } from '../util'; -import { TransitionProps } from 'react-transition-group/Transition'; -import { TransitionGroupProps } from 'react-transition-group/TransitionGroup'; +import React, { type ReactElement, type CSSProperties } from 'react'; +import type { TransitionProps } from 'react-transition-group/Transition'; +import type { TransitionGroupProps } from 'react-transition-group/TransitionGroup'; +import type { CommonProps } from '../util'; + +export interface AnimateChildProps { + names?: Partial<{ + appear: string; + appearActive: string; + enter: string; + enterActive: string; + leave: string; + leaveActive: string; + }>; + onAppear?: (node: HTMLElement) => void; + onAppeared?: (node: HTMLElement) => void; + onAppearing?: (node: HTMLElement) => void; + onEnter?: (node: HTMLElement) => void; + onEntering?: (node: HTMLElement) => void; + onEntered?: (node: HTMLElement) => void; + onExit?: (node: HTMLElement) => void; + onExiting?: (node: HTMLElement) => void; + onExited?: (node: HTMLElement) => void; +} +/** + * @api Animate + */ export interface AnimateProps extends React.HTMLAttributes, CommonProps { /** * 动画 className */ - animation?: string | any; + animation?: string | Partial>; /** * 子元素第一次挂载时是否执行动画 @@ -26,11 +47,6 @@ export interface AnimateProps extends React.HTMLAttributes, CommonP */ singleMode?: boolean; - /** - * 子元素 - */ - children?: React.ReactElement | Array; - /** * 执行第一次挂载动画前触发的回调函数 */ @@ -77,4 +93,61 @@ export interface AnimateProps extends React.HTMLAttributes, CommonP afterLeave?: TransitionProps['onExited']; } -export default class Animate extends React.Component {} +/** + * @api Animate.Expand + */ +export interface ExpandProps { + /** + * 动画 className + */ + animation?: string | Partial>; + /** + * 执行进场动画前触发的回调函数 + */ + beforeEnter?: (node: HTMLElement) => void; + /** + * 执行进场动画,添加 xxx-enter-active 类名后触发的回调函数 + */ + onEnter?: (node: HTMLElement) => void; + /** + * 执行完进场动画后触发的回调函数 + */ + afterEnter?: (node: HTMLElement) => void; + + /** + * 执行离场动画前触发的回调函数 + */ + beforeLeave?: TransitionProps['onExit']; + + /** + * 执行离场动画,添加 xxx-leave-active 类名后触发的回调函数 + */ + onLeave?: TransitionProps['onExiting']; + + /** + * 执行完离场动画后触发的回调函数 + */ + afterLeave?: TransitionProps['onExited']; +} + +/** + * @api Animate.OverlayAnimate + */ +export interface OverlayAnimateProps { + animation?: string | false | Record<'in' | 'out', string>; + visible?: boolean; + children?: ReactElement; + timeout?: number; + style?: CSSProperties; + mountOnEnter?: boolean; + unmountOnExit?: boolean; + onEnter?: (node: HTMLElement, isAppearing: boolean) => void; + onEntering?: (node: HTMLElement, isAppearing: boolean) => void; + onEntered?: (node: HTMLElement, isAppearing: boolean) => void; + onExit?: (node: HTMLElement) => void; + onExiting?: (node: HTMLElement) => void; + onExited?: (node: HTMLElement) => void; + appear?: boolean; + enter?: boolean; + exit?: boolean; +}