리액트 기초반 5주차 - 1

귀찮Lee·2022년 4월 10일
0

22년 4월 5일(화)
[스파르타코딩클럽] 리액트 기초반 - 5주차 - 1

◎ 미들웨어(middleware)

  • 미들웨어(middleware): 운영 체제와 응용 소프트웨어의 중간에서 조정과 중개의 역할을 수행하는 소프트웨어
  • 리덕스에서 비동기 통신을 할 때 필요한 미들웨어 : redux-thunk, ...

◎ redux-thunk

  • redux 미들웨어 사용시 데이터 처리 :
    액션이 일어나고 → 미들웨어가 할 일 하기 → 리듀서에서 처리
  • redux-thunk: 객체 대신 함수를 생성하는 액션 생성함수
    (원래 액션 생성 함수는 객체를 반환/ 함수 생성하면 액션 발생 전에 어떤 행동을 사전에 처리할 수 있다.)
  • 설치하기
yarn add redux-thunk
  • Store에 미들웨어 추가
// src > redux > configStore.js
import { createStore, combineReducers, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk"; // thunk import
import bucket from "./modules/bucket";

export const history = createBrowserHistory();

const middlewares = [thunk]; // middleware 리스트를 넣음

const enhancer = applyMiddleware(...middlewares); // middleware 리스트를 하나로 합침
const rootReducer = combineReducers({ bucket });
const store = createStore(rootReducer, enhancer); // Store를 "reducer들의 모임 + 추가 기능" 의 형식으로 만듦

export default store;

-Middleware 적용하는 기본 방식

// reducer 파일에 적용

// middleware에서 사용할 fire database 함수를 가져옴
import { collection, doc, getDoc, getDocs, addDoc, updateDoc, deleteDoc } from "firebase/firestore";
// 비동기 통신시에 사용
import { async } from "@firebase/util";

//middlewares 기본 형식
// async는 비동기 통신시에 사용
export const functionName = () => {
  // dispatch, getState 등등의 사용할 내용을 변수형태로 추가
  return async function (dispatch, getState) {
    
    // Do Something
    
    // Action Creator를 통해 Action을 dispatch함.
    // 예시
    dispatch(loadBucket(bucket_list));
  };
};
  • Firestore 적용하기 (예시)
// src > redux > modules > bucket.js

// import
import {db} from "../../firebase"
// middleware에서 사용할 fire database 함수를 가져옴
import { collection, doc, getDoc, getDocs, addDoc, updateDoc, deleteDoc } from "firebase/firestore"; 
import { async } from "@firebase/util";

//Actions
const LOAD = 'bucket/LOAD';
const CREATE = 'bucket/CREATE';
const UPDATE = 'bucket/UPDATE';
const REMOVE = 'bucket/REMOVE';
const LOADED = 'bucket/LOADED'

// 초기 State 값 설정
const initialState = {
  is_loaded: false, // 로딩이 되었는지 확인하는 value
  list: []
  };

// Action Creators
export const loadBucket = (bucket_list) => {
  return {type: LOAD, bucket_list};
}

export const createBucket = (text) => {
    return {type: CREATE, text};
}

export const updateBucket = (index) => {
  return {type: UPDATE, index};
}

export const removeBucket = (index) => {
  return {type: REMOVE, index};
}

export const isLoaded = (loaded) => {
  return {type: LOADED, loaded}
}

//middlewares
export const loadBucketFB = () => {
  return async function (dispatch) {
    
    // Firebase Data Loading
    const bucket_data = await getDocs(collection(db, "bucket"));
    let bucket_list = [];
    bucket_data.forEach((bucket) => {
      bucket_list.push({id:bucket.id, ...bucket.data()});
    });
	
    // Dispatch 하기
    dispatch(loadBucket(bucket_list));
  };
};

export const addBucketFB = (text) => {
  return async function(dispatch) {
    
    // 로딩중으로 상태전환
    dispatch(isLoaded(false));
    
    // Firebase Data Add(Create)
    const docRef = await addDoc(collection(db,"bucket"), {text, completed:false});
    //Add(Create)한 데이터를 다시 가져오기
    const _bucket = await getDoc(docRef)
    const bucket_data = {id:_bucket.id, ..._bucket.data()}

    // Dispatch 하기 (Client에도 해당 내용을 적용)
    dispatch(createBucket(text));
  };
};

export const updateBucketFB = (bucket_id) => {
  return async function(dispatch, getState) {
    
    // Firebase Data Update
    const docRef = doc(db, "bucket", bucket_id);
    await updateDoc(docRef,{completed: true});
    
    // bucket_index 값 찾기 , 상황에 따라 해당 로직이 꼭 필요하지는 않음
    const bucket_list = getState().bucket.list;
    const bucket_index = bucket_list.findIndex((b) => {
      return b.id === bucket_id;
    })
    
    // Dispatch 하기 (Client에도 해당 내용을 적용)
    dispatch(updateBucket(bucket_index))
  };
};

export const deleteBucketFB = (bucket_id) => {
  return async function (dispatch, getState) {
    
    // 필수적인 데이터 값은 ERROR 처리르 해주는 것이 좋다.
    if (!bucket_id){
      window.alert("ERROR!");
      return;
    }
    
    // Firebase Data Remove
    const docRef = doc(db, "bucket", bucket_id);
    await deleteDoc(docRef)
	
    // bucket_index 값 찾기 , 상황에 따라 해당 로직이 꼭 필요하지는 않음
    const bucket_list = getState().bucket.list;
    const bucket_index = bucket_list.findIndex((b) => {
      return b.id === bucket_id;
    })
    
    // Dispatch 하기 (Client에도 해당 내용을 적용)
    dispatch(removeBucket(bucket_index));

  }
}

// Reducer
export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    // do reducer stuff
    // return state;
    case "bucket/LOAD":
      return {list: action.bucket_list, is_loaded: true};

    case "bucket/CREATE":
      const new_bucket_list = [...state.list, { text:action.text, completed: false}];
      return { ...state, list: new_bucket_list, is_loaded: true };
    
    case "bucket/UPDATE":
      const update_bucket_list = state.list.map((list, idx) => {
        if (idx == action.index) {
          let new_list = {text: list.text, completed: true};
          return new_list;
        } else {
          return list;
        }
      })
      return {...state, list: update_bucket_list};
      
    case "bucket/REMOVE":
      const remove_bucket_list = state.list.filter((list_item, idx)=> {
        return idx != action.index
      })
      return {...state, list: remove_bucket_list };

    case "bucket/LOADED":
      return {...state, is_loaded: action.loaded};
    default:
      return state;
  }
}
  • Component에서 사용하기 (예시)
import { useDispatch } from "react-redux";

const dispatch = useDispatch();
dispatch(loadBucketFB());

머테리얼 UI 사용하기

  • 머테리얼 UI : Bootstrap과 같이 만들어놓은 CSS를 가져다가 사용할 수 있다.

  • 머테리얼 UI 설치 (공식문서 참고)

yarn add @material-ui/core @material-ui/icons
  • 예시
import Button from "@material-ui/core/Button";


<Button variant="outlined" color="primary">완료하기</Button>
<Button variant="outlined" color="primary">완료하기</Button>

◎ 페이지 의도적으로 가리기(로딩 화면)

  • 로딩 스피너를 만드는 이유
    • redux에 넣어둔 초기 데이터 때문에 잘못된 정보가 보이거나, 이상한 UI가 초반에 보일 수 있다.
    • 수정/추가한 정보가 바로 반영되지 않아서 여러번 API를 호출하는 현상이 있을 수 있다.
  • 로딩 스피너 컴포넌트 style 예시
import React from "react";
import styled from "styled-components";
import {Eco} from "@material-ui/icons";

const Spinner = (props) => {

    return (
      <Outter>
        <Eco style={{ color: "#673ab7", fontSize: "150px" }} />
      </Outter>
    );
}

const Outter = styled.div`
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh; 
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #ede2ff;
`;
// align-items 자식 요소 좌우정렬, justify-content 자식 요소 상하정렬

export default Spinner;
  • 로딩 스피너 컴포넌트 적용
...
import { useDispatch, useSelector } from "react-redux";
...
import Spinner from "./Spinner";
...
function App() {
  
  const is_loaded = useSelector(state => state.bucket.is_loaded);
  
  return(
    ...
    // !is_loaded : is_loaded 의 반대 
    // boolean && Component : boolean이 True 일때, 해당 값을 반환
    {!is_loaded && <Spinner />} 
  )
}
profile
장비를 정지합니다.

0개의 댓글

관련 채용 정보