第一个组件 Transition 详细文档
是一个很底层的组件,其他组件的实现都是基于它
用法:
'entering''entered''exiting''exited'
第一种:传一个回调函数
当in为true state 变为 entering, 500ms 后变为 entered
当in为false state 变为 exiting, 500ms 后变为 exited
同时可以监听每个阶段的回调函数
也可以启用或者禁用 enter={false} 入场动画
function App() {
const [inProp, setInProp] = useState(false);
return (
<div>
<Transition in={inProp} timeout={500}>
{state => (
// ...
)}
</Transition>
<button onClick={() => setInProp(true)}>
Click to Enter
</button>
</div>
);
}
内部结构有点复杂,判断情况也很多,这里仅仅给出核心代码
核心源码:
performEnter(mounting) { const { enter } = this.props const appearing = this.context ? this.context.isMounting : mounting const [maybeNode, maybeAppearing] = this.props.nodeRef ? [appearing] : [ReactDOM.findDOMNode(this), appearing] const timeouts = this.getTimeouts() const enterTimeout = appearing ? timeouts.appear : timeouts.enter // no enter animation skip right to ENTERED // if we are mounting and running this it means appear _must_ be set if ((!mounting && !enter) || config.disabled) { this.safeSetState({ status: ENTERED }, () => { this.props.onEntered(maybeNode) }) return } this.props.onEnter(maybeNode, maybeAppearing)// 先把state 设置为 entering, 然后 timeout(500ms)后添加 entered this.safeSetState({ status: ENTERING }, () => { this.props.onEntering(maybeNode, maybeAppearing) this.onTransitionEnd(enterTimeout, () => { this.safeSetState({ status: ENTERED }, () => { this.props.onEntered(maybeNode, maybeAppearing) }) }) })}
第二种作为底层组件, 略
CSSTransition
[详细文档](http://reactcommunity.org/react-transition-group/css-transition)
用法
function App() {
const [inProp, setInProp] = useState(false);
return (
<div>
<CSSTransition in={inProp} timeout={200} classNames="my-node">
<div>
{"I'll receive my-node-* classes"}
</div>
</CSSTransition>
<button type="button" onClick={() => setInProp(true)}>
Click to Enter
</button>
</div>
);
}
用法比 transition 更加舒服,更加像一个真正的,能用的组件
同样是通过 in 来控制入场和出场,增加了 classNames="my-node" 前缀
每个阶段会替子元素添加 这些css class
.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;
}
原理分析
CSSTransition.js
render() { const { classNames: _, ...props } = this.props; return ( <Transition {...props} onEnter={this.onEnter}// 重点 onEntered={this.onEntered}// 重点 onEntering={this.onEntering}// 重点 onExit={this.onExit}// 重点 onExiting={this.onExiting}// 重点 onExited={this.onExited}// 重点 /> );}
Transition.jsreturn ( // allows for nested Transitions <TransitionGroupContext.Provider value={null}> {typeof children === 'function' ? children(status, childProps) : React.cloneElement(React.Children.only(children), childProps)}// 重点 </TransitionGroupContext.Provider>)
通过监听底层组件 transition 的各个阶段回调函数,通过dom操作来添加class类
例如
onEnter = (maybeNode, maybeAppearing) => { const [node, appearing] = this.resolveArguments(maybeNode, maybeAppearing) this.removeClasses(node, 'exit'); this.addClass(node, appearing ? 'appear' : 'enter', 'base'); if (this.props.onEnter) { this.props.onEnter(maybeNode, maybeAppearing) }}
TransitionGroup
用法
<TransitionGroup className="todo-list"> {items.map(({ id, text }) => ( <CSSTransition key={id} timeout={500} classNames="item" > <ListGroup.Item> <Button className="remove-btn" variant="danger" size="sm" onClick={() => setItems(items => items.filter(item => item.id !== id) ) } > × </Button> {text} </ListGroup.Item> </CSSTransition> ))}</TransitionGroup>
.item-enter {.item-enter {opacity: 0;}.item-enter-active {opacity: 1;transition: opacity 500ms ease-in;}.item-exit {opacity: 1;}.item-exit-active {opacity: 0;transition: opacity 500ms ease-in;}
用法更加简单,只需要,给子节点包一个 CSSTransition 就可以
核心原理解析
比如删除一个子结点,会等出场动画结束后,才会删除这个元素
这里用到了 保存react结点的方法
唯一有点黑科技的地方是,比如删除一个子结点,会等出场动画结束后,才会删除这个元素
这是一个非常常见的需求,
TransitionGroup 的做法是
把 children 放在 state里面维护
上一次的 children 和当前children进行合并,找出当前删除的item项,用上一个补过来
pre current
item1 item1
item2 item2
item3 XXXX
例如 item3已经删除了,那么就先用 上一次的 item3补过来,然后给 item3 设置 in={false}
然后 放一个 handleExited,监听动画结束后删除该 item3, 结束
static getDerivedStateFromProps( nextProps, { children: prevChildMapping, handleExited, firstRender }) { return { children: firstRender ? getInitialChildMapping(nextProps, handleExited) : getNextChildMapping(nextProps, prevChildMapping, handleExited), firstRender: false, }}
export function getNextChildMapping(nextProps, prevChildMapping, onExited) { let nextChildMapping = getChildMapping(nextProps.children) let children = mergeChildMappings(prevChildMapping, nextChildMapping) Object.keys(children).forEach(key => { let child = children[key] if (!isValidElement(child)) return const hasPrev = key in prevChildMapping const hasNext = key in nextChildMapping const prevChild = prevChildMapping[key] const isLeaving = isValidElement(prevChild) && !prevChild.props.in // item is new (entering) if (hasNext && (!hasPrev || isLeaving)) { // console.log('entering', key)// 新入场的元素 children[key] = cloneElement(child, { onExited: onExited.bind(null, child), in: true, exit: getProp(child, 'exit', nextProps), enter: getProp(child, 'enter', nextProps), }) } else if (!hasNext && hasPrev && !isLeaving) { // item is old (exiting) // console.log('leaving', key)// 要删除的这个元素, 给它最后的挣扎,结束后,就会执行 onExited 函数 children[key] = cloneElement(child, { in: false }) } else if (hasNext && hasPrev && isValidElement(prevChild)) { // item hasn't changed transition states // copy over the last transition props; // console.log('unchanged', key) children[key] = cloneElement(child, { onExited: onExited.bind(null, child), in: prevChild.props.in, exit: getProp(child, 'exit', nextProps), enter: getProp(child, 'enter', nextProps), }) } }) return children}
删除元素后执行的方法,
// node is `undefined` when user provided `nodeRef` prophandleExited(child, node) { let currentChildMapping = getChildMapping(this.props.children) if (child.key in currentChildMapping) return if (child.props.onExited) { child.props.onExited(node) } if (this.mounted) { this.setState(state => { let children = { ...state.children } delete children[child.key] return { children } }) }}
未完待续