원리와 예제를 통해 React-native-reanimated V2 입문하기

Dev-Yuns
Cross-Platform Korea
8 min readJul 24, 2021
Photo by Mateusz Butkiewicz on Unsplash

최근 개발 중인 앱에서 꽤 복잡한 애니메이션을 구현할 일이 있어 react-native-reanimated V2를 사용해 볼 수 있었습니다. V1의 경우 함수형 스타일에 익숙하지 않아 꽤 진입장벽이 있었는데, V2에서 다시 익숙한 형태로 코딩을 할 수 있게되어 사용성이 더 좋아진 것 같습니다. 이번 포스팅에서는 Reanimated 의 배경과 원리를 간단히 알아보고 예시를 통해 살펴보겠습니다.

Reanimated의 등장 배경

부드러운 애니메이션을 구현하기 위해서는 초당 60프레임의 화면전환이 필요합니다. 그리고 이를 위해서는 16밀리초 내에 프레임이 렌더링되어야 해야합니다. RN에서 기본적으로 제공되는 gestureAnimated API를 사용하면 애니메이션의 계산을 UI ThreadJS Thread의 커뮤니케이션에 의존해야 합니다. 그리고 두 쓰레드 간의 통신은 비동기로 이루어지기 때문에 response가 16 밀리초 이내에 오는 것을 보장할 수 없습니다. 특히 모바일의 성능이 떨어지는 경우에는 시간이 더 지연될 수도 있습니다.

Thread 간의 통신은 Bridge를 통해 비동기적으로 메시지를 주고 받습니다

반면 reanimated는 모든 애니메이션 관련 로직을 UI Thread에서 실행함으로써 JS Thread가 무거운 작업으로 인해 병목현상이 발생하더라도 프레임 드랍없이 애니메이션을 실행할 수 있도록 해줍니다.

V1에서는 JS측에서 애니메이션을 선언하여 UI thread에서 동작할 수 있는 DSL(Domain-Specific language)를 제공했습니다. 그러나 V1은 함수형 코드 스타일, 자바스크립트의 value들을 DSL에서 사용하기 위한 여러가지 툴들이 필요하다는 불편함이 존재했습니다. 또한 JS Thread에서 UI Thread를 향한 애니메이션과 제스쳐의 선언은 꽤 비용이 들어가는 작업이었습니다.

Reanimated V2 개념

Worklet

V2에서는 이러한 문제점을 개선하고 사용성을 높이기 위해worklet이라는 개념을 소개합니다. worklet은 자바스크립트 함수인데, UI Thread내의 독립된 context에서 동작합니다. 이 함수는 JS Thread에서 parameter를 받을 수 있고, 함수 내부에서 JS Thread의 상수들에도 접근할 수 있습니다. V1에서는 numberstring만 사용할 수 있었던 것에 반해 모든 자바스크립트 타입을 사용할 수 있다는 것도 큰 차이점입니다.

worklet 함수를 사용하기 위해서는 함수 내부의 첫 줄에 worklet이라는 키워드를 추가하기만 하면 됩니다. 내부에서 비동기적으로 JS 함수를 호출할 수도 있지만, worklet 함수는 다른 context에서 동작한다는 점을 유의해야 합니다. 물론 worklet 함수 간에는 동기적으로 호출이 가능합니다.

// 아래와 같이 'worklet' 키워드와 함께 함수를 선언하면 별개의 컴파일 과정을 거칩니다.
function workletFunc() {
"worklet";
// do something...}

정리하자면 worklet 의 목적은 UI Thread 에서View Property 를 업데이트하거나 이벤트에 반응할 때, UI Thread 내부에서 실행시킬 자바스크립트 코드를 정의해두는 것입니다.

SharedValue

sharedValueJS ThreadUI Thread 양측 모두에서 읽고 수정할 수 있는 mutable한 값 입니다. RN의 Animated API 에서 제공하는 Animated.Values 와 유사하다고 볼 수도 있습니다. sharedValue의 값은 .value 를 통해 접근할 수 있습니다.

두 쓰레드 모두에서 값에 접근할 수 있지만, 값이 업데이트 되는 과정은 약간 다릅니다. 애니메이션 값의 업데이트는 대부분 UI Thread 측에서 일어나므로 sharedValue 의 값은 UI Thread 에서 읽히도록 최적화되어 있습니다. 따라서 UI Thread 에서 수행되는 읽기와 쓰기는 모두 동기적으로 실행됩니다. 즉 worklet함수에서 값이 업데이트되면 해당 호출 직후에 바로 값이 업데이트될 것입니다.

반면 JS Thread 에서의sharedValue업데이트는 비동기입니다. 업데이트가 즉시 실행되지 않고 Reanimated 코어는 업데이트가 UI Thread 에서 수행되도록 예약하여 동시성 문제를 해결합니다. 따라서 JS Thread 에서 sharedValue 값을 업데이트하는 것은 마치 reactstate 처럼 즉시 이루어지는 것이 아니라 다음 렌더링을 기다려야 합니다.

import {useSharedValue} from 'react-native-reanimated';//const sharedValue = useSharedValue(0);console.log(sharedValue.value);//

reanimated는 workletsharedValue를 통해 사용성과 성능이라는 두 마리 토끼를 모두 잡았다고 생각합니다. 이제 간단한 예제를 통해 어떻게 사용하는지 알아보겠습니다.

간단한 애니메이션 예제

PanGestureHandler 를 사용해서 손가락을 따라 컴포넌트가 움직이는 애니메이션을 만들어보겠습니다. 저는 Expo 에서 프로젝트를 실행 했으며 typescript 를 사용했습니다.

  1. Container layout 잡기

먼저 컴포넌트가 특정 View 내부에서만 애니메이션이 되도록하기 위해 onLayout 이벤트를 통해 컨테이너 뷰의 width 값과 height 값을 전달 해줍니다. 아래 코드에서 Box 컴포넌트가 애니메이션이 될 대상이고, Home 이 랜더링되면서 onLayout 이벤트를 통해 Container 의 값을 전달해주고 있습니다.

2. 애니메이션 값 만들기

컴포넌트의 움직임은 translate 값에 의존하게 됩니다. useSharedValue 를 통해 선언해줍니다. 이 값은 이벤트 함수 내부에서 이벤트의 값에 따라 계속 업데이트 되며 style 값에 바인딩 되어 업데이트된 값이 실제 style props에 반영될 수 있도록 합니다.

const translateX = useSharedValue(0);
const translateY = useSharedValue(0);

3. 이벤트 함수 만들기

이벤트가 일어나면 수행될 이벤트 함수를 만들어줍니다. reanimated에서 importuseAnimatedGestureHandler 를 사용합니다. 제네릭으로 필요한 타입을 주입해줄 수 있습니다. onStart는 제스처가 시작될 때, onActive 는 제스처가 진행되는 동안, onEnd 는 제스처가 종료되었을 때 호출됩니다. 각 프로퍼티에 할당된 콜백은 모두 event 객체와 context 객체를 받아올 수 있습니다.

4. 애니메이션 스타일 만들기

reanimated 에서는 useAnimatedStyle 이라는 훅을 통해 애니메이션 스타일을 관리합니다. 이 훅을 통해 sharedValueView properties 간의 관계가 만들어집니다. 훅을 통해 만들어진 animStyle 은 매번 sharedValue 값이 업데이트 될 때마다 업데이트됩니다. 만약 특정 조건에서 업데이트 되도록 하고 싶다면 useEffect 와 유사하게 두번째 인자로 [dependencies] 를 추가해줄 수도 있습니다. 이 훅을 통해 만들어진 값은 반드시 Reanimated 에서 importAnimated 컴포넌트의 style 프로퍼티에 할당해주어야 합니다.

5. 애니메이션 값 할당

앞에서 만든 이벤트와 스타일 값을 할당하고, 애니메이션이 될 컴포넌트를 감싸줍니다. 이제 터치 이벤트에 반응하여 애니메이션이 실행될 것입니다.

V1보다 훨씬 쉬워진 방법으로 아래와 같이 아주 잘 작동하는 것을 확인하실 수 있습니다. 만약 for문 등을 통해 JS Thread를 느리게 만들 수 있는 함수를 임의로 만들어서 실행시켜도 애니메이션이 잘 동작하는 것을 확인하실 수 있을 것입니다. 테스트해본 결과 웹에서도 아주 부드럽게 동작합니다. 다만 현재 v2는 react native 0.62 이상에서 제대로 동작하기 때문에 참고하시기 바랍니다.

완성된 예제는 아래 repo에서 확인하실 수 있습니다. 프로젝트에 reanimated를 한번 사용해보시는 것도 추천드립니다. 글의 내용에 오류가 있거나 질문이 있으시면 언제든지 댓글 부탁드립니다. 감사합니다.

--

--