[React Native] Redux와 Thunk Middleware 사용하기

in #kr7 years ago (edited)

이번에는 ReduxThunk Middleware를 사용해서 프로젝트의 전체적인 구조를 다시 잡아 보겠습니다. [React Native] 인스타그램 UI 만들기 시리즈의 개발환경을 그대로 사용합니다.

 

라이브러리 설치하기

먼저 리덕스를 구현하는 필요한 라이브러리를 설치합니다. Redux 라이브러리는 이전 글 "Redux로 Counter 앱 만들기"에서도 한번 포스팅했었습니다.

redux 와 react-redux 설치

$ yarn add redux react-redux


설치가 완료되고 나면 package.json 파일의 내용은 다음과 같습니다.

 

프로젝트 폴더 구조 만들기

이전 프로젝트에서 사용했던 폴더 구조를 조금 조정하겠습니다. 첫 번째로 src 폴더를 생성합니다. 그리고 App.js파일과 components 폴더를 src에 옮김니다. 그다음 Redux에 필요한 ./src/reducers 폴더를 생성합니다. reducers 폴더에는 리듀서 관련 파일을 넣을 것입니다. 마지막으로 루트에 App.js 파일을 생성합니다.

우리가 생성한 폴더 구조는 다음과 비슷해야합니다.

 

Redux Store 설정하기

Redux 및 개념에 익숙하지 않은 경우에는 redux 문서를 참고하세요.
루트에 있는 ./App.js 파일에 Redux Store를 만듭니다 . Store에는 앱에 필요한 모든 데이터가 저장됩니다. 그리고 앱은 필요한 모든 데이터를 모두 Store에서 가져옵니다.

import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';

import allReducers from './src/reducers';
import App from './src/App';

const store = createStore(allReducers);
  • Reducers 는 단순히 일부 데이터를 반환하는 함수라고 볼 수 있습니다.
  • 앱에는 여러 개의 reducers가 있을 수 있습니다. 우리는 이 reducers를 사용하여 가능한 많은 Store 을 구성 할 것 입니다.
  • 위의 코드에서 우리는 모든 Reducers를 포함하는 allReducers 객체를 사용하고 있습니다.
  • createStore 함수를 redux 모듈에서 import 합니다. 그리고 createStore() 함수를 사용하여 Store를 생성합니다.


./App.js 파일의 전체 코드는 다음과 같아야 합니다.

import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';

import App from './src/App';
import allReducers from './src/reducers';

const store = createStore(allReducers);

export default class Root extends Component {
  render() {
    return (
      <Provider store={ store }>
        <App/>
      </Provider>
    )
  }
}
  • App.js 파일에서 제일 중요한 App 컴포넌트를 import하여 가져옵니다.
  • Provider 컴포넌트를 react-redux 모듈에서 import 합니다. 이 컴포넌트는 App 컴포넌트를 하위 컴포넌트로 사용합니다. 그리고 Store 데이터를 App의 모든 부분에서 사용할 수 있도록 해줍니다.
  • 마지막으로 storeProvider 컴포넌트에 전달합니다. Store는 AllReducers 객체를 전달받아 생성되었습니다.

 

Redux Reducers 만들기

우리는 하나의 리듀서(reducer)만 만들 것입니다. 그리고 만들어진 steemReducer 리듀서는 스팀잇 API 서버에서 데이터를 가져올 것입니다. ./src/reducers 폴더에 steemReducer.js 파일을 생성합니다.

./src/reducers/steemReducer.js

import { createAction, handleActions } from 'redux-actions';

// 액션 타입을 정의해줍니다.
const GET_FEEDS = 'steem/getFeeds';

// 액션 생성 함수를 만듭니다.
export const getFeeds = createAction(GET_FEEDS);

// 초기 State를 정의합니다.
const initialState = {
  feeds: []
}

// 리듀서 함수를 정의합니다.
export default handleActions({
  [GET_FEEDS]: (state, action) => {
    state = {
      ...state,
      feeds: [
        ...state.feeds,
        ...action.payload
      ]
    }
    return state;
  },
}, initialState);
  • handleActions() 함수에 의해서 정의된 reducer 함수는 stateaction을 인자값으로 전달받습니다.
  • 앱에서 Action이 전달되는 경우, action은 우리가 만든 모든 Reducers로 보내질 것입다.

 

마지막으로 모든 reducer를 하나의 개체로 결합할 파일이 필요합니다. reducer 폴더 아래에 index.js 파일을 만듭니다. 지금은 리듀서가 하나라서 불필요한 작업일 수 있습니다. 하지만 리듀서가 여러개일 경우에는 반드시 필요한 작업입니다.

./reducers/index.js

import { combineReducers } from 'redux';
import steem from './steemReducer';

export default combineReducers({
  steem
});

 

Redux Thunk Middleware 사용하기

스팀잇 피드 목록을 가져 오는 작업은 Async operations 에 해당합니다. Async operations 은 오퍼레이션(operation)에 대한 응답이 바로 오지 않습니다. 따라서 Async operations에 대한 응답을 받을때까지 프로그램 실행을 잠시 중단하는 매커니즘이 필요합니다. 비동기로 가져오기 오퍼레이션(Async fetch operation)의 경우에 redux-thunk를 사용합니다.

라이브러리 설치하기

$ yarn add redux-thunk


미들웨어 설정하기

App.js 파일로 돌아가서 store에 Thunk 미들웨어를 인식 시켜야 합니다.

./App.js

import thunk from 'redux-thunk'
import { createStore, applyMiddleware } from 'redux';
const store = createStore(allReducers, applyMiddleware(thunk));


이제 thunk action을 포함한 모든 actions을 생성할 수 있습니다.

 

Redux Actions 만들기

리듀서에 fetchFeeds()함수를 생성합니다. fetchFeeds() 함수는 스팀잇 서버에서 피드를 가져오는 비동기 오퍼레이션을 수행할 것입니다.

./src/reducers/steemReducer.js

export const fetchFeeds = (tag) => {
  const data = {
      id: 1,
      jsonrpc: "2.0",
      method: "call",
      params: [
          "database_api",
          "get_discussions_by_created",
          [
              {
                  tag: tag,
                  limit: 10,
              }
          ]
      ]
  };
  return (dispatch, state) => {
    return fetch('https://api.steemit.com',
    {
        method: 'POST',
        body: JSON.stringify(data)
    })
    .then(res => res.json())
    .then(res => {
      dispatch(getFeeds(res.result))
    })
    .catch(error => {
      console.error('ERROR', error); 
    });
  };
}

 

Redux components에 적용하기

모든 Redux 설정이 끝나면, 우리는 이제 컴포넌트에서 리덕스를 사용할 수 있습니다. HomeTab.js 파일을 수정합니다.

./src/components/AppTabNavigator/HomeTab.js

import { connect } from 'react-redux';
import { fetchFeeds } from '../../reducers/steemReducer';

// (...)

// props에 전달할 state값 정의
const mapStateToProps = (state) => {
    return {
        feeds: state.steem.feeds
    }
};

// props에 전달할 액션 함수 정의
const mapDispatchToProps = { fetchFeeds };

// 컴포넌트와 리덕스를 연결
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(HomeTab);
  • steemReducer 리듀서에서 fetchFeeds 액션을 import 하였습니다.
  • mapStateToProps() 함수는 Props에 전달할 값을 정의합니다. steem 리듀서에서feeds 값을 Props에 전달하고 있습니다.
  • mapDispatchToProps() 함수는 Props에 전달할 액션을 정의합니다.
  • 마지막으로 connect() 함수를 사용하여 컴포넌트와 리덕스를 연결합니다. 이제 Reducers와 Actions를 Props로 전환하였습다.

 

그다음 componentWillMount() 함수를 수정합니다.

componentWillMount() {

  // this.fetchFeeds().then(feeds => {
  //     this.setState({
  //         feeds
  //     })
  // });
 
  this.props.fetchFeeds('kr'); // 리듀서 액션 호출
  
  this.fetchFollowing().then(followings => {
    this.setState({
      followings
    })
  });
}
  • 원래 this.fetchFeeds() 함수를 호출하던 부분을 주석처리 하였습니다.
  • 리듀서 액션 this.props.fetchFeeds()를 호출하도록 수정합니다.

 

마지막으로 render() 부분의 컴포넌트를 수정합니다.

render() {
  return (
    <Container style={style.container}>
      <Header>
        {/* (...) */}
      </Header>
      <Content>
        {/* 여기부터 스토리 헤더 시작 */}
        {/* (...) */}
        {/* 여기까지 스토리 헤더 끝 */}
        {
          !this.props.feeds || this.props.feeds.length === 0
            ?
            <Spinner color='blue'/>
            :
          this.props.feeds.map(feed => (
            <CardComponent data={ feed } key={ feed.url }/>
          ))
        }
      </Content>
    </Container>
  );
}
  • this.state.feedsthis.props.feeds로 수정하였습니다. 이제 스팀잇 피드 가져오는 부분을 리듀서에서 처리하기 때문에, Props에서 가져와야 합니다.
  • 그리고 <Spinner color='blue'/> 컴포넌트를 추가하여 피드를 가져오기 전에 로딩 이미지가 보이도록 하였습니다.

 

여기까지 작업한 구동 앱 화면입니다.

UI는 달라진 부분이 없어서 변화가 크진 않네요.

 

작업한 소스코드는 모두 깃허브에 업로드 되어있습니다.


* * *

인스타그램UI 형태의 스팀잇 모바일 앱을 원하는 분이 있다면, 개발을 더 진행하여 앱을 완성할 가치는 있을 것 같습니다. 하지만 partikoesteem와 같은 훌륭한 스팀잇 모바일 앱이 존재하므로, 저는 다른 형태의 모바일 앱을 개발하는 것이가치 있을 것으로 생각합니다.

그리고 이제 많은 분이 제 블로그 글을 보고 간단한 모바일 앱은 만들 수 있다고 생각합니다. 앞으로 다양한 아이디어를 가진 모바일 앱이 나오길 기대합니다. 개인적으로 스팀이 기반의 모바일 앱이 나오면 더 좋겠습니다. 예를 들어, 스팀잇 기반의 웹툰 서비스가 나오면 좋지 않을까요? 광고 수익과 스팀 저자 보상을 받을 수 있는 플랫폼이라면 괜찮을 거로 생각합니다.

여기까지 읽어주셔서 감사합니다.


Sponsored ( Powered by dclick )
DCLICK: An Incentivized Ad platform by Proof of Click - 스팀 기반 애드센스를 소개합니다.

안녕하세요 스티미언 여러분. 오늘 여러분께 스팀 블록체인 기반 광고 플랫폼 DCLICK을 소개...

Sort:  

glory7님이 anpigon님을 멘션하셨습니당. 아래 링크를 누르시면 연결되용~ ^^
glory7님의 [축] travisung님 당선! (후기 1: 투표 마감까지)

월요일 오전10시경부터 4시까지, 작은 기적이 일어났습니다. travisung님이 11표로 꼴찌에 머무르고 있다가 KR 커뮤니티의 전폭적인 지원으로 약 6시간만에 83표로 10위로 당선. KR 커뮤니티의 단합력을 스팀잇 유저들에게...

짱짱맨 호출에 응답하여 보팅하였습니다.

스팀잇 기반 웹툰 서비스를 생각은 해 보았었는데, 이미지가 모두 공개되어버려서 좀 고민되더군요 ㅎㅎ 암호화 해서 올리고 전용 뷰어로만 볼 수 있게 하면 좀 나으려나요 ㅋㅋ

제가 생각했던 건 네이버 웹툰 같은 무료 서비스였는데, 유료 서비스는 고민을 해봐야겠어요.
steem을 지불한 계정만 해당 웹툰(포스팅 내용)을 볼수 있게 하려면 어떤 기술이 필요할까요?
생각해보니 재미있겠습니다. ㅎㅎ

무료서비스라도 공개를 중단해야 하는 경우가 있으니까요 ㅎㅎㅎ
포스트 용량 제한도 있으니 이런저런 고민이 필요하지 싶습니다.

그런 경우가 발생할수 있겠네요. 그렇다면 블록체인에 기록하는 것보다 컨텐츠 서버가 별도로 있어야 겠어요.

오호! 따라하고 싶은데 나중에 해봐야겠네요.
최근 컴퓨터 앞에 있는 시간이 얼마 없네요.
어제도 post 쓰지도 못하고 정신이 없네요. ^^

연초라서 많이 바쁘신가봐요? 나중에 시간날때 재미있는 모바일앱을 만들어보세요!
코딩맨님도 같이 공부하면서 저의 부족한 정보를 채워주시면 좋겠습니다.ㅋ

늘 좋은 글 감사합니다 :)

요즘 스몬 땜시 개발글은 사라져 가고 있지만 ... 자체 공부는 하고 하고 있네요

얼마전에 글쓰기 툴(율리시스)도 구독 신청했으니 조만간에 글 쓸거 같네요 (돈 아까워서라도 ㅋㅋ)

리엑트로 가즈앙~~

아직 플러터 공부는 하고 계신 거죠? 플러터 글이 안 올라와서 조금 심심했습니다. ㅋ
그런데 율리시스 앱을 구매하셨군요. 원사마님은 이제 글 작성하는데 속도가 붙을 것 같습니다.
저는 Typora 앱과 Dropbox 조합으로 사용하고 있습니다. 율리시스 앱 구매에 대해서도 조금 고민해봐야겠습니다.

돈쓰면 동기부여가 되서요 ㅋ(가격도 나쁘지 않고여)

앱 자체도 써보니 좋은거 같네요 ^^

Posted using Partiko Android

고마워요. 곰돌이님

Hi @anpigon!

Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your UA account score is currently 2.887 which ranks you at #11752 across all Steem accounts.
Your rank has improved 83 places in the last three days (old rank 11835).

In our last Algorithmic Curation Round, consisting of 209 contributions, your post is ranked at #119.

Evaluation of your UA score:
  • Only a few people are following you, try to convince more people with good work.
  • The readers like your work!
  • Good user engagement!

Feel free to join our @steem-ua Discord server