2개 이상의 컴포넌트와 상호작용하기 그리고 디버거 사용하기!
1. FramerX 스터디 연재#1.1 - 코드 작성을 위해 알아야 할 기초 개념
2. FramerX 스터디 연재#1.2 - 간단한 인터렉션 예제와 함께 동작 원리 학습
3. FramerX 스터디 연재#2.1 - 기본 제공되는 많은 도구를 활용하는 첫 번째 방법
지난 연재#2.1에 이어 배경색을 점 점 더 붉게 변하도록 하는 나머지 부분을 완성해 보자.
버튼이 커지기 시작하면 배경색을 빨간색 쪽으로 변화시킬 것이다. 배경 역할의 프레임(back)의 배경색을 기본색인 흰색에서 빨간색까지 변화시키면 된다. 값을 변화시키기 위해선 likeScale와 마찬가지로 변수가 필요하다. 배경색용 변수를 하나 추가하자. backgroundColor라는 이름을 지어줬다.
const data = Data({
backgroundColor: Animatable('#FFF'),
likeScale: Animatable(1)
})
컬러 값은 애니메이션에 사용할 값이기 때문에 likeScale 값과 마찬가지로 Animatable함수로 값을 변환시킨다.
* 여기서 잠깐!
Animatable 함수의 역할이 scale 값 정도에선 뭘 해주는지 크게 와 닫지 않았었다. 1부터 2로 변화한다면 중간값들을 만들어 주는 정도로 이해할 수 있었는데... 그걸 굳이 이렇게 할 것 까지야? 그런 의문 말이다. 그러나 컬러 값이라면 어떨까? #FFF(흰색)에서 빨간색인 #F00이 중간값은 어떻게 될까? 어떻게 할진 모르겠지만 뭔가 굉장히 복잡할 것 같지 않은가? 이렇게 여러 가지 단위의 값들을 간단히 처리해 주는 역할을 Animatable 함수가 대신 처리해 주는 것이다. 매우 편리하다 할 수 있다.
버튼이 커지기 시작하는 코드는 keepGrowing 함수 안에 있다. 배경색을 변경하는 코드도 여기에 있어야 할 것이다. 컬러 값을 애니메이션 시키는 방법도 animate 함수로 똑같이 처리한다. 그야말로 만능 도구인 샘이다. keepGrowing 함수에 거침없이 코드를 추가해 보자.
const keepGrowing = () => {
if (isGrowing) {
animate.ease(data.backgroundColor, '#F00')
animate.ease(data.likeScale, data.likeScale.get() + 1)
}
}
확인해 보면 아무 변화도 일어나지 않는다! 왜냐하면 backgroundColor 값이 back 프레임과 연결되어 있지 않기 때문이다. like 프레임과 연결된 함수는 무엇인가? KeepGrowingButton이다. 마찬가지로 back 프레임에도 연결된 함수가 필요하다. FramerX는 디자인 요소 하나당 Override 함수 하나만을 연결시킬 수 있다. 그런 이유로 back 프레임에 연결할 Overrider 함수를 추가로 만들어야 한다. 다음과 같이 background 값을 제공할 함수 하나를 만들고 back 프레임의 Code/Override로 연결해 주자.
export const BackgroundStatus: Override = () => {
return {
background: data.backgroundColor
}
}
이제 비슷하게 되는 듯 하지만 손을 떼도 배경색은 빨갛게 변하고 만다. 손을 떼었을 때 즉, onTapEnd에서 흰색으로 회복시키는 코드가 필요하다. 추가해 보자. KeepGrowingButton 함수의 onTapEnd에 흰색으로 복귀하는 애니메이션을 추가한다.
onTapEnd()
isGrowing = false
animate.spring(data.likeScale, 1)
animate.spring(data.backgroundColor, '#FFF')
}
드디어 끝났다. 완전한 코드는 다음 링크에서 확인할 수 있다.
* 완성된 전체 코드
https://gist.github.com/ibare/91bb49c0fe98988a3b3347969abb3688
완성 코드를 보면 몇 가지 본문에서 설명한 코드 모양과 다른 점을 발견할 수 있을 것이다. 추가된 상수 STABLE_COLOR과 GROWING_COLOR를 사용하고 있는 부분 하나와 keepGrowing 함수에 추가된 if 문 하나가 그것이다. 하나씩 살펴보자.
코드를 작성하다 보면 코드 내에 필요한 값을 사용할 때가 많이 있다. 예를 들면 Animatable('#FFF')처럼 '#FFF'라는 문자열 값을 사용하는 것이다. 이것 자체가 특별히 문제라기보다는 저 값이 동일한 의미를 가진 채 코드 이곳저곳에서 반복 사용된다면 조금 귀찮은 일이 생기기 시작할 수 있다는 것이다. 이 예제에서 색상값 '#FFF'는 코드 내에 두 번 등장한다. 최초의 컬러를 지정할 때 1번 그리고 onTapEnd에서 원래 색으로 복귀하기 위해 1번.
세상에 없는 인터렉션을 만들어 실험하게 될 때 이런 값들은 수십 번 바꿔가며 원하는 최상의 값을 찾게 된다. 이렇게 "배경의 기본 색"이라는 동일 의미로 사용된 값이 변경되어야 한다면? 색을 10번 바꿔 봤다면 실제론 코드에선 20번 편집을 해야하는 번거로움이 생긴다. 조금 과장해서 조금 더 코드가 복잡해서 '#FFF'가 6번 정도 반복 사용되었다면 기본 색을 10번 바꾸면 60번 편집해야 하는 것이다. 60번 편집 과정에서 미처 바꾸지 못하거나 잘못된 색으로 변경하는 실수를 나올 확률은 얼마나 될까? 한 번이라도 실수가 나온다면 잘못 고쳐진 부분을 찾기 위해 또 시간을 낭비하게 될 것이다.
그래서 보통 1번 이상 사용하게 되는 값은 따로 변수에 담아 놓고 사용하는 것이 여러 가지 측면에서 효과적이다. 이런 값을 변수(let)가 아닌 상수(const)로 만드는 이유는 코드에서 혹시라도 변경하는 실수 조차 원천적으로 차단하기 위함인데 직업 프로그래머라면 좋은 습관이지만 우리는 직업 프로그래머가 되기 위해 코드를 배우는 것이 아니니 상수를 사용하든 변수를 사용하든 큰 차이가 나지는 않을 것이다.
* 두 개의 상수는 왜 대문자일까?
초기 색을 뜻하는 값을 담은 상수는 이름을 STABLE_COLOR로 지었고, 가장 진하게 될 컬러 값을 담은 상수는 GROWING_COLOR라 명명했다. 그런데 둘 다 모두 대문자로 지었는데 그 이유는 프로그래머들의 관습 같은 것이다. Javascript나 TypeScript는 둘 다 대소문자를 구분하는 언어다. 동일한 이름의 변수를 1개 이상 만들 수 없지만 대소문자 표기가 다르다면 color와 Color, COLOR처럼 여러 개 만들 수 있다. 그래서 대문자로 상수명을 지은 이유는 코드에서 상수를 사용할 때 대문자로 되어있는 것은 변경할 수 없는 "상수"라는 의미를 나타내자고 정한 관례적 표기법이다. - 관례라고 하면 현실에선 나쁜 의미인 경우가 많은데 이런 관례는 좋은 관례라 할 수 있다 ^^
하나 더!
* 단어와 단어의 구분
한 단어 이상을 조합하여 이름을 짓게 될 때 단어와 단어 사이 구분을 어떻게 할 것인가? 라는 것도 있다. backgroundcolorvalue 나 BACKGROUNDCOLORVALUE처럼 3 단어가 조합된 하나의 이름이 있다면 보기 불편하기 때문이다. 여러 가지 방식이 있는데 소문자를 사용할 경우 단어의 첫 글자를 대문자로 표기하는 "낙타 표기법"이 많이 사용된다. 낙타 표기법으로 변경하면 BackgroundColorValue 또는 첫 단어만 소문자 표기하는 backgroundColorValue가 된다. 훨씬 읽기 편하지 않은가? ^^ 좀 더 고전적인 방식으로 단어 구분을 언더스코어(_)를 사용하는 방식이 있다. background_color_value 이런 식인데 "스네이크 표기법"이라 부른다. 하지만 요즘은 많이 사용하지 않는다. - 표기법도 유행이 좀 있다 ^^. - 스네이크 표기법이 제한적으로 사용되는 곳이 있는데 대문자 상수명을 표기할 때다. 모두 대문자로 명명할 경우 단어 구분할 방법이 없기 때문에 언더스코어를 이용한 스네이크 표기법을 사용한다.
두 번째 keepGrowing 함수에 추가된 if 문에 대해 알아보자. 프로토타이핑을 만들다 보면 자연스럽게 디테일에 신경 쓰게 된다. 어쩌면 이 디테일한 부분을 추구하기 위해 어려움에도 불구하고 코드 쓰기 방식의 프로토타이핑 도구가 있는 게 아닌가 싶다. 암튼, 디테일을 추가하다 보면 세세한 코드가 추가될 수 있다. 추가된 if 문은 그런 측면의 코드다. like 버튼이 커지자마자 배경색이 변하는 것이 아니라 일정 수준 이상 커졌을 때부터 배경색이 반응하면 좀 더 좋지 않을까? 라는 생각의 구현이다.
그래서 isGrowing 이 true일 때 바로 animate 시키는 것이 아니라 backgroundColor는 like 버튼 크기가 최소 2배 이상 커졌을 때만 반응하도록 코드를 작성하였다.
if (isGrowing) {
if (data.likeScale.get() > 2) {
animate.ease(data.backgroundColor, GROWING_COLOR)
}
animate.ease(data.likeScale, data.likeScale.get() + 1)
}
난 1.3배 이상일 때 해보고 싶은데? 비교에 쓰인 2를 이런 값 저런 값으로 변경해 가며 테스트해 보면 될 것이다.
Framer 클래식 버전에서도 제공되었던 Inspector를 좀 더 적극적으로 이용할 수 있는 방법에 대해 알아보자. 클래식 버전과 달리 FramerX의 Inspector는 실행 창인 Preview 창에 완전히 결합되어 있다.
Inspector는 사실 개발자들을 위한 도구이며 엄청나게(?) 많은 기능을 제공한다. 하지만 그런 걸 일일이 배울 필요는 없다. 다만 FramerX를 이용할 때 Inspector가 제공하는 몇 가지 기능은 알아두면 아주 아주 아주! 유용할 수 있기에 잠깐 배우고 넘어가자.
Inspector는 여러 가지 기능을 Tab으로 그룹화하여 제공한다. Elements, Network, Debugger, Console 탭 등등. 우리의 관심은 Debugger와 Console 탭이다.
먼저 이런 가정을 한번 해보자. keepGrowing 함수에서 isGrowing 이 true일 때 likeScale의 실제 값을 몇일까? 확인하고 싶다. Framer 클래식에선 print라는 함수를 제공했다. FramerX에선 print함수는 없다. 대신 더 많은 기능을 가진 console이라는 객체를 제공한다. console 객체는 log, info, error, dir 등등 여러 가지 함수를 제공하는데 우리는 log 함수만 사용해 보도록 하자. - 특별히 다른 함수를 사용할 일이 없다 - log 함수는 다음과 같이 확인하고 싶은 값을 입력으로 넘겨준다.
if (isGrowing) {
console.log(data.likeScale)
....
}
Preview창에서 실행하여 Inspector 창을 확인해 본다. console.log가 실행된 결과는 Console 탭에서 확인할 수 있다.
data.likeScale 값은 실제 AnimatableValue 형태의 객체이며 값은 value 속성에 담겨있는 것을 확인할 수 있다. 값 만 표시하고 싶다면 console.log(data.likeScale.value) 또는 console.log(data.likeScale.get()) 이렇게 하면 되는데 후자를 추천한다. Console 탭 내부에도 필터가 존재한다. All, Errors, Warnings, Logs 등이 그것인데 각각 console.log로 출력된 결과만 보고 싶다면 Logs 탭을 선택하면 되고 모두 보고 싶다면 All을 선택해 놓으면 된다. All로 선택하면 이리가 다루기 버거운 많은 정보가 표시되니 Logs 만 보길 바란다.
이제 코드의 어떤 부분에서 어떤 값이 실제로 무엇인지 확인할 수 있는 방법을 알았다. 필요한 곳에 console.log를 삽입해 두면 되는 것이다. 하지만 이 기능만으론 좀 아쉽다. 실제 코드가 한 줄 한 줄 어떤 순서로 실행되는지 시각적으로 확인할 수 있는 기능도 제공되는데 어떻게 사용할 수 있는지 알아보자.
코드를 실행하는 상황을 시각적으로 확인하며 원하는 코드만큼 실행을 제어할 수 있는 도구를 Debugger라 한다. 옛날 옛적 컴퓨터 크기가 단독 주택만 했던 시절. 컴퓨터 내부에 벌레가 들어가 전기적 합선을 일으켜 컴퓨터가 오작동하는 상황을 고치기 위해 벌레를 잡는 일이 있었는데 여기서 유래된 용어라 한다. 암튼, Debugger 탭을 선택하면 Console 탭보다 더 복잡한 모습을 확인할 수 있다. 배워볼 엄두가 안 날 정도로 복잡하다 생각하시는 분들도 있을 듯하다. ^^. 다 배울 필요는 없고 유용한 몇 가지만 배워보자.
만약 좋아요 버튼에서 손을 떼었는데 버튼 크기가 줄어들지 않는 상황이 발생했다. 코드를 살펴봐도 왜 그런 결과가 발생했는지 도통 모르겠다. 대체 왜 onTapEnd 함수가 어떻게 실행되길래? 직접 확인해 보고 싶다. 그럼 onTapEnd 함수의 첫 번째 라인에 debugger라고 입력한 후 실행해 보자. 실행한 후 Inspector 창을 활성화시켜 놓아야 작동한다.
그림처럼 debugger 명령에 실행이 실제로 멈춰있는 것을 확인할 수 있을 것이다. 자, 이제 Debugger를 사용해 보자. isGrowing의 실제 값을 알고 싶은가? isGrowing 변수 위에 마우스 커서를 올려놓으면 된다. 원하는 변수의 실제 값을 알고 싶으면 동일하게 확인할 수 있다. 코드를 한 줄씩 실행해 볼 수 있을까? 당연히 가능하다.
그림의 왼쪽 상단에 위치한 아이콘이 보일 것이다.
아이콘 버튼 각각의 기능은 왼쪽부터
중단점 활성/비활성 (Disable all breakpoints)
다시 실행 (Continue script execution)
한 줄씩 실행, 함수도 하나의 명령으로 취급 (Step over)
한 줄씩 실행하나 함수 호출이 있다면 함수로 진입 (Step into)
함수 안쪽이라면 함수를 호출한 곳까지 실행 (Step out)
중단점 활성/비활성 버튼은 항상 활성 상태로 켜 놓길 바란다. 꺼놓으면 코드 실행을 멈출 수 없다. 주로 사용하는 기능은 Step over, Step into 두 가지다. over와 into의 대상은 함수다. 실행할 라인에 함수 호출이 없다면 두 기능은 동일하게 동작한다.
자, Step into와 Step over를 실행해서 한 줄씩 코드를 실행해 볼 수 있다. 그리고 원하는 위치에서 멈추게 하고 싶다면 debugger 명령 말고 중단점을(breakpoints) 설정하면 훨씬 편리하다. debugger 명령은 주로 Inspector 창에서 확인하고 싶은 특정 위치에서 멈추고 싶을 때 코드에 직접 삽입하여 빠르게 중단시킬 때 사용하고 세부적으로는 중단점을 많이 사용한다. 중단점 설정 방법은 Inspector의 Debugger 창에 보이는 소스 코드 창에서 왼쪽 줄번호를 클릭해서 중단점을 몇 개라도 만들 수 있다. 중단점이 설정되면 Continue script execution 버튼을 이용해서 확인이 필요하지 않은 코드는 빠르게 실행시키고 중단점에서 코드를 멈추게 할 수 있다. 글로 설명을 듣는 것보단 이것이 무엇인지 알았으니 직접 실행하며 느껴보자.
* 소스 코드 모양이 다른데?
Debugger 창에서 보는 코드는 우리가 에디터로 작성했던 코드와 다른 부분들이 있다. 똑같아 보이는 부분도 있지만 완전히 다른 형태로 변형된 코드도 있다. 흐름을 이해할 수 있는 수준에서 거의 유사하나 익숙하지 않은 상태에선 전혀 다른 코드처럼 느껴질 수 도 있겠다. 왜 그런가 하면 TypeScript는 브라우저가 실행할 수 없는 언어이기 때문이다. 브라우저가 실행시킬 수 있는 언어는 Javascript 밖에 없다. 이게 무슨 소리냐고? TypeScript로 코드를 작성하면 FramerX가 이를 Javascript로 변경한 후 실행하게 된다. 최대한 원본 코드와 유사하게 보이는 처리가 되어있어서 코드를 확인하는데 큰 문제는 없지만 그래도 완벽하게 똑같이 보여주지 못하는 부분이 있다. 그럼 애초에 Javascript를 사용하지 왜 TypeScript를 사용했을까? 그것은 FramerX 팀의 선택이니 우리가 뭐라 할 수 없겠다. ^^
* 브라우저라니? 그건 무슨 소린가?
브라우저가 지원하는 언어가 Javascript 뿐이란 소리는 무슨 소리인가? 우린 브라우저를 이용하고 있지 않고 FramerX를 사용하고 있는데? 맞다 FramerX를 사용하고 있는 것 맞다. 그런데 FramerX가 브라우저 기반으로 만들어져 있다. 일반 브라우저처럼 인터넷 탐색을 할 수는 없지만 그 외에는 브라우저와 동일한 기능을 가지고 있고 동일한 환경을 누릴 수 있다.
이제 FramerX의 기본적인 방식을 익히기 위한 네 번의 연재를 마쳤다. 다음 연재#3부터는 본격적으로 FramerX의 React 기능을 접목하여 클래식 버전과는 확연히 다른 기능들을 학습해 보자.
그렇다! FramerX가 React 기반으로 만들어져 있다고 하는데 우린 아직 React 모습을 본 적도, 사용해본 적 도 없는 것이다!!