일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- await
- react-native-permissions
- debounce
- Promise
- React-Native업데이트
- react-native-image-picker
- private-access-to-photos
- Throttle
- RN아키텍쳐
- javascript
- rn
- 비동기
- motion.div
- react
- animation
- CS
- promise.all
- react animation
- ios
- RN업데이트
- no-permission-handler-detected
- Hash-table
- react native
- async
- RN새로운아키텍쳐
- Swift
- react-native
- hydration mismatch
- named type
- axios
- Today
- Total
하루살이 개발일지
React Native Reanimated란? 본문
1. React Native Reanimated란?
React Native는 Software Mansion에서 만든 라이브러리이다.
Reanimated는 UI 스레드에서 실행되는 복잡한 애니메이션과 상호작용을 구현할 때 사용되는 라이브러리로서, React Native 애플리케이션에서 매끄러운 애니메이션 경험을 제공한다.
1-1. Reanimated 의 필요성
1️⃣ 성능 최적화
Reanimated는 애니메이션 작업을 주로 UI 스레드에서 처리해서 자바스크립트 스레드의 부하를 줄인다. 이를 통해 애니메이션이 JS 스레드에 의존하지 않기 때문에, JS 스레드가 바쁘더라도 애니메이션의 부드러움이 유지된다.
2️⃣ 복잡한 애니메이션 구현
Reanimated는 복잡한 애니메이션과 상호작용을 섬세하게 제어할 수 있는 API를 제공한다. 이를 통해 더욱 동적이고 인터렉티브한 사용자 경험을 설계할 수 있다.
3️⃣ Declarative 인터페이스
Reanimated는 선언적(declarative) 코딩 스타일을 사용해 애니메이션 로직을 정의한다. 이는 애니메이션을 더 쉽게 구현할 수 있게 해주며 코드 가독성과 유지보수성을 향상시킨다.
1-2. 기본 애니메이션과의 차이점
1️⃣ 실행 방식
기본 애니메이션 라이브러리 : 자바스크립트 스레드를 사용해 애니메이션 값을 계산하고, 이를 브리지를 통해 네이티브 측으로 전송한다. 이 과정에서 딜레이나 프레임 손실이 발생할 수 있는데 이는 애니메이션의 부드러움을 저해할 수 있다.
Reanimated : 대부분의 계산을 네이티브 측에서 처리해서 이러한 문제를 최소화한다.
2️⃣ 성능
Reanimated는 애니메이션 실행을 위한 네이티브 코드 최적화가 이루어져 있어 높은 성능이 보장된다. 특히 복잡한 인터랙션이나 동시에 여러 애니메이션을 실행하는 경우에도 성능이 우수하다.
1-3. React Native의 스레드
스레드란?
컴퓨터가 작업을 동시에 처리할 수 있게 해주는 기능. React Native는 주로 두 개의 스레드를 사용한다 :
- UI 스레드 : 모든 UI 관련 작업은 ui 스레드에서 처리된다.
- JavaScript 스레드 : 이 스레드에서는 React 렌더링 로직이 처리된다. 즉 React의 렌더 단계와 레이아웃 처리가 실행되는 스레드이다.
렌더링 시나리오
그림에 대한 설명
이 이미지는 React Native의 렌더링 파이프라인의 각 단계를 나타내는데
1.Event: 사용자의 상호작용 또는 시스템 이벤트. 예를 들어 사용자가 스크린을 탭하거나 스크롤하는 행위 등
2.Render: React 컴포넌트의 새로운 상태나 프로퍼티를 바탕으로 새로운 뷰를 생성하거나 업데이트를 준비하는 단계. '렌더'는 새로운 컴포넌트 트리를 만드는 과정을 의미
3.Layout: 렌더링된 컴포넌트의 레이아웃 계산이 수행. 여기서는 각 요소의 크기와 위치가 계산되어 화면에 어떻게 배치될지 결정됨
4.Commit: 준비된 렌더링 결과(컴포넌트 트리)를 실제 네이티브 뷰에 적용하는 단계. 이 과정에서 변경 사항이 실제로 화면에 반영됨
5.Mount: 커밋 단계에서 적용된 변경 사항이 실제로 화면에 나타나고, 사용자에게 보이게 되는 단계
6.Interruption: 이벤트 처리나 다른 렌더링 작업 등의 이유로 현재 진행 중인 단계가 중단될 수 있다는 것. 높은 우선 순위를 가진 이벤트가 발생하면 현재의 렌더, 레이아웃 또는 커밋 과정이 중단될 수 있음
1️⃣ JavaScript 스레드 렌더링
대부분의 경우, 렌더링 작업은 JavaScript 스레드에서 이루어진다.
2️⃣ UI 스레드 렌더링
어떤 중요한 이벤트가 발생하면, 렌더링 과정을 UI 스레드에서 바로 처리해 빠르게 화면을 갱신할 수 있다.
3️⃣ 이벤트에 의한 중단 - 기본 또는 지속적
낮은 우선순위 이벤트가 발생했을때이다. ex. 비동기 데이터 로드(후 메인 스레드에서 다시 렌더링하는 작업)
이 경우 JS 스레드에서 진행중이던 렌더링 작업은 계속해서 진행된다. 하지만 UI 스레드는 낮은 우선순위 이벤트를 처리하기 위해 일시적으로 렌더링을 중단할 수 있다. (만약 높은 우선순위 이벤트가 있다면, 이를 먼저 처리하고 낮은 우선순위를 처리함)
즉 JS 스레드에서 렌더링 과정은 중단되지 않고 계속 진행되어 앱이 계속 부드럽게 동작할 수 있다.
4️⃣ 이벤트에 의한 중단 - 이산
높은 우선순위 이벤트가 발생했을 때이다. ex. 버튼 클릭, 스크롤 행위
이 경우 UI 스레드는 이벤트를 처리하기 위해 JS 스레드에서 진행중이던 렌더링 작업을 일시 중단한다. 중단된 렌더링은 이벤트 처리가 끝난 후 UI 스레드에서 동기적으로 재개되고, 필요한 상태 업데이트가 병합되어서 처리된다.
즉 중요 이벤트가 우선적으로 처리되고 이벤트 처리 후에는 중단되었던 렌더링 작업이 다시 시작돼 사용자의 중요한 입력에 즉시 반응할 수 있다.
1-4. Reanimated의 worklet
worklet이란?
UI 스레드에서 실행되는 작은 자바스크립트 코드 조각이다.
Reanimated에서 작업하면 대부분의 경우 코드가 자동으로 worklet화되어 UI 스레드에서 실행된다. 이를 통해 Reanimated에서 복잡한 애니메이션 로직을 JS 스레드에 분리해 UI 스레드에서 직접 실행시킬 수 있게 된다.
이는 위에서 설명한 '이산적 이벤트에 의한 중단' 원리와 관련이 있다. 예를 들어 사용자가 버튼을 누르는 중요한 상호작용이 발생하면 UI 스레드가 즉각 반응해 이벤트를 처리해준다.
이렇게 Worklet은 이벤트 처리를 UI 스레드에서 직접 실행하게 함으로써, JS 스레드와 UI 스레드간의 커뮤니케이션을 최소화한다. 이는 애니메이션과 같은 UI 업데이트가 중요한 사용자 입력에 대해 빠르고 부드럽게 반응할 수 있게 해준다. 즉 사용자 인터렉션이 발생했을 때 worklet을 통해 UI 스레드가 높은 우선순위의 작업을 처리하면서도 애니메이션을 중단하지 않고 계속 진행시킬 수 있게 해주는 것이다.
2. 기본적인 사용
2-1. useSharedValue
useSharedValue
Reanimated에서 제공하는 hook으로서, 애니메이션에서 사용되는 값을 관리한다. 이 값을 여러 worklet간 공유할 수 있다.
useSharedValue를 사용하면 UI 스레드와 JS 스레드 간의 동기화 없이 공유된 값을 읽고 쓸 수 있다.
Shared Value
useSharedValue에 의해 생성된 값으로 Reanimated의 네이티브 및 JS 코드 사이에서 '공유'된다. 이 값은 Reanimated의 네이티브 레이어에서 직접 접근하고 수정할 수 있으므로, 애니메이션 또는 상태가 UI 스레드에서 업데이트될 때 JS 스레드와 동기화할 필요가 없다.
declarative animation
useSharedValue로 생성된 값은 선언적 방식으로 애니메이션을 정의하는데 사용된다. 즉, 애니메이션의 시작, 끝, 타이밍, 상호작용 등을 명시적으로 정의하지 않고, 값의 변화만 선언하면 Reanimated가 이에 따른 애니메이션을 자동으로 처리한다.
예제
import { useSharedValue } from 'react-native-reanimated';
const sharedValue = useSharedValue(0); // 초기값
useSharedValue 훅을 사용해 sharedValue를 생성하고 초기값을 할당한다.
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [{ translateX: sharedValue.value }], // sharedValue에 따라 X축으로 이동하는 스타일
};
});
useAnimatedStyle을 통해 shared value를 이용해 애니메이션을 생성한다. 이는 해당 값이 변할 때마다 스타일이 업데이트된다.
const handlePress = () => {
sharedValue.value = 100;
};
sharedValue를 업데이트하면 애니메이션이 실행된다. 이때 value 속성을 통해 값을 직접 변경한다.
버튼을 누르면 애니메이션되는 것을 볼 수 있다.
전체 코드 공유
export const Test = memo(() => {
const sharedValue = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [{translateX: sharedValue.value}],
};
});
const handlePress = () => {
sharedValue.value = withTiming(100, {
duration: 500,
easing: Easing.out(Easing.cubic),
});
};
return (
<SafeAreaView style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
<Animated.View
style={[
{
width: 100,
height: 100,
backgroundColor: 'violet',
alignItems: 'center',
justifyContent: 'center',
},
animatedStyle,
]}>
<Button onPress={handlePress} title="Click" />
</Animated.View>
</SafeAreaView>
);
});
2-2. Animating styles and props
기본적으로, 스타일을 애니메이션화하는 과정에서 공유값을 인라인 스타일로 직접 전달할 수 있지만, 이 방식은 공유값에서 저장된 값을 직접 접근하거나 연산을 적용하는 데 한계가 있다.
<Animated.View style={{ width: width * 5 }} /> // this won't work
이와 같은 코드는 동작하지 않는다.
useAnimatedStyle
대신, useAnimatedStyle 훅을 사용하면 공유값에 저장된 값에 접근하고 이를 스타일 속성에 적용하기 전에 바꿀 수 있다. 이는 좀 더 복잡한 연산의 애니메이션을 생성할 때 유용하다.
export default function App() {
const translateX = useSharedValue(0);
const handlePress = () => {
translateX.value += 50;
};
const animatedStyles = useAnimatedStyle(() => ({
transform: [{ translateX: withSpring(translateX.value * 2) }],
}));
return (
<>
<Animated.View style={[styles.box, animatedStyles]} />
<View style={styles.container}>
<Button onPress={handlePress} title="Click me" />
</View>
</>
);
이 방식을 통해 스타일에 변경사항을 할당하기 전 2를 곱할 수 있다. 이렇게 하면 인라인으로 사용했을 때보다 훨씬 로직을 한 곳에 모아 볼 수 있다는 장점도 있다.
useAnimatedProps
또한 스타일뿐만 아니라 컴포넌트의 프로퍼티를 애니메이션화할 필요가 있을 때가 있다. 예를 들어 SVG의 경우 스타일 대신 프로퍼티로 값을 정의한다. 이 경우 createAnimatedComponent를 사용하여 컴포넌트(Reanimated의 구성요소가 아닌)를 애니메이션화할 수 있다.
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
export const Test = memo(() => {
const r = useSharedValue(20);
const handlePress = () => {
r.value += 10;
};
return (
<View style={styles.container}>
<Svg style={styles.svg}>
<AnimatedCircle cx="50%" cy="50%" r={r} fill="blue" />
</Svg>
<Button onPress={handlePress} title="Click me" />
</View>
)
});
원의 반지름(r) 에 애니메이션을 주고 싶다면, shared value값을 prop으로 전달하면 된다.
이렇게까지만 해도 잘 동작하지만, useAnimatedStyle과 동일하게 useAnimatedProps를 사용하면 로직을 캡슐화하고 공유값에 접근할 수 있으며, 이를 컴포넌트의 프로퍼티로 전달하기 전에 조정할 수 있다.
다음과 같이 useAnimationProps와 withTiming을 사용해 원을 부드럽게 늘릴 수 있다.
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
export const TestScreen = memo(() => {
const r = useSharedValue(20);
const handlePress = () => {
r.value += 10;
};
const animatedProps = useAnimatedProps(() => ({
r: withTiming(r.value),
}));
return (
<View style={styles.container}>
<Svg style={styles.svg}>
<AnimatedCircle cx="50%" cy="50%" fill="#b58df1" animatedProps={animatedProps} />
</Svg>
<Button onPress={handlePress} title="Click me" />
</View>
);
});
이외에도 아주 다양한 기능과 api가 있지만, 이후 글에서 이어서 작성하도록 하겠다.
출처
https://docs.swmansion.com/react-native-reanimated/
https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary/#worklet
https://reactnative.dev/architecture/threading-model
'앱개발 > React-Native' 카테고리의 다른 글
React Native Reanimated에 대해서 (4) | 2024.09.01 |
---|---|
[RN] Deep Link란? (0) | 2024.06.13 |
React-Native의 동작원리 및 새로운 아키텍쳐의 도입 / React-Native 새로운 아키텍쳐 특징 및 구버전과의 차이점 (8) | 2024.03.16 |
React-Native Cli로 React-Native 프로젝트 세팅하기 (iOS, macOS) (0) | 2024.01.18 |
[react-native] <Text> 컴포넌트 quick fix의 import가 되지 않는 현상 해결 (1) | 2023.10.25 |