【问题标题】:Building Dynamic Menu With Click Handler使用点击处理程序构建动态菜单
【发布时间】:2019-09-30 05:12:22
【问题描述】:

我在此代码中查找问题时遇到了很多麻烦...我正在尝试构建一个水平菜单,单击时会展开,然后用户可以单击他们想要的选项。要关闭展开的菜单,他们可以单击左侧的“X”。 Here's an image of what I want it to look like

我问过以前的question 并做了一些更改,但不知道从这里开始做什么。

我的主要问题:

  • 如何从buildLanguageSelector添加动态创建的列表元素?

  • 如何向menux 添加点击处理程序,以便它们在点击时切换?现在,传递给点击处理程序的thisnull

    const LanguageSelector = (props) => {
      const { languages, onSelectLanguage } = props
      const listContainer = document.createElement('ul')
      listContainer.style.position = 'absolute'
      listContainer.style.top = '0'
    
      const buildLanguageSelector = () => {
        if (props.ui.languages.length) {
          [...props.ui.languages].forEach(function (language, i) {
            const el = document.createElement('li')
            el.value = language.languageCode
            el.innerHTML = language.name
            el.className = "item"
            el.addEventListener("click", function(e) {
              e.stopPropagation()
            })
            listContainer.appendChild(el)
          })
        } else {
        }
      }
    
      buildLanguageSelector()
    
      const openMenu = (e) => {
        console.log(e)
        e.parentElement.classList.add('open')
      }
      const closeMenu = (e) => {
        e.parentElement.classList.remove('open')
      }
    
      return (
        <div className='menu'>
          // `this` being passed into `openMenu` and `closeMenu` is null
          <div className='open-button' onClick= {openMenu(this)}>Menu</div>
          <div className='close-button' onClick= {closeMenu(this)}>&#10005;</div>
         // I know that this part is incorrect-- however I'm not sure how 
         // to insert the dynamically created li's from buildLanguageSelector
          <div>{listContainer}</div>
        </div>
      )
    }
      
    body {
        background: white;
    }
    .menu {
        background: white;
        border-radius: 17px;
        height: 34px;
        width: 100px;
        display: flex;
        flex-direction: row;
        justify-content: center;
        align-items: center;
        cursor: pointer;
    }
    
    .menu .item {
        display: none;
        color: grey;
    }
    .menu #open-button {
        display: block;
    }
    .menu #close-button {
        display: none;
        color: grey;
    }
    
    .menu.open {
        justify-content: space-around;
        width: 300px;
    }
    .menu.open .item {
        display: block;
    }
    .menu.open .item:hover {
        color: black;
    }
    .menu.open .close-button {
        display: block;
    }
    .menu.open .close-button:hover {
        color: black;
    }
    .menu.open .open-button {
        display: none;
    }

【问题讨论】:

    标签: javascript html css reactjs


    【解决方案1】:

    更新:添加了示例菜单实现

    我意识到从风格上来说这不是你想要的,但从概念上来说这是我认为你想要去的方向:

    const languages = [
      "English",
      "Spanish",
      "French",
      "Wookie",
      "Klingon"
    ]
    
    function Menu () {
      // keep track of whether the menu is open
      const [isOpen, setOpen] = React.useState(false);
      
      // in a real app this would notify interested
      // parties (redux dispatch or whatever)
      const [selectedLanguage, setLanguage] = React.useState(languages[0]);	
      
      // convenience function for setting the language
      // and closing the menu
      const onLangSelect = language => {
    setLanguage(language);
    setOpen(false);
      };
      
      return (
    <div>
      <button onClick={() => setOpen(!isOpen)}>{selectedLanguage} v</button>
      <ul className="language-menu">
        {
          // if the menu is open, render the items…
          isOpen && (
            // by iterating over the available languages and emitting an item for each.
            // item.onClick invokes onLangSelect, passing in the selected language
            // we're also flagging the current item with a css class here
            languages.map(lang => (
              <li className={selectedLanguage === lang ? 'selected' : ''} onClick={() => onLangSelect(lang)} key={lang}>
                {lang}
              </li>
            ))			
          )
        }
      </ul>	
    </div>
      )
    }
      
    ReactDOM.render(<Menu />, document.querySelector("#app"))
    body {
      background: #20262E;
      padding: 20px;
      font-family: Helvetica;
    }
    
    #app {
      background: #fff;
      border-radius: 4px;
      padding: 20px;
      transition: all 0.2s;
    }
    
    .language-menu {
      position: absolute;
      background: white;
      font-size: 0.875rem;
      min-width: 150px;
      padding: 0;
      margin: 0;
      list-style: none;
      
    }
    
    .language-menu li {
      padding: 1em;
    }
    
    .language-menu li:hover {
      background: lightblue;
    }
    
    li.selected {
      background: bisque;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.0/umd/react-dom.production.min.js"></script>
    
    <div id="app"></div>

    您不应该直接操作 DOM。这样做会削弱 React 的全部意义。只需发出您需要的节点。所以不要这样:

    // don't do this. there's no need to manually create dom elements in react
    [...props.ui.languages].forEach(function (language, i) {
      const el = document.createElement('li')
      el.value = language.languageCode
      el.innerHTML = language.name
      el.className = "item"
      el.addEventListener("click", function(e) {
        e.stopPropagation()
      })
      listContainer.appendChild(el)
    })
    

    只需使用 jsx 发出标记:

    // within render method
    [...props.ui.languages].map((language) => (
      <li key={language} onclick={...}>{language}</li>
    ))
    

    当你这样做时:

    // this sets onClick to undefined because openMenu doesn't return anything
    onClick={openMenu(this)}
    

    您将onclick 处理程序设置为openMenu(this)返回值,即undefined,因为openMenu 不返回任何内容。

    同样,不要操纵 DOM。而不是:

    e.parentElement.classList.add('open')
    

    使用setState 跟踪菜单是否打开:

    this.setState({open: true});
    

    然后进行相应的渲染:

    const {open} = this.state;
    <div className={open ? 'menu open' : 'menu'}>
     ...
    </div>
    

    (有像 clsx 这样的包可以帮助编写类名;我在这里手动发出 menu open 以保持示例简单。)

    【讨论】:

    • 感谢您的回复。我有几个问题。这不是一个类组件,只是一个功能组件。那么,如果不在渲染中,我会将代码放在哪里来动态创建 li?或者(我缺乏反应知识可能会在这里显示),我应该把它变成一个类组件吗?这将在 Main 类组件中呈现,据我了解,当组件在类组件中呈现时,它应该是功能性的。另外 - 如果我使用 redux,setState 部分会如何变化?这可能更像是一个设计问题。我应该在全局状态下设置打开/关闭吗?
    • 您可以使用hookssetState 一样在功能组件中完成所有这些操作。动态创建的&lt;li&gt; 元素可以根据需要直接在渲染中发出,或者您可以使用返回它们的函数。菜单的状态(打开/关闭)应该在组件本身内隔离,因为这是唯一与它相关的事情。这是组件的状态,而不是应用程序的状态。我会用一个更全面的例子来更新我的答案。
    猜你喜欢
    • 1970-01-01
    • 2014-07-14
    • 1970-01-01
    • 2019-06-22
    • 1970-01-01
    • 1970-01-01
    • 2011-10-16
    • 2016-10-17
    • 1970-01-01
    相关资源
    最近更新 更多