ReactNative - 기본 사용법 (1)

MUNGI JO·2024년 8월 19일

React와 React Native

목록 보기
6/6

서론

React Native를 시작하며 기본적인 스타일링부터 시작해서 각 플랫폼 별로 별도 스타일을 적용하는 방법까지 학습하고 애니메이션, web에서는 hover효과 적용을, 앱에서는 클릭 시 버튼 색상 변경 등 Hook을 이용한 동적 변경에 대해서 알아보고자 한다.

React Native Design

기본 속성

모든 속성을 다루진 않지만 많이 사용하며 중요하다고 생각했던 것 위주로 정리하였다.

  • flex: 크기를 비율로 설정, 같은 부모를 가진 여러 자식뷰에 flex:1을 동일하게 준다면, 모두 같은 크기를 가지게 된다. 즉, 부모 view 크기의 특정 비율만큼 차지하게 하는 속성이다.
  • flexDirection: 자식 뷰들이 배치될 방향의 주축을 정한다. row는 가로축으로 column은 세로축으로 자식 뷰들을 정렬한다.
  • alignItems: 교차 축에서 자식 요소를 어떻게 정렬할 지 결정한다. 기본적으론 수직이며 flexdirection이 row일때 세로 column일때 가로이다. flex-start(좌측), flex-end(우측), center(중앙), stretch(자식 요소가 교차축에서 가득 차도록 - 자식 요소가 고정된 크기가 아닐때), baseline(자식 요소들을 텍스트의 기준선으로 정렬)
  • justifyContent: 주 축에서 자식 요소의 정렬을 결정. 마찬가지로 direction값에 따라 alignItems와 반대로 적용된다. start, end, center는 축 기준으로 동일하게 작용하며 추가적으로 space-between(시작, 끝 자식 요소를 끝점에 정렬 후 자식요소 균등 간격배치), space-around(자식 요소들 사이에 동일한 간격으로 배치), space-evenly(around에서 컨테이너 양 끝에도 같은 간격이 적용됨)
  • width, height: 고정 크기(고정 숫자 - px) 및 상대 크기(화면 비율 - %) 적용가능
  • overflow: react native에서는 기본적으로 visible과 hidden만 사용 가능하다. visible은 컨테이너 안의 자식뷰들이 컨테이너를 초과해도 보여지게 된다. hidden은 자식뷰가 컨테이너를 초과하려 하면 넘어가는 부분이 보이지 않게 숨겨진다. 즉, 창문안쪽과 바깥쪽이라고 보면 된다.
  • textAlign: 텍스트 정렬 시 사용한다. left, right, center가 있다.
  • cursor: 마우스 커서를 요소 위에 올릴때 변화시킬 수 있다. auto와 pointer가 있고 pointer로 설정 시 커서를 둘 때 흔히 아는 클릭 형태의 커서가 된다.

🤚 fontFamily사용 시 fontWeight가 세밀하게 적용되지 않을 수 있으므로 주의바란다.

React Native Hover

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>

Animation

위에서 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();
  };
  • toValue: 애니메이션이 도달해야 할 목표값으로 초기값 설정시 사용한 Animated.Value가 이 toValue로 변화한다.
  • delay: 시작 지연 시간
  • isInteraction: 다른 상호작용과 연결되는 지 - 기본값 true
  • easing: 애니메이션 가속도 함수 - 기본값: Easing.inOut(Easing.ease)
  • duration: 애니메이션 지속 시간
  • useNativeDriver: backgroundColor 애니메이션에서는 useNativeDriver를 false로 설정
  • loop: 특정 애니메이션 무한 반복
  • sequence: 여러 애니메이션 순차 반복 즉, 위 코드에서는 1 -> 0 -> 1 -> 0으로 반복실행
  • Animated.parallel: 여러 애니메이션을 동시에 실행 - 개별 duration적용이 되어야 할 경우에는 별로다.
  • Animated.stagger(duration): 애니메이션들 간에 지정된 시간만큼 간격을 두고 순차적으로 실행 시킨다.
  • Animated.delay(1000): 애니메이션 실행 전 delay를 줄 수 있따. 물론 각 애니메이션에서 속성으로 제공한다.
  • animationValue: 애니메이션을 통해 변경될 값으로 최종적으론 변경되는 toValue값이 들어가게 된다. 따라서 중간 값을 가지기 싫다면 나 같은 경우에는 애니메이션이 끝난 후 setValue를 통해 재설정 하도록 하였다.

비동기 프로그래밍

대표적인 비동기 프로그래밍인 promise와 async/await에 관해서 알아보자

promise

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)

async/await

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타입 에러가 발생할 수 있다.

Audio 설정

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);
  }

React Native Convension

해당 사이트를 참고하였다.

참고자료


profile
안녕하세요. 개발에 이제 막 뛰어든 신입 개발자 입니다.

0개의 댓글