Mobile 개발/RN(React Native)

스무디 한잔 마시며 끝내는 React Native - Counter App

히핑소 2020. 7. 28. 20:11
반응형

교재 내용을 따라 기본 Project 생성 및 설정 진행.

Count 프로젝트 생성, TypeScript 설치, styped-component 설치 그리고 babel-plugin 을 설치합니다.

babel-plugin 은 react-native의 상대 경로를, 절대 경로로 변경해주는 plugin 로

예로 import ../../../Button 이라면 import ~/Button 식의 경로를 사용 할 수 있습니다.

$ react-native init Count
$ cd Count
$ npm install --save styled-components
$ npm install --save-dev typescript @types/react @types/react-native
$ npm install --save-dev typescript @types/styled-components babel-plugin-root-import

이 후 typescript 설정을 위해 tsconfig.json 파일을 생성하고 다음과 같이 입력

{
    "compilerOptions": {
        "allowJs":true,
        "allowSyntheticDefaultImports":true, 
        "esModuleInterop": true,
        "isolatedModules": true,
        "jsx":"react",
        "lib":["es6"],
        "moduleResolution": "node",
        "noEmit":true,
        "strict": true,
        "target": "esnext"
    },
    "exclude": ["node_modules", "babel.config.js", "metro.config.js","jest.config.js"]
}

절대경로로 컴포넌트를 추가하기 위해 아래와 같이 수정합니다.

module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: [
    [
      'babel-plugin-root-import',
      {
        rootPathPrefix: '~',
        rootPathSuffix: 'src',
      },
    ],
  ],
};

 Main code 부 수정. App.js -> src/App.tsx 로 변경후 아래와 같이 작성.

교재 따라치다가 길어서, code 주워왔습니다.

import React, {Fragment} from 'react';
import {StatusBar, SafeAreaView} from 'react-native';
import {
  Header,
  LearnMoreLinks,
  Colors,
  DebugInstructions,
  ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
import Styled from 'styled-components/native';
const ScrollView = Styled.ScrollView`
 background-color: ${Colors.lighter};
`;
const Body = Styled.View`
 background-color: ${Colors.white};
`;
const SectionContainer = Styled.View`
 margin-top: 32px;
 padding-horizontal: 24px;
`;
const SectionDescription = Styled.Text`
 margin-top: 8px;
 font-size: 18px;
 font-weight: 400;
 color: ${Colors.dark};
`;
const HighLight = Styled.Text`
 font-weight: 700;
`;
interface Props {}
const App = ({}: Props) => {
  return (
    <Fragment>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView>
        <ScrollView contentInsetAdjustmentBehavior="automatic">
          <Header />
          <Body>
            <SectionContainer>
              <SectionDescription>Step One</SectionDescription>
              <SectionDescription>
                Edit <HighLight>App.js</HighLight> to change this screen and
                then come back to see your edits.
              </SectionDescription>
            </SectionContainer>
            <SectionContainer>
              <SectionDescription>See Your Changes</SectionDescription>
              <SectionDescription>
                <ReloadInstructions />
              </SectionDescription>
            </SectionContainer>
            <SectionContainer>
              <SectionDescription>Debug</SectionDescription>
              <SectionDescription>
                <DebugInstructions />
              </SectionDescription>
            </SectionContainer>
            <SectionContainer>
              <SectionDescription>Learn More</SectionDescription>
              <SectionDescription>
                Read the docs to discover what to do next:
              </SectionDescription>
            </SectionContainer>
            <LearnMoreLinks />
          </Body>
        </ScrollView>
      </SafeAreaView>
    </Fragment>
  );
};
export default App;

 

마지막으로, index.js 의 import path 를 수정합니다.

이제 run 하면 app  실행

$ npm run android
또는 $ react-native run-android

 

완료 화면

 

기본 환경 설정 및 app 실행을 끝냈으니,

Count App 을 본격적으로 만들어보겠습니다.

 

1. app 에 들어갈 icon 다운로드 https://material.io/resources/icons/?icon=trending_down&style=baseline

add_circle (+) , remove_circle (-) IOS 또는 Android 로 png download 하고 

src/Assets/Images/ 폴더 생성 후, 다운로드한 이미지를 넣습니다.

- @2x @3x 로 naming 하면 React-Native 에서 알아서 resolution 에 맞는 적절한 size의 image가 들어갑니다.

2. App.tsx 를 다시 작성.

- <View Component 내에 <Counter> function conponent 를 넣습니다 />

- Counter function 은 String 형 title param 과 number 형 initValue param 2개  Props 를 가집니다. 

import React from 'react';
import Styled from 'styled-components/native';
import Counter from './Screens/Counter';

const Container = Styled.View`
 flex: 1;
 background-color: #EEE;
`;

const App = () => {
  return (
    <Container>
      <Counter title="This is a Counter App" initValue={5} />
    </Container>
  );
};
export default App;

 

3. Counter 폴더에 index.tsx 코드 작성.

- TitleContainer Styled View Component에  title props로 넘어온 text를 뿌려주고

- CountLabel Styled Text Component에 initValue + count , 5 + 0 = 5 값으로 처음 app이 실행됩니다.

- ButtonContainer Styled View Component 안에는 Button function component 2개를 row 로 넣고
props로 string text 와 onPress event를 전달합니다.

- state  : const [count, setCount] = useState<number>(0); 
count 값은 0으로 초기화하고, onPress 시 setCount() 를 호출하여 count variable 값을 + / - 합니다.

import React, {useState} from 'react';
import Styled from 'styled-components/native';
import Button from '~/Components/Button';

const Container = Styled.SafeAreaView`
 flex: 1;
 background-color: #fec;
`;
const TitleContainer = Styled.View`
 flex: 1;
 justify-content: center;
 align-items: center;
`;
const TitleLabel = Styled.Text`
 font-size: 24px;
`;
const CountContainer = Styled.View`
flex: 2;
 justify-content: center;
 align-items: center;
 background-color: #fee;
`;
const CountLabel = Styled.Text`
 font-size: 24px;
 font-weight: bold;
`;
const ButtonContainer = Styled.View`
 flex: 1;
 flex-direction: row;
 flex-wrap: wrap;
 justify-content: space-around;
 background-color: #fff;
`;

interface Props {
  title?: string;
  initValue: number;
}

const Counter = ({title, initValue}: Props) => {
  const [count, setCount] = useState<number>(0);
  return (
    <Container>
      {title && (
        <TitleContainer>
          <TitleLabel>{title}</TitleLabel>
        </TitleContainer>
      )}
      <CountContainer>
        <CountLabel>{initValue + count}</CountLabel>
      </CountContainer>
      <ButtonContainer>
        <Button iconName="plus" onPress={() => setCount(count + 1)} />
        <Button iconName="minus" onPress={() => setCount(count - 1)} />
      </ButtonContainer>
    </Container>
  );
};
export default Counter;

4. Button 폴더에 index.tsx 코드 작성.

- TouchableOpacity component 내에 Image 를 넣고, Props 인자로 iconName string 과 onPress event 가 넘어와 click 시 상위 Click Event 를 Counter function component 로 넘겨줍니다.

import React from 'react';
import Styled from 'styled-components/native';

const Container = Styled.TouchableOpacity``;
const Icon = Styled.Image``;

interface Props {
  iconName: 'plus' | 'minus';
  onPress?: () => void;
}

const Button = ({iconName, onPress}: Props) => {
  return (
    <Container onPress={onPress}>
      <Icon
        source={
          iconName === 'plus'
            ? require('~/Assets/Images/add.png')
            : require('~/Assets/Images/remove.png')
        }
      />
    </Container>
  );
};

export default Button;

 

코드는 모두 작성되었고 가장 중요한

5. 동작 확인.

$ npm run android
또는 $ react-native run-android

 

결론, 소규모 project에는 사용하지 않는 편이 유리해 보입니다.

- Visual Studio Code 에 tsx + babel plugin 쓰면 waring 이 많이 보여서 불편 (왼쪽)

- styled-component 활용해보니 편하지는 않다

- styled-component를 부른건 지, function component 를 부른건 지 분간이 안가
코드 가독성이 떨어진다 (오른쪽), 

- 코드가 길어진다.

- expo 쓰다가 react-native cli 넘어오니 여러모로 복잡한게 많다
expo counter app 예제는 여기 https://yannichoongs.tistory.com/156

 

styled-component를 잘 정리해 놓으면

GUI 디자이너가 따로 있고 engineer 는 가져다 쓰기만 하면 되는 형태가 될 것 같네요.

반응형