LifeSports Application(ReactNative & Node.js) - 7. Redux

yellow_note·2021년 9월 6일
0

#1 Redux

redux는 상태를 관리해주는 프레임워크입니다. 우선 react를 생각해보겠습니다. 앞서 작성한 MyPage의 구성도를 살펴 보면 다음과 같습니다.

MyPageScreen부터 시작해서 조그마한 컴포넌트들까지 수많은 DOM(Document Object Model)들로 이루어져 있죠. 그리고 이 DOM들은 트리 형태로 구성되어 DOM Tree가 됩니다. 그러면 여기서 각 컴포넌트들에 변수들이 존재한다고 생각을 해보겠습니다. 트리의 특성상 root에서부터 내려오는 구조이기 때문에 각 컴포넌트들에 존재하는 변수들은 아래로 밖에 내려가질 못합니다. 즉, 상위 컴포넌트에서는 하위 컴포넌트에 존재하는 변수를 사용할 수 없다는 의미이죠. 이러한 문제점을 해결하기 위해 사용되는 프레임워크가 redux입니다.

물론 redux를 사용하지 않고 context라는 기능을 이용하여 상위 컴포넌트에서 변수를 사용할 수는 있지만 개인적으로 context를 이용하면 코드가 복잡해지고 변수가 너무 복잡해져 사용이 번거로웠던 기억이 있어서 추천할만한 기능은 아닌 것 같습니다. 하지만 redux는 위의 사진처럼 하나의 store에 변수들을 관리해주기 때문에 사용이 쉽고, 모든 변수들을 한눈에 파악이 가능하다는 장점이 있습니다. 그러면 redux를 사용해보도록 하겠습니다.

#2 redux 설치

npm install --save redux react-redux redux-actions immer redux-devtools-extension

그러면 redux를 가지고 로그인과 회원가입의 상태를 관리하는 모듈을 작성해보도록 하겠습니다.

  • ./src/modules/auth.js
import { createAction, handleActions } from "redux-actions";
import produce from "immer";

const CHANGE_FIELD = 'auth/CHANGE_FIELD';       
const INITIALIZE_FORM = 'auth/INITIALIZE_FORM';

export const changeField = createAction(
    CHANGE_FIELD, ({ 
        form, 
        key, 
        value 
    }) => ({
        form,
        key,
        value,
    }),
);
export const initializeForm  = createAction(INITIALIZE_FORM, form => form);

const initialState = {
    register: {
        email: '',
        password: '',
        passwordConfirm: '',
        nickname: '',
        phoneNumber: '',
    },
    login: {
        email: '',
        password: '',
    },
};

const auth = handleActions(
    {
        [CHANGE_FIELD]: (state, { payload: { form, key, value }}) => 
            produce(state, draft => {
                draft[form][key] = value;
            }),
        [INITIALIZE_FORM]: (state, { payload: form }) => ({
            ...state,
            [form]: initialState[form],
        }),
    },
    initialState,
);

export default auth;

CHANGE_FIELD를 기준으로 코드를 살펴보겠습니다.

1) const CHANGE_FIELD = 'auth/CHANGE_FIELD' : 액션을 위해 CHANGE_FIELD라는 변수에 'auth/CHANGE_FIELD'라는 값을 넣어줍니다.

2) export const changeField = createAction( CHANGE_FIELD, ({ form, key, value }) => ({ form, key, value, }), ); : changeField라는 변수를 export시켜 이 값을 호출한다면 auth/CHANGE_FIELD라는 액션명을 갖고 form, key, value의 값을 업데이트시키는 액션을 만듭니다.

3) [CHANGE_FIELD]: (state, { payload: { form, key, value }}) => produce(state, draft => { draft[form][key] = value; }) : produce를 사용해 값을 변경하기 위한 코드입니다. form은 register, login 값을 가지며 form이 register일 경우 initialState의 register 변수를 가져오고 login일 경우 initialState의 login 변수를 가져와 상태를 업데이트시킵니다.

  • ./src/modules/index.js
import { combineReducers } from "redux";
import auth from './auth';

const rootReducer = combineReducers({
    auth,
});

export default rootReducer;
  • ./src/App.js
import React from 'react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from './src/modules';
import StackNavigatior from './src/navigator/MainNavigation';

const store = createStore(rootReducer, composeWithDevTools());

const App = () => {
  return(
    <Provider store={ store }>
      <StackNavigatior />
    </Provider>
  );
};

export default App;

App컴포넌트에 store로 만들어 감싸줬으니 대략적으로 redux를 적용한 상태가 됐습니다. 이어서 LoginForm을 통해 redux를 통한 상태관리를 해보겠습니다.

#2 LoginForm

  • LoginForm
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { View, StyleSheet } from 'react-native';
import StyledBorderButton from '../../../styles/common/StyledBorderButton';
import StyledFullButton from '../../../styles/common/StyledFullButton';
import StyledTextInput from '../../../styles/common/StyledTextInput';
import palette from '../../../styles/palette';
import { changeField, initializeForm } from '../../../modules/auth';

const LoginForm = ({ navigation }) => {
    const dispatch = useDispatch();
    const { form } = useSelector(({ auth }) => ({
        form: auth.login,
    }));

    const onChange = e => {
        const inputAccessoryViewID = e.target._internalFiberInstanceHandleDEV.memoizedProps.inputAccessoryViewID;
        const value = e.nativeEvent.text;

        dispatch(
            changeField({
                form: 'login',
                key: inputAccessoryViewID,
                value
            }),
        );
    };

    ...

    useEffect(() => {
        dispatch(initializeForm('login'));
    }, [dispatch]);
    
    return(
        <View style={ styles.loginBox }>
            <StyledTextInput 
                inputAccessoryViewID="email"
                placeholder="E-mail"
                placeholderTextColor={ palette.gray[5] }
                onChange={ onChange }
                value={ form.email }
            />
            <StyledTextInput 
                inputAccessoryViewID="password"
                placeholder="Password"
                placeholderTextColor={ palette.gray[5] }
                onChange={ onChange }
                value={ form.password }
            />
            ...
        </View>
    );
};

...

export default LoginForm;

코드를 보면 useDipatch라는 훅을 사용했습니다. useDispatch는 앞서 작성한 액션함수를 발생시키는 훅입니다. dispatch(액션함수)를 이용하여 'auth/CHANGE_FIELD'액션을 발생시킬 수 있는 것이죠. 그리고 useSelector는 리덕스의 state를 선택할 수 있는 함수입니다. 이런 방식으로 state와 액션함수를 사용하여 onChange에 email, password의 값을 변경할 수 있는 기능을 만들었습니다.

  • ./src/styles/common/StyledTextInput.js
import React from 'react';
import { StyleSheet, TextInput } from 'react-native';
import palette from '../palette';

const StyledTextInput = ({
    name,
    placeholder,
    placeholderTextColor,
    inputAccessoryViewID,
    onChange,
}) => {
    return(
        <TextInput
            style={ styles.input }
            inputAccessoryViewID={ name }
            placeholder={ placeholder }
            placeholderTextColor={ placeholderTextColor }
            inputAccessoryViewID={ inputAccessoryViewID }
            onChange={ onChange }
        />
    );
};

const styles = StyleSheet.create({
    input: {
        width: 300,
        height: 40,
        borderRadius: 4,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: palette.white[0],
        color: palette.black[0],
        fontSize: 15,
        margin: 10,
    },
});

export default StyledTextInput;

그러면 state가 잘 저장되는지 확인해보도록 하겠습니다.
https://github.com/jhen0409/react-native-debugger 이곳에서 os에 맞는 디버거를 설치하고 실행시킨 뒤에 ctrl + t를 누른 뒤에 에뮬레이터에서 안드로이드 기준 ctrl + m을 눌러 debug모드를 실행하고 에뮬레이터의 포트번호를 디버거에 입력해주도록 하겠습니다. 그러면 다음과 같은 화면이 나타납니다.

그리고 로그인 화면에서 email의 임의의 값을 부여하면 state창에서 login.email의 값이 변하는 것을 확인할 수 있습니다.

그러면 마찬가지로 회원가입 화면도 진행해보도록 하겠습니다.

  • ./src/pages/auth/components/Register.js
import React, { useEffect } from 'react';
import { View, StyleSheet } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { changeField, initializeForm } from '../../../modules/auth';
import StyledFullButton from '../../../styles/common/StyledFullButton';
import StyledTextInput from '../../../styles/common/StyledTextInput';
import palette from '../../../styles/palette';

const RegisterForm = ({ navigation }) => {
    const dispatch = useDispatch();
    const { form } = useSelector(({ auth }) => ({
        form: auth.register,
    }));

    const onChange = e => {
        const inputAccessoryViewID = e.target._internalFiberInstanceHandleDEV.memoizedProps.inputAccessoryViewID;
        const value = e.nativeEvent.text;
        
        dispatch(
            changeField({
                form: 'register',
                key: inputAccessoryViewID,
                value
            })
        );
    };

    const toPressSignUp = e => {
        e.preventDefault();

        const { 
            email, 
            password, 
            passwordConfirm,
            nickname,
            phoneNumber
        } = form;

        if([email, 
            password, 
            passwordConfirm, 
            nickname, 
            phoneNumber].includes('')
        ) {
            // add error state
            return;
        }

        if(password !== passwordConfirm) {
            dispatch(changeField({ form: 'register', key: 'password', value: '' }));
            dispatch(changeField({ form: 'register', key: 'passwordConfirm', value: '' }));

            return;
        }

        navigation.navigate(
            'SignIn', {
                name: 'SignIn'
            },
        );
    };

    useEffect(() => {
        dispatch(initializeForm('register'));
    }, [dispatch]);

    return(
        <View style={ styles.container }>
            <StyledTextInput 
                inputAccessoryViewID="email"
                placeholder="E-mail"
                placeholderTextColor={ palette.gray[5] }
                onChange={ onChange }
                value={ form.email }
            />
            <StyledTextInput 
                inputAccessoryViewID="password"
                placeholder="Password"
                placeholderTextColor={ palette.gray[5] }
                onChange={ onChange }
                value={ form.password }
            />
            <StyledTextInput 
                inputAccessoryViewID="passwordConfirm"
                placeholder="Re-Password"
                placeholderTextColor={ palette.gray[5] }
                onChange={ onChange }
                value={ form.passwordConfirm }
            />
            <StyledTextInput 
                inputAccessoryViewID="nickname"
                placeholder="Nickname"
                placeholderTextColor={ palette.gray[5] }
                onChange={ onChange }
                value={ form.nickname }
            />
            <StyledTextInput 
                inputAccessoryViewID="phoneNumber"
                placeholder="Phone-Number"
                placeholderTextColor={ palette.gray[5] }
                onChange={ onChange }
                value={ form.phoneNumber }
            />
            <StyledFullButton 
                onPress={ toPressSignUp }
                text="Sign Up"
            />
        </View>
    );
};

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
    },
});

export default RegisterForm;

마찬가지로 register의 state값이 잘 변경되는지 확인해보겠습니다.


잘 변경되는 모습을 볼 수 있습니다. 이렇게 redux를 이용해 state에 접근하는 방법을 사용했습니다. 다음 포스트에서는 node.js로 auth-service를 만들어 보도록 하겠습니다.

참고

  • 리액트를 다루는 기술 - 저자 : 김민준

0개의 댓글

관련 채용 정보