场景

基础组件未加出现隐藏显得太生硬,产品要求加些动效

实现

某一项组件的出现和消失,在 React 开发中,一般是通过状态来改变,移除组件往往需要一个中间状态去添加一个类名,所以抽出这部分逻辑作为中间状态。

添加三种状态

  • none 
  • appear(出现)
  • leave(消失)

通过一个 visible 状态控制组件的出现和消失:

const STATUS_NONE = 'none';
const STATUS_APPEAR = 'appear';
const STATUS_LEAVE = 'leave';

const defaultDelay = 200

function useAnimateWithVisible(props: {
    className: string;
  visible: boolean;
  delay?: number;
}) {
  const { className, visible, delay = defaultDelay } = props
  const [status, setStatus] = useState(STATUS_NONE)
  const [nextVisible, changeVisible] = useState(visible)
  
  const preVisibleRef = useRef({ visible })
  
    useEffect(() => {
    if (visible && !valueRef.current.visible) {
      changeVisible(true)
            setStatus(STATUS_APPEAR);
    } else if (!visible && valueRef.current.visible) {
      setStatus(STATUS_LEAVE);
      setTimeout(() => {
        changeVisible(false)
                setStatus(STATUS_NONE)
      }, delay)
    }
    valueRef.current.visible = visible
  }, [visible]);
  
  const classNameWrapper = useMemo(() => {
    return `${className}__${status}`;
  }, [className, status]);
  
  return {
    className: classNameWrapper,
    visible: nextVisible,
  }
}

通过一个延迟给要移除的组件一个临时的状态

使用如下:

function Test() {
  const [visible, changeVisible] = useState(false)
  const { visible: usedVisible, className } = useAnimateWithVisible(
    { visible, className: 'some-component-wrapper' )
  )
  
  return (
    <>
        <Button
            onClick={() => { changeVisible(!visible) }}
            >
        {!visible ? 'show' : 'hidden' }
            </Button>
        {usedVisible && (
            <SomeComp
            className={classnames({ [className]: true, 'some-component': true })}
              visible={usedVisible}
                />
          )}
    </>
  )
}

接着为你的组件对应的类名 xx__appear 、 xx__leave 添加样式即可。

还有一种是与列表中移除组件,这类组件是通过列表数据渲染出来的,这种可以通过添加一个实现

function useAnimate(props: {
  delay?: number;
  className: string
  
}) {
  const { className = '', delay = defaultDelay } = props;
  const [status, setStatus] = useState(STATUS_NONE);

  useEffect(() => {
    setStatus(STATUS_APPEAR);
  }, []);
  
  const onLeave = useCallback(() => {
    setStatus(STATUS_LEAVE);
    return new Promise(resolve => {
      setTimeout(() => {
        resolve();
      }, defaultDelay);
    });
  }, []);

  const classNameWrapper = useMemo(() => {
    return `${className}__${status}`;
  }, [className, status]);

  return {
        className: classNameWrapper,
    onLeave,
  };
}

使用:

function Item(props: {
  onDelete: (key: string | number) => void
    value: string | number
    label: string
}) {
  const { label, value, onDelete } = props
  const { onLeave, className } = useAnimate({ className: 'item-wrapper' })
  return (
    <div
        className={classnames({
            [className]: true,
            'item-wrapper': true
        })}
    >
        {label}
        <span
            onClick={() => {
          onLeave().then(() => {
            onDelete(value)
          })
        }}
            >
        X
            </span>
    </div>
  )
}

在移除之前添加一个延迟,在此过程中的类名更换为 xx__leave ,使用者再对此类名添加css动画样式,也就可以实现对应的动效。

总结

虽然这个hooks实现都很简单,但是给我们带来特别大的便捷。这就是React hooks的魅力所在吧


fall_wind
82 声望9 粉丝

九层之台 起于垒土