ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [TIL] 2022.2.22.(수)
    코드스테이츠 43기 2023. 2. 22. 16:51

    React-custom-component과제

    modal

    import { useState } from "react";
    import styled from "styled-components";
    
    export const ModalContainer = styled.div`
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100%;
      // TODO : Modal을 구현하는데 전체적으로 필요한 CSS를 구현합니다.
    `;
    
    export const ModalBackdrop = styled.div`
      // TODO : Modal이 떴을 때의 배경을 깔아주는 CSS를 구현합니다.
      background-color: rgba(0, 0, 0, 0.5);
      position: fixed;
      left: 0;
      right: 0;
      top: 0;
      bottom: 0;
      display: flex;
      justify-content: center;
      align-items: center;
    `;
    
    export const ModalBtn = styled.button`
      background-color: var(--coz-purple-600);
      text-decoration: none;
      border: none;
      padding: 20px;
      color: white;
      border-radius: 30px;
      cursor: grab;
    `;
    
    export const ModalView = styled.div.attrs((props) => ({
      // attrs 메소드를 이용해서 아래와 같이 div 엘리먼트에 속성을 추가할 수 있습니다.
      role: "dialog",
    }))`
      // TODO : Modal창 CSS를 구현합니다.
      background-color: white;
      width: 300px;
      height: 150px;
      display: flex;
      flex-direction: column;
      display: flex;
      justify-content: center;
      align-items: center;
      border-radius: 30px;
      > button {
      }
    `;
    
    export const Modal = () => {
      const [isOpen, setIsOpen] = useState(false);
    
      const openModalHandler = (event) => {
        setIsOpen(!isOpen);
        //ModalBtn 클릭시 발생되는 change 이벤트 핸들러
        //클릭할때마다 상태가 Boolean값으로 변경된다.
        // TODO : isOpen의 상태를 변경하는 메소드를 구현합니다.
      };
    
      return (
        <>
          <ModalContainer>
            <ModalBtn
              onClick={openModalHandler}
              // TODO : 클릭하면 Modal이 열린 상태(isOpen)를 boolean 타입으로 변경하는 메소드가 실행되어야 합니다.
            >
              Open Modal
              {/* TODO : 조건부 렌더링을 활용해서 Modal이 열린 상태(isOpen이 true인 상태)일 때는 ModalBtn의 내부 텍스트가 'Opened!' 로 Modal이 닫힌 상태(isOpen이 false인 상태)일 때는 ModalBtn 의 내부 텍스트가 'Open Modal'이 되도록 구현해야 합니다. */}
            </ModalBtn>
    
            {/* TODO : 조건부 렌더링을 활용해서 Modal이 열린 상태(isOpen이 true인 상태)일 때만 모달창과 배경이 뜰 수 있게 구현해야 합니다. */}
            {isOpen ? (
              <ModalBackdrop onClick={openModalHandler}>
                <ModalView onClick={(e) => e.stopPropagation()}>
                  <button onClick={openModalHandler}>X</button>
                  <div>43기 화이팅!</div>
                </ModalView>
              </ModalBackdrop>
            ) : null}
          </ModalContainer>
        </>
      );
    };
    
    //자식요소에서 이벤트가 발생했을 때,
    //부모요소에서도 같은 이벤트가 발생한것처럼 동작
    //=>이벤트 버블링
    // 막기 위해서는? event => event.stopPropagation()

    -click할때마다 바뀌는 함수를 구현해주기 위해서 setIsOpen(!isOpen)을 해주었다. isOpen의 반대 boolean값으로 변경된다.

    -이후isOpen이 true인 상태에만 랜더링하는 함수를 만들었다.(삼항연산자 이용)

    -부모요소에서 이벤트가 발생했을때, 자식요소에서 이벤트가 발생하는것이 이벤트 버블링인줄 알았으나... 자식요소에서 이벤트가 발생했을때, 그 상위요소까지 이벤트가 실행되는 현상이 이벤트 버블링이다. 이걸 막기 위해서 stop.Propagation을 이용하면 된다.

    -모달구현이 너무 어렵고 어떻게 접근할지 감이 오지 않았는데... 수업들으면서 따라쳐보니까 그 다음것들은 어렵지 않게 풀었다!

     

    Tab

     import { useState } from 'react';
    import styled from 'styled-components';
    
    const TabMenu = styled.ul`
      background-color: #dcdcdc;
      color: rgba(73, 73, 73, 0.5);
      font-weight: bold;
      display: flex;
      flex-direction: row;
      justify-items: center;
      align-items: center;
      list-style: none;
      margin-bottom: 7rem;
    
      .submenu {
        width: 100%;
        padding: 15px 10px;
        cursor: pointer;
      }
    
      .focused {
        background-color: #4000c7;
        color: rgba(255, 255, 255, 1);
        transition: 0.3s;
      }
    
      & div.desc {
        text-align: center;
      }
    `;
    
    const Desc = styled.div`
      text-align: center;
    `;
    
    export const Tab = () => {
      const [currentTab, setCurrentTab] = useState(0);
    
      const menuArr = [
        { name: 'Tab1', content: 'Tab menu ONE' },
        { name: 'Tab2', content: 'Tab menu TWO' },
        { name: 'Tab3', content: 'Tab menu THREE' }
      ];
    
      const selectMenuHandler = (index) => {
        setCurrentTab(index);
      };
    
      return (
        <>
          <div>
            <TabMenu>
              {menuArr.map((ele, index) => {
                return (
                  <li
                    key={index}
                    className={currentTab === index ? 'submenu focused' : 'submenu'}
                    onClick={() => selectMenuHandler(index)}
                  >
                    {ele.name}
                  </li>
                );
              })}
            </TabMenu>
            <Desc>
              <p>{menuArr[currentTab].content}</p>
            </Desc>
          </div>
        </>
      );
    };

     

    -탭은 어렵지 않게 구현했던것같다.

    -다만, text(Desc 컴포넌트 안의 내용)구현할때 filter쓰고 맵쓰고 난리 쳤는데... 그냥 menuArr[currentTab].content 하면 되는거였다 ㅋㅋ

    Tags

    import { useState } from "react";
    import styled from "styled-components";
    
    // TODO: Styled-Component 라이브러리를 활용해 여러분만의 tag 를 자유롭게 꾸며 보세요!
    
    export const TagsInput = styled.div`
      margin: 8rem auto;
      display: flex;
      align-items: flex-start;
      flex-wrap: wrap;
      min-height: 48px;
      width: 480px;
      padding: 0 8px;
      border: 1px solid rgb(214, 216, 218);
      border-radius: 6px;
    
      > ul {
        display: flex;
        flex-wrap: wrap;
        padding: 0;
        margin: 8px 0 0 0;
    
        > .tag {
          width: auto;
          height: 32px;
          display: flex;
          align-items: center;
          justify-content: center;
          color: #fff;
          padding: 0 8px;
          font-size: 14px;
          list-style: none;
          border-radius: 6px;
          margin: 0 8px 8px 0;
          background: var(--coz-purple-600);
          > .tag-close-icon {
            display: block;
            width: 16px;
            height: 16px;
            line-height: 16px;
            text-align: center;
            font-size: 14px;
            margin-left: 8px;
            color: var(--coz-purple-600);
            border-radius: 50%;
            background: #fff;
            cursor: pointer;
          }
        }
      }
    
      > input {
        flex: 1;
        border: none;
        height: 46px;
        font-size: 14px;
        padding: 4px 0 0 0;
        :focus {
          outline: transparent;
        }
      }
    
      &:focus-within {
        border: 1px solid var(--coz-purple-600);
      }
    `;
    
    export const Tag = () => {
      const initialTags = ["CodeStates", "kimcoding"];
    
      const [tags, setTags] = useState(initialTags);
      const removeTags = (indexToRemove) => {
        console.log(indexToRemove);
        setTags(tags.filter((el, index) => index !== indexToRemove));
        // TODO : 태그를 삭제하는 메소드를 완성하세요.
      };
    
      const addTags = (event) => {
        let value = event.target.value;
        if (!tags.includes(value) && value !== "") {
          setTags([...tags, value]);
          //push를 하면 안되는 이유는 리액트는 주소값이 바뀌어야 변한것을 인지하기 때문에.!(참조형 데이터)
          //원시형데이터는 상관없음.
          console.log(tags);
          event.target.value = "";
        }
        // TODO : tags 배열에 새로운 태그를 추가하는 메소드를 완성하세요.
        // 이 메소드는 태그 추가 외에도 아래 3 가지 기능을 수행할 수 있어야 합니다.
        // - 이미 입력되어 있는 태그인지 검사하여 이미 있는 태그라면 추가하지 말기
        // - 아무것도 입력하지 않은 채 Enter 키 입력시 메소드 실행하지 말기
        // - 태그가 추가되면 input 창 비우기
      };
    
      return (
        <>
          <TagsInput>
            <ul id="tags">
              {tags.map((tag, index) => (
                <li key={index} className="tag">
                  <span className="tag-title">{tag}</span>
                  <span
                    className="tag-close-icon"
                    onClick={() => removeTags(index)}
                  >
                    {
                      "x" /* TODO :  tag-close-icon이 tag-title 오른쪽에 x 로 표시되도록 하고,
                                삭제 아이콘을 click 했을 때 removeTags 메소드가 실행되어야 합니다. */
                    }
                  </span>
                </li>
              ))}
            </ul>
            <input
              className="tag-input"
              type="text"
              onKeyUp={(e) => {
                {
                  if (e.key === "Enter") {
                    addTags(e);
                  }
                  /* 키보드의 Enter 키에 의해 addTags 메소드가 실행되어야 합니다. */
                }
              }}
              placeholder="Press enter to add tags"
            />
          </TagsInput>
        </>
      );
    };

    -tags를 변경할때 push를 썼는데 작동되지않았다. 그래서 그냥 push는 쓰면 안되는건가 했는데... 다 이유가 있었다. 리액트는 참조형데이터의 경우, 주소값이 바뀌어야 변한것을 인지하기 때문이다.(!) 원시형데이터는 바뀌는대로 인지하니까 상관없다고 한다.

     

    과제하면서 필요했던 지식들

    useRef

    • useRef는 .current 프로퍼티로 전달된 인자로 초기화된 변경 가능한 ref 객체를 반환한다. 
    • 반환된 객체는 컴포넌트의 전 생애주기를 통해 유지된다.
    • useRef는 .current 프로퍼티에 변경가능한 값을 담고있는 상자와 같다.
    • .cureent 프로퍼티를 변경하더라도 리렌더링을 발생시키진 않는다.

    onBlur

    onBlur 이벤트는 엘리먼트(또는 자식엘리먼트)에서 포커스가 사라졌을때 호출된다. (인풋의 바깥영역을 클릭했을때라든가)

    반대로 onFocus이벤트는 엘리먼트(또는 자식 엘리먼트)가 포커스 될때 호출된다.

    '코드스테이츠 43기' 카테고리의 다른 글

    [2022.2.28.]  (0) 2023.02.28
    [TIL] 2022.2.24.(금) 상태관리(리덕스)  (0) 2023.02.24
    [TIL] 2022.2.21.(화)  (0) 2023.02.21
    [TIL] 2022.2.20.(월)  (0) 2023.02.20
    [TIL /2023.02.17] UI/UX  (0) 2023.02.17
Designed by Tistory.