React TransitionGroup 和 React.cloneElement 不发送更新的道具
React TransitionGroup and React.cloneElement do not send updated props
我正在学习 Chang Wang 关于使用 HOC 和 ReactTransitionGroup
(Part 1 Part 2) in conjunction with Huan Ji's tutorial on page transitions (Link).
制作可重用 React 转换的教程
我面临的问题是 React.cloneElement
似乎没有将更新的道具传递到其 children 之一,而其他 children 确实正确接收更新的道具。
首先,一些代码:
TransitionContainer.js
TransitionContainer
是一个容器组件,类似于环霁教程中的App
。它向 children.
注入状态的一部分
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
钩子,并包装一个组件。 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
- 当组件的
componentWillLeave
或componentWillEnter
钩子被调用时,相应的选项切片被发送到doTransition
- 在 doTransition 中:
- 调用了用户提供的 transitionBegin 函数 (
optionSlice.transitionBegin
)
- 默认
action.willLeave
或action.willEnter
派发
- 为动画持续时间设置了超时 (
optionSlice.duration
)。超时完成时:
- 调用了用户提供的 transitionEnd 函数 (
optionSlice.transitionEnd
)
- 默认
actions.transitionComplete
派发
optionSlice本质上只是允许用户传入一些选项。 optionSlice.transitionBegin
和 optionSlice.transitionEnd
只是可选函数,如果适合用例,则在动画进行时执行。我目前没有为我的组件传递任何东西,但我希望尽快把它变成一个库,所以我只是覆盖我的基础。
为什么我还要跟踪过渡状态?
根据进入的元素,退出动画会发生变化,反之亦然。
比如上图中,蓝色进入时红色向右移动,蓝色退出时红色向左移动。但是,当绿色进入时,红色向左移动,而当绿色退出时,红色向右移动。为了控制这一点,我需要知道当前转换的状态。
问题:
TransitionGroup
包含两个元素,一进一出(受react-router控制)。它将一个名为 transitionState
的道具传递给它的 children。 Transition
HOC(TransitionGroup
的 children)通过动画过程调度某些 redux 动作。正在进入的 Transition
组件按预期接收道具更改,但正在退出的组件被冻结。它的道具不会改变。
总是退出的那个没有收到更新的道具。我试过切换包装组件(退出和进入),问题不是由于包装组件。
图片
On-Screen 过渡:
React 中的转换 DOM
现有组件 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
更新为以上内容。现在,componentWillEnter
和 componentWillLeave
钩子没有被调用。我在 childFactory
函数中记录了 React.cloneElement(child, {...})
,挂钩(以及我定义的函数,如 doTransition
)出现在 prototype
属性中。仅调用 constructor
、componentWillMount
和 componentWillUnmount
。我怀疑这是因为 key
道具没有通过 React.cloneElement
注入。 transitionState
和 dispatch
正在被注入。
更新二:
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源码后,我意识到我把密钥放错了地方。现在一切都很好。非常感谢您的帮助!!
确定进入和离开Children
想象一下渲染下面的示例 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.children
和nextProps.children
,我们可以确定:
1。
{ type: 'div', props: { key: 'one', children: 'Foo' }}
即将离开
2。
{ type: 'div', props: { key: 'three', children: 'Baz' }}
正在进入。
在常规的 React 应用程序中,这意味着 <div>Foo</div>
将不再被渲染,但 children 的 <TransitionGroup>
.[=55 的情况并非如此=]
<TransitionGroup>
的工作原理
那么 <TransitionGroup>
究竟是如何能够继续渲染 props.children
中不再存在的组件的呢?
<TransitionGroup>
的作用是维护一个 children
数组的状态。每当 <TransitionGroup>
接收到新道具时,此数组将由 merging 当前 state.children
和 nextProps.children
更新。 (初始数组是使用初始 children
属性在 constructor
中创建的)。
现在,当 <TransitionGroup>
呈现时,它会呈现 state.children
数组中的每个 child。渲染完成后,它会在任何进入或离开 children 时调用 performEnter
和 performLeave
。这反过来将执行组件的转换方法。
在离开组件的 componentWillLeave
方法(如果有的话)执行完毕后,它将自己从 state.children
数组中移除,这样它就不再呈现(假设它没有 re-enter 在它离开的时候)。
将道具传递给离开 Children?
现在的问题是,为什么不将更新的属性传递给离开元素?那么,它将如何接收道具呢?道具从 parent 组件传递到 child 组件。如果您查看上面的示例 JSX,您可以看到离开元素处于分离状态。它没有 parent 并且它被渲染只是因为 <TransitionGroup>
将它存储在它的 state
中。
当您尝试通过 React.cloneElement
将状态注入 <TransitionGroup>
的 children 时,离开组件不是 children 中的一个。
好消息
您可以将 childFactory
道具传递给您的 <TransitionGroup>
。默认 childFactory
只是 returns child,但您可以查看 <CSSTransitionGroup>
以获得更多 advanced child factory。
您可以通过这个 child 包装器将正确的 props 注入 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。通读起来非常简单。
我正在学习 Chang Wang 关于使用 HOC 和 ReactTransitionGroup
(Part 1 Part 2) in conjunction with Huan Ji's tutorial on page transitions (Link).
我面临的问题是 React.cloneElement
似乎没有将更新的道具传递到其 children 之一,而其他 children 确实正确接收更新的道具。
首先,一些代码:
TransitionContainer.js
TransitionContainer
是一个容器组件,类似于环霁教程中的App
。它向 children.
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
钩子,并包装一个组件。 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
- 当组件的
componentWillLeave
或componentWillEnter
钩子被调用时,相应的选项切片被发送到doTransition
- 在 doTransition 中:
- 调用了用户提供的 transitionBegin 函数 (
optionSlice.transitionBegin
) - 默认
action.willLeave
或action.willEnter
派发 - 为动画持续时间设置了超时 (
optionSlice.duration
)。超时完成时:- 调用了用户提供的 transitionEnd 函数 (
optionSlice.transitionEnd
) - 默认
actions.transitionComplete
派发
- 调用了用户提供的 transitionEnd 函数 (
- 调用了用户提供的 transitionBegin 函数 (
optionSlice本质上只是允许用户传入一些选项。 optionSlice.transitionBegin
和 optionSlice.transitionEnd
只是可选函数,如果适合用例,则在动画进行时执行。我目前没有为我的组件传递任何东西,但我希望尽快把它变成一个库,所以我只是覆盖我的基础。
为什么我还要跟踪过渡状态?
根据进入的元素,退出动画会发生变化,反之亦然。
比如上图中,蓝色进入时红色向右移动,蓝色退出时红色向左移动。但是,当绿色进入时,红色向左移动,而当绿色退出时,红色向右移动。为了控制这一点,我需要知道当前转换的状态。
问题:
TransitionGroup
包含两个元素,一进一出(受react-router控制)。它将一个名为 transitionState
的道具传递给它的 children。 Transition
HOC(TransitionGroup
的 children)通过动画过程调度某些 redux 动作。正在进入的 Transition
组件按预期接收道具更改,但正在退出的组件被冻结。它的道具不会改变。
总是退出的那个没有收到更新的道具。我试过切换包装组件(退出和进入),问题不是由于包装组件。
图片
On-Screen 过渡:
React 中的转换 DOM
现有组件 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
更新为以上内容。现在,componentWillEnter
和 componentWillLeave
钩子没有被调用。我在 childFactory
函数中记录了 React.cloneElement(child, {...})
,挂钩(以及我定义的函数,如 doTransition
)出现在 prototype
属性中。仅调用 constructor
、componentWillMount
和 componentWillUnmount
。我怀疑这是因为 key
道具没有通过 React.cloneElement
注入。 transitionState
和 dispatch
正在被注入。
更新二:
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源码后,我意识到我把密钥放错了地方。现在一切都很好。非常感谢您的帮助!!
确定进入和离开Children
想象一下渲染下面的示例 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.children
和nextProps.children
,我们可以确定:
1。
{ type: 'div', props: { key: 'one', children: 'Foo' }}
即将离开
2。
{ type: 'div', props: { key: 'three', children: 'Baz' }}
正在进入。
在常规的 React 应用程序中,这意味着 <div>Foo</div>
将不再被渲染,但 children 的 <TransitionGroup>
.[=55 的情况并非如此=]
<TransitionGroup>
的工作原理
那么 <TransitionGroup>
究竟是如何能够继续渲染 props.children
中不再存在的组件的呢?
<TransitionGroup>
的作用是维护一个 children
数组的状态。每当 <TransitionGroup>
接收到新道具时,此数组将由 merging 当前 state.children
和 nextProps.children
更新。 (初始数组是使用初始 children
属性在 constructor
中创建的)。
现在,当 <TransitionGroup>
呈现时,它会呈现 state.children
数组中的每个 child。渲染完成后,它会在任何进入或离开 children 时调用 performEnter
和 performLeave
。这反过来将执行组件的转换方法。
在离开组件的 componentWillLeave
方法(如果有的话)执行完毕后,它将自己从 state.children
数组中移除,这样它就不再呈现(假设它没有 re-enter 在它离开的时候)。
将道具传递给离开 Children?
现在的问题是,为什么不将更新的属性传递给离开元素?那么,它将如何接收道具呢?道具从 parent 组件传递到 child 组件。如果您查看上面的示例 JSX,您可以看到离开元素处于分离状态。它没有 parent 并且它被渲染只是因为 <TransitionGroup>
将它存储在它的 state
中。
当您尝试通过 React.cloneElement
将状态注入 <TransitionGroup>
的 children 时,离开组件不是 children 中的一个。
好消息
您可以将 childFactory
道具传递给您的 <TransitionGroup>
。默认 childFactory
只是 returns child,但您可以查看 <CSSTransitionGroup>
以获得更多 advanced child factory。
您可以通过这个 child 包装器将正确的 props 注入 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。通读起来非常简单。