React Native - Facebook Flux

Flux는 React Native에 한정된 이야기가 아니라 React에서 사용되는 아키텍처이지만 React Native의 앱을 만드는 데도 사용합니다. 2015년 12월 시점에서 최유력의 Flux계 아키텍처는 Redux 입니다만, 이번은 Facebook의 구현인 facebook/flux 를 설명하고 싶습니다. (Redux는 나중에)

아키텍처는 다음과 같습니다. 한 방향으로 데이터가 흐르고 있지만 포인트입니다. 이것은 React의 철학과도 일치합니다.



Flux에는 아래와 같은 역할이 있어, 각각을 실장해 갑니다.
  • Dispatcher
  • Action
  • Store
  • (Constant Action-Store간에 사용하는 비밀번호를 수납)

  • Install



    facebook/flux에서 사용되는 모듈을 설치합니다. React Native에서도 문제없이 설치할 수 있습니다.
    $ npm install flux --save
    $ npm install events --save
    $ npm install keymirror --save
    $ npm install object-assign --save 
    

    Directory structure



    Example과 같은 Todo 앱을 React Native를 만들어 보겠습니다. 디렉토리도 마찬가지로 만들 수 있습니다.
    index.ios.js
    app/
    ├── actions
    │   └── TodoActions.js
    ├── app.js
    ├── components
    │   ├── Footer.js
    │   ├── Header.js
    │   ├── MainSection.js
    │   └── TodoApp.js
    ├── constants
    │   └── TodoConstants.js
    ├── dispatcher
    │   └── Dispatcher.js
    └── stores
        └── TodoStore.js
    

    코드 해설





    index.ios.js에서 Register
    // index.ios.js
    'use strict';
    
    import React, { AppRegistry } from 'react-native';
    import App from './app/app';
    
    var ReactNativeAdvent2015 = React.createClass({
      render() {
        return (
          <App />
        );
      }
    });
    
    AppRegistry.registerComponent('ReactNativeAdvent2015', () => ReactNativeAdvent2015);
    

    app.js는 맨 위에 구성 요소를 호출합니다. 이 최고 사용법은 login하고 있습니다,하지 않는 등의 구분을 여기에 기술하거나 할 수 있습니다.
    // app/app.js
    import React from 'react-native';
    import TodoApp from './components/TodoApp';
    
    var App  = React.createClass({
      render() {
        return (
          <TodoApp />
        );
      }
    });
    
    export default App;
    

    Todo 앱에서는 메인 화면입니다. TodoStore를 사용하여 이벤트를 추가하고 TodoStore에 Getter를 가지고 있습니다. 단방향이므로 TodoStore에 Setter는 없습니다.
    // compoents/TodoApp.js
    import React, { StyleSheet, Text, View} from 'react-native';
    import Header from './Header';
    import Footer from './Footer';
    import MainSection from './MainSection';
    import TodoStore from '../stores/TodoStore';
    
    
    var TodoApp  = React.createClass({
    
      getInitialState: function() {
        return {
          allTodos: TodoStore.getAll(),
          areAllComplete: TodoStore.areAllComplete()
        }
      },
    
      componentDidMount: function() {
         TodoStore.addChangeListener(this._onChange);
       },
    
       componentWillUnmount: function() {
         TodoStore.removeChangeListener(this._onChange);
       },
    
      render(){
        return(
          <View style={styles.container}>
            <Header/>
            <MainSection
              allTodos={this.state.allTodos}
              areAllComplete={this.state.areAllComplete}
            />
            <Footer/>
          </View>
        )
      },
    
      _onChange: function() {
        this.setState({
          allTodos: TodoStore.getAll(),
          areAllComplete: TodoStore.areAllComplete()
        });
      }
    });
    

    Header에서 입력을 수락합니다. 버튼을 누르면 Actions.create 로 액션을 실행합니다.
    // components/Header.js
    
    import React, {View, Text, TextInput} from 'react-native';
    import Button from 'react-native-button';
    import TodoActions from '../actions/TodoActions';
    
    
    var Header  = React.createClass({
    
      getInitialState(){
        return {
          text: ""
        }
      },
      _handlePress(){
        TodoActions.create(this.state.text);
        this.setState({text: ""});
      },
    
      render(){
        return(
          <View style={{flexDirection: 'row'}}>
          <TextInput
            style={{width:100, height: 25, borderColor: 'gray', borderWidth: 1}}
            onChangeText={(text) => this.setState({text})}
            value={this.state.text}
          />
          <Button onPress={this._handlePress}>Add</Button>
          </View>
        )
      }
    });
    
    export default Header;
    

    기본적으로 Action/Store/Dispatcher는 facebook/flux의 example 그대로입니다. 해당 부분만 봅시다. Action에서는 Type과 Text를 지정하여 dispatcher에 전달합니다.
    // TodoAction抜粋
    var AppDispatcher = require('../dispatcher/Dispatcher');
    var TodoConstants = require('../constants/TodoConstants');
    
    var TodoActions = {
    
      create: function(text) {
        AppDispatcher.dispatch({
          actionType: TodoConstants.TODO_CREATE,
          text: text
        });
      },
    

    실은 Dispatcher는 2행으로 대충 하지 않습니다. Store에 던지기 위한 도구라고 생각하면 좋을 것입니다.
    // Dispatcher.js
    var Dispatcher = require('flux').Dispatcher;
    module.exports = new Dispatcher();
    

    TodoConst는 키만 저장합니다. TODO_CREATE라는 문자열만이 Store와의 단어로 사용됩니다.
    // TodoConstant抜粋
    var keyMirror = require('keymirror');
    
    module.exports = keyMirror({
      TODO_CREATE: null,
      ...
    

    그러므로 Action은 단어를 가지고 Text를 던지는 이미지가 됩니다. 그리고 Store에서 그 단어를 가지고 저장합니다.
    // todoStore抜粋
    var _todos = {};
    
    function create(text) {
      var id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);
      _todos[id] = {
        id: id,
        complete: false,
        text: text
      };
    }
    
    Dispatcher.register(function(action) {
      var text;
    
      switch(action.actionType) {
        case TodoConstants.TODO_CREATE:
          text = action.text.trim();
          if (text !== '') {
            create(text);
            TodoStore.emitChange();
          }
          break;
     ...
    

    저장 후 TodoStore.emitChange에서 이벤트를 발화시켜 변경 사항을 Component에 알립니다. TodoApp.js의 _onChange가 알림을 받고 구성 요소의 상태를 변경합니다. 변경 사항은 자식 구성 요소로 props를 통해 직접 전달되며 MainSection으로 변경 사항이 전파됩니다.
    import React, {View, Text} from 'react-native';
    
    var MainSection  = React.createClass({
      render(){
        const {allTodos, areAllComplete} = this.props;
        let todos = Object.keys(allTodos).map((id) => {
           return (
            <View>
              <Text>{allTodos[id].text}</Text>
            </View>
          )
        });
        return(
          <View>{todos}</View>
        )
      }
    });
    

    기본적으로 이러한 흐름으로 Flux는 데이터가 업데이트되어 갑니다. 반드시 한 방향에서 데이터가 흐르기 때문에 데이터 흐름이 명확하고 전망이 좋은 앱을 만들 수 있습니다.

    내일은 facebook/flux의 변종 Reflux를 살펴 보겠습니다.

    좋은 웹페이지 즐겨찾기