RN) 토이플젝_영화앱_1

소정·2023년 6월 7일
0

ReactNative

목록 보기
16/17

🔨 작업 순서

0) react navigation과 AsysncStorage 라이브러리 추가
1) Main.tsx에 최상위 네비게이터 배치
2) Intro 화면 컴포넌트 제작
3) login 관련 3개 화면 컴포넌트 제작 [screen_login 폴더]
4) Main화면 관련 4개 컴포넌트 제작 [screen_main 폴더]


[0] react navigation과 AsysncStorage 라이브러리 추가

1. 네비게이터 추가

https://reactnavigation.org/docs/getting-started

1.1 ) 코어 라이브러리 추가

1.2) 사용할 네비게이터 추가

1.2.1) 스택

1.2.2) 바텀 탭

2. AsyncStorage 추가

https://react-native-async-storage.github.io/async-storage/docs/install/


모든 화면에서 쓸 types.tsx 파일

  • 타입 스크립트에서는 네비게이터에 지정할 스크린 화면 리스트 타입을 미리 만들어야함
  • types.tsx 파일을 만들어 관리 할 것, 필요한 곳에서 임포트 해서 사용하기
    => 한 곳에 모아 보기 편리하게 하기 위해

//1. 최상위 스택 네비게이터의 화면 리스트 타입 지정
export type RootScreenList = {
    Intro : undefined,
    LoginNav : undefined,
    MainNav : undefined, //undefined = putExtra
}


// 2. LoginNav Stack Navigator 화면 리스트 지정
export type LoginNavScreenList = {
    Login : undefined,
    SignUp : undefined,
    ResetPassWd : undefined,

    //메인 Nav화면으로 전환할 수 있도록 등록
    MainNav : undefined,
}

[1] Main.tsx에 최상위 네비게이터 배치

  1. 최상위 네비게이터컨테이너와 createStackNavigator 임포트
  2. 스크린(화면)리스트를 한 파일에 모아두어 관리하기 편하게 만들기
    --> 타입 스크립트에서는 네비게이터에 지정할 스크린 화면 리스트 타입을 미리 만들어야함
    --> types.tsx 파일을 만들어 관리 할 것, 필요한 곳에서 임포트 해서 사용하기
  3. 앱 전체 화면들을 전환 할 수 있는 최 상위 stack navigtor 객체 생성 & 타입 지정
  4. 최상위 네비게이터에 의해 전환될 페이지(screen or navigator) 사용하기 위한 임포트

// 앱 제작의 주요 작업 순서
// 0) react navigation과 AsysncStorage 라이브러리 추가
// 1) Main.tsx에 최상위 네비게이터 배치
// 2) Intro 화면 컴포넌트 제작
// 3) login 관련 3개 화면 컴포넌트 제작 [screen_login 폴더]
// 4) Main화면 관련 4개 컴포넌트 제작 [screen_main 폴더]

////////////////////////////////////////////////////////////////

//1. 
import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";

// 2.
// 타입 스크립트에서는 네비게이터에 지정할 스크린 화면 리스트 타입을 미리 만들어야함
// types.tsx 파일을 만들어 관리 할 것, 필요한 곳에서 임포트 해서 사용하기
// 한 곳에 모아 보기 편리하게 하기 위해
import { RootScreenList } from "./types";

// 3. 앱 전체 화면들을 전환 할 수 있는 최 상위 stack navigtor 객체 생성 & 타입 지정
const rootStack = createStackNavigator<RootScreenList>()

// 4. 최상위 네비게이터에 의해 전환될 페이지(screen or navigator) 사용하기 위한 임포트
import Intro from "./Intro";
import LoginNav from "./navigators/LoginNav";
import MainNav from "./navigators/MainNav";

//네비게이션 콘테이너를 가진 최상위 root 컴포넌트 제작 - 앱의 시작 컴포넌트(index.js에서 설정)
export default function Main() : JSX.Element {

    return (
        <NavigationContainer>

            <rootStack.Navigator screenOptions={{headerShown : false}}>
                <rootStack.Screen name="Intro" component={Intro}></rootStack.Screen>
                <rootStack.Screen name="LoginNav" component={LoginNav}></rootStack.Screen>
                <rootStack.Screen name="MainNav" component={MainNav}></rootStack.Screen>
            </rootStack.Navigator>

        </NavigationContainer>
    )

}




[2] Intro 화면 컴포넌트 제작

  1. 네비게이션에 등록하면 파라미터로 props {navigation, route} 객체 받음
  2. Stack Navigator의 screen으로 등록된 컴포넌트에서 전달받은 props의 타입 지정
    => 그냥 Props만 임포트 하면 안되고 이 프로프가 스택에 연결된 3 화면중 누구것인지 명시해야함!!!
  3. 로그인한 적이 있는지 검사한 후 결과에 따라 로그인 Nav 또는 메인 Nav로 이동 : AsyncStorage(비동기)사용
  4. AsyncStorage가 비동기 방식으로 읽어오는 사이에 잠깐 보여질 수 있는 로딩 화면 지정
import React from "react";
import { View, Text, StyleSheet, Button, ActivityIndicator } from "react-native";
// ActivityIndicator - 화면지시자 , 빙빙 도는 모양, 프로그레스바
import AsyncStorage from '@react-native-async-storage/async-storage'

// 2. Stack Navigator의 screen으로 등록된 컴포넌트에서 전달받은 props의 타입 지정
import { StackScreenProps } from "@react-navigation/stack"
import { RootScreenList } from "./types"; 

//그냥 Props만 임포트 하면 안되고 이 프로프가 스택에 연결된 3 화면중 누구것인지 명시해야함!!!
type IntroProps = StackScreenProps<RootScreenList , 'Intro'>  

//function component
// 1. 네비게이션에 등록하면 파라미터로 props {navigation, route} 객체 받음
export default function Intro(props : IntroProps):JSX.Element {

    // 2. 로그인한 적이 있는지 검사한 후 결과에 따라 로그인 Nav 또는 메인 Nav로 이동
    AsyncStorage.getItem('email').then((value)=>{
        if(value) props.navigation.replace('MainNav') //replace는 현재 화면 스택에 안 쌓고 감
        else props.navigation.replace('LoginNav')
    })

    return (
        // 화면 전환 테스트 목록으로 보여질 임시 화면 제작
        // <View style={style.root}>
            
        //     <Text >인트로</Text>
        //     <Button title="로그인 페이지 이동" 
        //         onPress={()=>props.navigation.navigate("LoginNav")}></Button>
        //     <Button title="메인 페이지 이동"
        //         onPress={()=>props.navigation.navigate("MainNav")}></Button>
        // </View>

        // 3. AsyncStorage가 비동기 방식으로 읽어오는 사이에 잠깐 보여질 수 있는 로딩 화면
        <View style={style.root}>
            <ActivityIndicator></ActivityIndicator>
        </View>
        
    )

}


const style = StyleSheet.create({

    root : {
        padding : 16,
        flex : 1,
        justifyContent : 'center',
        alignItems : 'center',
    }

})



[3] login 관련 3개 화면 컴포넌트 제작 [screen_login 폴더]

1. 로그인 네비게이션

  • 로그인, 회원가입, 비밀번호 재설정화면 Stack Navigator 가진 화면
  1. LoginNav의 Stack Navigator 객체 생성
  2. 로그인 네브에서 전환 할 화면 스크린들 type지정
  3. 네비게이터가 보여줄 화면 컴포넌트들 import
  4. Stack.Screen으로 등록
import React from "react";

//LoginNav의 Stack Navigator 객체 생성
import {createStackNavigator} from '@react-navigation/stack'

//이 로그인 네브에서 전환 할 화면 스크린들 type지정
import { LoginNavScreenList } from "../types";

const Stack = createStackNavigator<LoginNavScreenList>()

//네비게이터가 보여줄 화면 컴포넌트들 import
import SignUp from "../screen_login/SignUp";
import ResetPassWd from "../screen_login/ResetPassWd";
import Login from "../screen_login/Login";

export default function LoginNav() : JSX.Element{

    return (
        <Stack.Navigator 
            screenOptions={{headerShown : false,}}
            >
            <Stack.Screen name="Login" component={Login}></Stack.Screen>
            <Stack.Screen name="SignUp" component={SignUp}></Stack.Screen>
            <Stack.Screen name="ResetPassWd" component={ResetPassWd}></Stack.Screen>
        </Stack.Navigator>
    )

}

2. 로그인 화면

2.1) 공통으로 사용하는 컴포넌트 (InputText)

  • 여러 페이지에서 공통으로 쓸 것을 미리 다른 파일로 만들어 어디서든 쓸 수 있도록 한다

  • 같은 모양을 쓰지만 각각의 input마다 달라야하는 placeholder 같은 것을 나만의 프로퍼티로 만들어 넘겨 받는다 (props type 설정)

import React from 'react'
import {View, TextInput, StyleSheet} from 'react-native'



//props type [TextInput 컴포넌트의 각 속성들을 전달받기 위한 타입]
type Props = {
    placeholder : string | undefined,
    secureTextEntry? : boolean | undefined, //nullable로 만들어서 꼭 안써도 되게 함
    onChangeText? : (text:string)=>void | undefined
}

export default function inputComponent(props : Props):JSX.Element{
    return (
        <View style={style.container}>
            <TextInput 
                placeholder={props.placeholder}  //컴포넌트를 사용하는 곳에서 힌트에 대한 property를 전달받아야한다
                secureTextEntry = {props.secureTextEntry} //텍스트 시큐어 기능
                onChangeText={props.onChangeText} //글씨 바뀔때마다 반응하는 넘
                placeholderTextColor='#C3C2C8'
                style={style.input}></TextInput>
        </View>
    )
}

const style= StyleSheet.create({
    container:{
        width:'100%',
        height: 40,
        paddingLeft:16,
        paddingRight:16,
        borderWidth:1,
        borderColor:'#D3D3D3',
        borderRadius: 4,
        backgroundColor: '#FAFAFA',
        marginTop:8,
        marginBottom:8,
    },
    input:{
        flex:1,   //TextInput의 높이를 container높이 40에 맞게
        color: '#292929',
    },
})

2.2) login 화면

  1. loginNav와 연결된 화면 중 누구인지 명시하기
  2. 공통으로 사용한다고 만들어 놓은 컴포넌티 임포트 하여 사용하기

  • 로그인 완료하면 Main으로 가야하는데 LoginNavScreenList에는 MainNav가 없음 메인 Nav화면으로 전환할 수 있도록 types.tsx에 등록
import React from 'react'
import { View, Text, StyleSheet, Button } from 'react-native'

//공통으로 사용하는 컴포넌트 improt
import InputComponent from '../components/inputComponent'


// 네비게이션이터에서 네비게이션 받아오기 위한 작업
import { StackScreenProps } from '@react-navigation/stack'
import { LoginNavScreenList } from '../types' //어떤 화면인데?

type LoginProps= StackScreenProps<LoginNavScreenList,'Login'> //타입지정

export default function Login(props : LoginProps) : JSX.Element { //props 객체 {navigation, route}

    //우선 테스트 목적 화면
    // return (

    //     <View style={style.root}>
    //         <Text>로그인</Text>
    //     </View>

    // )

    return (

        <View style={style.root}>
            {/* 두 영역으로 구성 : 로그인 콘텐츠 영역, 아래쪽에 회사 or 앱 이름 표시 영역 */}
            <View style={style.content}>
                {/* 1.1 로고 */}
                <Text style={style.logo}>Movie</Text>

                {/* 1.2 이메일과 비밀번호 입력 박스 제작 */}
                {/* 자주쓰는 컴포넌트를 따로 빼서 제작 - 재사용을 위해 */}
                {/* TextInput은 로그인,회원가입, 비밀번호 재설정 화면에서도 모두 사용
                    사용빈도가 높아, 이를 일일이 스타일 하기 번거로우니 별도의 CustomCoponent로 제작하여 재사용
                */}
                <InputComponent placeholder='이메일'></InputComponent> 
                <InputComponent placeholder='비밀번호' secureTextEntry={true}></InputComponent>
                {/* 내가 만드는 속성 (placeholder) */}

                {/* 1.3 비밀번호 재설정 */}
                <Text style={style.resetPW} onPress={()=>props.navigation.navigate('ResetPassWd')}>비밀번호 재설정</Text>

                {/* 1.4 로그인 버튼 만들기 */}
                <View style={{width : '100%', marginBottom : 24}}>
                    <Button title='login'></Button>
                </View>


                {/* 1.5 회원가입 안내 글씨 */}
                <Text style={style.singup}>계정이 없으신가요? 
                    <Text style={style.singupLink} onPress={()=>props.navigation.navigate('SignUp')}> 가입하기</Text>
                </Text>

            </View>

            <View style={style.footer}>
                <Text style={style.footercopyright}>Movie info app by SJ</Text>
            </View>
        </View>

    )

}

const style = StyleSheet.create({

    root : {
        flex :1,
        backgroundColor : 'FEFFFF'
    },
    content : {
        flex :1,
        justifyContent : 'center',
        alignItems : 'center',
        padding : 32,
    },
    footer : {
        borderTopWidth : 1,
        borderTopColor : '#D3D3D3',
        padding : 8,
    },
    logo : {
        color :'#292929',
        fontSize : 40,
        fontWeight : 'bold',
        marginBottom : 32,
    },
    resetPW : {
        width : '100%',
        color : '#3796EF',
        textAlign : 'right',
        marginTop : 8,
        marginBottom : 16,
        marginRight : 8,
    },
    singup : {
        color : '#929292',

    },
    singupLink : {
        color : '#3796EF',
    },
    footercopyright : {
        textAlign : 'center',
        padding : 8,
    }

})

3. 회원가입 화면

3.1) 공통으로 쓸 탭 컴포넌트

  1. 회원가입 화면에서 나만의 속성으로 넘겨 받을 것 interface로 받아보기 (type 선언 아닌 또 다른 방법)
  2. 탭 선택 값을 boolean 으로 받음...
import React from 'react'
import { TouchableOpacity, Text, StyleSheet } from 'react-native'

// type 타입만드는 또 다른 방법
interface Props{
    label:string,
    selected?: boolean | undefined,
    onPress?: ()=>void | undefined,
}

export default function tabComponent(props:Props):JSX.Element{

    //탭의 선택여부에 따라 글씨색상 달라야 함
    let color= props.selected? '#292929' : '#929292'

    //탭의 선택여부에 따라 아래경계선의 색상도 변경
    style.container.borderBottomColor= color
    
    return (
        // 탭 터치에 따른 처리 메소드를 지정하기위해
        <TouchableOpacity style={style.container} onPress={props.onPress}>
            {/* 탭에 보여질 글씨는 props로 전달받기 */}
            <Text style={{color:color}}>{props.label}</Text>
        </TouchableOpacity>
    )
}

const style= StyleSheet.create({
    container:{
        flex:1, //탭이 놓여질때 다른 탭과의 flex값을 같게 하여 가로 너비를 균등하게 하기 위함
        borderBottomWidth:2,
        borderBottomColor:'#929292',
        paddingBottom:8,
        alignItems:'center',
        justifyContent:'center',
    }
})

3.2) 회원가입 화면

  1. loginNav와 연결된 화면 중 누구인지 명시하기
  2. 다른 파일로 만들어 놓은 공통 사용 컴포넌트 임포트
  3. 탭 선택이 동적으로 변하게 하기 위해 state변수를 두고 현재 선택된 탭 번호와 map으로 만든 탭 index번호가 같을 때 boolean값이 true가 되게 한다

  • 화면 제작 중 자바스크립트 영역에서 if문 사용 불가하다
  • 조건문 처리를 논리연산자 특징을 사용하여 한다
    && (논리 연산자) = 앞 조건이 false면 뒤 조건 안봄
    & (비트 연산자) = 앞 조건이 false여도 뒤도 쳐다봄
    논리연산자의 특징을 이용하여 처리 앞 조건 값이 true일 때만 뒤 실행문이 실행하도록 함
import React, {useState} from 'react'
import { View, Text, Button, StyleSheet } from 'react-native'

// [2]공통 사용 컴포넌트 임포트
import TabComponent from '../components/tabComponent'
import InputComponent from '../components/inputComponent'

// [1]네비게이션이터에서 네비게이션 받아오기 위한 작업
import { StackScreenProps } from '@react-navigation/stack'
import { LoginNavScreenList } from '../types' //어떤 화면인데?

type SignUpProps = StackScreenProps<LoginNavScreenList ,"SignUp"> //타입지정

export default function SignUp(props : SignUpProps) : JSX.Element { //props 객체 {navigation, route}

    // //우선 테스트 목적 화면
    // return (

    //     <View style={style.root}>
    //         <Text>SignUp</Text>
    //     </View>

    // )

    // tab 작업에서 사용할 state 변수 생성
    const [tabs, setTabs] = useState(['전화번호', '이메일']) //탭 라벨을 string배열로 만들기
    const [tabIndex, setTabIndex] = useState(0) //현재 선택된 탭 번호


    // 완료 버튼 클릭 시 실행하는 메소드
    const Signup = () => {
        // 원래는 서버에 정보 보내는 작업 코드 작성///

        //전송작업 끝나면 회원가입화면 종료 및 이전 로그인화면으로 이동
        props.navigation.goBack()
    }

    return (
        <View style={style.root}>
            {/* Content, footer영역으로 구성 */}
            <View style={style.content}>

                {/* [3] 1.1 전화번호와 이메일 중 원하는 정보로 회원가입할 수 있도록 탭으로 구성 */}
                <View style={style.tabContainer}>
                    {/* 탭 텀포넌트는 RN에 없음.. 그래서 Custom component로 제작 */}
                    {/* <TabComponent label='전화번호' selected={true}></TabComponent>
                    <TabComponent label='이메일' selected={false}></TabComponent> */}

                    {/* 탭 라벨의 개수만큼 탭 컴포넌트를 만들기 위해 map() 메소드 이용 */}

                    {
                        tabs.map( (value, index)=>{
                            return <TabComponent 
                                label={value} 
                                key={index}
                                selected = { index == tabIndex }
                                onPress= {()=>setTabIndex(index)}
                                ></TabComponent>
                        } )
                    }

                </View>

                {/* 1.2 정보 입력 */}
                <InputComponent placeholder={tabs[tabIndex]}></InputComponent>

                {/* 1.3 이메일 탭일때는 비밀번호 입력 컴포넌트가 추가로 존재 해야됨 */}
                {
                    //if(tabIndex == 1){} //JSX {}안에서는 if문법 불가능 연산자는 가능
                    
                    // && (논리 연산자) = 앞 조건이 false면 뒤 조건 안봄
                    // & (비트 연산자) = 앞 조건이 false여도 뒤도 쳐다봄

                    //논리연산자의 특징을 이용하여 처리 앞 조건 값이 true일 때만 뒤 실행문이 실행됨
                    tabIndex == 1 && <InputComponent placeholder='비밀번호' secureTextEntry={true}></InputComponent>
                }

                {/* 1.4 전화번호 탭일 때는 [다음] 버튼 */}
                {
                    tabIndex === 0 && <View style={{width:'100%', margin : 16,}}><Button title='다음' onPress={()=>setTabIndex(1)}></Button></View>
                }

                {/* 1.5 이메일 탭일 때 [완료] 버튼 */}
                {
                    tabIndex === 1 && <View style={{width:'100%', margin : 16,}}><Button title='완료' onPress={()=>Signup()}></Button></View>
                }

                {/* 1.6 전화번호 탭일 때 입력에 대한 이유를 안내하는 메세지 표시 */}
                {
                    tabIndex === 0 && <Text style={style.telMsg}>Movie APP의 업데이트 내용을 SMS로 수신할 수 있으며, 언제든지 수신 취소 가능합니다</Text>
                }

            </View>


            <View style={style.footer}>
                <Text style={style.footerMsg}>이미 계정이 있으신가요? <Text style={style.footerGoBack} onPress={()=>props.navigation.goBack()}>로그인</Text></Text>
            </View>

        </View>
    )

}

const style = StyleSheet.create({

    root : {
        flex :1,
        backgroundColor : '#FEFFFF'
    },
    content : {
        flex :1,
        alignItems : 'center',
        padding : 32,
    },
    telMsg : {
        textAlign : 'center',
        fontSize : 12,
        color : '#929292',
        marginLeft :8,
        marginRight : 8,
    },
    footer : {
        borderTopWidth : 1,
        borderTopColor : '#D3D3D3',
        padding : 8,
    },
    footerMsg : {
        textAlign : 'center',
        color : '#929292',
    },
    footerGoBack : {
        color : '#3796EF',
    },
    tabContainer : {
        flexDirection : 'row',
        marginBottom : 16,
    }
})



4. 비밀번호 재설정


import React, {useState} from 'react'
import { View, Text, Button, Alert, Image, StyleSheet } from 'react-native'


//공통사용 컴포넌트 임포트
import TabComponent from '../components/tabComponent'
import InputComponent from '../components/inputComponent'


// 네비게이션이터에서 네비게이션 받아오기 위한 작업
import { StackScreenProps } from '@react-navigation/stack'
import { LoginNavScreenList } from '../types' //어떤 화면인데?

type ResetPassWdProps = StackScreenProps<LoginNavScreenList ,"ResetPassWd"> //타입지정

export default function ResetPassWd(props : ResetPassWdProps) : JSX.Element { //props 객체 {navigation, route}

    //우선 테스트 목적 화면
    // return (

    //     <View style={style.root}>
    //         <Text>ResetPassWd</Text>
    //     </View>

    // )

    // 탭에 따른 화면 구성을 위한 state 변수들
    const [tabs, setTabs] = useState<string[]>(['이메일', '전화번호']) //탭이름
    const [tabIndex, setTabIndex] = useState<number>(0) //현재 탭 번호

    //탭 선택에 따른 안내 메세지 
    const message = [
        '이메일을 입력하면 임시 비밀번호를 보내드립니다',
        '전화번호를 입력하면 임시 비밀번호를 보내드립니다',
    ]

    //2. 비밀번호 재설정 화면
    return (
        <View style={style.root}>
            {/* 1. 콘텐츠 영역 */}
            <View style={style.content}>
                {/* 1.1 자물쇠 모양의 이미지 표시 영역 */}
                <View style={style.lockImgcontainer}>
                    <Image source={require('../Images/lock.png')}></Image>
                </View>

                {/* 1.2 타이틀 안내문구 표시 */}
                <Text style={style.title}>로그인에 문제가 있나요</Text>

                {/* 1.3 이메일 또는 전화번호 탭 선택에 대한 안내문구 */}
                <Text style={style.msg}> {message[tabIndex]} </Text>

                {/* 1.4 탭 만들기 - 공통 컴포넌트 사용하여 */}
                <View style={style.tabContainer}>
                    {
                        tabs.map( (value, index) => {
                            return <TabComponent label={tabs[index]} selected={index==tabIndex} onPress={()=>setTabIndex(index)} key={index}></TabComponent>
                        })
                    }
                </View>

                {/* 1.5 정보 입력 박스 */}
                <InputComponent placeholder={tabs[tabIndex]}></InputComponent>

                {/* 1.6 전송 버튼 */}
                <View style={{width:'100%', margin:16}}>
                    <Button title='전송' onPress={()=> Alert.alert('임시비밀 번호 발송', '로그인 후 정보수정을 통해 안전한 비밀번호로 변경 하세요') }></Button>
                </View>

            </View>

            {/* 2. footer 영역 */}
            <View style={style.footer}>
                <Text style={style.footerMsg}>로그인 화면으로 돌아가기 <Text style={style.footerGoBack} onPress={()=>props.navigation.goBack()}>클릭</Text></Text>
            </View>
        </View>
    )

}

const style = StyleSheet.create({

    root : {
        flex :1,
        padding:16,
        backgroundColor : '#FEFFFF'
    },
    content : {
        flex : 1,
        width : '100%',
        alignItems : 'center',
        padding : 16,
    },
    lockImgcontainer : {
        padding : 24,
        borderWidth : 2,
        borderColor : '#292929',
        borderRadius : 100,
    },
    title : {
        fontSize : 16,
        marginBottom : 16,
        marginTop : 16,
    },
    msg : {
        textAlign : 'center',
        marginBottom : 14,
        color : '#292929',
    },
    tabContainer : {
        flexDirection : 'row',
        marginBottom : 16,
    },
    footer : {
        borderTopWidth : 1,
        borderTopColor : '#D3D3D3',
        padding : 8,
    },
    footerMsg : {
        textAlign : 'center',
        color : '#929292',
    },
    footerGoBack : {
        color : '#3796EF',
    },
})



profile
보조기억장치

0개의 댓글