react-transition-group结合多个styled-components组件动画不生效的问题

问题描述

问题相关环境

"react": "^16.13.1",
"react-transition-group": "^4.4.1",
"styled-components": "^5.2.0"

需求描述

创建sidebar,点击控制按钮时,显示侧边栏的各个Item。但是没有动画,组件的显示与关闭都是瞬间完成,这样看起来很突兀,所以想给元素enter和exit的时候加一个过渡动画。

问题复现

index.js

import React,{ Component } from 'react'
import { CSSTransition,TransitionGroup } from 'react-transition-group'
import { connect } from 'react-redux'
import { actionCreators } from './store'

import { 
    SideBarLeftWrapper,
    SideBarLeftBtn,
    SideBarMenu,
    SideBarMenuItem
 } from './style'


import menuItemsJson from '../../static/json/sideBar.config.json'
  
class SideBar extends Component {

    constructor(props){
      super(props)
      this.getMenuItems = this.getMenuItems.bind(this)
    }

    getMenuItems = () => {
        const {isShowMenu} = this.props
        return(
          <TransitionGroup>
            {
              menuItemsJson.map( (item,index) => (
                // isShowmenu控制某些css元素的显示与否
                <CSSTransition
                  in={isShowMenu}
                  timeout={200}
                  classNames='slide'
                  unmountOnExit
                >
                  <SideBarMenuItem>
                      <span className='iconfont' dangerouslySetInnerHTML=/>
                  </SideBarMenuItem>
                </CSSTransition>
              ) )
            }
            
          </TransitionGroup>
        ) 
    }

    
    render() {
        const {handleMenuBtnClick,isShowMenu} = this.props
        return (
            <SideBarLeftWrapper isShowMenu={isShowMenu}>
                <SideBarLeftBtn isShowMenu={isShowMenu} onClick={() => {handleMenuBtnClick(isShowMenu)}}>
                    <span
                        className="iconfont menu-btn"
                    >&#xe7f2;</span>
                </SideBarLeftBtn>
                  { this.getMenuItems() }
            </SideBarLeftWrapper>
            
        )
    }

     
}



const mapStateToProps = (state) => {
    return ({
        isShowMenu: state.getIn(['sidebar','isShowMenu'])
    })
}

const mapStateToDispatch = (dispatch) => {
    return {
        handleMenuBtnClick: (isShowMenu) => {
            dispatch(actionCreators.menuFocus(isShowMenu))
        }
    }
}

export default connect(mapStateToProps,mapStateToDispatch)(SideBar);

style.js

import styled from 'styled-components'

export const SideBarLeftWrapper = styled.div`
  width: ${props => (props.isShowMenu ? '130px' : '0')};
  height: 96%;
  margin: 10px 0 0 10px;
  float: left;
  z-index: 100;
  position: absolute;
  transition: all 0.5s;
  -moz-transition: width 0.5;
`

export const SideBarLeftBtn = styled.div`
  width: 20px;
  height: 20px;  
  background: rgba(246,246,246,0.2);
  z-index: 999;
  margin: 5px 10px 10px 10px;
  margin-left: ${props => props.isShowMenu ? '100px' : '5px'};
  transform: ${props => props.isShowMenu ? 'rotate(450deg)' : 'rotate(0deg)'};
  transition: all 1s;
  cursor: pointer;
  .menu-btn{
    font-size: 25px;
    color: white;
  }
  /* &:hover{
    transform:scale(1.3);
    -ms-transform:scale(1.3); 	
    -moz-transform:scale(1.3); 	
    -webkit-transform:scale(1.3); 
    -o-transform:scale(1.3); 	
  } */
`

export const SideBarMenu = styled.div`
  width: 98%;
  height: 90%;
`

export const SideBarMenuItem = styled.button`
	float: left;
	margin: 5px;
	padding: 0;
	border: 3px solid;
	background: none; 
	color: #a8d8ea;
	vertical-align: middle;
  overflow: hidden;
	position: relative;
	z-index: 1;
  -webkit-transition: all 1s;
	transition: all 1s;
  border-radius: 15px;
  font-weight: 500;
  min-width: 120px;
  max-width: 200px;
  letter-spacing: 2px;
  font-size: 14px;
  &:focus {
    outline: 0;
  }
   > span {
    display: block;
  }
 
  &:after {
    color: #fff;
  }
  
  :hover {
    border-color: #3f51b5;
    background-color: rgba(63, 81, 181, 0.1);
  }
  &:hover {
    border-color: #fff;
    background-color: #21333C;
  }
  &::after {
    opacity: 1;
    color: #fff;
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }
  &:hover > span {
    opacity: 1;
    color: #fff;
    border: 3px #ffffff;
  }
  &.slide-enter {
      opacity:0;
  }
  &.slide-enter-active {
      opacity: 1;
      transition: all .5s ease-in;
  }
  &.slide-exit {
      opacity: 1;
  }
  &.slide-exit-active {
    opacity: 0;
    transition: all .5s ease-in;
  }
`

最外层SideBarLeftWrapper为侧边栏容器div,SideBarLeftBtn为列表显示/关闭的空值按钮,点击时改变isShowMenu的值,isShowMenu变量空值列表元素的显示与否。SideBarMenu为侧边栏各元素的父元素,SideBarMenuItem为具体的每个侧边栏按钮元素,也就是要添加动画的对象。

在实践过程中,SideBarMenuItem元素从json文件中读取,使用map方法循环遍历后返回该元素,如果要为每个元素添加动画,那么要将CSSTransition标签包裹在每个元素外面,而且其里面只能有一个直接元素,也就是子元素(孙子元素管不着):

 menuItemsJson.map( (item,index) => (
    // isShowmenu控制某些css元素的显示与否
    <CSSTransition
      in={isShowMenu}
      timeout={200}
      classNames='slide'
      unmountOnExit
    >
      <SideBarMenuItem>
          <span className='iconfont' dangerouslySetInnerHTML=/>
      </SideBarMenuItem>
    </CSSTransition>
  ) )

由于之前在学习React的时候,看到当时的代码,多个CSSTransition标签外面要用TransitionGroup标签包裹起来,而我加上它以后有,反而没有动画

image

而当去除了TransitionGroup父元素后,动画则生效了

getMenuItems = () => {
    const {isShowMenu} = this.props
    return(
          menuItemsJson.map( (item,index) => (
            // isShowmenu控制某些css元素的显示与否
            <CSSTransition
              in={isShowMenu}
              timeout={200}
              classNames='slide'
              unmountOnExit
            >
              <SideBarMenuItem>
                  <span className='iconfont' dangerouslySetInnerHTML=/>
              </SideBarMenuItem>
            </CSSTransition>
          ) )
    ) 
    }

image

之前在学习完react后,就没有进行实践和练习,这次算是回顾+学习,遇到这个问题也是琢磨了很久,但是至于原因目前还没有摸清楚。

补充: 官网使用的标签搭配,见:https://reactcommunity.org/react-transition-group/transition-group