分析
过渡动画一般是两个 div
div1 稳定状态 => 退出状态
div2 进入状态 => 稳定状态
路由过渡动画
import { animated, useTransition } from '@react-spring/web'
import { Outlet, useLocation } from 'react-router-dom'
export const WelcomeLayout: React.FC = () => {
const location = useLocation() // 获取当前地址栏的信息
// location.pathname 获取当前的路径
// 假设路径为:
// location.pathname === /welcome/1 旧路由
// location.pathname === /welcome/2 新路由
const transitions = useTransition(location.pathname, {
// 进入状态
form: { transform: 'translateX(100%)' }, // 从屏幕的右边进入
// 稳定状态
enter: { transform: 'translateX(0%)' },
// 退出状态
leave: { transform: 'translateX(-100%)' },
config: { duration: 4000 }
})
// 当我们切换路由的时候,从旧路由到新路由,
// useTransition 作用就是你每次有一个路由我就给你创建一个过渡动画,
// 旧过渡和新过渡就放在 transitions 里面,
// 这个 transitions 数组里面我怎么去表示它每一个动画用什么元素?
return transitions((style, pathname) => {
// 用以下方法表示 transitions 数组里面的每一个动画用的元素
// transitions 会返回两个 div
// 当为旧路由的时候返回 animated.div 表示第一个
// 当为新路由的时候返回 animated.div 表示第二个
// 这里为何写一个 key,因为这是一个遍历
// 这个 style 就是不停变化的样式
// 这里的 style 从 100% => 0%
// 每次变就会把这个 style 赋值到这个 div 上面,
// 但这个 div 不能是普通的 div
// 所以要用 animated.div,key 表示标符, style 表示它的状态在不停的变化
return <animated.div key={pathname} style={style}>
// 里面就是正常的写代码
<div style={{ textAlign: 'center' }}>
<Outlet/>
</div>
</animated.div>
})
}
以上代码案例预览:
可以发现当我点击下一页的时候,页面提前更新了。
因为使用了 Outlet 表示子元素,当 path 改变的时候 这个 Outlet 就会改变。
使用 Outlet 会自己变,那么这里就不能使用这种写法,那么有什么办法可以记录每一次的 Outlet 然后把它放到这个位置上。
使用哈希表来做缓存,并且注意每次不要用最新的值,就可以解决路径切换的时候新旧同时显示的问题。
import { animated, useTransition } from '@react-spring/web'
import type { ReactNode } from 'react'
import { useLocation, useOutlet } from 'react-router-dom'
const map: Record<string, ReactNode> = {}
export const WelcomeLayout: React.FC = () => {
const location = useLocation() // 获取当前地址栏最新的信息
// location.pathname === /welcome/1 旧路由
// location.pathname === /welcome/2 新路由
// 拿到对应的 outlet, 也就是拿到当前显示的子组件
const outlet = useOutlet()
// 每进入一个 location 我就把它放到 map 里面
// welcome/1 我就将其对应的outlet存下来,welcome/2 我就将其对应的outlet存下来
map[location.pathname] = outlet // 将其存到 map 里面
const transitions = useTransition(location.pathname, {
// 进入状态
form: { transform: 'translateX(100%)' },
// 稳定状态
enter: { transform: 'translateX(0%)' },
leave: { transform: 'translateX(-100%)' },
config: { duration: 4000 }
})
return transitions((style, pathname) => {
return <animated.div key={pathname} style={style}>
<div style={{ textAlign: 'center' }}>
{/* 这里显示从 map 拿到的, location.pathname 永远都是最新的,pathname 是旧的 */}
{map[pathname]}
</div>
</animated.div>
})
}
以上代码案例预览:
最终代码
以上代码只要你的路径不是 welcome 开头, 它就会把这个 WelcomeLayout 组件摘掉,但并不代表 JS 内存中没有 map,这个 map 和这个组件的生命周期并不相同,map 只要一初始化就永远存在于 JS 的内存中,而 WelcomeLayout 只会在路径以 welcome 开头的时候才存在,所以就会导致内存泄漏,当我们的 WelcomeLayout 不存在的时候,它的几个 outlet 会存在 map 里面,我们需要 map 和 WelcomeLayout 生命周期同步,于是把 map 放到 WelcomeLayout 里面,并用 useRef 让其数据不会刷新
import { animated, useTransition } from '@react-spring/web'
import type { ReactNode } from 'react'
import { useRef } from 'react'
import { useLocation, useOutlet } from 'react-router-dom'
export const WelcomeLayout: React.FC = () => {
// 使用 useRef 不会刷新数据
const map = useRef<Record<string, ReactNode>>({})
const location = useLocation()
const outlet = useOutlet()
map.current[location.pathname] = outlet
const transitions = useTransition(location.pathname, {
form: {
transform: location.pathname === '/welcome/1'
? 'translateX(0%)'
: 'translateX(100%)'
},
enter: { transform: 'translateX(0%)' },
leave: { transform: 'translateX(-100%)' },
config: { duration: 4000 }
})
return transitions((style, pathname) =>
<animated.div key={pathname} style={style}>
{map.current[pathname]}
</animated.div>
)
}