React Native/React Native_study

[리액트 네이티브를 다루는 기술 #16] 8장 Firebase로 사진 공유 앱 만들기1 (p .419 ~ 444)

bocoder
728x90
반응형

@ List

* 8.2 회원 인증 기능 구현하기

** 8.2.1 회원 인증을 위한 UI 준비하기

** 8.2.2 인풋 상태 관리하기

** 8.2.3 인풋에서 키보드 리턴 처리하기

** 8.2.4 컴포넌트 분리하기

 

@ Note

1. Firebase 로그인 제공업체 목록

 

2. rest 연산자, spread 연사자

 - [BorderedInput] 의 파라미터 부분에서는 rest 연산자를 사용하고, JSX 부분에서는 spread 연산자를 사용해 Props로 받아온 모든 키와 값을 TextInput 컴포넌트의 Props로 지정함

  > rest 연산자 : 파라미터나 값을 선언하는 부분에서 사용

const object = { a: 1 , b: 2, c: 3 };
const { a, ...rest } = object;
console.log(a); // 1
console.log(rest); // { b: 2, c: 3 }

// 배열에서도 사용 가능
const array = [1, 2, 3, 4, 5];
const [a, b, ...rest] = array;
console.log(a); // 1
console.log(b); // 2
console.log(rest); // [3, 4, 5]

  > spread 연산자 : 특정 객체나 배열에 사용

const value = { b: 2, c: 3 };
const object = { a: 1, ...value };
console.log(object); // { a: 1, b: 2, c: 3}

 

3. Keyboard, KeyboardAvoidingView 사용

 - Keyboard.dismiss() 를 통해, 확인 버튼  클릭 시 키보드 사라지게 함

 - iOS에서는 인풋에 포커스가 잡혀서 키보드가 나타나면 화면을 가리게 되고, 이 때 <keyboardAvoidingView> 를 사용

 

4. TextInput 의 다양한 Props 사용

  <BorderedInput
    hasMarginBottom
    placeholder="이메일"
    value={form.email}
    onChangeText={createChangeTextHandler('email')}
    autoCapitalize="none" // 첫번째 문자 자동 대문자 설정 비활성화
    autoCorrect={false} // 자동 수정 비활성화
    autoCompleteType="email" // 과거 입력값 자동 표시
    keyboardType="email-address" // 키보드 타입
    returnKeyType="next"
    onSubmitEditing={() => passwordRef.current.focus()}
    // secureTextEntry // 입력 텍스트 마스킹
  />

 

5. forwardRef 사용

 - 특정 컴포넌트 내부에 있는 또 다른 컴포넌트에 ref를 설정하려면 forwardRef 함수를 사용해야 함

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

function BorderedInput({hasMarginBottom, ...rest}, ref) {
  return (
    <TextInput
      style={[styles.input, hasMarginBottom && styles.margin]}
      ref={ref}
      {...rest}
    />
  );
}

(...)

export default React.forwardRef(BorderedInput);

 

@ Result

iOS / android - 최초 화면

 

0123
iOS / android - 회원 인증을 위한 UI 구성

 

012
iOS / android - 이메일 및 비밀번호 입력 환경 셋팅

 

@ Git

 - https://github.com/eunbok-bocoder/PublicGalleryBocoder/commits/main

 

# Source tree

 

# components > BorderedInput.js

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

function BorderedInput({hasMarginBottom, ...rest}, ref) {
  return (
    <TextInput
      style={[styles.input, hasMarginBottom && styles.margin]}
      ref={ref}
      {...rest}
    />
  );
}

const styles = StyleSheet.create({
  input: {
    borderColor: '#bdbdbd',
    borderWidth: 1,
    paddingHorizontal: 16,
    borderRadius: 4,
    height: 48,
    backgroundColor: 'white',
  },
  margin: {
    marginBottom: 16,
  },
});

export default React.forwardRef(BorderedInput);

 

# components > CustomButton.js

import React from 'react';
import {StyleSheet, View, Pressable, Text, Platform} from 'react-native';

function CustomButton({onPress, title, hasMarginBottom, theme}) {
  const isPrimary = theme === 'primary';

  return (
    <View style={[styles.block, hasMarginBottom && styles.margin]}>
      <Pressable
        onPress={onPress}
        style={({pressed}) => [
          styles.wrapper,
          isPrimary && styles.primaryWrapper,
          Platform.OS === 'ios' && pressed & {opacity: 0.5},
        ]}
        android_ripple={{
          color: isPrimary ? '#fffff' : '#6200ee',
        }}>
        <Text
          style={[
            styles.text,
            isPrimary ? styles.primaryText : styles.secondaryText,
          ]}>
          {title}
        </Text>
      </Pressable>
    </View>
  );
}

CustomButton.defaultProps = {
  theme: 'primary',
};

const styles = StyleSheet.create({
  block: {
    borderRadius: 4,
    overflow: 'hidden',
  },
  wrapper: {
    borderRadius: 4,
    height: 48,
    alignItems: 'center',
    justifyContent: 'center',
    // backgroundColor: '#6200ee',
  },
  primaryWrapper: {
    backgroundColor: '#6200ee',
  },
  text: {
    fontWeight: 'bold',
    fontSize: 14,
    color: 'white',
  },
  primaryText: {
    color: 'white',
  },
  secondaryText: {
    color: '#6200ee',
  },
  margin: {
    marginBottom: 8,
  },
});

export default CustomButton;

 

# components > SignButton.js

import React from 'react';
import {StyleSheet, View} from 'react-native';
import CustomButton from '../components/CustomButton';
import {useNavigation} from '@react-navigation/native';

function SignButtons({isSignUp, onSubmit}) {
  const navigation = useNavigation();

  const primaryTitle = isSignUp ? '회원가입' : '로그인';
  const secondaryTitle = isSignUp ? '로그인' : '회원가입';

  const onSecondaryButtonPress = () => {
    if (isSignUp) {
      navigation.goBack();
    } else {
      navigation.push('SignIn', {isSignUp: true});
    }
  };

  return (
    <View style={styles.buttons}>
      <CustomButton title={primaryTitle} hasMarginBottom onPress={onSubmit} />
      <CustomButton
        title={secondaryTitle}
        theme="secondary"
        onPress={onSecondaryButtonPress}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  buttons: {
    marginTop: 64,
  },
});

export default SignButtons;

 

# components > SignForm.js

import React, {useRef} from 'react';
import BorderedInput from './BorderedInput';

function SignForm({isSignUp, onSubmit, form, createChangeTextHandler}) {
  const passwordRef = useRef();
  const confirmPasswordRef = useRef();

  return (
    <>
      <BorderedInput
        hasMarginBottom
        placeholder="이메일"
        value={form.email}
        onChangeText={createChangeTextHandler('email')}
        autoCapitalize="none" // 첫번째 문자 자동 대문자 설정 비활성화
        autoCorrect={false} // 자동 수정 비활성화
        autoCompleteType="email" // 과거 입력값 자동 표시
        keyboardType="email-address" // 키보드 타입
        returnKeyType="next"
        onSubmitEditing={() => passwordRef.current.focus()}
      />
      <BorderedInput
        placeholder="비밀번호"
        secureTextEntry // 입력 텍스트 마스킹
        hasMarginBottom={isSignUp}
        value={form.password}
        onChangeText={createChangeTextHandler('password')}
        ref={passwordRef}
        returnKeyType={isSignUp ? 'next' : 'done'}
        onSubmitEditing={() => {
          if (isSignUp) {
            confirmPasswordRef.current.focus();
          } else {
            onSubmit();
          }
        }}
      />
      {isSignUp && (
        <BorderedInput
          placeholder="비밀번호 확인"
          secureTextEntry
          value={form.confirmPassword}
          onChangeText={createChangeTextHandler('confirmPassword')}
          ref={confirmPasswordRef}
          returnKeyType="done"
          onSubmitEditing={onSubmit}
        />
      )}
    </>
  );
}

export default SignForm;

 

# screens > RootStack.js

import React from 'react';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import SignInScreen from './SignInScreen';

const Stack = createNativeStackNavigator();

function RootStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="SignIn"
        component={SignInScreen}
        options={{headerShown: false}}
      />
    </Stack.Navigator>
  );
}

export default RootStack;

 

# screens > SignInScreen.js

import React, {useRef, useState} from 'react';
import {
  Keyboard,
  KeyboardAvoidingView,
  Platform,
  StyleSheet,
  Text,
  View,
} from 'react-native';
import {SafeAreaView} from 'react-native-safe-area-context';
import SignButtons from '../components/SignButton';
import SignForm from '../components/SignForm';

function SignInScreen({navigation, route}) {
  const {isSignUp} = route.params ?? {}; // null 병합 연산자
  const [form, setForm] = useState({
    email: '',
    password: '',
    confirmPassword: '',
  });

  const createChangeTextHandler = name => value => {
    setForm({...form, [name]: value});
  };

  const onSubmit = () => {
    Keyboard.dismiss();
    console.log(form);
  };

  return (
    <KeyboardAvoidingView
      style={styles.KeyboardAvoidingView}
      behavior={Platform.select({ios: 'padding'})}>
      <SafeAreaView style={styles.fullscreen}>
        <Text style={styles.text}>PublicGallery</Text>
        <View style={styles.form}>
          <SignForm
            isSignUp={isSignUp}
            onSubmit={onSubmit}
            form={form}
            createChangeTextHandler={createChangeTextHandler}
          />
          <SignButtons isSignUp={isSignUp} onSubmit={onSubmit} />
        </View>
      </SafeAreaView>
    </KeyboardAvoidingView>
  );
}

const styles = StyleSheet.create({
  KeyboardAvoidingView: {
    flex: 1,
  },
  fullscreen: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 32,
    fontWeight: 'bold',
  },
  form: {
    marginTop: 64,
    width: '100%',
    paddingHorizontal: 16,
  },
  //   buttons: {
  //     marginTop: 64,
  //   },
});

export default SignInScreen;

 

728x90
반응형