저희 리액트 네이티브 스터디에서는 김범준의 '처음 배우는 리액트 네이티브'를 읽고 직접 실습하며 내용 요약을 진행합니다. 1주차에는 [1장: 리액트 네이티브란?], [2장: 리액트 네이티브 시작하기], [3장: 컴포넌트]를 읽고 리액트 네이티브에 대한 전반적인 개요를 접했으며, 주요 개념 중 하나인 컴포넌트에 대해 자세히 공부했습니다.
1️⃣ 1장 리액트 네이티브란?
리액트 네이티브는 2015년 3월 페이스북에 의해 공개된 오픈소스 프로젝트로, 이름에서부터 드러나듯 리액트에 기반을 두고 제작되었다.
하지만 리액트와는 달리 웹 브라우저가 아닌 네이티브(iOS, 안드로이드)에서 동작하는 애플리케이션을 만드는 자바스크립트 프레임워크다.
리액트 네이티브로 개발된 대표적인 앱으로는 페이스북, 인스타그램, 스카이프, 에어비앤비, 핀터레스트 등으로 다양하다.
네이티브 앱을 개발하기 위해서는 iOS와 안드로이드 개발자가 각각 필요하고, 유지보수하는 비용도 두 배가 된다. 이를 해결하기 위해 등장한 것이 바로 하이브리드 앱이다.
초창기 하이브리드 앱은 웹 브라우저를 사용해 화면을 표시하고 필요한 기능들만 네이티브의 기술을 사용했다. 하지만 브라우저에서 제공하는 성능 이상을 발휘할 수 없었기 때문에,
이를 보완하기 위해 페이스북은 브라우저 대신 네이티브 브릿지(Native Bridge)를 사용하는 리액트 네이티브를 발표했다.
📌 리액트 네이티브의 장단점
iOS의 swift나 Objective-C, 안드로이드의 kotlin, java 등을 사용하지 않고도 웹 개발자에게 익숙한 기술을 이용하여 모바일 개발을 가능하게 해주는 리액트 네이티브.
말만 들어보면 장점만 있을 것 같지만 그만큼의 단점도 존재한다.
장점
- 작성된 코드 대부분 플랫폼 간 공유가 가능해 iOS와 안드로이드를 동시에 개발할 수 있다.
자바스크립트만 알고 있으면 쉽게 시작할 수 있으며, 리액트와 같이 컴포넌트 재사용이 가능하다. - 변경된 코드를 저장하기만 해도 자동으로 변경된 내용이 적용된 화면을 확인할 수 있는 fast refresh 기능을 제공한다.
앱의 reload 없이도 즉각적인 수정 내용 확인이 가능하다. - 작성된 코드가 각 플랫폼에 맞는 네이티브 엘리먼트로 전환되기 때문에 큰 성능 저하 없이 개발이 가능하다.
웹뷰를 이용하여 렌더링하는 코도바나 아이오닉 같은 크로스 플랫폼보다 성능이 우수하다.
단점
- 네이티브의 새로운 기능을 지원하기까지 시간이 걸리고, 브릿지를 사용하기 때문에 네이티브 앱보다 성능이 떨어진다.
- 문제의 종류에 따라 원인을 찾고 해결하는 데 많은 시간이 걸려 유지보수가 어렵다.
- 잦은 업데이트로 많은 수정사항이 생기기도 한다.
📌 리액트 네이티브의 동작 방식
브릿지
브릿지는 자바스크립트 코드로 네이티브와 통신할 수 있도록 도와주는 연결다리라고 생각하면 쉽다.
우리가 작성한 자바스크립트 코드가 실행되는 자바스크립트 스레드에서 정보를 받아 네이티브로 전달하는 브릿지가 있기 때문에 우리는 자바스크립트만 알면 된다.
네이티브 영역에는 UI를 담당하는 Main 스레드, 레이아웃을 계산하는 데 사용되는 Shadow 스레드, 그리고 자체 스레드가 존제하는 네이티브 모듈이 있다.
리액트 네이티브는 네이티브와 통신하는 모든 자바스크립트의 기능을 분리된 스레드로 처리하여 성능을 향상시킨다.
가상 DOM
리액트에서도 사용되는 가상 DOM은 데이터가 변할 경우 자동으로 화면을 다시 그린다.
하지만 실제 DOM과의 비교를 통해 달라진 부분만 리렌더링 하기 때문에 데이터 변화를 즉각적으로 반영한다.
Virtual DOM Process
- 데이터의 변화를 감지
- 변화된 데이터를 이용하여 가상 DOM을 그림
- 가상 DOM과 실제 DOM을 비교하여 차이점 확인
- 차이점이 있는 부분만 실제 DOM에 적용하여 그림
JSX
JSX(JavaScript Xml)란 자바스크립트의 확장 문법으로 마크업 언어 XML과 유사하다.
function App() {
return (
<div>
Hello <b>react native</b>
</div>
};
};
HTML 코드를 작성하는 것과 비슷하기 때문에 자바스크립트만 이용하는 것보다 훨씬 가독성도 높고 작성하기도 쉽다.
이 코드가 바벨을 통해 자바스크립트로 변환되면 아래와 같아진다.
보기만 해도 복잡하다
function App() {
return React.createElement(
"div",
null,
"Hello ",
React.createElement(
"b",
null,
"react native"
)
);
};
2️⃣ 2장 리액트 네이티브 시작하기
제일 번거롭고 복잡한 설치와 환경 설정, 그리고 프로젝트 생성에 대해 정리해봤다.
맥북 사용자이기 때문에 MacOS 환경만 다뤘다...!!
📌 설치 및 환경 설정
실제 앱을 만드는 것이기 때문에 자바스크립트로 개발하더라도 네이티브에서 개발할 때와 동일한 툴들을 필요로 한다.
Xcode는 원래 사용하고 있었기 때문에 새로 설치는 필요 없었지만 안드로이드 스튜디오 설치할 땐 약간 맥북한테 미안했다. .
왓치맨 설치
왓치맨은 페이스북에서 제작한 파일 시스템 변경 감지 도구로, 파일의 변화를 감지하고 파일의 변화가 조건을 만족시키면 특정 동작을 실행시킨다. 리액트 네이티브에서 왓치맨은 소스코드의 변화를 감지하고 자동으로 빌드하여 화면에 업로드하는 역할을 담당한다.
왓치맨은 맥용 패키지 매니저인 홈브류를 통해 설치하기 때문에 우선 홈브류를 설치해야 하는데, 공식 웹사이트에서 설치 명령어를 확인할 수 있다.
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
설치가 완료되면 아래 명령어로 정상적으로 설치되었는지 확인한다.
brew --version
버전이 잘 나타나면 설치가 제대로 된 것으로, 홈브류를 이용해서 왓치맨을 설치하는 명령어를 입력한다.
brew install watchman
설치가 완료되면 아래 명령어로 정상적으로 설치되었는지 확인한다.
watchman --version
Node.js 설치
Node.js를 설치하면 npm(Node Package Manager)도 함께 설치가 된다. 우리는 npm을 통해 다양한 개발자들이 만들어놓은 패키지들을 설치하고 활용할 수 있다.
nvm(Node Version Manager)을 활용하면 맥 환경에서 여러 버전의 Node.js를 설치하고 관리할 수 있다. 향후 Node.js의 LTS 버전이 업데이트되었을 때 이에 대응하기 위해서도 nvm 사용을 권장한다고 한다.
터미널에서 nvm 깃허브 페이지에 있는 설치 명령어를 실행한다.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
설치가 완료되면 .zshrc 파일을 열고 다음 코드를 추가한다. nvm 깃허브 페이지에서 복붙해야 오타 없이 잘 실행된다...
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
맥OS 카탈리나부터 zsh이 기본 터미널로 지정되었다. bash를 사용 중이라면 .bash_profile 파일에 작성하면 된다.
nvm 설치와 설정이 완료되었다면 터미널을 재시작하고 다음 명령어로 nvm 설치 확인까지 마친다.
nvm --version
nvm이 정상적으로 설치되었다면 nvm을 이용해서 Node.js를 설치한다.
nvm install --lts
설치가 완료되면 다음 명령어로 Node.js의 버전으로 설치를 확인한다.
node --version
Xcode 설치
맥의 특장점! iOS 개발 환경을 준비하기 위해 Xcode 설치가 필요하다. 애플 앱스토어에서 검색해 설치하면 된다.
설치가 완료되면 Xcode Command Line Tools를 설정해야 한다. Xcode를 실행해 Preferences > Location
에서 다음과 같이 Command Line Tools의 가장 최신 버전을 선택한다.
코코아팟 설치
코코아팟은 맥이나 iOS 개발에 사용되는 라이브러리를 관리해주는 도구로, 아래 명령어를 통해 설치한다.
sudo gem install cocoapods
설치가 완료되면 다음 명령어를 이용하여 정상적으로 설치되었는지 확인한다.
pod --version
iOS 시뮬레이터
가상 기기에서 테스트를 진행하기 위한 iOS 시뮬레이터를 실행해보자. Xcode 메뉴 Open Developer Tool > Simulator
로 실행하고, 시뮬레이터 메뉴 File > Open Simulator
에서 기기를 선택한다.
새로 나온 아이폰13 써보기... ㅎ
JDK 설치
안드로이드 개발을 위해 JDK(Java Developer Kit)를 설치해야 한다.
아래 명령어로 홈브류를 통해 JDK를 설치한다.
brew cask install adoptopenjdk/openjdk/adoptopenjdk8
설치가 완료되면 다음 두 명령어로 정상적으로 설치되었는지 확인한다.
java -version
javac -version
안드로이드 스튜디오 설치
안드로이드 스튜디오는 iOS의 Xcode와 같은 역할을 한다고 생각하면 된다. 공식 웹사이트에서 맥OS용을 다운로드 한다.
설치가 완료되면 실행한 후 설정을 진행한다.
사실 저는 설정까지 이미 마쳐서 앞부분은 사진 없이 설명만...
- Install Type을 선택하는 화면에서 Custom을 선택하고 진행한다.
- SDK Components Setup 화면에서 다음 목록들을 선택하고 진행한다.
- Android SDK
- Android SDK Platform
- Performance (Intel Ⓡ HAXM)
- Android Virtual Device
설정이 완료되면 SDK Manager 메뉴로 이동해서 추가 설치를 진행한다.
SDK Platforms 탭의 오른쪽 하단에 있는 Show Package Details를 클릭하고 필요한 항목들을 선택해서 설치한다.
- Android SDK Platform 29
- Intel x86 Atom_64 System Image 또는 Google APIs Intel x86 Atom System Image
그리고 SDK Tools 탭에서도 Show Package Details를 클릭해서 Android SDK Build-Tools 29.0.2를 선택하고 설치를 진행한다.
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/tools/bin
export PATH=$PATH:$ANDROID_HOME/platform-tools
설치가 완료되면 .zshrc 파일을 열고 위 내용을 추가한다. 첫 줄이 가리키는 위치는 설치된 안드로이드 SDK의 위치와 동일해야 한다.
SDK Manager 메뉴의 Android SDK Location에서 확인할 수 있고, 첫 줄의 내용은 다음 명령어로 확인할 수 있다.
echo $HOME/Library/Android/sdk
두 경로가 동일하다면 다음 명령어를 이용해 설치가 정상적으로 진행되었는지 확인한다.
adb --version
안드로이드 에뮬레이터
에뮬레이터를 이용해 안드로이드를 테스트하기 위한 가상 기기를 만들어 보자.
AVD Manager에서 Create Virtual Device를 누르면 아래처럼 기기를 선택할 수 있다.
기기를 선택했다면 안드로이드 이미지 선택 화면에서 앞에서 설치한 이미지(API Level 29)를 선택한 후 진행한다.
모든 선택이 완료되면 추가된 기기의 Actions 탭에서 녹색 시작 버튼을 클릭하면 에뮬레이터가 실행된다.
에디터 설치
에디터는 VS Code를 사용한다. 다양한 확장 프로그램을 사용하기 위해...
📌 리액트 네이티브 프로젝트 만들기
이제 모든 설치가 완료되었으니, 리액트 네이티브 프로젝트를 생성하고 동작해보자.
생성 방법으로는 Expo를 이용하는 방법과 리액트 네이티브 CLI를 이용하는 두 가지 방법이 있다.
Expo
Expo는 리액트 네이티브를 처음 시작하는 사람도 접근하기 편하게 되어 있고, 프로젝트를 쉽게 배포 및 관리할 수 있도록 다양한 기능을 제공한다. 그리고 프로젝트의 QR코드를 제공하기 때문에 물리적 기기에서도 직접 실행해볼 수 있다. 또한 리액트 네이티브 웹을 지원하며 iOS, 안드로이드 뿐만 아니라 웹도 함께 개발할 수 있다.
하지만 Expo에서 제공하는 API만 사용이 가능하고 인앱 결제가 안 되는 등 단점들도 많이 존재한다... 하지만 Expo로 프로젝트를 시작하고 나중에 CLI로 옮기는 경우도 많다고 한다.
Expo를 이용하려면 npm을 이용해서 expo-cli를 설치해야 한다. 권한 문제로 오류가 뜬다면 앞에 sudo
를 붙여 진행한다.
npm install -g expo-cli
설치가 완료되면 다음 명령어로 Expo 프로젝트를 생성한다.
expo init my-first-expo
프로젝트 생성이 완료되면 생성된 프로젝트로 이동해서 프로젝트를 실행한다.
cd my-first-expo
npm start
실행이 완료되면 QR코드와 함께 여러 메뉴들이 쫙 뽑히고, 브라우저에서도 페이지가 열리는 걸 확인할 수 있다.
실제 기기에서 실행하려면 Expo 앱을 다운받아놔야 Expo 앱으로 실행이 된다.
터미널에서 i를 입력하면 아이폰 시뮬레이터가 실행되고, a를 입력하면 안드로이드 에뮬레이터가 실행된다.
아래는 실행 완료된 모습이다.
갤럭시 스킨 입히는 방법은 따로 글을 찌겠씁니다...
Expo에서는 또한 웹처럼 console.log()
를 찍을 수도 있다. App.js
파일을 변경해 보자.
...
export default function App() {
console.log('콘솔로그 테스트');
return (...);
}
...
Expo에서 리액트 네이티브 CLI 프로젝트로 변경해야 하는 상황에는 eject 명령어를 사용하면 된다. eject 명령어를 실행하면 Expo 프로젝트가 감추고 있던 것들이 드러나며, 리액트 네이티브 CLI 프로젝트로 시작한 것처럼 프로젝트가 변경되고 Expo 프로젝트 내에 존재하던 제약들도 없어진다. 하지만 다시 Expo 프로젝트로 돌아올 수 없다는 점에 주의해야 한다.
React Native CLI
리액트 네이티브 CLI의 장점은 Expo의 단점이다. 즉 Expo와 반대로 리액트 네이티브 CLI에서는 필요한 기능이 있을 경우 모듈을 직접 만들어 사용할 수 있다. 하지만 Expo에 비해 배포가 불편하고 리액트 네이티브를 처음 다루는 이용자에게 좀 더 어렵게 느껴진다는 단점이 있다.
아래 명령어를 이용해 리액트 네이티브 CLI 프로젝트를 생성한다.
npx react-native init MyFirstCLI
리액트 네이티브 CLI로 프로젝트를 생성할 때는 프로젝트 이름으로 영문과 숫자만 입력이 가능하다.
또한 다음 명령어처럼 특정 버전의 리액트 네이티브를 사용해서 프로젝트를 생성할 수도 있다.
npx react-native init 프로젝트 이름 —version X.XX.X
프로젝트 생성이 완료되면 프로젝트 폴더로 이동해서 프로젝트를 실행해보자. (ios만 테스트해보겠습니답)
cd MyFirstCLI
npm run ios
리액트 네이티브가 실행되면서 터미널 창 하나가 추가로 열리고 Metro가 실행되는 것을 볼 수 있다.
Metro는 리액트 네이티브를 위한 자바스크립트 번들러로서 리액트 네이티브가 실행될 때마다 자바스크립트 파일들을 단일 파일로 컴파일하는 역할을 한다.
사실 Expo로 진행할 것이기 때문에 잘 돌아가는지까지만 확인하고 넘어가려고 합니다...
3장 컴포넌트
우선 실습을 위해 아래 명령어를 이용하여 프로젝트를 생성한다.
expo init react-native-component
📌 JSX
생성한 react-native-component
프로젝트의 App.js
파일 내에서 꼭 알아야 하는 JSX 문법 몇 개를 알아보자.
하나의 부모
JSX에서는 여러 개의 요소를 표현할 경우 반드시 하나의 부모로 감싸야 한다.
아래의 코드에서 View
컴포넌트가 없어지면 반환되는 요소가 하나가 아니게 되기 때문에 오류가 나타난다.
...
export default function App() {
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<StatusBar style="auto" />
</View>
);
}
...
View
컴포넌트는 <div>
와 비슷한 역할을 하는 컴포넌트이다. 이처럼 특정 역할을 하는 컴포넌트로 감싸고 싶지 않을 때는 Fragment
컴포넌트를 사용하면 된다.<Fragment></Fragment>
의 단축 문법인 <></>
으로 표현해도 된다.
...
import React, { Fragment } from 'react';
import { Text } from 'react-native';
export default function App() {
return (
<Fragment>
<Text>Open up App.js to start working on your app!</Text>
<StatusBar style="auto" />
</Fragment>
);
}
...
자바스크립트 변수
JSX 내부에서는 아래와 같이 중괄호 안에 넣으면 자바스크립트의 변수를 전달하여 이용할 수 있다.
...
export default function App() {
const name = 'dazzlynnnn';
return (
<View>
<Text>My name is {name}</Text>
<StatusBar style="auto" />
</View>
);
}
...
자바스크립트 조건문
개발을 하다 보면 조건에 따라 나타나는 요소를 다르게 하고 싶은 경우가 생긴다. 이럴 때 JSX 내부에서도 자바스크립트의 조건문을 이용할 수 있지만, 약간의 제약이 따르므로 최대한 간단하게 작성하는 것이 좋다.
- if 조건문
if문을 사용할 수는 있지만, 즉시실행함수 형태로 작성해야 한다.... export default function App() { const name = 'dazzlynnnn'; return ( <View> <Text> {(() => { if (name === 'jsx') return 'My name is jsx'; else if (name === 'dazzlynnnn') return 'My name is dazzlynnnn'; else return 'My name is React Native'; })()} </Text> <StatusBar style="auto" /> </View> ); } ...
- 삼항 연산자
if문보다 조건문을 간단하게 쓸 수 있는 방법이다.... export default function App() { const name = 'dazzlynnnn'; return ( <View> <Text> ***My name is {name === 'dazzlynnn' ? 'dazzlynnnn' : 'React Native'}*** </Text> <StatusBar style="auto" /> </View> ); } ...
- AND 연산자와 OR 연산자
위와 같이... export default function App() { const name = 'dazzlynnnn'; return ( <View> {name === 'dazzlynnnn' && ( <Text>My name is dazzlynnnn</Text> )} <StatusBar style="auto" /> </View> ); } ...
&&
(AND) 연산자를 사용하면 앞의 조건문이 참일 때만 렌더링을 한다.||
(OR)는 반대인 경우 쓰면 된다. 값의 여부에 따라 렌더링을 달리 할 때 주로 사용된다.
null과 undefined
조건에 따라 출력하는 값을 변경하다 보면 컴포넌트가 null
이나 undefined
를 반환하는 경우가 있다.
JSX의 경우 null
은 허용하지만 undefined
는 오류가 발생한다는 점을 주의해야 한다.
주석
JSX에서의 주석은 자바스크립트의 주석과 약간 차이가 있다. 태그 안에서는 자바스크립트와 마찬가지로 //
나 /* */
를 이용해 주석을 작성할 수 있지만, 이외에서는 {/* */}
를 이용해야 한다.
스타일링
JSX에서는 HTML과 달리 style
에 문자열로 입력하는 것이 아니라 객체 형태로 입력해야 한다.
(처음에 자바스크립트 모르고 리액트 무작정 시작했을 때 그냥 문자열로 나왔다가 엄청 헤맸다 . .)
그리고 background-color
와 같이 하이픈으로 연결된 속성은 카멜표기법으로 backgroundColor
처럼 작성해야 한다.
...
<View
style={{
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Text>style test</Text>
<StatusBar style="auto" />
</View>
...
📌 컴포넌트
컴포넌트란 재사용이 가능한 조립 블록으로 화면에 나타나는 UI 요소이다. App.js
파일도 index.js
에서 사용되는 App
이라는 컴포넌트이다.
컴포넌트는 단순히 UI 역할만 하는 것이 아니라 부모로부터 받은 속성인 props
나 자신의 상태인 state
에 따라 표현이 달라지고 다양한 기능을 수행한다.
내장 컴포넌트
리액트 네이티브에서는 다양한 내장 컴포넌트들이 제공된다. 위에서 사용했던 View
, Text
컴포넌트만큼 대표적인 Button
컴포넌트를 사용해서 실습해 보자.
프로젝트에 src
폴더를 생성하고 그 안에 App
컴포넌트를 작성할 App.js
파일을 생성한 다음 Button
컴포넌트를 사용해 컴포넌트를 구성한다. (속성 설정은 공식문서 참고)
import React from 'react';
import { Text, View, Button } from 'react-native';
const App = () => {
return (
<View
style={{
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Text style={{ fontSize: 30, marginBottom: 10 }}>Button Component</Text>
<Button title="Button" onPress={() => alert('Click!')} />
</View>
);
};
export default App;
루트 디렉토리에 있는 App.js
파일은 아래와 같이 수정해서 src
디렉토리에서 만든 App
컴포넌트를 사용하도록 한다.
import App from './src/App';
export default App;
안드로이드에서의 버튼 색이 다른 것을 확인할 수 있는데, 문서에서 color
속성을 확인해보면 원인을 알 수 있다.
Button
컴포넌트의 color
속성은 iOS에서는 텍스트 색을 나타내는 값이지만 안드로이드에서는 버튼의 바탕색을 나타내는 값이다. 이렇게 iOS와 안드로이드가 약간씩 다르게 표현되거나 특징 플랫폼에만 적용되는 속성이 있다.
(개발하다가 제일 화가 나는 부분................)
커스텀 컴포넌트 만들기
프로젝트에 필요한 컴포넌트를 직접 만들 수도 있다. 위 실습처럼 두 플랫폼에서 다르게 나타나는 버튼의 단점을 보완하기 위해 TouchableOpacity
컴포넌트과 Text
컴포넌트를 이용해서 MyButton
컴포넌트를 만들어 보자.
제작한 컴포넌트를 관리하기 위해 components
폴더를 src
폴더 아래에 만든다. (그래야 나중에 컴포넌트가 줄줄이 늘어나도 이게 컴포넌트인지 스크린인지 안 헷갈림)
그리고 components
폴더 안에 MyButton.js
파일을 생성하고 다음과 같이 컴포넌트를 작성한다.
import React from 'react';
import { TouchableOpacity, Text } from 'react-native';
const MyButton = () => {
return (
*<TouchableOpacity
style={{
backgroundColor: '#3498db',
padding: 16,
margin: 10,
borderRadius: 8,
}}
onPress={() => alert('Click!')}
>
<Text style={{ color: 'white', fontSize: 24 }}>My Button</Text>
</TouchableOpacity>
);
};
export default MyButton;
그리고 App.js
에서 MyButton
컴포넌트를 사용할 수 있게 해준다.
...
import MyButton from './components/MyButton';
const App = () => {
...
<Text
style={{
fontSize: 30,
marginBottom: 10
}}
>
My Button Component
</Text>
<MyButton />
</View>
);
};
export default App;
이제야 스타일이 통일됐다. (편안)
📌 props와 state
props
props
는 properties
를 줄인 말로, 부모 컴포넌트로부터 전달된 속성값을 말한다. 부모 컴포넌트가 자식 컴포넌트의 props
를 설정하면 자식 컴포넌트에서는 이를 사용할 수는 있지만 변경은 불가능하다. 전달해주는 컴포넌트에서만 변경이나 추가 가능!
예를 들어 App
컴포넌트에서 MyButton
컴포넌트를 호출할 때 버튼에 들어갈 내용을 props
로 전달해 보자.
부모 컴포넌트에서 태그 속성으로 props
를 전달하는 방법 외에 컴포넌트의 태그 사이에 값을 입력해서 children props
로 전달하는 방법도 있다.
...
const App = () => {
...
<MyButton title="props"/>
<MyButton title="props">Children props</MyButton>
...
};
...
이를 자식 컴포넌트인 MyButton
에서는 함수의 파라미터로 받아서 사용할 수 있다. props
에 children
이 있다면 title
보다 우선시되도록 작성했다.
...
const MyButton = props => {
return (
...
<Text style={{...}}>{props.children || props.title}</Text>
...
);
};
...
defaultProps
- 반드시 전달되어야 하는 중요한 값은 defaultProps로 지정하면 빈 값이 나타나는 상황을 방지할 수 있다.
- 컴포넌트를 쭉 정의하고, 아래처럼 기본값을 지정해 주면
title
값이 전달되지 않았을 때defaultProps
값이 들어가게 된다.
...
const MyButton = props => {...};
MyButton.defaultProps = {
title: 'Button',
};
export default MyButton;
propTypes
컴포넌트가 복잡해지면 props가 많아지고, 그에 따라 타입 실수가 일어나거나 필수 props 전달을 놓칠 수도 있다.
이를 방지하기 위해 잘못된 props가 전달되었다는 것을 경고하기 위해 propTypes를 사용한다.
npm install prop-types
명령어로 라이브러리를 설치하고 쓸 파일 위에 import
해줘야 한다. title
의 타입이 number
로 넘어와야 한다고 지정해 보고 경고 메시지를 살펴보자.
...
import propTypes from 'prop-types';
const MyButton = props => {...};
MyButton.propTypes = {
title: propTypes.number,
};
export default MyButton;
expected 'number'라는데 'string'으로 넘어 왔다고 알려주고 있다.
이번에는 필수 전달 여부를 설정해 보자. 선언된 타입 뒤에 isRequired만 붙여 주면 된다.
...
import propTypes from 'prop-types';
const MyButton = props => {...};
MyButton.propTypes = {
title: propTypes.string.isRequired,
name: propTypes.string.isRequired,
};
export default MyButton;
그럼 이런 경고가 뜨는데, title은 넘겨 주고 있지만 name은 넘겨 주지 않았기 때문에 undefined로 뜨기 때문이다.
state
props
는 부모 컴포넌트에서 받은 값으로 변경할 수 없는 반면, state
는 컴포넌트 내부에서 생성되고 값을 변경할 수 있으며 이를 이용해 컴포넌트의 상태를 관리한다.
다시 말해, state
는 컴포넌트에서 변화할 수 있는 값을 나타내며, 상태가 변하면 컴포넌트는 리렌더링 된다.
리액트에서와 동일하게 상태 관리를 하면 되는데, 6장에서 전반적인 Hooks에 대한 내용이 나오기 때문에 이번 장에서는 useState만 살펴보자.
const [state, setState] = useState(initialState);
useState
는 상태를 관리하는 변수와 그 변수를 변경할 수 있는 세터 함수를 배열로 반환한다.
상태 변수는 세터 함수를 통해서만 변경할 수 있고, useState
의 파라미터에 상태의 초깃값을 전달할 수 있다. 정해주지 않으면 undefined
로 설정되어 에러가 날 수 있으므로 설정하는 것이 좋다.
components
폴더 밑에 Counter.js
를 생성하고 useState
를 이용해서 Counter
컴포넌트를 만들어 보자.
import React, { useState } from 'react';
import { View, Text } from 'react-native';
import MyButton from './MyButton';
const Counter = () => {
const [count, setCount] = useState(0);
const [double, setDouble] = useState(0);
return (
<View style={{ alignItems: 'center' }}>
<Text style={{ fontSize: 30, margin: 10 }}>count: {count}</Text>
<Text style={{ fontSize: 30, margin: 10 }}>double: {double}</Text>
<MyButton
title="+"
onPress={() => {
setCount(count + 1);
setDouble(double + 2);
}}
/>
<MyButton
title="-"
onPress={() => {
setCount(count - 1);
setDouble(double - 2);
}}
/>
</View>
);
};
export default Counter;
props
로 onPress
를 넘겨 줬기 때문에 이를 MyButton
의 TouchableOpacity
에 onPress
로 연결해 줘야 한다.
...
const MyButton = (props) => {
return (
<TouchableOpacity
style={{...}}
onPress={() => props.onPress()}
>
...
</TouchableOpacity>
);
};
📌 이벤트
리액트 네이티브는 사용자의 행동에 따라 상호작용하는 이벤트를 다양하게 제공한다. 가장 많이 사용되는 이벤트들에 대해 알아보자.
press 이벤트
웹 프로그래밍에서 가장 많이 사용하는 이벤트 중 하나는 사용자가 특정 DOM을 클릭했을 때 호출되는 onClick
이벤트일 것이다.
이와 비슷하게 리액트 네이티브에는 Press
이벤트가 있다.
TouchableOpacity
컴포넌트에서 설정할 수 있는 Press
이벤트의 종류
onPressIn
: 터치가 시작될 때 항상 호출onPressOut
: 터치가 해제될 때 항상 호출onPress
: 터치가 해제될 때 onPressOut 이후 호출onLongPress
: 터치가 일정 시간 이상 지속되면 호출
실습을 위해 components
폴더에 EventButton.js
파일을 생성하고 아래 코드를 작성한다.
import React from 'react';
import { TouchableOpacity, Text } from 'react-native';
const EventButton = () => {
const _onPressIn = () => console.log('Press In!\n');
const _onPressOut = () => console.log('Press Out!\n');
const _onPress = () => console.log('Press!\n');
const _onLongPress = () => console.log('Long Press!\n');
return (
<TouchableOpacity
style={{
backgroundColor: '#f1c40f',
padding: 16,
margin: 10,
borderRadius: 8,
}}
onPressIn={_onPressIn}
onPressOut={_onPressOut}
onPress={_onPress}
onLongPress={_onLongPress}
>
<Text style={{ color: 'white', fontSize: 24 }}>Press</Text>
</TouchableOpacity>
);
};
export default EventButton;
_on~ 함수 앞에 붙는 언더바는 코딩 컨벤션으로, 이벤트 핸들러 함수 앞에 붙이도록 정의한다.
- 짧게 터치했을 때
- 길게 터치했을 때
onPress
와 onLongPress
은 사용자가 클릭하는 시간에 따라 둘 중 하나만 호출된다는 점에 주의해야 한다.
만약 onLongPress
가 호출되는 시간을 조절하고 싶다면 delayLongPress
의 값을 조절해서 원하는 시간으로 설정할 수 있다.
<TouchableOpacity
...
delayLongPress={3000} // 3초
>
change 이벤트
변화를 감지하는 change
이벤트는 값을 입력하는 TextInput
컴포넌트에서 많이 사용된다.
실습을 위해 components
폴더 밑에 EventInput.js
파일을 생성하고 아래 코드를 넣어준다.
import React, { useState } from 'react';
import { View, Text, TextInput } from 'react-native';
const EventInput = () => {
const[text, setText] = useState('');
const _onChangeText = text => setText(text);
return (
<View>
<Text style={{ margin: 10, fontSize: 30 }}>text: {text}</Text>
<TextInput
style={{ borderWidth: 1, padding: 10, fontSize: 20 }}
placeholder="Enter a text..."
onChangeText={_onChangeText}
/>
</View>
);
};
export default EventInput;
onChangeText
는 컴포넌트의 텍스트가 변경되었을 때 변경된 텍스트의 문자열만 인수로 전달하며 호출한다.
pressable 컴포넌트
리액트 네이티브 0.63 버전부터 기존의 TouchableOpacity
컴포넌트를 대체하는 Pressable
컴포넌트가 추가되었다.press
이벤트도 동일하게 존재하고 동작 방식도 같지만, HitRect
와 PressRect
라는 추가적인 특징이 있다.
버튼이 작을 때 정확히 버튼을 터치하기 어려울 수 있기 때문에, HitRect
를 통해 버튼 모양보다 약간 떨어진 부분까지 이벤트가 발생할 수 있도록 설정할 수 있다.
또한 해당 버튼이 동작하지 않게 하기 위해 누른 상태에서 밖으로 손가락을 뺄 때 얼마나 멀어져야 버튼 밖으로 벗어났다고 판단할 수 있을지 PressRect
를 통해 설정한다.
EventButton.js
의 TouchableOpacity
를 Pressable
로 수정해 보자.
...
import { Pressable, Text } from 'react-native';
const EventButton = () => {
...
return (
<Pressable
style={{...}}
onPressIn={_onPressIn}
onPressOut={_onPressOut}
onPress={_onPress}
onLongPress={_onLongPress}
delayLongPress={3000}
pressRetentionOffset={{bottom: 50, left: 50, right: 50, top: 50}}
hitSlop={50}
>
<Text style={{ color: 'white', fontSize: 24 }}>Press</Text>
...
시뮬레이터에서 테스트를 하면 버튼에서 조금 떨어져 있어도 터치되고, 버튼을 누른 상태에서 이동했을 때 항상 같은 위치에서 onPressOut이 호출되는 것을 확인할 수 있다.
PressRect의 범위는 HitRect의 범위 끝에서부터 시작되므로 hitSlop의 값에 따라 PressRect의 범위가 달라진다는 것을 기억해야 한다.
'3-1기 스터디 > React Native' 카테고리의 다른 글
[5주차] 내비게이션 (0) | 2021.11.22 |
---|---|
[4주차] Hooks와 Context API (0) | 2021.11.17 |
[3주차] 리액트 네이티브로 ✅TODO List 만들기 (0) | 2021.11.08 |
[2주차] 4장 스타일링 (0) | 2021.11.08 |
댓글