ram2 ๐Ÿš—

[react] dropdown ๋งŒ๋“ค๊ธฐ (์•„์ด์ฝ˜, ๋“œ๋กญ๋‹ค์šด) ๋ณธ๋ฌธ

๐Ÿ–ฅ๏ธ react

[react] dropdown ๋งŒ๋“ค๊ธฐ (์•„์ด์ฝ˜, ๋“œ๋กญ๋‹ค์šด)

coram22 2024. 1. 17. 15:11
728x90
๋ฐ˜์‘ํ˜•
๋ฐ˜์‘ํ˜•

์ด์ „์— react์—์„œ ์ œ๊ณตํ•˜๋Š” dropdown ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ปค์Šคํ…€ ํ•œ ์ ์ด ์žˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋‚ด๊ฐ€ ์ด๋ฒˆ์— ๋งŒ๋“ค์–ด์•ผ ํ•˜๋Š” ๋“œ๋กญ๋‹ค์šด์€ ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ปค์Šคํ…€ํ•ด์„œ๋Š” ํ•  ์ˆ˜ ์—†์—ˆ๋‹ค.

์•ฝ 20๋ถ„์ •๋„ ํ•ด๋ณด๊ณ  ์•ˆ๋˜๋‹ˆ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ณด๊ธฐ ์‹œ์ž‘ํ–ˆ๋‹ค.

 

๋‚ด๊ฐ€ ํ•˜๊ณ  ์‹ถ์€ ๊ฑด, ์•„์ด์ฝ˜์„ ๋ˆŒ๋ €์„ ๋•Œ ๋“œ๋กญ๋ฐ•์Šค๊ฐ€ ๋‚˜์˜ค๋Š” ๊ฒƒ์ด์—ˆ๋Š”๋ฐ ์ด๋Ÿฌํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ฐพ์•„๋ดค์œผ๋‚˜, ๊ตฌ๋…์„ ํ•˜๊ฑฐ๋‚˜ ๋ˆ์„ ๋‚ด์•ผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์ด์—ˆ๋‹ค.

 

๊ณ ๋ฏผ ๋์— ๊ทธ๋ƒฅ ๋งŒ๋“ค๊ธฐ๋กœ ํ–ˆ๋‹ค.

 

๋Œ€๋žต์ ์ธ ์ƒ๊ฐ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

1. ์•„์ด์ฝ˜ ๋ฒ„ํŠผ์„ ์ƒ์„ฑํ•œ๋‹ค.
2. ๋ฒ„ํŠผ์„ ํด๋ฆญ ํ–ˆ์„ ๋•Œ dropdown์„ ๋ณด์—ฌ์ค€๋‹ค.
3. dropdown ์ปจํ…Œ์ด๋„ˆ ์•ˆ์— ๊ฐ list์— ๋ผ์šฐํ„ฐ๋ฅผ ์—ฐ๊ฒฐํ•ด ์›ํ•˜๋Š” ํŽ˜์ด์ง€๋กœ ์ด๋™์‹œํ‚จ๋‹ค.

 

1. ์•„์ด์ฝ˜ ๋ฒ„ํŠผ ์ƒ์„ฑํ•˜๊ธฐ

์ด๋ถ€๋ถ„์€ ์™„์ „ํ•œ ui ์˜์—ญ์ด๊ธฐ์— ๊ทธ๋ƒฅ ๋‚ด๊ฐ€ ์‚ฌ์šฉํ•œ ์•„์ด์ฝ˜ ๋ฒ„ํŠผ ์ฝ”๋“œ๋ฅผ ๋„ฃ์–ด๋‘๊ฒ ๋‹ค.

<ProfileButton
          onClick={toggleDropdown}
          style={{ backgroundColor: "transparent" }}
        >
         {/*  */}
          <AccountCircleIcon
            sx={{
              fontSize: "30px",
              color: "white",
              paddingTop: "5px",
              paddingLeft: "20px",
              paddingRight: "80px",
            }}
          />
</ProfileButton>
const ProfileButton = styled.button`
  text-align: center;
  background-color: ${theme.colors.background};
  color: ${theme.colors.white1};
  border-color: transparent;
  width: 35px;
  height: 35px;
  margin-right: 130px;
`;

 

2. ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ dropdown ๋ณด์—ฌ์ฃผ๊ธฐ

2-1. ์ƒํƒœ ๊ด€๋ฆฌ

์ด๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋Š” useEffect๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

๋ฒ„ํŠผ์ด ๋ˆŒ๋ ธ๋Š”์ง€, ์•ˆ๋ˆŒ๋ ธ๋Š”์ง€์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ํ™”๋ฉด ์ƒํƒœ๋ฅผ ๋ณด์—ฌ์ค˜์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

const [isDropdownOpen, setIsDropdownOpen] = useState(false);

 

์ด๋ฅผ ์œ„ํ•ด ๋จผ์ € false๋กœ ์ดˆ๊ธฐํ™” ๋œ isDropdownOpen ์ด๋ผ๋Š” ์ƒํƒœ ๋ณ€์ˆ˜๋ฅผ ์ƒ์„ฑํ•˜์˜€๋‹ค.

์ด ๋ณ€์ˆ˜๋Š” dropdown์ด ์—ด๋ ค ์žˆ๋Š”์ง€, ๋‹ซํ˜€ ์žˆ๋Š”์ง€๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค. ์˜ˆ์ƒํ–ˆ๋“ฏ, false๋Š” ๋‹ซํžŒ ์ƒํƒœ์ด๊ณ , true๋Š” ์—ด๋ฆฐ ์ƒํƒœ์ด๋‹ค.

๋‹ค์Œ์œผ๋กœ setIsDropdownOpen์ด๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ ์—ฐ๊ฒฐํ–ˆ๋Š”๋ฐ, ์ด ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด isDropdownOpen์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

const ref = useRef(null);

 

useRef๋Š” ๋‚˜๋„ ์•„์ง ๊ณต๋ถ€๋ฅผ ๋” ํ•ด๋ด์•ผ ํ•˜๊ฒ ์ง€๋งŒ, DOM์— ์ง์ ‘ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค€๋‹ค๊ณ  ํ•œ๋‹ค. DOM์— ์ ‘๊ทผํ•ด์„œ ๋ฌด์—‡์„ ์–ป์„ ์ˆ˜ ์žˆ๋Š”์ง€ ๊ถ๊ธˆํ–ˆ๋‹ค. ์ฐพ์•„๋ณด๋‹ˆ, ์–ด๋–ค ์ž…๋ ฅ ํ•„๋“œ์— ์ž๋™์œผ๋กœ ํฌ์ปค์Šค๋ฅผ ๋งž์ถ”๊ฑฐ๋‚˜, ์–ด๋–ค ์š”์†Œ์˜ ํฌ๊ธฐ ํ˜น์€ ์œ„์น˜๋ฅผ ์•Œ์•„๋‚ด์•ผ ํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‚˜๋Š” ๋ฒ„ํŠผ์„ ๋‹ค์‹œ ํด๋ฆญํ•˜๊ฑฐ๋‚˜, ์™ธ๋ถ€ ๋ฐ”ํƒ•์„ ํด๋ฆญํ•˜๋ฉด ๋“œ๋กญ๋ฐ•์Šค๊ฐ€ ๋‹ซํžˆ๊ฒŒ ํ•˜๊ณ  ์‹ถ์—ˆ๋‹ค. 

์ด ref๋ฅผ ์‚ฌ์šฉํ•œ ์ž์„ธํ•œ ์ฝ”๋“œ๋Š” 2-2๊ฐ€ ๋  ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋ถ€๋ถ„์—์„œ ๋” ๋‹ค๋ฃฐ ์˜ˆ์ •์ด๋‹ค.

 

 

const toggleDropdown = () => {
  setIsDropdownOpen(!isDropdownOpen);
};

 

์ด ์ฝ”๋“œ๋Š” ํ•จ์ˆ˜์˜ dropDown์ด ์—ด๋ ค ์žˆ์œผ๋ฉด ๋‹ซ๊ณ , ๋‹ซํ˜€์žˆ์œผ๋ฉด ์—ด๊ฒ ๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค.

์ด ํ•จ์ˆ˜๋Š” ์•„์ด์ฝ˜์„ ํด๋ฆญํ–ˆ์„ ๋•Œ ์‹คํ–‰ํ•ด์•ผ ํ•œ๋‹ค.

 

 

2-2. ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ

๋‹ค์Œ์œผ๋กœ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

์•ž์„œ ์‚ด์ง ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด, ๋“œ๋กญ๋ฐ•์Šค ์™ธ๋ถ€์˜ ์˜์—ญ์„ ํด๋ฆญํ•˜๋ฉด ๋“œ๋กญ๋ฐ•์Šค๊ฐ€ ๋‹ซํžˆ๊ฒŒ ํ•˜๊ณ  ์‹ถ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

useEffect(() => {
    const handleClickOutside = (event) => {
      if (ref.current && !ref.current.contains(event.target)) {
        setIsDropdownOpen(false);
      }
    };

    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [ref]);

 

์ •๋ฆฌํ•˜๋ฉด, ๋ฌด์–ธ๊ฐ€๋ฅผ ํด๋ฆญํ–ˆ์„ ๋•Œ dropDown ์™ธ๋ถ€์˜ ๊ฒƒ์ธ์ง€๋ฅผ ํ™•์ธํ•œ๋‹ค. ๋งŒ์•ฝ ์™ธ๋ถ€์˜ ๊ฒƒ์ด๋ผ๋ฉด 

ref.current.contains(event.target) ๋Š” false๊ฐ€ ๋œ๋‹ค.

๊ทธ๋ž˜์„œ ์™ธ๋ถ€๋ผ๋ฉด, dropDown์„ ๋‹ซ๊ฒŒ ๋œ๋‹ค.

 

3. ๋“œ๋กญ๋‹ค์šด ๋งŒ๋“ค๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ

<DropdownContainer ref={ref}>
	{/* ์•„์ด์ฝ˜ ๋ฒ„ํŠผ */}
        <ProfileButton
          onClick={toggleDropdown}
          style={{ backgroundColor: "transparent" }}
        >
        {/* ์—ฌ๊ธฐ์— ์‚ฌ์šฉํ•  ์•„์ด์ฝ˜์„ ๋„ฃ์–ด์ค€๋‹ค. */}
          <AccountCircleIcon
            sx={{
              fontSize: "30px",
              color: "white",
              paddingTop: "5px",
              paddingLeft: "20px",
              paddingRight: "80px",
            }}
          />
        </ProfileButton>
        
        {/* ๋“œ๋กญ๋ฐ•์Šค๊ฐ€ ์—ด๋ ค ์žˆ์„ ๋•Œ StyledDropdown ๋ณด์—ฌ์ฃผ๊ธฐ */}
        {isDropdownOpen && (
          <StyledDropdown>
            <ul style={{display: "flex", flexDirection: "column"}}>
              <DropdownTextStyle href="/myPage">๋‚ด ๋ฒจ๋กœ๊ทธ</DropdownTextStyle>
              <DropdownTextStyle href="/notFound">์ž„์‹œ ๊ธ€</DropdownTextStyle>
              <DropdownTextStyle href="/notFound">์ฝ๊ธฐ ๋ชฉ๋ก</DropdownTextStyle>
              <DropdownTextStyle href="/notFound">์„ค์ •</DropdownTextStyle>
              <DropdownTextStyle href="/notFound">๋กœ๊ทธ์•„์›ƒ</DropdownTextStyle>
            </ul>
          </StyledDropdown>
        )}
</DropdownContainer>

 

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด, ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๋“œ๋กญ๋‹ค์šด ๋ฐ•์Šค๋ฅผ ์ง์ ‘ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค !!

 

๊ฒฐ๊ณผ

-> ํ”„๋กœํ•„ ์•„์ด์ฝ˜ ์„ ํƒํ–ˆ์„ ๋•Œ

 

-> ๋“œ๋กญ๋‹ค์šด์— ์žˆ๋Š” ๋ฉ”๋‰ด ํ˜ธ๋ฒ„ ์‹œ

 

 

 

 

728x90
๋ฐ˜์‘ํ˜•

'๐Ÿ–ฅ๏ธ react' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[react] ThemeProvider ์‚ฌ์šฉํ•˜๊ธฐ  (0) 2024.01.20
[react] react-dropdown ์ปค์Šคํ…€ ํ•˜๊ธฐ  (0) 2024.01.15
[react] markdown editor, viewer ๊ตฌํ˜„ํ•˜๊ธฐ  (2) 2023.01.29
[react] useEffect()  (0) 2022.12.27
styled components  (0) 2022.12.27