React Native를 시작하며 기본적인 스타일링부터 시작해서 각 플랫폼 별로 별도 스타일을 적용하는 방법까지 학습하고 애니메이션, web에서는 hover효과 적용을, 앱에서는 클릭 시 버튼 색상 변경 등 Hook을 이용한 동적 변경에 대해서 알아보고자 한다.
모든 속성을 다루진 않지만 많이 사용하며 중요하다고 생각했던 것 위주로 정리하였다.
React native는 모바일 플랫폼을 기준으로 개발되었기 때문에 hover효과는 web환경에서만 작동한다. hover를 적용하고 싶을때는 onHoverIn과 onHoverOut을 사용할 수 있는데 onHoverIn은 마우스 커서가 Hover가 적용될 대상 영역 안에 들어갔을때 자동으로 콜백되는 함수고 onHoverOut은 대상 영역 안에서 바깥으로 out될 때 자동으로 콜백되는 함수다. 따라서 이 변경 이벤트 발생시에 색상을 변경해주면 되는 간단한 작업이다. 이때 Animated함수를 사용하여 천천히 애니메이션되며 변경되도록 구현하였다.
// useRef hook으로 값 참조.
const bgColor = useRef(new Animated.Value(0)).current;
// 변경될 색상
const interpolatedBgColor = bgColor.interpolate({
// interpolate - animation 값이 변경됨에 따라 중간값을 계산해줌.
// 따라서 0에서 1로 변경시 색상이 천천히 변경되는 효과 발생(duration)
inputRange: [0, 1],
outputRange: ["#ffffff", "#ecfdf5"], // 0일때 white, 1일때 green
});
// 색상 변경 시 애니메이션으로 천천히 바뀌도록
const handleHoverAnimation = (isActive, animationValue) => {
// Animated.timing으로 value값 변경 가능.
Animated.timing(backgroundColor, {
toValue: isActive ? 1 : 0, // 활성화 시 1로, 비활성화 시 0으로 설정
delay: 0, // 시작 지연 시간
isInteraction: true, // 다른 상호작용과 연결되는 지 - 기본값 true
easing: Easing.inOut(Easing.ease), // 애니메이션 가속도 함수 - 기본값: Easing.inOut(Easing.ease)
duration: 200, // 애니메이션 지속 시간
useNativeDriver: false, // backgroundColor 애니메이션에서는 useNativeDriver를 false로 설정
}).start()
};
// pressable view는 전체 컨테이너 크기 및 padding등 전체 구조를 잡음.
<Pressable
style={styles.recordBtn}
onPressIn={() => Platform.OS !== "web" && handleHoverAnimation(true)} // 웹이 아닌 경우에만 press 동작
onPressOut={() => Platform.OS !== "web" && handleHoverAnimation(false)} // 웹이 아닌 경우에만 press 동작
onHoverIn={() => Platform.OS === "web" && handleHoverAnimation(true)} // 웹에서만 hover 동작
onHoverOut={() => Platform.OS === "web" && handleHoverAnimation(false)} // 웹에서만 hover 동작
>
// 변경될 view가 보이는 animated.view
<Animated.View
style={[
styles.uploadAnimated,
{ backgroundColor: interpolatedUploadBackgroundColor },
]}
>
// animated view안에 설정할 다른 view
<추가될 view>
</Animated.View>
</Pressable>
위에서 Hover를 사용할 때도 Animation을 통해 Hover를 controll 했는데 더욱 자세히 알아보고자 한다.
// 배경 애니메이션 처리
const Animation = () => {
// loop: 특정 애니메이션 무한 반복
Animated.loop(
// sequence: 여러 애니메이션 순차 반복 즉, 1 -> 0 -> 1 -> 0으로 반복실행
/*
Animated.parallel ([ // 여러 애니메이션을 동시에 실행 - 개별 duration적용이 되어야 할 경우에는 별로다.
// 여기에 삽입될 Animated.timing 등
])
Animated.stagger(300, [ // 애니메이션들 간에 지정된 시간만큼 간격을 두고 순차적으로 실행 시킨다.
// 여기에 삽입될 Animated.timing 등
])
*/
Animated.sequence([
// Animated.delay(1000), // stagger 없이 그냥 delay를 줄 수도 있다.
// animationValue: 변경될 값
Animated.timing(animationValue, {
toValue: 1,
duration: 1000,
useNativeDriver: false,
}),
Animated.timing(animationValue, {
toValue: 0,
duration: 1000,
useNativeDriver: false,
}),
/* Animated.spring: 값이 스프링 처럼 튀는 애니메이션 설정 가능{
toValue: 1,
friction: 5, // 저항이 크면 덜 튕긴다.
tension: 40, // 애니메이션의 강도
useNativeDriver: true,
}
*/
])
).start();
};
대표적인 비동기 프로그래밍인 promise와 async/await에 관해서 알아보자
const fetchData = () => {
return fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Error:', error);
});
}
fetch함수를 통해 결과를 반환받고 then, catch, finally 메서드 체인을 사용해서 결과값에 대해 가공이 가능하다. 하지만 비동기 작업이 중첩되어 코드가 복잡해지고 가독성이 async/await에 비해 떨어지는 경향이 있다. (callback hell)
const fetchData = async() => {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
비동기 작업을 마치 동기 코드처럼 작성할 수 있어 가독성이 높아지지만 async함수 내에서만 await를 사용할 수 있다.
async 함수도 항상 promise 형태로 반환하게 되는데 보이지 않지만 Promise.resolve()로 감싸져서 반환되기 때문에 async를 사용할때 promise타입 에러가 발생할 수 있다.
expo를 사용중이기 때문에 expo-av를 사용하여 Audio를 읽기, 쓰기, 재생하기를 Hook로 나눠 구현하였다.
import { Audio } from 'expo-av';
오디오를 사용하기 전에 여타 그렇듯이 사용자의 디바이스 기능에 접근하려면 권한을 먼저 얻어야 한다. 매우 간단하게 오디오 권한 요청 후 권한을 얻지 못하면 Error log를 띄우게 했다
// 오디오 권한 요청
const permission = await Audio.requestPermissionsAsync();
if (permission.status !== 'granted') {
// GRANTED, UNDETERMINED, DENIED 3가지 있음.
throw new Error('Permission to access microphone is required!');
}
녹음 시작은 Recording을 주면 되는데 Audio에 따로 option을 설정해서 커스텀또한 가능하다.
const { recording } = await Audio.Recording.createAsync(
Audio.RECORDING_OPTIONS_PRESET_HIGH_QUALITY
);
저장의 경우 Platfrom별로 나누어서 web에서는 download폴더 내부에 저장되도록 document에서 download에 경로 설정 후 일정 랜덤 값으로 recording-${Math.random}.m4a로 저장되도록 했다.
if (Platform.OS === 'android') {
// Android의 공용 저장소에 저장
newUri = await saveToAndroidPublicFolder(uri);
} else if (Platform.OS === 'ios') {
// iOS의 Recording 폴더에 저장
newUri = await saveToIosPublicFolder(uri);
} else if (Platform.OS === 'web') {
// 웹에서는 브라우저의 download 기능을 사용
downloadFileForWeb(uri);
}
해당 사이트를 참고하였다.