React TransitionGroup 和 React.cloneElement 不发送更新的 props [英] React TransitionGroup and React.cloneElement do not send updated props

查看:16
本文介绍了React TransitionGroup 和 React.cloneElement 不发送更新的 props的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在学习 Chang Wang 的教程,使用 HOC 和 ReactTransitionGroup(

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

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

问题:

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

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

图片

屏幕过渡:

React DOM 中的转换

在这种情况下,退出组件 Transition(Connect(Home))) 没有收到更新的 props.

任何想法为什么会这样?在此先感谢您的帮助.

更新 1:

从'react'导入React;从react-addons-transition-group"导入 TransitionGroup;从'react-redux'导入{connect};var childFactoryMaker = (transitionState,dispatch) =>(孩子) =>{控制台日志(子)返回 React.cloneElement(child, {键:(child.props.route.path + "//" + child.type.displayName),过渡状态:过渡状态,派遣:派遣})}类 TransitionContainer 扩展了 React.Component{使成为(){让{过渡状态,派遣,孩子们} = this.props返回(<div><TransitionGroup childFactory={childFactoryMaker(transitionState,dispatch)}>{孩子们}</TransitionGroup>

)}}export default connect((state)=>({transitionState:state.transitions}),(dispatch)=>({dispatch:dispatch}))(TransitionContainer)

我已将我的 TransitionContainer 更新为上述内容.现在,不会调用 componentWillEntercomponentWillLeave 钩子.我在 childFactory 函数中记录了 React.cloneElement(child, {...}) 和钩子(以及我定义的函数,如 doTransition) 出现在 prototype 属性中.只调用constructorcomponentWillMountcomponentWillUnmount.我怀疑这是因为 key 道具没有通过 React.cloneElement 注入.transitionStatedispatch 正在被注入.

更新 2:

从'react'导入React;从react-addons-transition-group"导入 TransitionGroup;从'react-redux'导入{connect};var childFactoryMaker = (transitionState,dispatch) =>(孩子) =>{console.log(React.cloneElement(child, {过渡状态:过渡状态,派遣:派遣}));返回 React.cloneElement(child, {键:(child.props.route.path + "//" + child.type.displayName),过渡状态:过渡状态,派遣:派遣})}类 TransitionContainer 扩展了 React.Component{使成为(){让{过渡状态,派遣,孩子们} = this.props返回(<div><TransitionGroup childFactory={childFactoryMaker(transitionState,dispatch)}>{React.Children.map(this.props.children,(孩子) =>React.cloneElement(child,//这些孩子都是Transition HOC的实例{ key: child.props.route.path + "//" + child.type.displayName}))}</TransitionGroup>

)}}export default connect((state)=>({transitionState:state.transitions}),(dispatch)=>({dispatch:dispatch}))(TransitionContainer)

进一步查看TransitionGroup源码后,发现我把key放错了地方.现在一切都很好.非常感谢您的帮助!!

解决方案

决定进入和离开孩子

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

<div key="one">Foo</div><div key="two">Bar</div></TransitionGroup>

children 道具将由元素组成:

<预><代码>[{ type: 'div', props: { key: 'one', children: 'Foo' }},{ type: 'div', props: { key: 'two', children: 'Bar' }}]

以上元素将被存储为state.children.然后,我们将 更新为:

<div key="two">Bar</div><div key="三">巴兹</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 应用程序中,这意味着

Foo</div> 将不再被渲染,但对于 <;TransitionGroup>.

的工作原理

那么 究竟如何能够继续渲染 props.children 中不再存在的组件?

的作用是维护一个处于其状态的 children 数组.每当 <TransitionGroup> 接收到新的 props 时,这个数组会被 合并当前state.childrennextProps.children.(初始数组是在 constructor 中使用初始 children 属性创建的.

现在,当 呈现时,它会呈现 state.children 数组中的每个孩子.渲染完成后,它会在任何进入或离开的孩子上调用 performEnterperformLeave.这将依次执行组件的转换方法.

离开组件的 componentWillLeave 方法(如果有的话)执行完毕后,它会从 state.children 数组中移除自己,这样它就不再渲染(假设它在离开时没有重新进入).

给留守儿童传递道具?

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

当您尝试通过 React.cloneElement 的子项注入状态时,离开组件不是这些子项之一.

好消息

您可以将 childFactory 道具传递给您的 .默认的 childFactory 只返回 child,但您可以查看 以了解更多 高级子工厂.

您可以通过这个 child 包装器将正确的 props 注入到孩子们(甚至离开的孩子们)中.

function childFactory(child) {返回 React.cloneElement(child, {过渡状态,派遣})}

用法:

var ConnectedTransitionGroup = connect(商店 =>({过渡状态:state.transitions}),调度 =>({ 派遣 }))(过渡组)使成为() {返回 (<ConnectedTransitionGroup childFactory={childFactory}>{孩子们}</ConnectedTransitionGroup>)}

React Transition Group 最近从主要 React 存储库中分离出来,您可以查看其源代码 这里.通读起来非常简单.

I am following Chang Wang's tutorial for making reusable React transitions with HOCs and ReactTransitionGroup(Part 1 Part 2) in conjunction with Huan Ji's tutorial on page transitions (Link).

The problem I am facing is that React.cloneElementdoes not seem to be passing updated props into one of its children, while other children do properly receive updated props.

First, some code:

TransitionContainer.js

TransitionContainer is a container component that is akin to App in Huan Ji's tutorial. It injects a slice of the state to it's children.

The children of the TransitionGroup are all an instance of an HOC called Transition (code further down)

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 is akin to Chang Wang's HOC. It takes some options, defines the componentWillEnter + componentWillLeave hooks, and wraps a component. TransitionContainer (above) injects props.transitionState into this HOC. However, sometimes the props do not update even if state changes (see The Problem below)

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}/>
    }
  }
}

options

Options have the following structure:

{
  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.}

  }
}

Transition Lifecycle (onEnter or onLeave)

  • When the component is mounted, actions.registerComponent is dispatched
    • componentWillMount
  • When the component's componentWillLeave or componentWillEnter hook is called, the corresponding slice of the options is sent to doTransition
  • In doTransition:
    • The user supplied transitionBegin function is called (optionSlice.transitionBegin)
    • The default action.willLeave or action.willEnter is dispatched
    • A timeout is set for the duration of the animation (optionSlice.duration). When the timeout is complete:
      • The user supplied transitionEnd function is called (optionSlice.transitionEnd)
      • The default actions.transitionComplete is dispatched

Essentially, optionSlice just allows the user to pass in some options. optionSlice.transitionBegin and optionSlice.transitionEnd are just optional functions that are executed while the animation is going, if that suits a use case. I'm not passing anything in currently for my components, but I hope to make this a library soon, so I'm just covering my bases.

Why Am I tracking transition states anyway?

Depending on the element that is entering, the exiting animation changes, and vice versa.

For example, in the image above, when the blue enters, red moves right, and when the blue exits, red moves left. However when the green enters, red moves left and when the green exits, red moves right. To control this is why I need to know the state of current transitions.

The Problem:

The TransitionGroup contains two elements, one entering, one exiting (controlled by react-router). It passes a prop called transitionState to its children. The Transition HOC (children of TransitionGroup) dispatches certain redux actions through the course of an animation. The Transition component that is entering receives the props change as expected, but the component that is exiting is frozen. It's props do not change.

It is always the one that is exiting that does not receive updated props. I have tried switching the wrapped components (exiting and entering), and the issues is not due to the wrapped components.

Images

On-Screen Transition:

Transition in React DOM

The exiting component Transition(Connect(Home))), in this case, is not receiving updated props.

Any ideas why this is the case? Thanks in advance for all the help.

Update 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)

I've updated my TransitionContainer to the above. Now, the componentWillEnter and componentWillLeave hooks are not being called. I logged the React.cloneElement(child, {...}) in the childFactory function, and the hooks (as well as my defined functions like doTransition) are present in the prototype attribute. Only constructor, componentWillMount and componentWillUnmount are called. I suspect this is because the key prop is not being injected through React.cloneElement. transitionState and dispatch are being injected though.

Update 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)

After further inspection of the TransitionGroup source, I realized that I put the key in the wrong place. All is well now. Thanks so much for the help!!

解决方案

Determining Entering and Leaving Children

Imagine rendering the sample JSX below:

<TransitionGroup>
  <div key="one">Foo</div>
  <div key="two">Bar</div>
</TransitionGroup>

The <TransitionGroup>'s children prop would be made up of the elements:

[
  { type: 'div', props: { key: 'one', children: 'Foo' }},
  { type: 'div', props: { key: 'two', children: 'Bar' }}
]

The above elements will be stored as state.children. Then, we update the <TransitionGroup> to:

<TransitionGroup>
  <div key="two">Bar</div>
  <div key="three">Baz</div>
</TransitionGroup>

When componentWillReceiveProps is called, its nextProps.children will be:

[
  { type: 'div', props: { key: 'two', children: 'Bar' }},
  { type: 'div', props: { key: 'three', children: 'Baz' }}
]

Comparing state.children and nextProps.children, we can determine that:

1. { type: 'div', props: { key: 'one', children: 'Foo' }} is leaving

2. { type: 'div', props: { key: 'three', children: 'Baz' }} is entering.

In a regular React application, this means that <div>Foo</div> would no longer be rendered, but that is not the case for the children of a <TransitionGroup>.

How <TransitionGroup> Works

So how exactly is <TransitionGroup> able to continue rendering components that no longer exist in props.children?

What <TransitionGroup> does is that it maintains a children array in its state. Whenever the <TransitionGroup> receives new props, this array is updated by merging the current state.children and the nextProps.children. (The initial array is created in the constructor using the initial children prop).

Now, when the <TransitionGroup> renders, it renders every child in the state.children array. After it has rendered, it calls performEnter and performLeave on any entering or leaving children. This in turn will perform the transitioning methods of the components.

After a leaving component's componentWillLeave method (if it has one) has finished executing, it will remove itself from the state.children array so that it no longer renders (assuming it didn't re-enter while it was leaving).

Passing Props to Leaving Children?

Now the question is, why aren't updated props being passed to the leaving element? Well, how would it receive props? Props are passed from a parent component to a child component. If you look at the example JSX above, you can see that the leaving element is in a detached state. It has no parent and it is only rendered because the <TransitionGroup> is storing it in its state.

When you are attempting to inject the state to the children of your <TransitionGroup> through React.cloneElement, the leaving component is not one of those children.

The Good News

You can pass a childFactory prop to your <TransitionGroup>. The default childFactory just returns the child, but you can take a look at the <CSSTransitionGroup> for a more advanced child factory.

You can inject the correct props into the children (even the leaving ones) through this child wrapper.

function childFactory(child) {
  return React.cloneElement(child, {
    transitionState,
    dispatch
  })
}

Usage:

var ConnectedTransitionGroup = connect(
  store => ({
   transitionState: state.transitions
  }),
  dispatch => ({ dispatch })
)(TransitionGroup)

render() {
  return (
    <ConnectedTransitionGroup childFactory={childFactory}>
      {children}
    </ConnectedTransitionGroup>
  )
}

React Transition Group was somewhat recently split out of the main React repo and you can view its source code here. It is pretty straightforward to read through.

这篇关于React TransitionGroup 和 React.cloneElement 不发送更新的 props的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆