Skip to content

ClintJang/sample-react-native-redux-architecture-patterns

Repository files navigation

React Native Redux Sample Project

React Native로 간단한 덧셈이 되는 Redux 아키텍쳐 패턴을 적용한 간단한 셈플입니다.
개발 소스를 받아서 빌드해 실행해보면, 아키텍쳐를 이해하는 데 더 도움이 되지 않을 까 싶습니다.

🌈 Result Image (GIF)

iOS android

The Main Structure

- ios
- android
- scr
	- store
		- index.js
	- actions
		- index.js
		- calculatorAction.js
		- types.js
	- reducers
		- index.js
		- calculatorReducer.js
	- components
		- Calculator.js
	- containers
		- Main
		    - index.js
		- Root
		    - index.js
	
- App.js

설명

Redux란?

시작이 되는 근원은 리엑티브 라고 할 수 있습니다.
리엑티브는 같은 말로 알엑스(Rx) 라고 부릅니다.
리엑티브 페러다임마이크로소프트가 창안한 개념으로 데이터의 흐름에 따른 변화를 만드는 비동기적인 프로그래밍 패러다임입니다.
기본 실행 모델이 데이터의 흐름을 통해 자동으로 전파하는 것입니다.

RX 패러다임은 다양한 개발언어로 확장 되었는 데,
Redux는 페이스북에서 만든 Flux 패턴의 구현체 중 하나입니다.

Redux 간략 설명

Redux는 앱의 상태 모두를 하나의 store안에 트리 구조로 저장합니다.

store를 변경시키는 것은 action (들) 뿐입니다.

action이 어떻게 (How) 변경시켜야 하는 지는 reducer(들)가 정의합니다.

화면(View)들은 실제 화면을 표현하고, 내부 구성의 명칭은 중요하진 않겠지만 component들을 담는 것을 Container(들) 이라 지칭하겠습니다.

Container(들)은 Store의 상태값이 변화되는 지 구독하고(subscribe, subscript) 있는 데,
redux에서는 props에 담아 넘겨줍니다. Props에 selector(들) 하고 있습니다.

props에 담아 넘겨준다는 것을 조금 더 상세하게 설명드리면, Container 의 화면에서는 사용하기 위해 props에 state를 map 하는 과정이 진행됩니다.
그리고 화면의 메인스레드 런루프에서 클릭등 이벤트가 발생해서 변경요청을 하면,
Container에서 action을 보내는 데, 그것을 dispatch action이라 합니다.
action을 dispatch하는 거죠....

추가적으로 state를 변경하기 위해 비동기 처리를 하는 경우가 있는 데 보통 대표적인 것이 네트워크 처리 이죠? 물론 그게 아니더라도 보통 비동기 처리를 하는 것이 대부분 일 것 같기도 합니다.
이렇게 비동기 처리 등을 위해 action과 reducer 사이에 middleware를 둡니다.

그 때 미들웨어에 thunk(들)과 saga (들).. 등 이(가) 있습니다.

진행 사항 간 특징

  • 쉘 명령어 react-native로 구현하였습니다.
  • middleware는 적용하지 않았습니다.
  • 동작 시키기 위한 React Native 외의 라이브러리는 없습니다.
  • 동작의 흐름을 보기위한 간단한 덧셈이 되는 셈플입니다.

상세 설명

기본적인 환경설정 (npm 설치 등등)이 되어있다면, 소스를 내려받고 소스 최상위 폴더에서 아래 쉘 명령어를 실행합니다.

$ npm install 

그리고 iOS 안에 실행파일(SampleReduxCalculator.xcodeproj) 혹은 안드로이드 스튜디오를 열고 프로젝트를 열어서 프로젝트를 실행시킵니다.

또는

$ react-native start

and 

$ react-native run-ios
or
$ react-native run-android

그럼 구현된 소스 상세 내용을 알아보겠습니다.

App.js

const store = initStore();

export default class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <Root />
      </Provider>
    );
  }
}

Store

Redux는 앱의 상태 모두를 하나의 store안에 트리 구조로 저장합니다.
스토어(Store)에는 전체 상태의 구조를 유지하기 위해..
변화시키며, 변화 시킬 데이터 구조를 가지고 있는 reducer(들)과 추가로 필요한 상태 트리구조 정보 또는 기타 변화시키는 역활을 담당하는 미들웨어 정보를 가지고 생성합니다.

export default function initStore() {
    const store = createStore( 
        reducers,
        applyMiddleware(
            // Middleware will not be applied to this sample.
        ), 
    );
    return store;
}

Action(s)

store를 변경시키는 것은 action (들) 뿐입니다.

const ActionCreators = Object.assign({},   
    calculatorAction,
);

export default ActionCreators;
const types = {
    CALCULATOR_UPDATE_SUM_FIRST: 'CALCULATOR_UPDATE_SUM_FIRST',
    CALCULATOR_UPDATE_SUM_SECOND: 'CALCULATOR_UPDATE_SUM_SECOND',
};

export default types;
export function updateSumValueFirst(num) {
    return {
        type: types.CALCULATOR_UPDATE_SUM_FIRST,
        payload: num
    };
}

export function updateSumValueSecond(num) {
    return {
        type: types.CALCULATOR_UPDATE_SUM_SECOND,
        payload: num
    };
}

Reducer(s)

action이 어떻게 (How) 변경시켜야 하는 지는 reducer(들)가 정의합니다.

export default combineReducers({
    calculator: CalculatorReducer,
});
const defaultState = {
    result : 0,
    sumInfo: {
        frist : 0,
        second : 0,
    },
}

export default calculator = (state = defaultState, action) => {    
    // For Debugger
    // console.log('payload:' + action.payload);

    switch (action.type) {
        case types.CALCULATOR_UPDATE_SUM_FIRST:
            return {
                // ...state,
                result : action.payload + state.sumInfo.second,
                sumInfo: {
                    frist:action.payload,
                    second:state.sumInfo.second
                }
            };
        case types.CALCULATOR_UPDATE_SUM_SECOND:
            return {
                // ...state,
                result : action.payload + state.sumInfo.frist,
                sumInfo: {
                    frist:state.sumInfo.frist,
                    second:action.payload
                }
            };
        default:
            return state;
    }
};

View (components, containers)

화면(View)들은 실제 화면을 표현하고, 내부 구성의 명칭은 중요하진 않겠지만 component들을 담는 것을 Container(들) 이라 지칭하겠습니다.

containers
export default class Root extends React.Component {
    render() {
        return (
            <View style={{ backgroundColor:'grey'}}>
                <Text style={{ marginLeft:20, marginTop:100, fontSize:24 }}>React Native Redux Sample</Text>
                <Main />
            </View>
            
        );
    }
}
export default class Main extends React.Component {
    render() {
        return (
            <Calculator />
        );
    }
}
components
  • Map State To Props : Container 의 화면에서는 사용하기 위해 props에 state를 map 하는 과정이 진행됩니다.


그리고 화면의 메인스레드 런루프에서 클릭등 이벤트가 발생해서 변경요청을 하면,

  • Map Dispatch To Props : Container에서 action을 보내는 데, 그것을 dispatch action이라 합니다. action을 dispatch하는 거죠....

  • src/components/Calculator.js

.. (중략)..

class Calculator extends Component {
    constructor(props, context) {
        super(props, context);
    }

    render() {
        return (
            <View style={calculatorStyles.container}>
                <TextInput 
                    style={ calculatorStyles.input }
                    keyboardType={'number-pad'} 
                    maxLength={1}
                    placeholder={'0'}
                    onChangeText={(text) =>  {
                        this.setState({text}); 
                        var numberAsInt = 0
                        if (text !== '') {
                            numberAsInt = parseInt(text, 10);
                        }
                        console.log(numberAsInt);
                        this.props.updateFirst(numberAsInt); 
                    }}
                    // onSubmitEditing = {() => { this.props.updateFirst(1); }}
                    // placeholderTextColor = {'red'}
                ></TextInput>
                <View style={calculatorStyles.view}>
                    <Text
                        s3yle={ calculatorStyles.text }
                    >+</Text>
                </View>
                <TextInput 
                    style={ calculatorStyles.input }
                    keyboardType={'number-pad'} 
                    maxLength={1}
                    placeholder={'0'}
                    onChangeText={(text) =>  {
                        this.setState({text}); 
                        var numberAsInt = 0
                        if (text !== '') {
                            numberAsInt = parseInt(text, 10);
                        }
                        console.log(numberAsInt);
                        this.props.updateSecond(numberAsInt); 
                    }}
                ></TextInput>
                <View style={ calculatorStyles.view }>
                    <Text
                        style={ calculatorStyles.text }
                    >=</Text>
                </View>
                <View style={ calculatorStyles.view }>
                    <Text 
                        style={ calculatorStyles.text }
                    >{this.props.result}</Text>
                </View>
                
            </View>
        );
    }
}

function mapStateToProps(state) {
    return {
        result: state.calculator.result,
        first: state.calculator.sumInfo.first,
        second: state.calculator.sumInfo.second
    };
}

function mapDispatchToProps(dispatch) {
    return {
        updateFirst:(num) => {
            dispatch(ActionCreator.updateSumValueFirst(num));

        },
        updateSecond:(num) => {
            dispatch(ActionCreator.updateSumValueSecond(num));
        }
    };
}

.. (중략) ..

export default connect(mapStateToProps, mapDispatchToProps)(Calculator);



즐거운 하루 되세요 🙇‍