样式组件 onFocus 和 onBlur 事件不起作用

styled-components onFocus and onBlur event does not work

我正在为我的应用程序使用 React JS + Typescript。对于样式,我使用 styled-components。我真的是样式化组件的新手。我创建了一个下拉菜单。逻辑工作正常。但是当我在下拉按钮之外时,它仍然显示选项列表。当列表不在下拉列表中时,我想隐藏它。我尝试了每一种方法,但没有成功。这是它在 focus:

之外时的样子

这是我的下拉组件

import React, { useState } from "react";
import styled from "styled-components";
import Arrow from './Arrow.svg'

const Wrapper = styled.div`
  box-sizing: border-box;
  border: 1 solid #d2d6dc;
`;

const MenuLabel = styled.span`
box-shadow: 0 1px 2px 0 rgba(0,0,0,.05);
border-radius: .375rem;
& after,
& before:{
  box-sizing: border-box;
  border: 0 solid #d2d6dc;
}
`;

const ItemList = styled.div`
color: #798697;
background: white;
line-height: 30px;
padding: .25em 2em .25em 2em;
cursor: defaul;
user-select: none;
transition: all .25s ease;
&:hover,
&.selected {
  background: #F7F7F7;
  color: #4A4A4A;
}

`;

const Button = styled.button<{ isOpen?: boolean }>`
display:inline-flex;
padding: 10px 30px;
font-size: 14px;
justify-content: center;
border-radius:5px;
position: relative;
background: white;
font-size: 12px;
border-color: gray;
transition: ease-in-out .2s;
& hover:{
color: gray;
}
&:focus {
  border: 1px solid blue;
  outline: none;
}

`

const CaratContainer = styled.img<{ isOpen?: boolean }>`
  transform: ${props => (props.isOpen ? "rotate(180deg)" : "rotate(0deg)")};
  transition: all 0.2s ease;
  height: 30px;
  width:20px
`;

const DropDown = styled.ul`
`

export interface IOptions {
  label: string;
  value: number;
}

export interface IDropdown {
  labelDefault: string;
  options: IOptions[];
  onClick?: () => void;
  style?: React.CSSProperties;
  onFocus?: () => void;
  onBlur?: () => void;
}


const Dropdown = ({ labelDefault, options, onClick, style, onFocus, onBlur }: IDropdown) => {
  const [isOpened, setIsOpened] = useState(false);
  const [selectedOption, setSelectedOption] = useState("");
  const [label, setLabel] = useState<string>("");
  const [isFocussed, setIsFocussed] = useState(false)

  const handleSelectedItem = (obj: any) => {
    setSelectedOption(obj.value);
    setLabel(obj.label);
    setIsOpened(!isOpened);
  };

  return (
    <Wrapper >

      <Button onClick={() => setIsOpened(!isOpened)}
        onFocus={() => { //I tried in onFocus but did not
          setIsFocussed(true);
          onFocus && onFocus()
        }}
        onBlur={() => {
          setIsFocussed(false);
          onBlur && onBlur()
        }}
        style={style}
      >
        <p> {selectedOption ? label : labelDefault}</p>
        <CaratContainer isOpen={isOpened} src={Arrow} />
      </Button>
      {isOpened ? options.map(el => (
        <ItemList     //This is the list I want to hide when it out of the dropbutton. 
          key={el.value.toString()}
          onClick={() => handleSelectedItem(el)}
          onFocus={() => {
            setIsFocussed(true);
            onFocus && onFocus()
          }}
          onBlur={() => {
            setIsFocussed(false);
            onBlur && onBlur()
          }}
        >
          {el.label}
        </ItemList>
      )) : null}


    </Wrapper>
  );
}
export default Dropdown;

这是父组件

import * as React from "react";
import Dropdown from "./dropdown";

const MockData = [
  { label: "one", value: 1 },
  { label: "two", value: 2 },
  { label: "three", value: 3 }
];

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <Dropdown labelDefault="Select a label" options={MockData} />
    </div>
  );
}

请记住,onBlur 表示该元素失去焦点。当另一个元素获得焦点或您只是单击页面上的某个位置时会发生这种情况,而不是当光标移出时。

我注意到的另一件事是你使用 isOpened 变量来确定你的 ItemList 是否应该被渲染并且插入符号应该被旋转,但是在 onFocusonBlur 方法,您正在设置 isFocussed 状态。

您似乎只是在使用 setIsFocussed 而从未使用 isFocussed 本身。您可能想查看 ESLint,它会帮助您轻松捕获此类错误。

如果我理解你的 issue/question,听起来你希望下拉菜单在失去焦点时关闭。

编辑 - 使用“处理外部事件”功能,不用担心焦点管理

创建一个效果,adds/removes 一个事件侦听器,用于在外部 下拉菜单的包装器之外发起的事件。

const dropdownRef = useRef();

useEffect(() => {
  const externalEventHandler = e => {
    if (!isOpened) return;

    const node = dropdownRef.current;

    if (node && node.contains(e.target)) {
      return;
    }

    setIsOpened(false);
  }

  if (isOpened) {
    document.addEventListener('click', externalEventHandler);
  } else {
    document.removeEventListener('click', externalEventHandler);
  }
  
  return () => {
    document.removeEventListener('click', externalEventHandler);
  }
}, [isOpened]);

必须使包装器成为非块级元素,否则您必须单击 完全 下拉菜单上方或下方。

const Wrapper = styled.div`
  display: inline; // <-- no block level element
  box-sizing: border-box;
  border: 1 solid #d2d6dc;
`;