前言
React Transition Group 是 React 官方提供的一个工具库,用于管理和实现 React 元素在不同时机(如 进入、退出 等)应用的「过渡动画」。
例举一个常见场景:我们要实现一个 Modal 弹框,在 open state 为 true 时打开弹框,弹框会自带一个 mask 底部蒙层,借助 Transition
可以轻松实现底部蒙层淡入淡出的过渡效果。
但要注意:React Transition Group 不能算作是动画库,因为它只提供触发机制(不同阶段的状态)、具体动画需要使用者自行编写。
React Transition Group 提供了一组可供在不同场景使用的过渡组件:Transition
,CSSTransition
,SwitchTransition
,TransitionGroup
。
其中比较常用的是:Transition
,CSSTransition
。
一、Transition
Transition
作为基础组件的存在,用于实现一个 UI 组件从 进入状态 到 退出状态 的转换。它不会改变呈现组件的行为,只跟踪组件的"进入"和"退出"状态,赋予这些状态意义和效果取决于你。
我们来看一个基础使用示例:元素进入和离开时,使用透明度实现淡入淡出过渡效果:
jsx
import { useState } from "react";
import { Transition } from "react-transition-group";
const duration = 300;
const defaultStyle = {
transition: `opacity ${duration}ms ease-in-out`,
opacity: 0,
};
const transitionStyles = {
entering: { opacity: 1 },
entered: { opacity: 1 },
exiting: { opacity: 0 },
exited: { opacity: 0 },
};
function App() {
const [inProp, setInProp] = useState(false);
return (
<div>
<Transition in={inProp} timeout={duration}>
{(state) => (
<div
style={{
...defaultStyle,
...transitionStyles[state],
}}
>
I'm a fade Transition!
</div>
)}
</Transition>
<button onClick={() => setInProp(!inProp)}>Click to Enter</button>
</div>
);
}
export default App;
Transition
作为一个 React 包裹组件使用,它包含三个属性信息:
in
:boolean 值,表示是否进入,基于此变量来处理 进入/退出;timeout
:number 值,控制动画过渡所需的时长;children
:一个函数,返回要应用过渡机制的内容元素;
在 children
函数中,通过参数 state
可以获取每个过渡阶段的状态,并基于此状态来为内容元素赋予效果。state 主要处于四个状态:'entering'、'entered'、'exiting'、'exited'
。
二、CSSTransition
CSSTransition
在 Transition
基础上,增加了将 进入/退出 状态以 className
方式设置在内容元素上,此外还扩展了「入场 appear」状态。
一个简单的 透明度过渡 淡入淡出 示例如下:
jsx
import { useState, useRef } from "react";
import { CSSTransition } from "react-transition-group";
import "./App.css";
function App() {
const [inProp, setInProp] = useState(false);
const nodeRef = useRef(null);
return (
<div>
<CSSTransition nodeRef={nodeRef} in={inProp} timeout={200} classNames="my-node" unmountOnExit>
<div ref={nodeRef}>
{"I'll receive my-node-* classes"}
</div>
</CSSTransition>
<button type="button" onClick={() => setInProp(!inProp)}>
Click to Enter
</button>
</div>
);
}
export default App;
// css
.my-node-enter {
opacity: 0;
}
.my-node-enter-active {
opacity: 1;
transition: opacity 200ms;
}
.my-node-exit {
opacity: 1;
}
.my-node-exit-active {
opacity: 0;
transition: opacity 200ms;
}
在 Transition
基础上增加了 classNames
属性,将过渡状态以 className
方式设置到 nodeRef
节点上。
每个阶段下 className
后缀如下:
perl
my-node-appear, my-node-appear-active, my-node-appear-done
my-node-enter, my-node-enter-active, my-node-enter-done
my-node-exit, my-node-exit-active, my-node-exit-done
三、实现一个 useTransition
我们从一个 Modal 交互功能
说起。
实现一个 Modal 弹框功能,并期望 mask 蒙层元素能够借助 CSS opacity + transition
来实现淡入淡出过渡效果,我们写出的初版代码如下:
jsx
import { useState } from "react";
const styles = {
root: {
position: "fixed",
inset: 0,
zIndex: 1000,
},
mask: {
position: "fixed",
inset: 0,
zIndex: -1,
backgroundColor: "rgba(0, 0, 0, 0.5)",
opacity: 0,
transition: "opacity .3s",
}
}
function App() {
const [open, setOpen] = useState(false);
return (
<div>
{open ? (
<div className="modal" style={styles.root}>
<div style={{ ...styles.mask, opacity: open ? 1 : 0 }} onClick={() => setOpen(false)}></div>
<div>Modal 内容</div>
</div>
) : null}
<button onClick={() => setOpen(true)}>打开 Modal</button>
</div>
)
}
export default App;
现在,通过打开关闭 Modal 你会发现 mask 蒙层并没有 opacity 淡入淡出
的过渡效果。其实很好理解原因:在 挂载/销毁 DOM 时没有留出执行过渡的时间。
所以我们要做的是:挂载 DOM 后给予时间去执行 淡入 过渡动画,销毁 DOM 前要留出时间先去执行 淡出 过渡动画。
这其实就是 Transition
核心逻辑,能够控制执行阶段。
下面我们通过封装一个 hook:useTransition
来实现 Transition
能力:
jsx
import { useState, useRef, useEffect } from "react";
type TStageType = "enter" | "leave";
type TStageDurations = Record<TStageType, number>;
const useTransition = (
open: boolean,
durations: TStageDurations = { enter: 0, leave: 0 }
) => {
// 在外部 open state 基础上,使用新的 show state 来控制 Transition 时机
const [show, setShow] = useState(open);
// Transition status
const [stage, setStage] = useState<TStageType | "">("");
const timerRef = useRef<TStageDurations>({
enter: undefined,
leave: undefined,
});
useEffect(() => {
if (open) {
// 1. 先挂载 DOM,借助计时器延迟使用 进入 动画
setShow(true);
timerRef.current.enter = window.setTimeout(() => {
setStage("enter");
}, durations.enter);
} else {
// 2. 先执行 退出 动画,再执行销毁 DOM
setStage("leave");
timerRef.current.leave = window.setTimeout(() => {
setShow(false);
}, durations.leave);
}
return () => {
window.clearTimeout(timerRef.current.enter);
window.clearTimeout(timerRef.current.leave);
};
}, [open, durations]);
return { show, stage };
};
export default useTransition;
使用方式如下:
jsx
function App() {
const [open, setOpen] = useState(false);
const { show, stage } = useTransition(open, { enter: 0, leave: 300 });
return (
<div>
{show ? (
<div className="modal" style={styles.root}>
<div style={{ ...styles.mask, opacity: stage === "enter" ? 1 : 0, }} onClick={() => setOpen(false)}></div>
<div>Modal 内容</div>
</div>
) : null}
<button onClick={() => setOpen(true)}>打开 Modal</button>
</div>
)
}
最后
感谢阅读,如有不足之处,欢迎指出。