80行代码实现Preact-Transition组件

80行代码实现Preact-Transition组件 Preact是3kb轻量化方案, 但是一些基础组件找起来比较困难, 用起来也比较别扭,其中一个就是Transition组件, 尝试过preact-transition-group, 但是直接npm安装使用就报错了...因为有个preact版本兼容问题
【80行代码实现Preact-Transition组件】transition.js

// 这一行代码没兼容, 新版preact children不一定是都是数组 359 -const child = children[0] +const child = children[0] || children 360return 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 ( {children} ) }

其实之前也写过弹窗过渡动画的组件, 也熟悉其生命周期, 所以简单梳理下状态变换流程就可以编写了
状态扭转也两个分支: 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行代码即可实现了
  1. 接口与CSSTransition类似
  2. 大概80行代码
  3. 无需设置duration, duration与transition-duration一样
  4. 无需繁琐设置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 = showconst { 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 ? ({children}) : undefined }export default Transition

使用Preact的同学可以尝试下, 简约框架搭配简约组件, 这里可以体验DEMO
https://github.com/deepkolos/pc-transition

    推荐阅读