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

标签 reactjs animation redux react-router reactcsstransitiongroup

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

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

首先,一些代码:

TransitionContainer.js
TransitionContainer是一个类似于 App 的容器组件在欢姬的教程中。它向它的 child 注入(inject)了状态的一部分。
TransitionGroup的 children 都是名为 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钩子(Hook),并包装一个组件。 TransitionContainer (上)注入(inject)props.transitionState进入这个 HOC。但是,有时即使状态发生变化, Prop 也不会更新(请参阅下面的 问题 )
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被 dispatch
  • componentWillMount
  • 当组件的componentWillLeavecomponentWillEnter钩子(Hook)被调用,相应的选项切片被发送到doTransition
  • 在 doTransition 中:
  • 调用用户提供的 transitionBegin 函数( optionSlice.transitionBegin )
  • 默认action.willLeaveaction.willEnter已发货
  • 为动画的持续时间设置超时 ( optionSlice.duration )。超时完成后:
  • 调用用户提供的 transitionEnd 函数( optionSlice.transitionEnd )
  • 默认actions.transitionComplete已发货

  • 本质上,optionSlice 只是允许用户传入一些选项。 optionSlice.transitionBeginoptionSlice.transitionEnd只是在动画进行时执行的可选函数,如果这适合用例的话。我目前没有为我的组件传递任何东西,但我希望很快将它变成一个库,所以我只是覆盖了我的基础。

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

    enter image description here

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

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

    问题:
    TransitionGroup包含两个元素,一个进入,一个退出(由 react-router 控制)。它传递了一个名为 transitionState 的 Prop 给它的 child 。 Transition HOC(TransitionGroup 的 child )在动画过程中分派(dispatch)某些 redux Action 。 Transition正在进入的组件按预期接收 Prop 更改,但正在退出的组件被卡住。它的 Prop 不会改变。

    它总是退出的那个没有收到更新的 Prop 。我试过切换被包裹的组件(退出和进入),问题不是因为被包裹的组件。

    图片

    屏幕转换:
    Transition

    React DOM 中的转换

    Transition2

    在这种情况下,退出组件 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以上。现在,componentWillEntercomponentWillLeave钩子(Hook)没有被调用。我登录了 React.cloneElement(child, {...})childFactory函数和钩子(Hook)(以及我定义的函数,如 doTransition )存在于 prototype 中属性。只有 constructor , componentWillMountcomponentWillUnmount叫做。我怀疑这是因为 key Prop 未通过 React.cloneElement 注入(inject). transitionStatedispatch虽然正在注入(inject)。

    更新 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 放错了地方。现在一切都很好。非常感谢你的帮忙!!

    最佳答案

    确定进入和离开 child

    想象一下渲染下面的示例 JSX:

    <TransitionGroup>
      <div key="one">Foo</div>
      <div key="two">Bar</div>
    </TransitionGroup>
    
    <TransitionGroup>children prop 将由以下元素组成:
    [
      { 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> 的 child 来说情况并非如此。 .

    如何<TransitionGroup>作品

    那么 <TransitionGroup> 究竟是怎样的?能够继续渲染 props.children 中不再存在的组件?

    什么 <TransitionGroup>确实是它维护了一个 children阵列处于其状态。每当<TransitionGroup>接收新 Prop ,此数组由 merging 更新当前state.childrennextProps.children . (初始数组是在 constructor 中使用初始 children Prop 创建的)。

    现在,当 <TransitionGroup>渲染,它渲染 state.children 中的每个 child 大批。渲染完成后,它会调用 performEnterperformLeave任何进入或离开的 child 。这将依次执行组件的转换方法。

    离开组件后的componentWillLeave方法(如果有)已完成执行,它将自己从 state.children 中删除数组,以便它不再呈现(假设它在离开时没有重新进入)。

    传递 Prop 给 child ?

    现在的问题是,为什么没有更新的 Prop 被传递给离开元素?那么,它会如何获得 Prop 呢? Prop 从父组件传递给子组件。如果您查看上面的示例 JSX,您可以看到离开元素处于分离状态。它没有父级,它只是因为 <TransitionGroup> 而被渲染。将其存储在其 state 中.

    当您尝试将状态注入(inject) <TransitionGroup> 的 child 时通过 React.cloneElement ,离开组件不是这些 child 之一。

    好消息

    您可以通过 childFactory支持您的 <TransitionGroup> .默认childFactory只是返回 child ,但你可以看看<CSSTransitionGroup>更多 advanced child factory .

    您可以通过这个 child 包装器将正确的 props 注入(inject)到 children (甚至是离开的 children )中。
    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 最近从主要 React 存储库中分离出来,您可以查看其源代码 here .通读一遍非常简单。

    关于reactjs - React TransitionGroup 和 React.cloneElement 不发送更新的 props,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41404232/

    相关文章:

    javascript - 如何根据 JSON 文件中的 bool 值自动更改文本

    javascript - 如何使用 react 中对象数组的时间戳进行迭代

    javascript - 在使用 redux 的 react 组件中访问 this.props 时未定义

    arrays - react Redux : View not getting updated even though mapStateToProps is

    reactjs - React/Next.js推荐的后端API URL等常量设置方式

    javascript - 填写表单后动态向对象添加新键

    ios - 如何通过 react native 发布 ios 应用程序

    html - CSS 滚动/翻译一个元素,如果它溢出

    android动画未在onAnimationEnd完成

    c# - WPF 中的图标混合动画