Preact是3kb轻量化方案, 但是一些基础组件找起来比较困难, 用起来也比较别扭,其中一个就是Transition组件, 尝试过**preact-transition-group**, 但是直接npm安装使用就报错了...因为有个preact版本兼容问题
transition.js
// 这一行代码没兼容, 新版preact children不一定是都是数组
359 - const child = children[0]
+ const child = children[0] || children
360 return cloneElement(child, childProps)
复制代码
但是看了transition.js (391行) + CSSTransition(181行), 感觉不需要这么多行代码即可实现所需的Transition组件
并且在使用过程中timeout的配置竟是比较迷惑... 所以就有了造论子的机会了
const PopupBaseLayout = ({
children,
canClass = '',
position = 'bottom',
}) => {
const { enter, onContentExited } = useContext(PopupContext)
const transProps = {
appear: true,
timeout: {
exit: 350,
enter: 1,
// appear需要配合enter timout为1ms, 弹窗动画才符合预期...
// 但是还是会偶现动画不触发的情况
},
classNames: {
enterActive: style.enter,
enterDone: style.enter,
exitActive: style.exit,
exitDone: style.exit,
},
onExited: onContentExited,
}
return (
<CSSTransition in={enter} {...transProps}>
<div className={[style.can, style[position], canClass].join(' ')}>
{children}
</div>
</CSSTransition>
)
}
复制代码
其实之前也写过弹窗过渡动画的组件, 也熟悉其生命周期, 所以简单梳理下状态变换流程就可以编写了
状态扭转也两个分支:
enter->entering->entered
exit->exiting->exited
而其中4个状态的流转可以由两个变量派生出来:show 和 switching
enter/entered/exit/exited
而entering则紧跟着enter, exiting则紧跟着exit
状态的对应关系:
show && !switching => entered
!show && !switching => exited
show && switching => enter
!show && switching => exit
剩下就是加上show变化对switching的变化即可
复制代码
最终写下来只需要80行代码即可实现了
- 接口与CSSTransition类似
- 无需设置duration, duration与transition-duration一样
- 无需繁琐设置classNames传递一个className即可, css里配合data-state来命中状态
import { useRef, useState, useEffect } from 'preact/hooks'
const nop = () => {}
// 接口尽量和React CSSTransition类似
function Transition({
appear = false,
in: show = false,
unmountOnExit = false,
children,
className, // 使用className+data-state组合, 方便复用
onEnter = nop,
onEntering = nop,
onEntered = nop,
onExit = nop,
onExiting = nop,
onExited = nop,
}) {
const canRef = useRef()
const { current: that } = useRef({ lastShow: show, switching: appear })
// Preact可以使用this, function TPL() {} 这种函数组件声明的时候, 箭头函数则不行
// 触发一次更新
const [renderId, setRenderId] = useState(0)
const [domReady, setDomReady] = useState(false)
// 当show变化的时候重置为switching为true
if (that.lastShow !== show) that.switching = true
that.lastShow = show
const { switching } = that
const renderDom = !(!show && !switching && unmountOnExit)
let state // 根据show 和 switching 派生出 state
if (show && !switching) state = 'entered'
if (!show && !switching) state = 'exited'
if (switching) {
that[show ? 'exiting' : 'entering'] = false
state = show ? 'enter' : 'exit'
domReady && show ? onEnter() : onExit()
domReady &&
requestAnimationFrame(() => {
that[show ? 'exiting' : 'entering'] = true
// 直接更新dom节点属性, 只是一帧的时间差
canRef.current &&
canRef.current.setAttribute(
'data-state',
show ? 'entering' : 'exiting',
)
show ? onEntering() : onExiting()
})
}
useEffect(() => {
!domReady && requestAnimationFrame(() => setDomReady(true))
})
const onTransitionEnd = e => {
if (!(e.target === canRef.current && switching)) return
that.switching = false
// 触发函数组件执行, 进而触发state的更新
setRenderId(renderId + 1)
show ? onEntered() : onExited()
}
return renderDom ? (
<div
ref={canRef}
className={className}
data-state={state}
onTransitionEnd={onTransitionEnd}
onwebkitTransitionEnd={onTransitionEnd}
>
{children}
</div>
) : undefined
}
export default Transition
复制代码
使用Preact的同学可以尝试下, 简约框架
搭配简约组件
, 这里可以体验DEMO
来源:oschina
链接:https://my.oschina.net/u/4312361/blog/4272236