我正在关注Chang Wang的教程,该教程使用HOC和ReactTransitionGroup
(Part 1 Part 2)进行可重用的React转换,以及Huan Ji的页面转换(Link)教程。
我面临的问题是React.cloneElement
似乎没有将更新的道具传递给它的一个子项,而其他子项确实正确地接收了更新的道具。
首先,一些代码:
TransitionContainer.jsTransitionContainer
是类似于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。但是,有时即使状态发生变化,道具也不会更新(请参阅下面的问题)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
本质上,optionSlice仅允许用户传递一些选项。
optionSlice.transitionBegin
和optionSlice.transitionEnd
只是在动画进行时执行的可选功能,如果合适的话。我目前没有为我的组件传递任何东西,但是我希望尽快将其变成一个库,所以我只是在介绍基础知识。为什么我仍要跟踪过渡状态?
根据输入的元素,退出的动画会发生变化,反之亦然。
例如,在上图中,当蓝色进入时,红色向右移动,而当蓝色退出时,红色向左移动。但是,当绿色进入时,红色向左移动,而绿色退出时,红色向右移动。要控制它,这就是为什么我需要知道当前过渡的状态的原因。
问题:
TransitionGroup
包含两个元素,一个进入,一个退出(由react-router控制)。它将一个名为transitionState
的道具传递给它的孩子。 Transition
HOC(TransitionGroup
的子代)在动画过程中分派某些还原动作。正在进入的Transition
组件按预期收到道具更改,但是正在退出的组件被冻结。它的道具不变。永远是一个退出的人,不会收到更新的道具。我尝试过切换包装的组件(退出和进入),但问题并非归因于包装的组件。
图片
屏幕过渡:
React DOM中的过渡
在这种情况下,现有组件Transition(Connect(Home)))没有收到更新的道具。
任何想法为什么会这样?在此先感谢您提供的所有帮助。
更新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
挂钩没有被调用。我在React.cloneElement(child, {...})
函数中记录了childFactory
,并且在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源代码之后,我意识到我将密钥放在错误的位置。现在一切都好。非常感谢你的帮忙!!
最佳答案
确定进入和离开孩子
想象一下渲染下面的示例JSX:
<TransitionGroup>
<div key="one">Foo</div>
<div key="two">Bar</div>
</TransitionGroup>
<TransitionGroup>
的children
道具将由以下元素组成:[
{ type: 'div', props: { key: 'one', children: 'Foo' }},
{ type: 'div', props: { key: 'two', children: 'Bar' }}
]
以上元素将存储为
state.children
。然后,将<TransitionGroup>
更新为:<TransitionGroup>
<div key="two">Bar</div>
<div key="three">Baz</div>
</TransitionGroup>
调用
componentWillReceiveProps
时,其nextProps.children
将为:[
{ type: 'div', props: { key: 'two', children: 'Bar' }},
{ type: 'div', props: { key: 'three', children: 'Baz' }}
]
比较
state.children
和nextProps.children
,我们可以确定:1。
{ type: 'div', props: { key: 'one', children: 'Foo' }}
离开2。
{ type: 'div', props: { key: 'three', children: 'Baz' }}
正在进入。在常规的React应用程序中,这意味着
<div>Foo</div>
将不再呈现,但<TransitionGroup>
的子级则不是这种情况。<TransitionGroup>
的工作方式那么
<TransitionGroup>
如何能够继续呈现props.children
中不再存在的组件?<TransitionGroup>
的作用是将children
数组保持在其状态。每当<TransitionGroup>
收到新的道具时,此数组将由merging当前的state.children
和nextProps.children
更新。 (使用初始constructor
道具在children
中创建初始数组)。现在,当
<TransitionGroup>
呈现时,它将呈现state.children
数组中的每个子级。渲染后,它将在任何进入或离开子级时调用performEnter
和performLeave
。反过来,这将执行组件的转换方法。在离开组件的
componentWillLeave
方法(如果有)执行完后,它将从state.children
数组中删除,使其不再呈现(假定它在离开时未重新进入)。将道具传递给离开的孩子?
现在的问题是,为什么不将更新的道具传递给剩余元素?好吧,它将如何获得道具?道具从父组件传递到子组件。如果查看上面的示例JSX,则可以看到left元素处于分离状态。它没有父级,并且仅由于
<TransitionGroup>
将其存储在其state
中而被呈现。当您尝试将状态通过
<TransitionGroup>
注入到React.cloneElement
的子级时,离开组件不是这些子级之一。好消息
您可以将
childFactory
道具传递给<TransitionGroup>
。默认的childFactory
仅返回子级,但是您可以查看<CSSTransitionGroup>
以获得更多的advanced child factory。您可以通过此儿童包装纸将正确的道具注入孩子(甚至是离开的孩子)中。
function childFactory(child) {
return React.cloneElement(child, {
transitionState,
dispatch
})
}
用法:
var ConnectedTransitionGroup = connect(
store => ({
transitionState: state.transitions
}),
dispatch => ({ dispatch })
)(TransitionGroup)
render() {
return (
<ConnectedTransitionGroup childFactory={childFactory}>
{children}
</ConnectedTransitionGroup>
)
}
最近,React Transition Group从主要的Repo中分离出来,您可以查看其源代码here。通俗易懂。