2022. 5. 23. 19:30ㆍReact Native/Intermediate
1. Redux를 통한 로그인 로직과 흐름 검증 - Redux
action creator는 action을 반환했다. 자세한 걸 아래 코드를 보자. payload의 token을 정의할 건데 이 토큰은 앱을 껐다가 다시 들어갈 때 로그인을 다시 하지 않아도 될 수 있도록 로그인 상태를 로컬스토리지에 저장할 필요가 있다.
이제 reducer를 보자. 이전에 만든 sample_reducer를 user_reducer로 바꾸고 활용하자. reducer는 이전 단계의 state, action 두 인자를 받아와서 새로운 state를 리턴하는 함수이다. 여기서 state는 각 클래스 안에 선언된 state가 아니라 store 내부에서 관리되는 state이다.
함수에서는 action의 type field 값을 이용해서 분기시키자.
types.js와 reducer의 index.js도 맞춰서 코드를 바꾸자.
// /app/store/types.js
export const SIGN_IN = 'sign_in';
export const SIGN_UP = 'sign_up';
// /app/store/actions/user_actions.js
import {SIGN_IN, SIGN_UP} from '../types';
export function signIn() {
return {
type: SIGN_IN,
payload: {
email: 'example@sample.com',
token: 'asdfghjkljsasdfghj',
},
};
}
export function signUp() {
return {
type: SIGN_UP,
payload: {
email: 'example@sample.com',
token: 'asdfghjkljsasdfghj',
},
};
}
// /app/store/reducers/user_reducer.js
import {SIGN_IN, SIGN_UP} from '../types';
export default function (state = {}, action) {
switch (action.type) {
case SIGN_IN:
return {
...state,
auth: {
email: action.payload.email || false,
token: action.payload.token || false,
},
};
case SIGN_UP:
return {
...state,
auth: {
email: action.payload.email || false,
token: action.payload.token || false,
},
};
default:
return state;
}
}
// /app/store/actions/reducers/index.js
import {combineReducers} from 'redux';
import User from './user_reducer';
const rootReducer = combineReducers({
User,
});
export default rootReducer;
2. Redux를 통한 로그인 로직과 흐름 검증 - 컴포넌트
컴포넌트를 구현해보자.
authForm.js 수정
2개의 action creator를 묶기 위한 함수 - bindActionCreators
export 를 authForm으로 하게 되면 redux 세계관과 연결이 안 된다. 따라서 connect(mapStateToProps, mapDispatchToProps)(AuthForm)을 리턴해줌으로 연결하자.
mapStateToProps 함수를 만들어 보자. User: state.User에서 왼쪽은 react native 세계관의 user, 오른쪽은 redux 세계관의 user를 의미한다.
지금 작성하는 부분은 로그인, 회원가입 버튼을 눌렀을 때 입력된 값을 서버로 전송하는 부분을 redux로 미리 확인하는 것이다.
이 작업은 이전에 console.log로 확인했던 부분을 대치해주면 된다. 입력값을 actioncreator로 넘겨주려고 한다. 그래야 입력된 정보를 가지고 있는 action이 리턴돼서 reducer를 통해 새로운 state 값이 업데이트 될 것이다.
// /app/components/auth/authForm.js
import {useNavigation} from '@react-navigation/native';
import React, {useState} from 'react';
import {
StyleSheet,
Text,
View,
TextInput,
Button,
Platform,
} from 'react-native';
import Input from '../../utils/forms/input';
import validationRules from '../../utils/forms/validationRules';
import {connect} from 'react-redux';
import {signIn, signUp} from '../../store/actions/user_actions';
import {bindActionCreators} from 'redux';
import {setTokens} from '../../utils/misc';
const AuthForm = ({goWithoutLogin}) => {
const [type, setType] = useState('로그인'); // 로그인 / 등록
const [action, setAction] = useState('로그인'); // 로그인 / 등록
const [actionMode, setActionMode] = useState('회원가입'); // 회원가입 / 로그인 화면으로
const [hasErrors, setHasErrors] = useState(false);
const [form, setForm] = useState({
email: {
value: '',
type: 'textInput',
rules: {
isRequired: true,
isEmail: true,
},
valid: false,
},
password: {
value: '',
type: 'textInput',
rules: {
isRequired: true,
minLength: 6,
},
valid: false,
},
confirmPassword: {
value: '',
type: 'textInput',
rules: {
confirmPassword: 'password',
},
valid: false,
},
});
// state = {
// type: 'Login',
// action: 'Login',
// actionMode: '새로 등록할게요~',
// hasErrors: false,
// form: {
// email: {
// value: '',
// type: 'textInputRevised',
// rules: {},
// valid: false,
// },
// password: {
// value: '',
// type: 'textInput',
// rules: {},
// valid: false,
// },
// confirmPassword: {
// value: '',
// type: 'textInput',
// rules: {},
// valid: false,
// },
// },
// };
updateInput = (name, value) => {
setHasErrors(false);
let formCopy = form;
formCopy[name].value = value;
let rules = formCopy[name].rules;
let valid = validationRules(value, rules, formCopy);
formCopy[name].valid = valid;
setForm(form => {
return {...formCopy};
});
// setForm({
// form: formCopy,
// });
// console.warn(form);
};
confirmPassword = () => {
return type != '로그인' ? (
<Input
value={form.confirmPassword.value}
type={form.confirmPassword.type}
secureTextEntry={true}
placeholder="비밀번호 재입력"
placeholderTextColor={'#ddd'}
onChangeText={value => updateInput('confirmPassword', value)}
/>
) : null;
};
formHasErrors = () => {
return hasErrors ? (
<View style={styles.errorContainer}>
<Text style={styles.errorLabel}>
앗! 로그인 정보를 다시 확인해주세요~
</Text>
</View>
) : null;
};
changeForm = () => {
type === '로그인'
? (setType('등록'), setAction('등록'), setActionMode('로그인 화면으로'))
: (setType('로그인'), setAction('로그인'), setActionMode('회원가입'));
};
submitUser = () => {
//Init.
let isFormValid = true;
let submittedForm = {};
const formCopy = form;
for (let key in formCopy) {
if (type === '로그인') {
if (key !== 'confirmPassword') {
isFormValid = isFormValid && formCopy[key].valid;
submittedForm[key] = formCopy[key].value;
}
} else {
isFormValid = isFormValid && formCopy[key].valid;
submittedForm[key] = formCopy[key].value;
}
}
if (isFormValid) {
if (type === '로그인') {
// if (typeof signIn === 'object' && signIn !== null && 'then' in signIn) {
signIn(submittedForm).then(() => {
manageAccess();
});
// }
} else {
// if (typeof signUp === 'object' && signUp !== null && 'then' in signUp) {
signUp(submittedForm).then(() => {
manageAccess();
});
// }
}
} else {
setHasErrors(true);
}
};
manageAccess = () => {
if (props.User.auth.userId) {
this.setState({hasErrors: true});
} else {
setTokens(props.User.auth, () => {
this.setState({hasErrors: false});
props.goWithoutLogin();
});
}
};
return (
<View>
<Input
value={form.email.value}
type={form.email.type}
autoCapitalize={'none'}
keyboardType={'email-address'}
placeholder="이메일 주소"
placeholderTextColor={'#ddd'}
onChangeText={value => updateInput('email', value)}
/>
<Input
value={form.password.value}
type={form.password.type}
secureTextEntry={true}
placeholder="비밀번호"
placeholderTextColor={'#ddd'}
onChangeText={value => updateInput('password', value)}
/>
{confirmPassword()}
{formHasErrors()}
<View style={{marginTop: 40}}>
<View style={styles.button}>
<Button title={action} color="#485670" onPress={submitUser} />
</View>
<View style={styles.button}>
<Button title={actionMode} color="#485670" onPress={changeForm} />
</View>
<View style={styles.button}>
<Button
title="비회원 로그인"
color="#485670"
onPress={() => goWithoutLogin()}
/>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
errorContainer: {
marginBottom: 10,
marginTop: 30,
padding: 20,
backgroundColor: '#ee3344',
},
errorLabel: {
color: '#fff',
fontSize: 15,
fontWeight: 'bold',
textAlignVertical: 'center',
textAlign: 'center',
},
button: {
...Platform.select({
ios: {
marginTop: 15,
},
android: {
marginTop: 15,
marginBottom: 10,
},
}),
},
});
function mapStateToProps(state) {
return {
User: state.User,
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({signIn, signUp}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(AuthForm);
참고 자료
iOS/Android 앱 개발을 위한 실전 React Native - Intermediate - 인프런 | 강의
React Native 기반 모바일 앱 개발을 위한 중급 강의입니다. 프론트엔드의 심화내용 학습 뿐만 아니라 Firebase 기반의 백엔드 내용까지 함께 배우면서, 서버 연동/ 로그인/ 데이터 송수신/ 공공API 활
www.inflearn.com
'React Native > Intermediate' 카테고리의 다른 글
[React Native] 로그인 화면 구현 (백엔드) - 2. Firebase 로그인 (0) | 2022.06.15 |
---|---|
[React Native] 로그인 화면 구현 (백엔드) - 1. Firebase 회원가입 (0) | 2022.06.14 |
[React Native] 로그인 화면 구현 (프론트엔드) - 5. 로그인/등록 버튼 이벤트 생성 (0) | 2022.05.23 |
[React Native] 로그인 화면 구현 (프론트엔드) - 4. 로그인 Validation (0) | 2022.05.23 |
[React Native] 로그인 화면 구현 (프론트엔드) - 3. 로그인, 회원가입 화면 버튼 및 이벤트 생성 (0) | 2022.05.23 |