80行代码实现Preact-Transition组件

2020-05-08  本文已影响0人  DeepKolos

80行代码实现Preact-Transition组件

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行代码即可实现了

  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 = 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

https://github.com/deepkolos/pc-transition

上一篇 下一篇

猜你喜欢

热点阅读