※ 해당 포스트는 http://100dayscss.com/의 2번 글의 설명을 위해 작성자가 다시 재구성한 포스팅입니다.

※ 무단 스크랩은 허용하지 않습니다.





햄버거 버튼이라고 불리는 메뉴 버튼을 만들어보겠습니다.

우선 눈에 잘 띄도록 배경부터 지정을 해보겠습니다.


먼저 width, height이 150px인 정사각형을 만들었고, 밋밋하지 않게하기 위해 정사각형에 약간의 box-shadow를 입힌 상태입니다. box-shadow를 모르시겠다면 여기를 클릭하세요.

다음에는 우리가 만든 녹색 상자 안에 버튼처럼 생긴 컴포넌트들을 안에다가 넣어줘 보겠습니다.


See the Pen ybQXLd by Munkyu Yang (@moonformeli) on CodePen.



/* 추가된 부분 */ 을 주목해서 봐주세요. .line 클래스를 가지는 3개의 div를 추가해 width, height, margin를 갖고 있죠? 그리고 딱딱해보이지 않도록 역시 box-shadow와 border-radius를 설정해주었습니다. 


여기서 주목할만한 부분은, #wrapper div의 상태 값이 position:absolute로 변경되었습니다. 이 이유는, CSS에서는 부모의 position 속성이 absolute 혹은 relative면 그 자식의 position이 absolute, relative일 경우 기준점이 부모의 좌측 최 상단에서부터(0,0 지점) 시작됩니다. 지금 보고 계시는 예제는 #wrapper의 시작 위치가 윈도우의 좌측상단으로 정해져있고 따로 움직이지 않았기때문에 여기서는 #wrapper의 position을 설정하지 않아도 괜찮습니다. 그런데 이 것을 움직이게 될 경우에는 자식 태그들을 position으로 움직이려면(특히 absolute의 경우) 정말 먼 길을 달려가야만 하겠죠? 그리고, 부모-자식 관계를 position으로 묶으면 추후에 코드 유지보수에도 많은 도움이 됩니다.


그리고 .line을 감싸는 #line-wrapper를 추가해 cursor에 대한 상태 값을 지정해 주었습니다. 이렇게 하지 않고 .line의 cursor를 변경하면, .line은 3개의 div이기 때문에 그 사이에 존재하는 공백(background)에서는 cursor가 적용되지 않습니다.


See the Pen XRygRK by Munkyu Yang (@moonformeli) on CodePen.


자, 여기서부터 조금씩 복잡해지기 시작합니다.


우선,

jQuery의 선택자에 대해 모르시는 분은 잠깐 보고 오세요.

- 선택자

- toggleClass

CSS의 animation에 대해 모르시는 분은 잠깐 보고 오세요.

- animation - w3schools

- animation - Youtube tutorial video by NetNinja (추천)


제 아무리 CSS가 날고 기어도 CSS는 CSS일 뿐, 액션에 대한 영역은 JS에게 맡겨야 하는 상황이 오게 마련이죠..

추가된 jQuery를 봐주시기 바랍니다. JavaScript를 쓰지 않고 jQuery를 사용한 이유는 jQuery가 DOM제어에 더 특화되어 있고 JavaScript를 사용하는 것보다 쉽게 구현할 수 있습니다. (편하신 분들은 JavaScript를 사용하셔도 괜찮습니다.)


.line들의 부모 div인 #line-wrapper를 클릭하게되면 각각의 아이디에 맞는 div를 찾아 각각 개별적인 클래스 네임을 부여해줍니다. toggleClass를 이용하면 경우의 수에 따라 addClass, removeClass를 매번 해줘야하는 상황을 피할 수 있죠. 저는 toggleClass를 더 선호하는 편입니다. 물론 내부적인 동작은 같을 겁니다.


추가된 CSS 코드에서는, 예를 들어, .line-top 클래스가 적용이 될 경우에 어떤 속성 값을 받을 건지를 설정을 해주었는데요. @keyframes를 사용하면 좀 더 복잡한 애니메이션 효과를 쉽게 구현할 수 있습니다. 직접 transform으로 구현해도 상관없지만 그럴 경우엔 transition까지 같이 사용해줘야하고, 애니메이션이 끝난 후에 상태를 어떻게 놔둘 것인지도 직접 정해줘야해서 까다로워지죠. forwards 속성을 지정하지 않으면 애니메이션이 끝남과 동시에 다시 처음의 상태로 돌아가게 됩니다. 저희는 이 다음의 애니메이션도 부여할 것이기 때문에 이대로 놔두겠습니다.



See the Pen ZKmymp by Munkyu Yang (@moonformeli) on CodePen.


추가된 CSS에서는 위, 아래의 .line이 겹쳐지고 난 후에 rotate 속성을 주어 회전하는 듯한 느낌을 주었습니다. 애니메이션을 저렇게 두 줄 이상으로 작성하게되면 첫 번째로 지정된 애니메이션부터 실행이되고 그 다음 순서로 넘어가게 됩니다. 여기서 두 번째 애니메이션을 순서에 맞게 진행시키고 싶다면, animation-delay 효과를 이용해야합니다. 그렇지 않으면 지정한 애니메이션이 모두 다 동시에 발생하게 되죠.  rotate을 할 때, translateY로 설정해주지 않으면 어떤 일이 일어날까요? .line-* 가 있었던 위치에서 rotate가 실행되게 됩니다. 왜냐하면, 지정한 애니메이션의 0% 지점에서 translateY를 따로 설정하지 않으면 자동적으로 초기 위치로 시작한다고 인식하기 때문입니다.

이제 다시 클릭을 했을 때 돌아가는 코드를 한 번에 보겠습니다.



See the Pen LyXjOM by Munkyu Yang (@moonformeli) on CodePen.


코드를 보시면 추가된 부분이 제법 많습니다. 우선 HTML을 보시면 .init 이라는 클래스를 지정해줬고 CSS에서는 이 클래스에서 animation을 none을 주고 우선순위를 부여합니다. 이렇게하는 이유는, 버튼을 다시 눌렀을 때 애니메이션을 반대로 지정해줘야 하는데 이런 기능을 가진 클래스를 처음부터 갖고 있으면 버튼을 누르기도 전에 reverse 애니메이션이 실행이 되어버립니다. 그런데 여기서 생각해야 할 것은, 애니메이션 실행 ↔ 애니메이션 반대로 실행 이 버튼의 클릭에 따라 한 가지만 실행이 되야하죠. 그리고 이러한 작업이 버튼 클릭에 따라 번갈아가면서 발생해야 합니다. 그 말은, .line-top에 있는 애니메이션은 그 반대의 애니메이션을 갖고 있는 .line-top-reverse와는 동시에 클래스로 존재해서는 안된다는 뜻이 됩니다.


그렇다면 버튼을 눌렀을 때 애니메이션이 실행되게 하기 위해선 버튼을 누른 후에 .line-top을 부여해줘야하고, 그 말은 다시, .line-top-reverse는 코드 실행 시작시에 처음으로 갖고 있는 클래스라는 뜻입니다. 그런데 윗 단락에서 설명한 것을 다시 말하자면, .line-top-reverse는 나름의 애니메이션을 갖고 있기 때문에, 코드 실행이 될 때 자동으로 실행이 되겠죠. 이 것을 어떻게 막냐,이 문제에 직면하게 됩니다. CSS에서는 조건문이라는 것이 없기 때문에 제어를 할 수가 없죠. 그래서 .init을 만들어 강제적으로 모든 애니메이션을 없애버린 것입니다. 그리고 jQuery 부분에서는 .line 클래스를 찾아 .init 클래스를 지워버리죠. 제가 만든 샘플 코드에서는 클릭 이벤트가 실행될 때마다 .init을 찾아 삭제하겠지만, 다른 방법들도 많으니 고민해보시기 바랍니다.


반대로 정의하고 싶은 애니메이션은 기존에 지정한 애니메이션을 반대로 작성해주기만 하면 금방이죠. 그런데 여기서 @keyframes line-mid-invisible에 주목해보세요. 의미없이 보이는 애니메이션이 들어가 있습니다. 시작과 끝에서 모두 scale로 같은 값을 지정한다는 것은 무슨 의미일까요? 저 부분은 그 자체로의 의미보다는 .mid-reverse 설정과 연결지어서 봐야합니다. 아무렇게나 만든 것 같은 line-mid-invisible 애니메이션이 먼저 지정이 되어 있고 그 다음에 본 애니메이션이 연결이 되어있죠.  이 것은 'animation delay'와 어느정도 연관이 있습니다.




최초의 클릭 후 애니메이션이 실행이 되면 클래스명의 네이밍은 어떻게 될까요. .line-mid이 생기고 .mid-reverse.init은 없어지겠죠.  그리고 다시 클릭하면 .mid-reverse 클래스명이 생기면서 reverse 애니메이션을 실행하게 될텐데, 우리가 원하는 #line-mid의 경우 rotate된 나머지 두 라인이 다시 원래대로 돌아올 때까지 기다렸다가 다음 애니메이션부터 같이 실행되길 원합니다. 그런 마음에서 animation-delay를 설정해 줬는데, 예상과는 다르게 애니메이션이 실행되기 전까지의 시간은 우리가 초기 속성값을 정해놓은 그 상태 그대로 기다리게 됩니다. 즉, 애니메이션 대기 큐에는 올라갔지만 기다리는 동안 이렇다할 상태변화가 없기 때문에 원래 갖고 있던 값 그대로 갖고 있는채로 기다리게 되는 것입니다. 


우리는 mid가 보이지 않았다가 서서히 나타나는 효과를 원했으므로, 애니메이션을 기다리는 동안(animation-delay)에도 계속 보이지 않는 상태로 유지시켜주면 더 좋겠죠. 그래서 scale을 0으로 만들고 기다리는 작업을 하고 있는 것입니다. 직접 한 번 실행해보면서 이해를 해보도록 하세요.


이로써 햄버거 메뉴 버튼 만들기는 끝났습니다. 기초적인 기능이지만 세련된 느낌을 줄 수 있는 좋은 효과이기때문에, 한 번 익혀놓으시면 다른 스타일로도 얼마든지 응용이 가능한 매우 유용한 기술입니다.


설명을 위해 글이 다소 길어졌지만 끝까지 읽어주셔서 감사합니다. 다음에는 다른 효과에 대해 알아보도록 하겠습니다.


CSS 카테고리에서는 CSS의 효과를 이용한 다양한 효과들을 어떻게 만드는지 설명하고, 

따라서 실습할 수 있도록 설명을 하는 곳으로 활용하겠습니다.





※ 해당 포스트는 Dave Ceddia 님의 동의 하에 글을 번역한 것임을 알립니다.


※ 자연스러운 글의 흐름을 위해 작성자의 임의로 의역한 부분이 있음을 알립니다.







React 컴포넌트가 제대로 렌더링이 안되고 있습니까?


그 전에 잠깐! React 컴포넌트가 componentWillMount 에서 서버로부터 데이터를 받아오는 작업을 아래와 같이 하고 있다면, 과연 무엇이 렌더링이 될까요?



사진의 출처 - Jay Galvin



만약 '아무것도 일어나지 않는다' 혹은 'a console error'라고 생각하셨다면, 정답입니다.

만약 '데이터를 받아온다'고 생각했다면, 이 포스트를 읽으시는게 좋겠군요.


State는 초기화되어 있지 않다

우선 여기서 짚고 넘어가야할 두 가지 중요한 사실들이 있습니다:

1. 컴포넌트의 상태 값(e.g. this.state) 의 시작 값은 null이다.
2. 비동기적으로 데이터를 처리할 때, 컴포넌트는 적어도 한 번, 데이터가 불러지기 전에 렌더링 된다.

- 데이터가 constructor, componentWillMount , componentDidMount 혹은 그 어디에 있는지와 상관이 없이 말이다.


그렇습니다. constructorcomponentWillMount가 최초의 렌더링 그 이전에 실행이 된다고 할지라도, 비동기 호출은 컴포넌트가 비동기 이전에 렌더링이 되는 것을 막아주지는 못합니다. 그래서 여러분이 여전히 문제를 일으키고 있는 것이고요.


해결방안

사실 해결책은 간단합니다. 가장 쉬운 방법은 constructor 안에서 state를 그럴싸한 값으로 기본 설정해주는 것입니다.


위의 컴포넌트에 대한 예제는 아래와 같이 풀어볼 수 있습니다.


그리고 아래처럼 또한 빈 값을 render 안에서 설정해줄 수도 있습니다.



이 것은 이상적인 방법은 아닙니다. 만약 기본 설정 값(default value)을 부여할 수 있다면, 그렇게 하세요.


낙수효과(Trickle-Down effect)의 실패에 따른 부작용

"비어있는 상태값(state)"이 만약 제대로 설정이 되어 있지 않다면, 결국에 이 문제는 개발자에게 더 큰 후폭풍을 안겨주게 됩니다.

(※ 작성자에 의한 의역임을 알림(or를 오타로 판단해 of로 해석). 원문 : The lack of default or “empty state” data can bite you another way, too)

바로 자식 컴포넌트에게 정의되지 않은(undefined) 상태값이 전달될 때 말이죠.


위의 예제를 조금 더 확장해 봅시다. 가령 Quiz 컴포넌트안에 있는 자식 컴포넌트로 list를 넘겨준다고 한다면,



문제가 보이시나요? Quiz가 처음으로 렌더링이 될 때, this.state.itemsundefined 입니다. 무슨 말인가하면, ItemList 컴포넌트가 items 이라는 데이터를 undefined 상태로 넘겨받게되고, 결국에는 화면에서 Uncaught TypeError: Cannot read property 'map' of undefined 를 보게되는 것이란 말입니다.


만약 ItemList안에 propTypes에 대한 정의가 컴포넌트에 새겨져 있었다면 디버깅하기는 더 수월했겠군요.



이렇게 정의가 되어 있었다면 콘솔에 나오는 메시지는 다음과 같습니다.

"Warning: Failed prop type: Required prop items was not specified in ItemList."

(경고: prop type 오류: 필수요건인 itemsItemList에 정의되어 있지 않음.)


그러나 여전히 Uncaught TypeError: Cannot read property 'map' of undefined 의 에러는 남아있습니다. propType의 유효성을 검사하는 것은 컴포넌트의 렌더링에 관한 문제를 해결해주지는 않습니다. 다만 알려주기만 할 뿐이죠.


그러나 최소한 이러한 방법은 디버그를 조금 더 수월하게는 해줍니다.


기본 Props 설정(Default Props)

한 가지 더 쉬운 방법이 있습니다. props의 default값을 설정해주면 됩니다.


Default props는  항상 최선책은 아닙니다. 이 방법을 사용하기전에 더 나은 해결책이 없는지, 이 방법이 그저 임기응변(band-aid solution)에 지나지 않는지 항상 확인하세요.


Default 값들을 설정하는 것이 그저 값들이 정의되지도 않아서 발생하는 수 많은 에러들을 모두 예방해줄 수 있을 것이라 생각하시나요? 그 전에 값(data)들을 제대로 정의하는 것을 먼저 고려하십시오.


아니면 prop이 단순한 선택적인 것에 불과하다고 말할 수 있습니까? prop이 전달되지 않은 상태에서 컴포넌트가 렌더링이 되는 경우에 대해서는 문제가 없다고 생각하십니까? 그렇다면 기본 값 설정에 대한 문제 해결을 받아들일 준비가 되었습니다.


이 문제는 몇 가지 방법으로 해결할 수 있습니다.


defaultProps 이용하기

이 메소드는 여러분의 컴포넌트가 상태(state)를 갖고 있지 않은 함수인지, React.Component를 상속받는 클래스인지에 얽매이지 않습니다.



이 메소드는 컴포넌트가 클래시 형식일 때, 여러분의 컴파일러가 ES7의 static initializer 문법을 지원할때만 동작합니다. 



render 조작

이 방법은 아래에 있는 render 안에서, ES6의 destructuring syntax에 의해 다음과 같이 표현됩니다.



이 라인은 다음과 같은 의미입니다.

"items 키를 this.props에서 가져오고, 만약 undefined상태라면 빈 배열로 초기화한다"



인자 값(arguments) 조작

만약 컴포넌트가 함수형이라면, 아래와 같이 초기화를 해줄 수 있습니다.



요약

  • 컴포넌트 라이프 사이클이 실행되는 동안에 이루어지는 비동기 호출은 컴포넌트가 데이터가 로드되기 전에 렌더링이 된다는 것을 의미합니다. 그래서..

  • constructor 안에서 state를 초기화하거나 빈 값을 적절히 처리해야 합니다.

  • 디버깅을 더 수월히 하기 위해 ProTypes를 사용하십시오.

  • defaulProps를 적절히 활용하세요.

  • 구조 변경(※ 의역, 원문: Destructuring syntax: 구문 파괴)은 깔끔하면서 쉽게 기본 값들을 설정할 수 있습니다.



+ Recent posts