React TransitionGroup和React.cloneElement不发送更新的道具 [英] React TransitionGroup and React.cloneElement do not send updated props
问题描述
我正在关注Chang Wang的教程,以使用HOC和 ReactTransitionGroup
(
根据输入的元素,退出的动画会发生变化,反之亦然。
例如,在上图中,当蓝色进入时,红色向右移动,而蓝色退出时,红色向左移动。但是,当绿色进入时,红色向左移动,而绿色退出时,红色向右移动。要控制它,这就是为什么我需要知道当前过渡的状态。
问题:
TransitionGroup
包含两个元素,一个进入,一个退出(由react-router控制)。它通过一个名为 transitionState
的道具给孩子。 Transition
HOC( TransitionGroup
的子代)在动画过程中分派某些还原动作。进入的 Transition
组件按预期收到道具更改,但是退出的组件被冻结。它的道具不会改变。
总是退出的那个不会收到更新的道具。我尝试过切换包装的组件(退出和进入),但问题不是由于包装的组件引起的。
图片
屏幕过渡:
React DOM中的转换
在这种情况下,退出组件Transition(Connect(Home)))没有收到更新的道具。
有什么想法?预先感谢所有帮助。
更新1:
import从'react'进行React;
从 react-addons-transition-group导入TransitionGroup;
从 react-redux导入{connect};
var childFactoryMaker =(transitionState,dispatch)=> (孩子)=> {
console.log(child)
return React.cloneElement(child,{
键:(child.props.route.path + // + child.type.displayName),
transitionState:transitionState,
dispatch:调度
}}
}
类TransitionContainer扩展了React.Component {
render(){
let {
transitionState,
派遣,
儿童
} = this.props
return(
÷ div>
< ; TransitionGroup childFactory = {childFactoryMaker(transitionState,dispatch)}>
{
儿童
}
< / TransitionGroup>
< / div>
)
}
}
导出默认值connect((state)=>({{transitionState:state.transitions}),(dispatch)=>({{dispatch:分派}))(TransitionContainer)
我已经更新了 TransitionContainer
到上面。现在,没有调用 componentWillEnter
和 componentWillLeave
钩子。我在 childFactory
函数中记录了 React.cloneElement(child,{...})
,然后将钩子(以及我定义的函数(如 doTransition
)都位于 prototype
属性中。仅调用构造函数
, componentWillMount
和 componentWillUnmount
。我怀疑这是因为 key
道具没有通过 React.cloneElement
注入。不过,正在注入 transitionState
和 dispatch
。
更新2:
导入从反应做出反应;
从 react-addons-transition-group导入TransitionGroup;
从 react-redux导入{connect};
var childFactoryMaker =(transitionState,dispatch)=> (孩子)=> {
console.log(React.cloneElement(child,{
transitionState:transitionState,
dispatch:dispatch
}));
返回React.cloneElement(child,{
键:(child.props.route.path + // + child.type.displayName),
transitionState:transitionState,
dispatch:dispatch
}}
}
类TransitionContainer扩展了React.Component {
render(){
let {
transitionState,
调度,
个孩子
} = this.props
return(
< div>
< TransitionGroup childFactory = {childFactoryMaker(transitionState,dispatch) }>
{
React.Children.map(this.props.children,
(child)=> React.cloneElement(child,//这些孩子都是Transition的实例HOC
{键:child.props.route.path + // + child.type.displayName}
)
)
}
< / TransitionGroup> ;
< / div>
)
}
}
export default connect((state)=>({{transitionState:state.trans itions}),(dispatch)=>({{dispatch:dispatch}))(TransitionContainer)
在进一步检查TransitionGroup源代码之后,我意识到我将密钥放在错误的位置。现在一切都好。非常感谢您的帮助!
确定进入和离开孩子的时间
想象一下在下面呈现示例JSX:
< TransitionGroup>
< div key = one> Foo< / div>
< div key = two> Bar< / div>
< / TransitionGroup>
< TransitionGroup>
的儿童
道具将由以下元素组成:
[
{类型:'div',道具:{键:'one',孩子:'Foo'}},
{类型:'div',道具:{键:'two',孩子:'Bar' }}
]
以上元素将存储为 state .children
。然后,将< TransitionGroup>
更新为:
< < TransitionGroup>
< div key = two> Bar< / div>
< div key = three> Baz< / div>
< / TransitionGroup>
调用 componentWillReceiveProps
时,其 nextProps.children
将是:
[
{类型:' div,道具:{键:两个,孩子:酒吧}},
{类型: div,道具:{键:三个,孩子:巴兹}}
]
比较 state.children
和 nextProps.children
,我们可以确定:
1。
{类型:'div',道具:{键:'one',子代:'Foo'}}
离开
2。
{类型:'div',道具:{键:'三',子代:'Baz'}}
正在输入。
在常规React应用程序中,这意味着将不再呈现< div> Foo< / div>
,但这不是
如何< TransitionGroup>的情况下,< TransitionGroup>
的孩子。
有效
< TransitionGroup>
的孩子。 那么< TransitionGroup>
到底能如何继续渲染那些 props.children
中不再存在?
什么< TransitionGroup>
所做的是在其状态下维持 children
数组。每当< TransitionGroup>
收到新道具时,此数组就会由合并当前的 state.children
和 nextProps.children
。 (初始数组是在构造函数
中使用初始 children
道具创建的。)
现在,当< TransitionGroup>
呈现时,它将呈现 state.children
数组。呈现后,它将在任何进入或离开的孩子上调用 performEnter
和 performLeave
。
离开组件的 componentWillLeave
方法(如果有的话)之后,将执行组件的转换方法。一个)完成执行后,它将从 state.children
数组中删除,使其不再呈现(假设它在离开时未重新进入)。
将道具传递给离开的孩子?
现在的问题是,为什么不将更新的道具传递给剩下的要素?好吧,它将如何获得道具?道具从父组件传递到子组件。如果查看上面的示例JSX,则可以看到left元素处于分离状态。它没有父级,仅由于< TransitionGroup>
将其存储在其状态
中而呈现。
当您尝试通过<$ c $向您的< TransitionGroup>
的子代注入状态时c> React.cloneElement ,剩下的部分不是这些孩子中的一个。
好消息
您可以将 childFactory
道具传递给您的< TransitionGroup>
。默认的 childFactory
只是返回子级,但是您可以查看< CSSTransitionGroup>
以获得更多的< a href = https://github.com/reactjs/react-transition-group/blob/master/src/CSSTransitionGroup.js#L31-L45 rel = noreferrer>高级子工厂。
您可以通过此儿童包装纸为孩子(甚至是离开的孩子)注入正确的道具。
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仓库中分离出来了,您可以在此处查看其源代码。通俗易懂。
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.cloneElement
does 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 dispatchedcomponentWillMount
- When the component's
componentWillLeave
orcomponentWillEnter
hook is called, the corresponding slice of the options is sent todoTransition
- In doTransition:
- The user supplied transitionBegin function is called (
optionSlice.transitionBegin
) - The default
action.willLeave
oraction.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
- The user supplied transitionEnd function is called (
- The user supplied transitionBegin function is called (
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不发送更新的道具的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!