[과제] react custom component

과제 개요

UI 컴포넌트 만들기

구현 과제

  • 모달 창 만들기
  • 토글 만들기
  • 탭 만들기
  • 태그 만들기

결과물

모달창 동작 화면
토글 동작 화면
탭 동작화면
태그 동작 화면

 

고민했던 부분과 해결 방법

 

1. 모달  - 배경에 클릭이벤트 적용 시 모달 창에도 클릭이벤트가 적용되는 문제

{/* TODO : 조건부 렌더링을 활용해서 Modal이 열린 상태(isOpen이 true인 상태)일 때만 모달창과 배경이 뜰 수 있게 구현해야 합니다. */}
{isOpen === true ? 
  <ModalBackdrop onClick={()=>{ openModalHandler() }}>
    <ModalView onClick={(e)=>{ e.stopPropagation() }}>
      Hello 
    </ModalView>
  </ModalBackdrop>
  : null}

모달을 연 후 다시 끌 때에는 배경을 눌러야 모달이 닫히도록 코드를 짰는데 버블링으로 인해 주변 요소까지 클릭 이벤트가 적용되는데, stopPropagation() 함수를 통해 버블링을 막을 수 있다. 위에서는 모달창 요소에 stopPropagation()을 주어 모달 창은 클릭이 되지 않도록 했다.

 

 

2. 토글  - 스위치 이동과 함께 옆으로 채워지는 색상

 

이번 과제에서는 css 구현을 위해 styled componunt 라이브러리를 사용했다.

스위치의 이동은 어려운 부분이 아니니 넘어가고, 새로 알게 된 부분은 배경 색상이 채워지는 애니메이션.

> .toggle-container {
    width: 50px;
    height: 24px;
    border-radius: 30px;
    
    /* ↓↓↓ 오른쪽에서 왼쪽으로 색상 채우기 ↓↓↓ */
    
    background: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(9,9,121,1) 15%, rgba(175,175,175,1) 100%);
    background-size: 200%;
    background-position: right;
    transition: 1s;
      
    &.toggle--checked{ //.toggle--checked 클래스가 활성화 되었을 경우
      background: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(9,9,121,1) 15%, rgba(175,175,175,1) 100%);
      background-size: 200%;
      background-position: left;
      transition: 1s;

    }
  }

옆으로 채워지는 색상 애니메이션 코드 시각화

스스로도 이해할겸 간단한 그림으로 그려보았다. 

 

일단 그라데이션 배경을 하나 만들고, background-size를 200% 정도로 토글보다 크게 설정한다. 사이즈를 토글에 딱 맞게 만들면 '움직임'이 있어야 하는 애니메이션을 적용할 수 없고, 토글 색상이 절반은 어두운 파란색, 절반은 밝은 회색으로 고정되어버리기 때문이다. 배경이 토글보다 커야 배경의 위치에 따라 기본 회색, 토글을 활성화 했을 때에는 파란색을 적용할 수 있다.

 

그럼 그 '위치' 는 어떻게 설정하나. background-position을 주어 해결할 수 있다. position을 오른쪽으로 설정해 평상시에는 배경이 토글의 오른쪽에 맞춰 붙어있을 수 있도록 하고, 토글을 클릭하여 활성화시켰을 때에는 position을 왼쪽으로 주어 배경이 토글의 왼쪽에 붙어있게끔 한다. 

 

이제 이 시작부분과 끝부분에 transition을 적용해 애니메이션 효과를 주면 끝!

 

* 애니메이션은 start 클래스(위에서는 toggle-container)와 end 클래스(위에서는 toggle--checked)를 만들고 애니메이션 시작 전 기본화면은 start에, 애니메이션 후 화면은 end에 만들고 애니메이션을 적용할 요소에 start클래스 적용 후 end클래스를 탈부착하는 방식으로 간단한 구현이 가능하다.

 

* 그라데이션을 쉽게 만들 수 있는 사이트가 있어 첨부해놓는다.

https://cssgradient.io/

 

 

 

3. 토글 - styled componunt에서 &(엠퍼센트)의 사용

 

위 코드를 보면 .toggle-container 안에 .toggle--checked가 &.toggle--checked의 형태로 들어가 있다. 여기서 엠퍼센트는 .toggle-container와 .toggle--checked 클래스를 둘 다 가진 경우를 처리해준다. 즉, 자바스크립트 문법으로 .toggle--checked클래스를 탈부착 할 때, .toggle--checked가 .toggle-container 옆에 부착되면 그 요소는 이 두 가지 클래스를 모두 가진 경우가 되므로 .toggle--checked를 실행시켜주는 것이다. 

 

엠퍼센트에 대해 자세히 적어놓은 좋은 글이 있어 첨부해놓는다.

https://betterprogramming.pub/12-coding-examples-of-ampersand-usages-in-styled-components-78ce9bec4d09

 

 

 

4. 태그 -  태그가 추가되면 input창 비우기

 

코드를 간단하게 설명하자면 사용자가 input 창에 키워드를 적고 엔터키를 누르는 순간 아래 return문 안의 input에 입력된 값을 onKeyUp 함수를 통해 위 addTags 함수로 전달해야 한다. addTags 함수에서는 입력받은 값을 태그에 추가하는 역할을 수행한다. 

const addTags = (event) => {
    
    // - 이미 입력되어 있는 태그인지 검사하여 이미 있는 태그라면 추가하지 말기
    // - 아무것도 입력하지 않은 채 Enter 키 입력시 메소드 실행하지 말기
    // - 태그가 추가되면 input 창 비우기
    
    let etv = event.target.value;

    if(!(tags.includes(etv)) && !(etv==='')){
      setTags([...tags, etv]);
      //일반 변수인 etv로는 변경 못 하니까 event.target.value 그대로 사용
      event.target.value = '';
      
    }
  };
return (
    <>
      <TagsInput>
        
 		/*   중략   */
        
        <input
          className="tag-input"
          type="text"
          onKeyUp={(event) => {
            {
              /* 키보드의 Enter 키에 의해 addTags 메소드 실행. */
              //addTags에 event.target.value를 보내버리면 event변수에 담겨버려 
              //addTags함수에서 내용을 변경할 수 없으므로
              //event만 보내서 함수에서 직접 event.target.value로 바꿔 사용하도록 함.
              
              return event.key === "Enter" ? addTags(event) : null ;
            }
          }}
          placeholder="Press enter to add tags"
        />
      </TagsInput>
    </>
  );

 

위는 올바르게 작동하는 최종 코드이다. onKeyUp에서 이용자가 엔터 키를 눌렀는지 체크해 true라면 addTags로 event를 전달해줬다. 그리고 위쪽 addTags 함수에서 전달받은 event를 이용해 event.target.value를 변수에 담고 이미 입력된 태그는 아닌지, 아무것도 입력하지 않은 채로 엔터를 누르지는 않았는지 검사한 후 태그에 담는다. 

 

*onChange, onKeyUp 등에서 받는 파라미터(event)는 객체이고, 그 안에 많은 값들이 있기 때문에 event 객체 안의 target 안의 value를 꺼내야 입력받은 값을 가져올 수 있다.

 

처음에 나는 onKeyUp에서 addTags로 event를 전달하는게 아닌 event.target.value를 전달했었다. 

이렇게 하면 '이미 입력되어 있는 태그인지 검사하여 이미 있는 태그라면 추가하지 말기' 와 '태그가 추가되면 input 창 비우기'를 통과하지 못 하는데, 이미 입력된 태그인지 검사하는 미션이 내 라이브화면에서는 똑바로 작동하는데 테스트만 통과하지 못했다. 그래서 두 미션을 따로 보고 왜 똑바로 작동하는데 테스트를 통과를 못 하나 싶어 골머리를 앓았는데, 이 문제의 열쇠는 오히려 input창 비우기에 있었다.

 

결과적으로는 처음부터 event.target.value가 아닌 event 객체를 전달해야 했다.

왜냐하면 리액트 특성 상 일반적인 변수로는 값의 변경이 어렵기 때문이다. input창을 비우려면 event.target.value를 태그에 넣은 후 빈 배열로 돌려놔야 하는데 처음부터 event.target.value를 전달해버리면 그 값이 addTags(event) 의 파라미터 형태로 들어가기 때문에 addTags 안에서 빈 문자열로 변경할 수가 없다. 

이렇게 하니 input 창 비우기 미션도 통과하고, 정확한 로직은 모르겠지만 이미 입력된 태그인지 검사하는 테스트도 통과해 일석이조의 효과를 보았다. onKeyUp 함수에서 이벤트 전달에 대해 한 번 더 생각해 볼 수 있어서 많은 도움이 되었다고 생각한다.