@ 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
@ 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;
'React Native > React Native_study' 카테고리의 다른 글
[리액트 네이티브를 다루는 기술 #18] 8장 Firebase로 사진 공유 앱 만들기1 (p .453 ~ 465) (0) | 2022.02.18 |
---|---|
[리액트 네이티브를 다루는 기술 #17] 8장 Firebase로 사진 공유 앱 만들기1 (p .445 ~ 452) (0) | 2022.02.15 |
[리액트 네이티브를 다루는 기술 #15] 8장 Firebase로 사진 공유 앱 만들기1 (p .407 ~ 418) (0) | 2022.01.20 |
[리액트 네이티브를 다루는 기술 #14] 7장 다이어리 앱 만들기2 (p .399 ~ 406) (0) | 2022.01.17 |
[리액트 네이티브를 다루는 기술 #13] 7장 다이어리 앱 만들기2 (p .380 ~ 398) (0) | 2022.01.14 |