본문 바로가기
앱 개발

[내일배움단] 앱개발 종합반 3주차 개발일지

by evekang 2022. 5. 26.

 

 

이번주 수업목표는 

  1. 앱 개발을 위한 필수 리액트 기초
  2. 앱다운 앱을 위한 기능 적용
  3. 앱 상의 페이지 구성

이다.

 

 

 

 

[ 리액트 필수 지식 ] 컴포넌트, 속성, 상태, useEffect


1. 컴포넌트(Component) : 정해진 엘리먼트들(요소)을 사용하여 만든 화면의 일부분

UI의 요소들을 재사용이 가능하도록 부분으로 조각내서 운영하는 기법이다. 화면의 모든 부분이다.

코드 전체를 감싸고 있는 함수를 뜻하기도 한다. App.js를 App 컴포넌트라고도 부를 수 있다.

 

페이스북 웹사이트는 운영되는 컴포넌트 수가 수만가지라고 한다.

 

버튼 하나도 컴포넌트가 될 수 있고, 버튼을 모아둔 영역도 컴포넌트가 될 수 있다!

코드 재사용이란 어려운 것은 아니고, 버튼 하나를 만들었을 때 이 버튼 코드를 여러 페이지에서 사용이 가능하도록 만든다는 것!

 

 

2. 속성(Props) : 컴포넌트에 데이터를 전달한다는 것으로, 그 모습은 키와 벨류의 형태이다.

(리액트 공식 문서 Component와 Props : https://ko.reactjs.org/docs/components-and-props.html)

 

<Text> 태그엔 numberOfLines라는 말줄임표 효과를 가지고 있는 속성이,

<Image> 태그엔 resizeMode라는 이미지가 영역을 차지하는 방식을 가지고 있는 속성이 있다.

 

 

[중요] 속성을 부여받는 컴포넌트에서 해당 속성 값을 받아서 사용할 수 있다.

 

Main Page에서 card 컴포넌트를 사용할 때 예시

  • 컴포넌트에 속성(데이터)을 넣어서 전달할 때는 키와 밸류(content={content})형태로 전달해줘야 한다.
  • 컴포넌트를 반복문 돌릴 때는 컴포넌트마다 고유하다는 것을 표현하기 위해서 map에서 나오는 인덱스(i)를 key={i} 속성 전달 형태로 꼭 넣어줘야 한다.

 

Card.js에서 속성값을 받을 때 예시

MainPage에서 넘겨준 속성은 실제 받게되는 Card 컴포넌트에서 딕셔너리 데이터를 받았다고 생각한다.

딕셔너리에서 키값을 바로 취해서 변수로써 함수 안에서 즉시 사용할 수 있는 방식(비구조 할당 방식)으로,

넘겨준 키값을 {키.값} 비구조 할당 방식으로 꺼내서 사용한다.

예) {content.title} => MainPage에서 content로 넘겨줬으니 content 보따리에서 title을 찾아서 보여주자.

 

 

 

 

3. 상태 (State, useState) : 리액트에서는 컴포넌트에서 보유 및 관리되는 데이터

(리액트 공식 문서 State and Lifecycle : https://ko.reactjs.org/docs/state-and-lifecycle.html)

 

리액트에서는 컴포넌트에서 보유 및 관리되는 데이터를 상태라고 한다. (가장 이해하기 난해한 부분ㅠㅠㅠㅠ)

컴포넌트에서 관리되는 데이터 = 상태(State)

상태(state)는 리액트 라이브러리에서 제공해주는 useState로 생성을 해서 → setState 함수로 수정/변경할 수 있다.

 

(useState와 useEffect 관련 문서는 Hook 으로 분류되어 있다.)

useState : https://ko.reactjs.org/docs/hooks-state.html

 

Using the State Hook – React

A JavaScript library for building user interfaces

ko.reactjs.org

useEffect : https://ko.reactjs.org/docs/hooks-effect.html

 

Using the Effect Hook – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

 

컴포넌트에 어떤 데이터(state)가 주어지냐에 따라 화면이 변경된다.

리액트 공식문서의 Hook 부분에서 useState와 관련된 예시와 설명이 잘 되어 있어서 가져왔다.

 

 

useEffect : 화면이 그려진 다음에 가장 먼저 실행되는 함수

앱이 시작하자마자 useEffect가 실행되는 줄 착각하고 있었는데 알고 보니 return 문의 화면이 그려지고 나서 실행되는 함수였다.

useEffect(()=>{

	...화면이 그려진 다음 가장 먼저 실행되야 할 코드 작성 공간

},[])

useEffect는 데이터를 준비할 때 사용한다.  데이터를 준비한다는 것은, 데이터를 서버로부터 혹은 어딘가로부터 받은 후 상태(state)에 반영한다는 것을 뜻한다.

1) 화면이 그려진다.

2) userEffect가 데이터를 준비한다.

3) 상태(state)가 업데이트되었으니 화면이 다시 그려진다.

 

 

 

특정 값들이 리렌더링 시에 변경되지 않는다면 React로 하여금 useEffect를 건너뛰도록 할 수 있다.

useEffect의 선택적 인수인 두번 째 인수로 배열을 넘기면 된다.

위의 예시에서 count를 두번째 인수로 넘기는데, 기존 count가 5이고 컴포넌트가 리렌더링(재시작)된 이후에도 변함없이 5라면, 리액트는 이전 렌더링값 5와 다음 렌더링값 5를 비교한다. 비교했을 때 값이 같게 되면 리렌더링을 건너뛰게 된다. 만약 리렌더링 이후 count 값이 6으로 바뀌었다면 useEffect가 실행된다.

 

 

useEffect를 단 한번만 실행하고 싶은 경우에는 두번째 인수를 빈 배열([])로 넘기면 된다. 

빈 배열을 넘기게 되면 useEffect 안의 prop과 state는 초기값을 유지하게 되며 재실행할 필요가 없음을 알고 리액트는 실행하지 않는다. (의존성 배열의 작동방법과 동일)

 

 

숙제를 하면서 막혔던 부분이 바로 이 부분인데, 꿀팁 찜을 보여주는 LikiPage에서 useEffect가 계속 실행되면서 다른 기능까지 먹어버리면서(?) 에러가 났었다. 

알고보니 내가 useEffect의 두번째 인자로 빈 배열을 주지 않아서 발생한 버그였다. 

자세한 원인은 모르겠지만  빈 배열을 넣어주지 않아서 계속 useEffect 함수를 실행시켰던 것 같다. 

빈 배열을 넣어주니 초기 렌더링 이후 한번만 실행되었고 나머지 기능도 잘 작동했다.

 

 

 

 

 

[ 앱 필수 기초지식 응용 ] 로딩화면, 카테고리 기능, 상태 바


import React,{useState,useEffect} from 'react';
import main from '../assets/main.png';
import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native';
import data from '../data.json';
import Card from '../components/Card';


export default function MainPage() {
  console.disableYellowBox = true;

  //useState 사용법
  //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수
  //setState는 state를 변경시킬때 사용해야하는 함수

  //모두 다 useState가 선물해줌
  //useState()안에 전달되는 값은 state 초기값
  const [state,setState] = useState([])
	
  //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수
  //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음
  useEffect(()=>{
    setState(data)
  },[])

  //data.json 데이터는 state에 담기므로 상태에서 꺼내옴
  let tip = state.tip;
  let todayWeather = 10 + 17;
  let todayCondition = "흐림"
  
  return (
    <ScrollView style={styles.container}>
      <Text style={styles.title}>나만의 꿀팁</Text>
			 <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text>
      <Image style={styles.mainImage} source={main}/>
      <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}>
        <TouchableOpacity style={styles.middleButton01}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity>
        <TouchableOpacity style={styles.middleButton02}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity>
        <TouchableOpacity style={styles.middleButton03}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity>
        <TouchableOpacity style={styles.middleButton04}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>
      </ScrollView>
      <View style={styles.cardContainer}>
         {
          tip.map((content,i)=>{
            return (<Card content={content} key={i}/>)
          })
        }
      </View>
    </ScrollView>
  );
}

이렇게 useEffect에 두번째 인자로 빈 배열을 넣고 한번만 실행하게 되면 에러가 난다. (undefined is not an object)

 

1) 화면이 그려지고, 2) useEffect가 데이터를 state에 useState를 이용하여 업데이트하면, 3) state가 변경되었으니 화면이 다시 그려지게 되는 순서인데, 1)번에서 오류가 난 것.

 

 

그래서 ready라는 새로운 상태(state)를 추가하고 ready 값이 true면 Loading 컴포넌트가 실행되게 한다.

import React,{useState,useEffect} from 'react';
import main from '../assets/main.png';
import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native';
import data from '../data.json';
import Card from '../components/Card';
import Loading from '../components/Loading';


export default function MainPage() {
  console.disableYellowBox = true;
  //return 구문 밖에서는 슬래시 두개 방식으로 주석

  const [state,setState] = useState([])

  //컴포넌트에 상태를 여러개 만들어도 됨
  //관리할 상태이름과 함수는 자유자재로 정의할 수 있음
  //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음.
  const [ready,setReady] = useState(true)

  useEffect(()=>{
	   
	//뒤의 1000 숫자는 1초를 뜻함
    //1초 뒤에 실행되는 코드들이 담겨 있는 함수
    setTimeout(()=>{
        setState(data)
        setReady(false)
    },1000)
  },[])

  let tip = state.tip;

	//처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨
  //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐)
  return ready ? <Loading/> :  (
    <ScrollView style={styles.container}>
      <Text style={styles.title}>나만의 꿀팁</Text>
	  <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text>
      <Image style={styles.mainImage} source={main}/>
      <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}>
        <TouchableOpacity style={styles.middleButton01}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity>
        <TouchableOpacity style={styles.middleButton02}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity>
        <TouchableOpacity style={styles.middleButton03}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity>
        <TouchableOpacity style={styles.middleButton04}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>
      </ScrollView>
      <View style={styles.cardContainer}>
         {
          tip.map((content,i)=>{
            return (<Card content={content} key={i}/>)
          })
        }
      </View>
    </ScrollView>
  );
}

아래 순서로 진행된 것

1) ready 값이 ture이므로 return 구문에서 ?물음표 바로 뒤의 Loading 컴포넌트가 화면에 그려짐.

2) 화면이 그려지고 난 다음, 1초 있다가 상태값들이 채워지고 변경됨.

3) ready 값이 false가 됨.

4) 상태값이 변경되었으므로 화면이 다시 그려짐.

5) ready 값이 false이므로 return 구문에서 :콜론 뒤의 MainPage 컴포넌트가 화면에 그려짐.

 

 

 

 

카테고리에도 상태를 달아서 카테고리별 다른 화면 보여주기 예

import React,{useState,useEffect} from 'react';
import main from '../assets/main.png';
import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native';
import data from '../data.json';
import Card from '../components/Card';
import Loading from '../components/Loading';


export default function MainPage() {
  console.disableYellowBox = true;

  const [state,setState] = useState([])
  const [ready,setReady] = useState(true)
  
  //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태
  const [cateState,setCateState] = useState([])

  useEffect(()=>{
    setTimeout(()=>{
        let tip = data.tip;
        setState(tip)
        setCateState(tip)
        setReady(false)
    },1000)
  },[])

    const category = (cate) => {
        if(cate == "전체보기"){
            //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화
            setCateState(state)
        }else{
            setCateState(state.filter((d)=>{
                return d.category == cate
            }))
        }
    }

  return ready ? <Loading/> :  (
    <ScrollView style={styles.container}>
      <Text style={styles.title}>나만의 꿀팁</Text>
	  <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text>
      <Image style={styles.mainImage} source={main}/>
      <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}>
        <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>
        <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity>
        <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity>
        <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity>
        <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>
      </ScrollView>
      <View style={styles.cardContainer}>
         {
          cateState.map((content,i)=>{
            return (<Card content={content} key={i}/>)
          })
        }
      </View>
    </ScrollView>
  );
}

카테고리 기능을 구현하기 위해서는 카테고리 상태가 필요하다.

그리고 버튼에 연결할 함수가 필요한데 이 함수는 카테고리에 따라 카테고리 상태 데이터를 새롭게 구성해주는 역할을 한다.

 

 

 

 

 

 

 

[ 앱 페이지 적용 ] 네비게이터 사용하기


여기서는 Stack Navigation을 사용한다. 

페이지 이동 시 Stack.screen에 등록된 모든 페이지 컴포넌트들은 navigation과 route라는 딕셔너리(객체)를 속성으로 넘겨받아 사용할 수 있다.

//navigation 객체가 가지고 있는 두 함수(setOptions와 navigate)

//해당 페이지의 제목을 설정할 수 있음
navigation.setOptions({
   title:'나만의 꿀팁'
})

//Stack.screen에서 name 속성으로 정해준 이름을 지정해주면 해당 페이지로 이동하는 함수
navigation.navigate("DetailPage")

//name 속성을 전달해주고, 두 번째 인자로 딕셔너리 데이터를 전달해주면, Detail 페이지에서 
//두번째 인자로 전달된 딕셔너리 데이터를 route 딕셔너리로 로 받을 수 있음
navigation.navigate("DetailPage",{
  title: title
})


//전달받은 데이터를 받는 route 딕셔너리
//비구조 할당 방식으로 route에 params 객체 키로 연결되어 전달되는 데이터를 꺼내 사용
//navigate 함수로 전달되는 딕셔너리 데이터는 다음과 같은 모습이기 때문입니다.
/*
  {
		route : {
			params :{
				title:title
			}
		}
	}

*/
const { title} = route.params;

 

 

반응형

댓글