我正在关注Chang Wang的教程,该教程使用HOC和ReactTransitionGroupPart 1 Part 2)进行可重用的React转换,以及Huan Ji的页面转换(Link)教程。

我面临的问题是React.cloneElement似乎没有将更新的道具传递给它的一个子项,而其他子项确实正确地接收了更新的道具。

首先,一些代码:

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。但是,有时即使状态发生变化,道具也不会更新(请参阅下面的问题)

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

调用组件的componentWillLeavecomponentWillEnter挂钩时,相应的选项片将发送到doTransition
在doTransition中:


用户提供的transitionBegin函数称为(optionSlice.transitionBegin
调度默认的action.willLeaveaction.willEnter
在动画持续时间内设置了超时(optionSlice.duration)。超时完成后:


用户提供的transitionEnd函数称为(optionSlice.transitionEnd
调度默认的actions.transitionComplete




本质上,optionSlice仅允许用户传递一些选项。 optionSlice.transitionBeginoptionSlice.transitionEnd只是在动画进行时执行的可选功能,如果合适的话。我目前没有为我的组件传递任何东西,但是我希望尽快将其变成一个库,所以我只是在介绍基础知识。

为什么我仍要跟踪过渡状态?

reactjs - React TransitionGroup和React.cloneElement不发送更新的 Prop-LMLPHP

根据输入的元素,退出的动画会发生变化,反之亦然。

例如,在上图中,当蓝色进入时,红色向右移动,而当蓝色退出时,红色向左移动。但是,当绿色进入时,红色向左移动,而绿色退出时,红色向右移动。要控制它,这就是为什么我需要知道当前过渡的状态的原因。

问题:

TransitionGroup包含两个元素,一个进入,一个退出(由react-router控制)。它将一个名为transitionState的道具传递给它的孩子。 Transition HOC(TransitionGroup的子代)在动画过程中分派某些还原动作。正在进入的Transition组件按预期收到道具更改,但是正在退出的组件被冻结。它的道具不变。

永远是一个退出的人,不会收到更新的道具。我尝试过切换包装的组件(退出和进入),但问题并非归因于包装的组件。

图片

屏幕过渡:
reactjs - React TransitionGroup和React.cloneElement不发送更新的 Prop-LMLPHP

React DOM中的过渡

reactjs - React TransitionGroup和React.cloneElement不发送更新的 Prop-LMLPHP

在这种情况下,现有组件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更新为上述内容。现在,componentWillEntercomponentWillLeave挂钩没有被调用。我在React.cloneElement(child, {...})函数中记录了childFactory,并且在doTransition属性中存在挂钩(以及我定义的函数,例如prototype)。仅调用constructorcomponentWillMountcomponentWillUnmount。我怀疑这是因为key道具没有通过React.cloneElement注入。但是,正在注入transitionStatedispatch

更新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.childrennextProps.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.childrennextProps.children更新。 (使用初始constructor道具在children中创建初始数组)。

现在,当<TransitionGroup>呈现时,它将呈现state.children数组中的每个子级。渲染后,它将在任何进入或离开子级时调用performEnterperformLeave。反过来,这将执行组件的转换方法。

在离开组件的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。通俗易懂。

10-08 01:29