我正在遵循Chang Wang的教程,使用HOC和ReactTransitionGroup
(第一部分
第二部分),结合Huan Ji的页面过渡教程(链接)进行可重复使用的React过渡动画制作。
我遇到的问题是,React.cloneElement
似乎没有将更新的props传递给其中一个子组件,而其他子组件则可以正确地接收到更新的props。
首先,一些代码:
TransitionContainer.js
TransitionContainer
是一个容器组件,类似于Huan Ji的教程中的App
。它向其子组件注入状态的一部分。
TransitionGroup
的子组件都是Transition
HOC的实例(下面有代码)。
import React from 'react';
import TransitionGroup from 'react-addons-transition-group';
import {connect} from 'react-redux';
class TransitionContainer extends React.Component{
render(){
console.log(this.props.transitionState);
console.log("transitionContainer");
return(
<div>
<TransitionGroup>
{
React.Children.map(this.props.children,
(child) => React.cloneElement(child, //These children are all instances of the Transition HOC
{ key: child.props.route.path + "//" + child.type.displayName,
dispatch: this.props.dispatch,
transitionState: this.props.transitionState
}
)
)
}
</TransitionGroup>
</div>
)
}
}
export default connect((state)=>({transitionState:state.transitions}),(dispatch)=>({dispatch:dispatch}))(TransitionContainer)
Transition.js
Transition
类似于Chang Wang的HOC。它需要一些选项,定义componentWillEnter
和componentWillLeave
钩子函数,并包装组件。TransitionContainer
(上面提到的)将props.transitionState
注入到这个HOC中。但是,有时候即使状态改变了,Props也不会更新(见下文的问题)。
import React from 'react';
import getDisplayName from 'react-display-name';
import merge from 'lodash/merge'
import classnames from 'classnames'
import * as actions from './actions/transitions'
export function transition(WrappedComponent, options) {
return class Transition extends React.Component {
static displayName = `Transition(${getDisplayName(WrappedComponent)})`;
constructor(props) {
super(props);
this.state = {
willLeave:false,
willEnter:false,
key: options.key
};
}
componentWillMount(){
this.props.dispatch(actions.registerComponent(this.state.key))
}
componentWillUnmount(){
this.props.dispatch(actions.destroyComponent(this.state.key))
}
resetState(){
this.setState(merge(this.state,{
willLeave: false,
willEnter: false
}));
}
doTransition(callback,optionSlice,willLeave,willEnter){
let {transitionState,dispatch} = this.props;
if(optionSlice.transitionBegin){
optionSlice.transitionBegin(transitionState,dispatch)
}
if(willLeave){
dispatch(actions.willLeave(this.state.key))
}
else if(willEnter){
dispatch(actions.willEnter(this.state.key))
}
this.setState(merge(this.state,{
willLeave: willLeave,
willEnter: willEnter
}));
setTimeout(()=>{
if(optionSlice.transitionComplete){
optionSlice.transitionEnd(transitionState,dispatch);
}
dispatch(actions.transitionComplete(this.state.key))
this.resetState();
callback();
},optionSlice.duration);
}
componentWillLeave(callback){
this.doTransition(callback,options.willLeave,true,false)
}
componentWillEnter(callback){
this.doTransition(callback,options.willEnter,false,true)
}
render() {
console.log(this.props.transitionState);
console.log(this.state.key);
var willEnterClasses = options.willEnter.classNames
var willLeaveClasses = options.willLeave.classNames
var classes = classnames(
{[willEnterClasses] : this.state.willEnter},
{[willLeaveClasses] : this.state.willLeave},
)
return <WrappedComponent animationClasses={classes} {...this.props}/>
}
}
}
选项
选项具有以下结构:
{
willEnter:{
classNames : "a b c",
duration: 1000,
transitionBegin: (state,dispatch) => {//some custom logic.},
transitionEnd: (state,dispatch) => {//some custom logic.}
// I currently am not passing anything here, but I hope to make this a library
// and am adding the feature to cover any use case that may require it.
},
willLeave:{
classNames : "a b c",
duration: 1000,
transitionBegin: (state,dispatch) => {//some custom logic.},
transitionEnd: (state,dispatch) => {//some custom logic.}
}
}
过渡生命周期(onEnter或onLeave)
- 组件挂载时,将调度
actions.registerComponent
componentWillMount
- 当调用组件的
componentWillLeave
或componentWillEnter
钩子时,相应选项的切片会发送到doTransition
- 在doTransition中:
- 调用用户提供的transitionBegin函数(
optionSlice.transitionBegin
) - 分派默认的
action.willLeave
或action.willEnter
- 设置超时以持续动画时间(
optionSlice.duration
)。 超时完成后:- 调用用户提供的transitionEnd函数(
optionSlice.transitionEnd
) - 分派默认的
actions.transitionComplete
- 调用用户提供的transitionEnd函数(
- 调用用户提供的transitionBegin函数(
实际上,optionSlice只允许用户传递一些选项。optionSlice.transitionBegin
和optionSlice.transitionEnd
只是可选的函数,在动画进行时执行,如果适用于某个用例。 目前我没有为我的组件传递任何参数,但我希望很快将其变成一个库,因此我只是做好准备。
为什么我要跟踪过渡状态?
根据进入的元素,退出动画会发生变化,反之亦然。
例如,在上面的图像中,当蓝色进入时,红色向右移动,而当蓝色退出时,红色向左移动。 但是当绿色进入时,红色向左移动,当绿色退出时,红色向右移动。 要控制这一点,我需要知道当前转换的状态。
问题:
TransitionGroup
包含两个元素,一个进入,一个退出(由react-router控制)。 它将一个名为transitionState
的prop传递给其子项。 Transition
HOC(TransitionGroup
的子项)通过整个动画过程中分派特定的redux操作。 输入的Transition
组件按预期接收到更改的props,但退出的组件被冻结。 它的props不会更改。
始终是正在退出的组件未接收到更新的props。 我已经尝试切换包装组件(退出和进入),问题不是由包装组件引起的。
图片
React DOM中的过渡效果
在这种情况下,正在退出的组件Transition(Connect(Home)))没有接收到更新的props。
有什么想法是这种情况吗?感谢您提前的所有帮助。
更新1:
import React from 'react';
import TransitionGroup from 'react-addons-transition-group';
import {connect} from 'react-redux';
var childFactoryMaker = (transitionState,dispatch) => (child) => {
console.log(child)
return React.cloneElement(child, {
key: (child.props.route.path + "//" + child.type.displayName),
transitionState: transitionState,
dispatch: dispatch
})
}
class TransitionContainer extends React.Component{
render(){
let{
transitionState,
dispatch,
children
} = this.props
return(
<div>
<TransitionGroup childFactory={childFactoryMaker(transitionState,dispatch)}>
{
children
}
</TransitionGroup>
</div>
)
}
}
export default connect((state)=>({transitionState:state.transitions}),(dispatch)=>({dispatch:dispatch}))(TransitionContainer)
我已经将我的TransitionContainer
更新为上述内容。现在,componentWillEnter
和componentWillLeave
钩子没有被调用。我在childFactory
函数中记录了React.cloneElement(child, {...})
,发现钩子(以及我定义的函数,如doTransition
)都存在于prototype
属性中。只有constructor
、componentWillMount
和componentWillUnmount
被调用。我怀疑这是因为key
属性没有通过React.cloneElement
注入。但是transitionState
和dispatch
已经被注入。
更新2:
import React from 'react';
import TransitionGroup from 'react-addons-transition-group';
import {connect} from 'react-redux';
var childFactoryMaker = (transitionState,dispatch) => (child) => {
console.log(React.cloneElement(child, {
transitionState: transitionState,
dispatch: dispatch
}));
return React.cloneElement(child, {
key: (child.props.route.path + "//" + child.type.displayName),
transitionState: transitionState,
dispatch: dispatch
})
}
class TransitionContainer extends React.Component{
render(){
let{
transitionState,
dispatch,
children
} = this.props
return(
<div>
<TransitionGroup childFactory={childFactoryMaker(transitionState,dispatch)}>
{
React.Children.map(this.props.children,
(child) => React.cloneElement(child, //These children are all instances of the Transition HOC
{ key: child.props.route.path + "//" + child.type.displayName}
)
)
}
</TransitionGroup>
</div>
)
}
}
export default connect((state)=>({transitionState:state.transitions}),(dispatch)=>({dispatch:dispatch}))(TransitionContainer)
在进一步检查TransitionGroup源代码后,我意识到我把key放错了地方。现在一切都好了。非常感谢您的帮助!!
Transition
HOC中访问状态,因此我自然会将其包装在connect()中。但是,问题在于TransitionGroup
将停止工作,因为连接的Transition HOC现在不实现componentWillLeave
和componentWillEnter
钩子。或者我没有看到什么?顺便说一句,谢谢@Paul S.提供的所有帮助。 - Jay S.{home: "willLeave", bom: "willEnter", otherPage1:"transitionComplete"}
我这样做的原因是可以根据所采取的路线动态更改过渡效果。@PaulS - Jay S.childFactory
属性传递给您的转换组,以将属性注入所有子元素。 - Paul ScomponentWillEnter
和componentWillLeave
钩子未被调用(动画未被执行)。请参见我的更新@Paul S。 - Jay S.