【问题标题】:addEventListener doesn't work on initial renderaddEventListener 在初始渲染时不起作用
【发布时间】:2021-09-06 01:12:04
【问题描述】:

我正在尝试在 React 中创建简单的手风琴。如果我打开/刷新整个页面,单击按钮时不会触发 onclick 事件。只有在我更改代码和组件重新渲染中的某些内容后,它才能正常工作。控制台中没有错误,老实说,我不知道这里发生了什么。提前致谢

import { useEffect } from "react"

function FAQ () {
  const accordionBtn = document.querySelectorAll('.accordionTitle')
  const allTexts = document.querySelectorAll('.text')

  useEffect(() => {
    accordionBtn.forEach(function (el) {
      el?.addEventListener('click', toggleAccordion)
    })
  }, [])
 
  function toggleAccordion (el: Event) {
    
    const targetText = (el?.currentTarget as Element).nextElementSibling?.classList
    const target = (el?.currentTarget as Element).classList

    if (!(targetText?.contains('show'))) {
      accordionBtn.forEach(function (el) {
        el.classList.remove('accordionTitleActive')
        allTexts.forEach(function (el) {
          el.classList.remove('show')
        })
      })
      targetText?.add('show')
      target?.add('accordionTitleActive')
    }
  }

  return (
    <div>
      <ul className='accordion'>
        <li>
          <h2 className='accordionTitle'>Title1 </h2>
          <div className='text show'>
            Content1
          </div>
        </li>
        <li>
          <h2 className='accordionTitle'>Title2 </h2>
          <div className='text'>
            Content2
          </div>
        </li>
        <li>
          <h2 className='accordionTitle'>Title3 </h2>
          <div className='text'>
           Content3
          </div>
        </li>
      </ul>
    </div>
  )
}

export default FAQ

【问题讨论】:

  • 您不应该以这种方式将像 querySelectorAll 这样的原生 DOM 方法与 React 混合使用。它会干扰 React 更新 DOM 的方式。

标签: javascript html reactjs typescript dom


【解决方案1】:

当您当时检索 DOM 元素时,这些元素甚至不在 DOM 中。如果您将 querySelectorAll 调用移至useEffect,它应该可以解决您的问题。请记住,在组件绘制到屏幕上之后调用 useEffect 回调。

但正如 Andy 在上面的评论中提到的,强烈不建议直接访问 DOM。即使你必须(我认为你的场景不需要)也有useRef

【讨论】:

    【解决方案2】:

    以下是我可能会采用的方法。它使用状态来维护手风琴的数量,然后您可以相应地调整子元素的类。

    const { useState } = React;
    
    function Example() {
    
      // Use state to identify the accordian you want open
      const [ show, setShow ] = useState(0);
    
      // `handleClick` grabs the id from the accordian
      // and then sets the state
      function handleClick(e) {
        const { id } = e.target.parentNode.dataset;
        setShow(Number(id));
      }
    
      // Now, for each element check to see if the id matches
      // the state and show that text
      return (
        <div onClick={handleClick}>
          <div data-id="1">
            <h3>Accordian one</h3>
            <p className={show === 1 && "show"}>Accordian one text</p>
          </div>
          <div data-id="2">
            <h3>Accordian two</h3>
            <p className={show === 2 && "show"}>Accordian two text</p>
          </div>
          <div data-id="3">
            <h3>Accordian three</h3>
            <p className={show === 3 && "show"}>Accordian three text</p>
          </div>
        </div>
      );
    };
    
    // Render it
    ReactDOM.render(
      <Example />,
      document.getElementById("react")
    );
    p { display: none; }
    h3:hover { cursor: pointer; }
    .show { display: block; }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
    <div id="react"></div>

    【讨论】:

    • 这太聪明了,我什至没有考虑这种方法。现在一切都很完美,谢谢
    【解决方案3】:

    我建议 - 重构您的代码以使用基于类的组件。并不是说现有代码丑陋,而是在 React 和平滑渲染 html 时应该使用最佳实践。此外,通过这样做,您还可以使用 React 组件生命周期方法 - 这将非常方便!

    一篇关于基于类的组件的好文章:Link

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-04-09
      • 2016-11-15
      • 1970-01-01
      • 1970-01-01
      • 2019-04-14
      • 1970-01-01
      • 1970-01-01
      • 2015-01-09
      相关资源
      最近更新 更多