在 React 中关闭有条件渲染的组件时动画流畅

Smooth animation when turning off a conditionally rendered component in React

我的App.js如下

export const App = () => {
  const [toggled, setToggled] = useState(false);
  const [ordering, setOrdering] = useState(false);

  const handleColorModeClick = () => {
    setToggled((s) => !s);
  };

  const handleOrdering = () => {
    setOrdering((s) => !s);
  };

  return (
    <Ordering.Provider value={{ ordering: ordering }}>
      <div className={`app ${toggled ? "theme-dark" : "theme-light"}`}>
        <Switch>
          <Route path="/" exact>
            <HeaderComponent toggled={toggled} onClick={handleColorModeClick} />
            <div>components2</div>
            <EateryInfo toggled={toggled} />
            {/* <CategoryItems toggled={toggled} /> */}
            <MenuButton toggled={toggled} />
          </Route>
          <Route path="/menu">
            <HeaderComponent toggled={toggled} onClick={handleColorModeClick} />
            <CategoryItems toggled={toggled} />
            <CheckBox
              text="Start Ordering"
              standAlone={true}
              handleOrdering={handleOrdering}
            />
            <MenuButton toggled={toggled} />
          </Route>
        </Switch>
      </div>
    </Ordering.Provider>
  );
};

我使用复选框

设置ordering变量的状态

然后我用它来有条件地渲染 QuantityChange 组件,就像这样

export const MenuEntry = ({ mealData, toggled }: MenuEntryProps) => {
  const orderingEnabled = useContext(Ordering);

  return (
    <div className="menu-entry">
      <MenuItem oneMenuItem={mealData} toggled={toggled} />
      {orderingEnabled.ordering ? <QuantityChange toggled={toggled} /> : ""}
    </div>
  );
};

所有这些工作正常,组件按需要渲染。

我想平稳过渡此组件的进入和退出。进入时的动画效果很好,但我不知道如何让退出动画正常工作。

视频是现在正在发生的事情,可以在此处的视频中找到 https://youtu.be/5kl1wCBwR_U(复选框位于右下角)

我查看了几个在线论坛来找到这个问题的答案,但我无法弄清楚。

我也试过使用 react-transition-group 但没有成功

export const QuantityChange = ({ toggled }: QuantityChangeProps) => {
  const orderingEnabled = useContext(Ordering);
  const duration = 500;
  return (
    <Transition in={orderingEnabled.ordering} timeout={duration} appear>
      {(status) => (
        <div
          className={`quantity-change flex ${
            toggled ? "theme-dark" : "theme-light"
          } fade-${status}`}
        >
          <span className="add-quantity">+</span>
          <span className="quantity">0</span>
          <span className="subtract-quantity">-</span>
        </div>
      )}
    </Transition>
  );
};

我查看了 onAnimationEnd,但无法理解。

看来您需要一个简单的 Accordion 东西。 您可以尝试类似的操作(下面的片段)。

此处的主要时刻之一是将 height 设置为 auto 值。它允许更改内容,并且不会限制其尺寸。

AccordionItem 有条件地呈现其 children。如果它应该关闭并且动画结束,则不会呈现 children。

const AccordionItem = (props) => {
  const { className, headline, open, children } = props

  const [height, setHeight] = React.useState(0)
  const [isOver, setOver] = React.useState(false)
  const bodyRef = React.useRef(null)

  const getDivHeight = React.useCallback(() => {
    const { height } = bodyRef.current ? bodyRef.current.getBoundingClientRect() : {}

    return height || 0
  }, [])

  // set `auto` to allow an inner content to change
  const handleTransitionEnd = React.useCallback(
    (e) => {
      if (e.propertyName === 'height') {
        setHeight(open ? 'auto' : 0)
        if (!open) {
          setOver(true)
        }
      }
    },
    [open]
  )

  React.useEffect(() => {
    setHeight(getDivHeight())
    setOver(false)
    
    if (!open) {
      requestAnimationFrame(() => {
        requestAnimationFrame(() => setHeight(0))
      })
    }

  }, [getDivHeight, open])
  
  const shouldHide = !open && isOver

  return (
    <div style={{overflow: 'hidden'}}>
      <div
        style={{ height, transition: "all 2s" }}
        onTransitionEnd={handleTransitionEnd}
      >
        <div ref={bodyRef}>
          {shouldHide ? null : children}
        </div>
      </div>
    </div>
  )
}



const App = () => {
  const [open, setOpen] = React.useState(false)

  return (
    <div>          
      <button onClick={() => setOpen(isOpen => !isOpen)}>toggle</button>
      
      <table style={{width: '100%'}}>
        <tr>
          <td>
            Hot Pongal
            <AccordionItem open={open}>
              <button>-</button>
              <input  />
              <button>+</button>
             </AccordionItem> 
          </td>
          <td>
            Hot Pongal
            <AccordionItem open={open}>
              <button>-</button>
              <input  />
              <button>+</button>
             </AccordionItem> 
          </td>
        </tr>
         <tr>
          <td>
            Hot Pongal
            <AccordionItem open={open}>
              <button>-</button>
              <input  />
              <button>+</button>
             </AccordionItem> 
          </td>
          <td>
            Hot Pongal
            <AccordionItem open={open}>
              <button>-</button>
              <input  />
              <button>+</button>
             </AccordionItem> 
          </td>
        </tr>
         <tr>
          <td>
            Hot Pongal
            <AccordionItem open={open}>
              <button>-</button>
              <input  />
              <button>+</button>
             </AccordionItem> 
          </td>
          <td>
            Hot Pongal
            <AccordionItem open={open}>
              <button>-</button>
              <input  />
              <button>+</button>
             </AccordionItem> 
          </td>
        </tr>
      </table>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>


<div id="root"></div>