React TransitionGroup и React.cloneElement не отправляют обновленные реквизиты
Я следую руководству Чан Вана по созданию многоразовых переходов React с помощью HOC и ReactTransitionGroup
( Часть 1 Часть 2) в сочетании с руководством Хуан Цзи по переходам страниц ( Ссылка).
Проблема, с которой я сталкиваюсь, заключается в том, что React.cloneElement
похоже, не передает обновленный реквизит одному из своих потомков, в то время как другие потомки правильно получают обновленные реквизиты.
Сначала немного кода:
TransitionContainer.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)
Transition.js
Transition
это похоже на 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
содержит два элемента, один входящий, другой выходящий (управляется реагирующим маршрутизатором). Проходит опору под названием transitionState
своим детям. Transition
HOC (дети TransitionGroup
) рассылает определенные избыточные действия в ходе анимации. Transition
входящий компонент получает изменение реквизита, как и ожидалось, но выходящий компонент заморожен. Это реквизиты не меняются.
Это всегда тот, кто выходит, который не получает обновленные реквизиты. Я попытался переключить упакованные компоненты (выход и вход), и проблема не связана с упакованными компонентами.
Изображений
Переход в 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
крючки не называются. Я вошел React.cloneElement(child, {...})
в childFactory
функция, и хуки (а также мои определенные функции, такие как doTransition
) присутствуют в prototype
приписывать. Только constructor
, componentWillMount
а также componentWillUnmount
называются. Я подозреваю, что это потому, что 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 я понял, что поставил ключ не в том месте. Теперь все хорошо. Большое спасибо за помощь!!
1 ответ
Определение въезда и ухода детей
Представьте себе рендеринг образца JSX ниже:
<TransitionGroup>
<div key="one">Foo</div>
<div key="two">Bar</div>
</TransitionGroup>
<TransitionGroup>
"s 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>
больше не будет оказываться, но это не относится к детям <TransitionGroup>
,
Как <TransitionGroup>
Работает
Так как именно <TransitionGroup>
возможность продолжить рендеринг компонентов, которые больше не существуют в props.children
?
Какие <TransitionGroup>
делает то, что он поддерживает children
массив в своем состоянии. Всякий раз, когда <TransitionGroup>
получает новые реквизиты, этот массив обновляется путем слияния текущего state.children
и nextProps.children
, (Первоначальный массив создается в constructor
используя начальный children
проп).
Теперь, когда <TransitionGroup>
оказывает, это делает каждого ребенка в state.children
массив. После того, как это оказало, это вызывает performEnter
а также performLeave
на любые входящие или выходящие дети. Это, в свою очередь, будет выполнять методы перехода компонентов.
После ухода компонента componentWillLeave
метод (если он есть) завершил выполнение, он удалит себя из state.children
массив, так что он больше не рендерится (при условии, что он не входил повторно, когда уходил).
Передача реквизита оставляющим детям?
Теперь вопрос в том, почему обновленные реквизиты не передаются уходящему элементу? Ну, как бы он получил реквизит? Реквизиты передаются от родительского компонента к дочернему компоненту. Если вы посмотрите на пример JSX выше, вы увидите, что выходящий элемент находится в отсоединенном состоянии. У него нет родителя, и он отображается только потому, что <TransitionGroup>
хранит его в своем state
,
Когда вы пытаетесь ввести состояние детей вашего <TransitionGroup>
через React.cloneElement
уходящий компонент не один из тех детей.
Хорошие новости
Вы можете передать childFactory
поддержать вашу <TransitionGroup>
, По умолчанию childFactory
просто возвращает ребенка, но вы можете взглянуть на <CSSTransitionGroup>
для более продвинутой детской фабрики.
Вы можете ввести правильные реквизиты в детей (даже оставляя) через эту дочернюю оболочку.
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, и вы можете посмотреть ее исходный код здесь. Это довольно просто прочитать.