코드스테이츠 43기

[TIL] 2022.2.22.(수)

_서리__ 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이벤트는 엘리먼트(또는 자식 엘리먼트)가 포커스 될때 호출된다.