持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第24天,点击查看活动详情
这章讲的是react-transition-group这个动画库里的TransitionGroup组件的使用和实现。
TransitionGroup
文档:reactcommunity.org/react-trans…
管理列表 (<Transition> and <CSSTransition>)
与过渡组件一样,是一个状态机,随时间管理组件的安装和卸载
主要用来给一组元素添加进场和离场动画
原理:用新的children和老的进行对比,如果多了就添加进场动画,少了就添加离场动画,动画结束再对渲染的children进行真正的添加和删除
例子
import { useState } from "react";
import "./App.css";
import "./test.css";
import { CSSTransition, TransitionGroup } from "./react-transition-group";
let id = 4;
// 动画 样式
function App() {
const [items, setItems] = useState([
{ id: 1, text: "Buy eggs" },
{ id: 2, text: "Pay bills" },
{ id: 3, text: "Invite friends over" },
]);
return (
<div className="App">
<header className="App-header">
<TransitionGroup className="todo-list">
{items.map(({ id, text }) => (
<CSSTransition key={id} timeout={500} classNames="fade">
<p
key={id}
onClick={() =>
setItems((items) => items.filter((item) => item.id !== id))
}
>
{id}
</p>
</CSSTransition>
))}
</TransitionGroup>
<button
onClick={() => {
id++;
setItems((items) => [...items, { id: id, text: id }]);
}}
>
Add Item
</button>
</header>
</div>
);
}
export default App;
实现
初始化
和前面组件类似的流程, 在 getDerivedStateFromProps 初始化元素,为每个 子元素赋予 { in: true }然后渲染出来
import TransitionGroupContext from "./TransitionGroupContext";
import React from "react";
class TransitionGroup extends React.Component {
constructor(props) {
super(props);
this.state = {
childrenn: {},
};
}
static getDerivedStateFromProps(nextProps, state) {
return {
// 初始化子元素
childrenn: getInitialChildMapping(nextProps.children),
};
}
render() {
const { children } = this.state;
// {key1:CSSTransition in:true,...}
const component = Object.values(children);
return (
// [CSSTransition in:true,CSSTransition in:true,CSSTransition in:true,]
{component}
);
}
}
export default TransitionGroup;
function getChilrenMapping(children, mapFn = (c) => c) {
const result = {};
React.Children.forEach(children, (child) => {
result[child.key] = mapFn(child);
});
// {key1:CSSTransition in:true,...}
return result;
}
function getInitialChildMapping(children) {
return getChilrenMapping(children, (c) =>
React.cloneElement(c, { in: true })
);
}
新加组件动画准备
同之前类似,需要上下文
import TransitionGroupContext from "./TransitionGroupContext";
。。。
constructor(props) {
super(props);
this.state = {
childrenn: {},
};
mounted = false;
}
// 初始化的时候不需要动画,但是更新的时候需要,挂载完成后,this.mounted = true;
// 这样新追入的 in为true的子元素 就有动画了
componentDidMount() {
this.mounted = true;
}
。。。
<TransitionGroupContext.Provider value={{ mounted: this.mount }}>
{component}
</TransitionGroupContext.Provider>
添加和删除
对比新老子组件,删除则对组件进行 in:false的操作,动画时间后再在渲染组件里面去掉该元素。
class TransitionGroup extends React.Component {
this.state = {
children: {},
// 第一次 初始化
firstRender: true,
handleExited: this.handleExited,
};
handleExited = (key) => {
// children删除这个元素
this.setState((state) => {
const children = { ...state.children };
delete children[key];
return { children };
});
};
static getDerivedStateFromProps(
nextProps,
{ children, firstRender, handleExited }
) {
return {
children: firstRender
? getInitialChildMapping(nextProps.children)
: getNextChildrenMapping(nextProps, children, handleExited),
//初始化后第二次 改为 false,之后 走另一个方法getNextChildrenMapping
firstRender: false,
};
}
function getNextChildrenMapping(nextProps, prevChildrenMapping, handleExited) {
const result = {};
// 获取 新传入的 列表
const nextChildrenMapping = getChilrenMapping(nextProps.children);
// 新旧列表 取 最大
const mergeMapping =
Object.keys(prevChildrenMapping).length >
Object.keys(nextChildrenMapping).length
? prevChildrenMapping
: nextChildrenMapping;
// 依次 对比 最大列表的元素
Object.keys(mergeMapping).forEach((key) => {
const isNext = key in nextChildrenMapping;
const isPrev = key in prevChildrenMapping;
if (!isPrev && isNext) {
// 旧的不存在,新的存在,说明是新加入的元素 添加进来,由于 mounted是true,所以是会触发进场动画的
result[key] = React.cloneElement(nextChildrenMapping[key], { in: true });
} else if (isPrev && !isNext) {
// 旧的存在,新的不存在 说明是删除了这个元素
// 需要把元素的in改为false,并且在动画结束后 讲 渲染的数组里把 该元素去掉
result[key] = React.cloneElement(prevChildrenMapping[key], {
in: false,
onExited: () => {
// handleExited必须通过传参的方式,因为这样才能绑定这个组件,在这里this.setState是指向不到的
// 而且 要放state里,静态方法static获取不到this的东西
handleExited(key);
},
});
// 新老都有,说明无变化
} else if (isPrev && isNext) {
result[key] = React.cloneElement(nextChildrenMapping[key], { in: true });
}
});
return result;
}