리액트 스터디 5

52164 단어 React리액트React

Firestore + Redux

설정

리덕스에서 파이어스토어의 데이터를 가져와보자. 파이어스토어는 비동기 통신을 통해 데이터를 가져오는데 리덕스에서 비동기 통신으로 얻은 데이터를 처리 하기 위해서는 중간에서 미들웨어로써 역할을 해줄 redux-thunk 가 필요하다. 다음과 같이 설치한다.

yarn add redux-thunk

이제 만들어둔 리덕스의 설정 파일인 configStore.js에 적용하면 된다.

import { createStore, combineReducers, applyMiddleware } from "redux";
import bucket from "./modules/bucket";
import { createBrowserHistory } from "history";
import thunk from 'redux-thunk';

export const history = createBrowserHistory();

const middlewares = [thunk];

const enhancer = applyMiddleware(...middlewares);
const rootReducer = combineReducers({ bucket });
const store = createStore(rootReducer, enhancer);

export default store;

Read

이 과정은 하나의 기능을 추가 할 때마다 일련의 작업을 반복할 것이다.

  • 비동기 통신 함수 작성
  • 리듀서 수정

bucket.js에 파이어스토어와 통신하는 함수를 추가한다.

import { firestore } from '../../firebase'

const bucket_db = firestore.collection("bucket");
export const loadBucketFB = () => {
    return function (dispatch) {
        bucket_db.get().then((docs) => {
            let bucket_data = [];
            docs.forEach((doc) => {
                if (doc.exists) {
                    bucket_data = [...bucket_data, { id: doc.id, ...doc.data() }]
                }
            })
          	dispatch(loadBucket(bucket_data));            
        });
    }
};

이번엔 리듀서를 수정해 액션이 실행되게 할 차례이다. bucket.js의 리듀서 스위치 케이스를 수정한다.

// in reducer switch
case "bucket/LOAD": {
            if (action.bucket.length > 0) {
                return {list: action.bucket};
            }
            return state;
        }

홈이 호출될 때 로드 될 수 있도록 App.js를 수정하자.

import { loadBucket, createBucket, loadBucketFB } from "./redux/modules/bucket";

const mapDispachToProps = (dispatch) => {
  return {
    load: () => {
      dispatch(loadBucketFB());
    },
    create: (bucket) => {
      dispatch(createBucket(bucket));
    }
  }
};

componentDidMount() {
    this.props.load();
};

이제 콘솔을 통해 데이터를 확인할 수 있다.

Create

조회와 마찬가지로 입력 함수를 추가하고 리듀서를 수정한다.

export const addBucketFB = (bucket) => {
    return function (dispatch) {
        let bucket_data = { text: bucket, completed: false };
        bucket_db.add(bucket_data).then(docRef => {
            bucket_data = { ...bucket_data, id: docRef.id };
            dispatch(createBucket(bucket_data));
        })

    }
}
...
//in switch
case "bucket/CREATE": {
            const new_bucket_list = [
                ...state.list,
                action.bucket
            ];
            return { list: new_bucket_list };
        }

App.js에서 연결해주자.

import { loadBucket, createBucket, loadBucketFB, addBucketFB } from "./redux/modules/bucket";
...
const mapDispachToProps = (dispatch) => {
  return {
    load: () => {
      dispatch(loadBucketFB());
    },
    create: (bucket) => {
      dispatch(addBucketFB(bucket));
    }
  }
};

Update

수정 함수를 만든다. 이번에는 리듀서에서 수행하는 작업이 기존과 동일하므로 리듀서를 수정할 필요는 없다.

export const updateBucketFB = (index) => {
    return function (dispatch, getState) {
        const _bucket_data = getState().bucket.list[index];
      	
        if (!_bucket_data.id) return;
      
        let bucket_data = { ..._bucket_data, completed: true };
        bucket_db.doc(bucket_data.id).update(bucket_data).then(docRef => {
            dispatch(updateBucket(index));
        }).catch(error => {
           console.error(error);
        });
    }
}

Detail.js 파일에서 updateBucketFB 함수를 불러와 기존의 액션과 교체해준다.

import { deleteBucket, updateBucketFB } from './redux/modules/bucket';
...
<Button onClick={() => {
                    dispatch(updateBucketFB(bucket_index));
                    props.history.goBack();
                }}>완료</Button>
...

Delete

업데이트와 마찬가지로 삭제 함수를 만든다. 이번에도 리듀서에서 수행하는 작업이 기존과 동일하므로 리듀서를 수정할 필요는 없다.

export const deleteBucketFB = (index) => {
    return function (dispatch, getState) {
        const _bucket_data = getState().bucket.list[index];
      	
        if (!_bucket_data.id) return;
      
        bucket_db.doc(_bucket_data.id).delete().then(docRef => {
            dispatch(deleteBucket(index))
        }).catch(error => {
           console.error(error);
        });
    }
}

Detail.js 파일에서 deleteBucketFB 함수를 불러와 기존의 액션과 교체해준다.

import { deleteBucketFB, updateBucketFB } from './redux/modules/bucket';
...
<Button onClick={() => {
                    dispatch(deleteBucketFB(bucket_index));
                    props.history.goBack();
                }}>삭제
                </Button>
...

Material UI

BootStrap 처럼 기본적인 UI 템플릿을 제공하는 Material UI를 추가해보자. bash 에서 아래와 같이 입력해 설치한다.

yarn add @material-ui/core @material-ui/icons

버튼 UI를 불러와서 몇가지 속성을 사용해보자.

import { Button } from '@material-ui/core';
import { ButtonGroup } from '@material-ui/core';
...
<ButtonGroup>
  <Button color="primary" />
  <Button color="secondary" />
  <Button />
</ButtonGroup>

잘 적용 된것을 확인할 수 있다.

Spinner

요청과 응답사이의 공백에서 기본값이 보이는 문제를 해결하기 위해 로딩을 가려주는 스피너를 만들어보자. 먼저 UI에 해당되는 Spinner.js 컴포넌트를 만들자.

import React from 'react'
import styled from 'styled-components'
import { Eco } from "@material-ui/icons"

export const Spinner = () => {
    return (
        <Outer>
            <Eco style={{ fontSize: "150px", color: "#673ab7" }} />
        </Outer>
    )
}

const Outer = styled.div`
    position: fixed;
    top:0;
    left: 0;
    width: 100vw;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: #ede2ff;
`;

bucket.js 파일에서 기본 값인 initialState에 로딩 여부를 확인 할 수 있는 변수 is_loaded를 추가하고 로드를 담당하는 리듀서의 bucket/LOAD 케이스를 수정한다.

const initialState = {
    list: [
        { text: "영화관 가기", completed: false },
        { text: "매일 책읽기", completed: false },
        { text: "수영 배우기", completed: false },
    ],
    is_loaded: false,
};
...
// in reducer switch
case "bucket/LOAD": {
            if (action.bucket.length > 0) {
                return { list: action.bucket, is_loaded: true };
            }
            return state;
        }
...

App.js에서 스피너를 불러오고 render에서 is_loaded의 값을 확인해 삼형연사자로 나뉘게 한 뒤 기존 홈화면을 <React.Fragment> 안으로 넣어준다.

import { Spinner } from './Spinner'

<div>
        {!this.props.is_loaded ? (<Spinner />) : (
          <React.Fragment>
           // 기존의 홈
          </React.Fragment>
        )
        }
      </div>

이제 추가, 수정, 삭제 이후 홈으로 돌아갈때도 스피너가 동작하도록 해보자. bucket.js 파일에서 액션, 액션 크리에이터, 리듀서 케이스를 추가하자.

const LOADED = "bucket/LOADED";
...
export const isLoaded = (loaded) => {
    return { type: LOADED, loaded };
}
...
// in reducer switch
case "bucket/LOADED": {
            return { ...state, is_loaded: action.loaded };
        }

기존의 입력, 수정, 삭제를 위한 비동기 통신 함수 안에서 스피너가 호출되도록 수정해보자.

export const addBucketFB = (bucket) => {
    return function (dispatch) {
        let bucket_data = { text: bucket, completed: false };
        
        dispatch(isLoaded(false));
        bucket_db.add(bucket_data).then(docRef => {
            bucket_data = { ...bucket_data, id: docRef.id };
            dispatch(createBucket(bucket_data));
            dispatch(isLoaded(true));
        })

    }
}

export const updateBucketFB = (index) => {
    return function (dispatch, getState) {
        const _bucket_data = getState().bucket.list[index];
        let bucket_data = { ..._bucket_data, completed: true };

        if (!_bucket_data.id) return;
        
        dispatch(isLoaded(false));
        bucket_db.doc(bucket_data.id).update(bucket_data).then(docRef => {
            dispatch(updateBucket(index));
            dispatch(isLoaded(true));
        }).catch(error => {
            console.error(error);
        });
    }
}

export const deleteBucketFB = (index) => {
    return function (dispatch, getState) {
        const _bucket_data = getState().bucket.list[index];

        if (!_bucket_data.id) return;
        
        dispatch(isLoaded(false));
        bucket_db.doc(_bucket_data.id).delete().then(docRef => {
            dispatch(deleteBucket(index))
            dispatch(isLoaded(true));
        }).catch(error => {
            console.error(error);
        });
    }
}

S3

아마존에서 제공하는 파일 스토리지 중 하나인 S3를 이용해 지금까지 만든 bucket-list를 정적 웹사이트 형식으로 배포해보자.

AWS 콘솔에 접속해 S3 서비스를 선택하고 버킷을 만들자. 이 때 모든 퍼블릭 액세스 차단 설정을 해제하고 밑의 현재 설정으로 인해 이 버킷과 그 안에 포함된 객체가 퍼블릭 상태가 될 수 있음을 알고 있습니다.를 체크하여 모두에게 서비스를 노출한다.

완성된 버킷목록의 액세스 탭 하위의 객체를 퍼블릭으로 설정할 수 있음이 나오면 완료 된 것이다.

좋은 웹페이지 즐겨찾기