15

我正在关注 Chang Wang 的使用 HOC 制作可重用 React 转换的教程和ReactTransitionGroup第 1 部分第 2 部分)以及 Huan Ji 的页面转换教程(链接)。

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

首先,一些代码:

过渡容器.js

TransitionContainer是类似于App环记教程中的容器组件。它将状态的一部分注入它的孩子。

的孩子TransitionGroup都是一个 HOC 的实例,称为Transition(代码进一步向下)

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)

过渡.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.willLeaveoraction.willEnter被调度
    • 为动画的持续时间设置了超时 ( optionSlice.duration)。超时完成后:
      • 调用用户提供的 transitionEnd 函数 ( optionSlice.transitionEnd)
      • 默认actions.transitionComplete发送

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

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

在此处输入图像描述

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

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

问题:

包含两个元素,TransitionGroup一个进入,一个退出(由 react-router 控制)。它传递一个调用transitionState它的孩子的道具。HOC(的Transition子级TransitionGroup)在动画过程中调度某些 redux 动作。正在进入的Transition组件按预期接收 props 更改,但正在退出的组件被冻结。它的道具不会改变。

退出的总是没有收到更新的道具。我尝试过切换包装组件(退出和进入),问题不是由于包装组件造成的。

图片

屏幕过渡: 过渡

React DOM 中的转换

过渡2

在这种情况下,现有组件 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到上述内容。现在,componentWillEnterandcomponentWillLeave钩子没有被调用。我React.cloneElement(child, {...})childFactory函数中记录了,并且钩子(以及我定义的函数,如doTransition)存在于prototype属性中。只有constructor,componentWillMountcomponentWillUnmount被调用。我怀疑这是因为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 源代码后,我意识到我把钥匙放错了地方。现在一切都很好。非常感谢你的帮忙!!

4

1 回答 1

17

确定进入和离开儿童

想象一下渲染下面的示例 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>将不再呈现,但对于 a 的子级则不是这样<TransitionGroup>

<TransitionGroup>工作原理

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

它的作用是在其状态下<TransitionGroup>维护一个children数组。每当<TransitionGroup>接收到新的 props 时,这个数组就会通过合并currentstate.childrennextProps.children. (初始数组是在constructor使用初始children道具中创建的)。

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

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

向留守儿童传递道具?

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

当您尝试将状态注入<TransitionGroup>through的子代时React.cloneElement,离开的组件不是这些子代之一。

好消息

您可以将childFactory道具传递给您的<TransitionGroup>. 默认childFactory只返回孩子,但你可以看看<CSSTransitionGroup>for a more 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 过渡组最近从主要的 React 存储库中分离出来,您可以在此处查看其源代码。通读非常简单。

于 2016-12-31T06:03:28.447 回答