일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- motion.div
- async
- Promise
- debounce
- named type
- animation
- javascript
- react
- RN아키텍쳐
- 비동기
- react-native-permissions
- axios
- react animation
- ios
- promise.all
- Hash-table
- react-native
- CS
- Throttle
- RN업데이트
- private-access-to-photos
- Swift
- rn
- no-permission-handler-detected
- react native
- await
- RN새로운아키텍쳐
- hydration mismatch
- react-native-image-picker
- React-Native업데이트
- Today
- Total
하루살이 개발일지
React Native Reanimated에 대해서 본문
React Native Reanimated는 iOS 및 Android 애플리케이션에 부드러운 애니메이션과 상호작용을 만들 수 있게 해주는 강력하고 직관적인 라이브러리이다. 리액트 네이티브에는 다양한 성능 좋은 애니메이션 라이브러리가 있으며, 기본적으로 제공되는 Animated API도 포함되어 있다. 하지만 이번에는 Reanimated를 깊이 있게 살펴보면서, 이 라이브러리가 왜 더 우수한 선택이라고 생각하는지 알아보려고 한다.
React Native Reanimated
# Reanimated 코드 실행
Reanimated의 핵심 강점은 리액트 네이티브 애플리케이션의 성능과 반응성을 향상시키는 능력에 있다. 이는 즉각적인 코드 실행을 통해서만 달성할 수 있는 부드러운 애니메이션을 제공한다.
Reanimated가 어떻게 작동하는지 이해하려면, 먼저 리액트 네이티브가 코드를 어떻게 실행하는지 이해해야 한다.
# 리액트 네이티브 스레드
리액트 네이티브는 시간 소모적인 JavaScript 코드를 UI의 반응성을 떨어뜨리지 않고 실행할 수 있도록 세 개의 별도 스레드를 사용한다. UI 스레드는 네이티브 스레드로, iOS에서는 Swift 또는 Objective C로, Android에서는 Java 또는 Kotlin으로 실행된다. 애플리케이션의 UI는 오직 UI 스레드에서만 조작된다.
JavaScript 스레드는 어떤 뷰를 표시할지와 그 방식 등 렌더링 로직을 담당한다. 이 스레드는 JavaScript 엔진을 통해 별도로 JavaScript를 실행한다. 마지막으로 브릿지는 UI와 JavaScript 스레드 간의 비동기 통신을 가능하게 한다. 성능 저하를 방지하기 위해서는 소량의 데이터만 전송하는 데 사용해야 한다.
이 상호작용은 JavaScript 스레드에서 시작되어 메인 스레드에 반영되어야 하므로, 이벤트 기반 상호작용을 처리할 때 문제가 발생할 수 있다.
# Reanimated vs. Animated API
UI와 JavaScript 스레드 간의 통신이 비동기적이기 때문에, Animated API는 업데이트를 최소한 한 프레임(약 16.67ms) 늦게 처리하게 된다. JavaScript 스레드가 React의 디핑(difference algorithm) 실행이나 네트워크 요청 처리 등의 다른 프로세스를 동시에 실행할 경우 지연 시간이 더 길어질 수 있다.
이러한 지연 현상을 프레임 드롭이라고 하며, 이는 사용자 경험(UX)에 악영향을 미쳐 애니메이션을 부자연스럽게 만들 수 있다. Reanimated는 애니메이션과 이벤트 처리 로직을 JavaScript 스레드에서 제거하고 이를 직접 UI 스레드에서 실행함으로써 이 문제를 해결한다.
또한, Reanimated는 워크렛(worklets)을 정의하는데, 이는 작은 JavaScript 코드 덩어리로, 별도의 JavaScript 가상 머신에서 실행되며 UI 스레드에서 동기적으로 실행된다. 워크렛은 애니메이션이 트리거되는 즉시 실행되어 더 만족스러운 사용자 경험을 제공한다.
# Shared Values
Shared Values는 일반적인 리액트 애플리케이션의 상태 데이터와 유사하게 Reanimated에서 동적 데이터를 저장한다. 그러나 상태 데이터와 달리 Shared Values는 UI 스레드와 JavaScript 스레드 간에 데이터를 공유한다.
이 Shared Values 내부의 데이터가 업데이트되면, Reanimated 라이브러리는 변경 사항을 등록하고 애니메이션을 실행하며, 이는 리액트가 상태 업데이트 시 컴포넌트를 다시 렌더링하는 것과 유사하다.
# Shared Value 생성
Shared Value 생성은 useState 훅을 사용하는 것과 매우 유사하다. 대신 useSharedValue 훅을 사용하면 된다.
예를 들어, 버튼이 눌렸을 때 높이가 변경되는 박스를 만들고자 한다면, Shared Value로 boxHeight 변수를 정의하여 애니메이션 중에 UI 스레드와 JavaScript 스레드 간에 이를 공유할 수 있게 한다.
const boxHeight = useSharedValue(150);
위 예시에서, boxHeight라는 Shared Value를 생성하고 초기값을 150으로 설정했다. 나중에 코드에서 boxHeight 값을 참조하려면 그냥 boxHeight 대신 boxHeight.value를 참조해야 한다.
useSharedValue 훅은 객체를 반환하며, 초기 값은 객체의 .value 속성에 저장된다. 사용자 입력 등 사전 정의된 이벤트에 따라 Shared Value를 업데이트하려면, 함수 내에서 .value 속성을 업데이트하면 된다. 그러면 Reanimated가 변경 사항을 등록할 것이다.
function toggleHeight() {
boxHeight.value === 450 ?
boxHeight.value = 150 :
boxHeight.value = 450;
}
이제 이러한 Shared Values를 Reanimated의 워크렛에서 어떻게 사용할 수 있는지 살펴보자.
# Reanimated 워크렛
워크렛은 UI 스레드에서 JavaScript 코드를 동기적으로 실행할 수 있게 해주는 간단한 함수이다. 일반적으로 워크렛은 리액트 컴포넌트의 스타일 속성을 반환한다. 워크렛은 참조하는 Shared Value의 변화에 의해 트리거된다.
워크렛을 선언하려면, 함수 정의의 시작 부분에 worklet 지시어를 사용할 수 있다. 아래 코드 블록에서는 worklet 지시어를 사용해 boxAnimation 함수를 선언하고 있으며, 이 함수가 워크렛임을 나타내고 있다. Shared Value인 boxHeight를 사용해 업데이트된 높이 속성을 반환하고 있다.
const boxAnimation = () => {
'worklet';
return {
height: withTiming(boxHeight.value, {duration: 750})
};
};
더 일반적으로는 useAnimatedStyle 훅을 사용해 워크렛을 선언할 수 있는데, 이는 콜백 함수를 인수로 전달하게 해준다. 이제 이 콜백 함수는 워크렛으로 취급될 것이다.
const boxAnimation = useAnimatedStyle(() => {
return {
height: withTiming(boxHeight.value, {duration: 750})
};
});
두 가지 워크렛 선언 방법 중에서는 useAnimatedStyle 훅을 사용하는 것을 추천한다. worklet 지시어 방법을 사용하려면, runOnUI 메서드를 호출하고 boxAnimation 함수를 매개변수로 전달하는 보조 함수를 추가해야 한다. 반면, useAnimatedStyle 훅은 이러한 보조 함수를 추상화하여 useAnimatedStyle 훅에 전달된 콜백을 자동으로 UI 스레드에서 실행하게 한다.
두 예시 모두에서 boxHeight.value 값이 업데이트될 때마다 워크렛은 박스가 수직으로 확장되거나 축소되는 애니메이션을 트리거할 것이다.
# 유틸리티 메서드
withTiming
위의 예시에서는 withTiming 유틸리티 메서드를 사용했으며, 이는 단순한 애니메이션을 생성하여 시작점에서 끝점으로 점진적으로 전환하도록 해준다. 이를 통해 전환의 지속 시간과 가속을 제어할 수 있다.
withTiming은 두 개의 매개변수를 받는다. 첫 번째 매개변수는 필수이며 업데이트될 Shared Value이다. 우리 경우에는 boxHeight이다.
두 번째 선택적 매개변수는 두 가지 속성이 있는 객체이다. duration은 애니메이션의 소요 시간을 제어하고, easing은 애니메이션의 가속과 감속을 제어한다. 기본값은 duration 300ms와 in-out quad easing이다.
withSpring
withSpring 메서드는 withTiming과 유사하지만, 요소가 끝점에 도달하기 전에 지나쳐서 끝 위치에 자리 잡는 애니메이션 효과를 만든다.
withSpring은 업데이트될 Shared Value라는 하나의 필수 매개변수만 있다. 또한, 아래 나열된 여섯 가지 선택적 매개변수도 있다. 하지만 대부분의 경우 기본값으로 충분하다.
- damping: 10
- mass: 1
- stiffness: 100
- overshootClamping: false
- restDisplacementThreshold: 0.01
- restSpeedThreshold: 2
useAnimatedStyle 훅
앞서 예시에서, useAnimatedStyle 훅은 Shared Value boxHeight와 boxHeight.value를 스타일 속성에서 사용하는 컴포넌트를 연결하는 워크렛을 생성한다. 리액트 컴포넌트에 속성을 부여할 때는 애니메이션을 적용할 수 있는 컴포넌트 버전을 사용해야 한다.
예를 들어, <View /> 태그 대신 <Animated.View /> 태그를 사용해야 한다. <Animated.View />의 모든 자식 요소는 부모에게 적용된 애니메이션의 영향을 받는다.
컴포넌트에 스타일을 설정할 때, 스타일을 배열로 전달해야 한다. 첫 번째 요소는 높이를 포함해 일반적으로 사용하는 모든 스타일을 담은 객체이다. 두 번째 요소는 앞서 정의한 워크렛이다.
이제 우리가 다룬 모든 Reanimated 도구들을 결합하여, 버튼을 누르면 확장되고 축소되는 간단한 회색 박스를 만들어보자.
// import 구문에서 Reanimated의 훅과 메서드를 사용할 수 있는 기능 추가
import React from 'react';
import { View, Button } from 'react-native';
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
export default function ExpandingTextBox() {
// useSharedValue를 통해 Shared Value 생성
const boxHeight = useSharedValue(150);
// useAnimatedStyle을 통해 워크렛 생성, withTiming 메서드 사용
const boxAnimation = useAnimatedStyle(() => {
return {
height: withTiming(boxHeight.value, {duration: 750})
}
});
// 박스의 높이를 확장하고 축소할 수 있도록 boxHeight 값을 토글하는 함수
function toggleHeight() {
boxHeight.value === 450 ?
boxHeight.value = 150 :
boxHeight.value = 450;
};
// 회색 박스에 사용할 스타일
const styles = {
box: {
width: 400,
height: 150,
backgroundColor: 'grey',
borderRadius: 15,
margin: 100,
padding: 20,
display: 'flex'
}
};
return (
<View style={styles.app}>
{/* Animated.View 컴포넌트로, 스타일.box에 포함된 일반적인 스타일과
높이 속성을 업데이트하는 worklet "boxHeight"를 함께 전달 */}
<Animated.View style={[styles.box, boxAnimation]}>
{/* 버튼이 눌릴 때마다 toggleHeight() 함수 실행 */}
<Button title='More' onPress={() => toggleHeight()} />
</Animated.View>
</View>
)
};
위 코드의 출력 결과는 아래 애니메이션과 같을 것이다.
# 결론
Reanimated는 이벤트 기반 상호작용을 JavaScript 스레드에서 분리하고 대신 이를 UI 스레드에서 동기적으로 실행한다. Reanimated를 사용하면 프레임 드롭이나 JavaScript 스레드의 작업 부하를 걱정할 필요가 없다.
Reanimated의 메서드, 훅 및 애니메이션에 대한 추가 정보는 Reanimated 문서를 참조하는 것을 추천한다.
Reference
https://blog.logrocket.com/deep-dive-react-native-reanimated/
'앱개발 > React-Native' 카테고리의 다른 글
Git 워크플로우 및 커밋/푸시 컨벤션 설정 (husky + commitizen) (2) | 2024.12.30 |
---|---|
생체인증을 통한 전자서명 (RSA 암호화와 SHA-256 해싱 알고리즘) (1) | 2024.12.29 |
[RN] Deep Link란? (0) | 2024.06.13 |
React Native Reanimated란? (0) | 2024.04.17 |
React-Native의 동작원리 및 새로운 아키텍쳐의 도입 / React-Native 새로운 아키텍쳐 특징 및 구버전과의 차이점 (8) | 2024.03.16 |