본문 바로가기
내마음대로만들어보자/React

[Router/Redux] 우정테스트 사이트 만들기(Feat.리액트)

by 소농민! 2021. 8. 9.
728x90

이번에는 퀴즈를 풀고나서 점수를 확인하고 대상에게 한마디를 남기고 랭킹정보를 볼 수 있도록 화면을 만들어보자

이때, 각화면을 라우팅시키고 데이터를 리덕스에넣어 적용시켜보자

 

 

 

1. 뷰 만들기

- Ranking.js

- Message.js

 

잊지않도록 기본형태는 다시한번 짚고넘어가자.

* Ranking.js

 

import React from "react";

import styled from "styled-components";

 

const Ranking = (props) => {

    return (

       <div>

           position fixed :제목부분 

        </div>

         <div>

           list

           list 

           ...  map 돌려서 랭킹정보를 보여준다.

          </div> 

          <button></button>

    )

}

 

export default Ranking;

 

Message.js는 Start.js와 내용만다르고 똑같은 형태이기때문에 시작페이지를 활용하자.

 

2. App.js 라우트 적용하기

 

- 패키지 설치하기

   yarn add react-router-dom 

   yarn add redux react-redux 

 

참고 공식문서!

https://reactrouter.com/web/guides/primary-components

 

React Router: Declarative Routing for React

Learn once, Route Anywhere

reactrouter.com

※ 라우팅이란?

    브라우저 주소에 따라 다른 페이지를 보여주는걸 라우팅이라고한다.

 

- index.js 브라우저라우터 적용하기

 import { BrowserRouter } from "react-router-dom";

...

// 이부분이 index.html에 있는 div#root에 우리가 만든 컴포넌트를 실제로 랜더링하도록 연결해주는 부분입니다.

ReactDOM.render(

    <BrowserRouter>

          <App />

    </BrowserRouter>,

document.getElementById("root") );

 

BrowserRouter란?

    웹 브라우저가 가지고 있는 주소 관련 정보를 props로 넘겨주는 친구. 현재 내가 어느 주소를 보고 있는 지 쉽게 알 수 있게 도와준다!

 

- App.js 라우터 적용하기

먼저 가장 중요한 임포트!!

import {Route, Switch} from "react-router-dom";

import { withRouter } from "react-router";

 

Route란? 

    컴포넌트에 path 속성을 이용하여 원하는 url를 지정하고 그 url에 접속하면 해당 컴포넌트만 렌더링이 된다.

    즉, 사용자의 경로에 따라 다른 컴포넌트를 보여줄 수 있다.

 

※ Switch 란?

    Route로 생성된 자식컴포넌트 중 매칭되는 첫번째 Route를 렌더링해준다.

    보통 메인 Route의 url경로를 "/"로 지정하고 다른 Route에는 "/detail", "/login" 이런식으로 지정하게 되는데,

    만약 Switch를 사용하지 않으면 그냥 메인페이지에 가기 위하여 "/" 경로를 접속하면

     "/"가 포함된 "/detail", "/login" 컴포넌트들이 다 렌더링 되는 경우가 발생한다. 

 

※ withRouter 란?

    라우트 컴포넌트가 아닌곳에서 match / location / history 를 사용할 수 있게 도와준다. 

    (컴포넌트에서 라우터로 접근하는 방법 중 하나)

 

기존 작성을 했었던 조건부렌더링은 지워주고 본격적으로 라우터 적용을 해보자.

render () {

     return ( 

       <div className="App">

          <Switch>

                <Route path="/quiz" component={Quiz} />

                <Route path="/" exact component={Start} />    //exact 는 주소가 정확한지 확인해주는친구.

                <Route path="/score" component={Score} />

               <Route path="/message" component={Message} />

              <Route path="/ranking" component={Ranking} />

        </Switch>

     </div>

    );

  }

}

     

※ Route 사용방법 복습!!

 1. 넘겨줄 props 가 없을때 (=우정테스트)

<Route path="주소[/home 처럼 /와 주소를 적어요]" component={[보여줄 컴포넌트]}/>

 

2. 넘겨줄 props가 있을때 (=버킷리스트)

<Route path="주소[/home 처럼 /와 주소를 적어요]" render={(props) => (<BucketList list={this.state.list} />)} />

 

마지막으로 연결해주면 끝! 

export default withRouter(App);

 

3. 리덕스 연결하기

 

- 폴더 생성

  → src > redux > modules

 

store를 먼저 만들어야 컴포넌트랑 연결할 수 있기때문에 모듈을 먼저 만들고 시작해보자.

quiz.js : Quiz.js에 대한 모듈 

 

* 리덕스(redux) 기본개념

  1) Action : 어플리케이션의 store(스토어), 즉 저장소로 data를 보내는 방법이다.

                     view에서 정의되어있는 액션을 호출하면 action creators(액션 생성자)는 어플리케이션의 state(상태)를 변경하여 준다. 

                     action type은 action creators(액션 생성자)를 통해 사용된다.

  

   2) Reducer : action을 통해 어떠한 행동을 정의했다면, 그 결과 상태가 어떻게 바뀌는지 특정하게 하는 함수

   3) Store : 무엇이 일어날지”를 나타내는 action,

                     action에 따라 상태를 수정하는 reducer를 저장하는 어플리케이션에 있는 단 하나의 객체

 

 

* quiz.js 완성코드

// Actions

// 퀴즈 데이터 가져온다
const GET_QUIZ = "quiz/GET_QUIZ";
// 유저의 응답(퀴즈 답)을 추가한다
const ADD_ANSWER = "quiz/ADD_ANSWER";
// 응답을 초기화 해준다
const RESET_ANSWER = "quiz/RESET_ANSWER";

const initialState = {
name: "손흥민",
score_texts: {
60: "앞으로 더 알아갈 필요가 있겠어!",
80: "너에 대해 모든걸 더 알고싶어!!",
100: "영혼의단짝!",
},
answers: [],
quiz: [
{ question: "손흥민의 소속팀은 토트넘이다.", answer: "O" },
{ question: "손흥민의 프로데뷔년도는 2011년이다.", answer: "X" },
{ question: "현재 소속팀의 등번호 7번이다.", answer: "O" },
{ question: "저번시즌 17골 득점 기록을 보유하고있다.", answer: "O" },
{ question: "손흥민은 인싸다.", answer: "O" },
],
};

// Action Creators
export const getQuiz = (quiz_list) => {
return { type: GET_QUIZ, quiz_list };
};

export const addAnswer = (answer) => {
return { type: ADD_ANSWER, answer };
};

export const resetAnswer = () => {
return { type: RESET_ANSWER };
}

// Reducer
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
// do reducer stuff
case "quiz/GET_QUIZ": {
return { ...state, quiz: action.quiz_list };
}

case "quiz/ADD_ANSWER": {
return { ...state, answers: [...state.answers, action.answer] };
}

case "quiz/RESET_ANSWER": {
return {...state, answers: []};
}

default:
return state;
}
}

* rank.js 완성코드

import {firestore} from "../../firebase";

const rank_db = firestore.collection("rank");
// Actions

// 유저 이름을 바꾼다
const ADD_USER_NAME = "rank/ADD_USER_NAME";
// 유저 메시지를 바꾼다
const ADD_USER_MESSAGE = "rank/ADD_USER_MESSAGE";
// 랭킹정보를 추가한다
const ADD_RANK = "rank/ADD_RANK";
// 랭킹정보를 가져온다
const GET_RANK = "rank/GET_RANK";
const IS_LOADED = "rank/IS_LOADED";


const initialState = {
user_name: "",
user_message: "",
user_score: "",
score_text: {
60: "앞으로 더 알아갈 필요가 있겠어!",
80: "너에 대해 모든걸 더 알고싶어!!",
100: "영혼의단짝!",
},
ranking: [],
is_loaded: false,
};

// Action Creators
export const addUserName = (user_name) => {
return { type: ADD_USER_NAME, user_name };
};

export const addUserMessage = (user_message) => {
return { type: ADD_USER_MESSAGE, user_message };
};

export const addRank = (rank_info) => {
return { type: ADD_RANK, rank_info };
};

export const getRank = (rank_list) => {
return { type: GET_RANK, rank_list };
};

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

export const addRankFB = (rank_info) => {
return function (dispatch) {
// 데이터를 저장할 동안 스피너가 뜨도록 해줍시다.
dispatch(isLoaded(false));

let rank_data = {
message: rank_info.message,
name: rank_info.name,
score: rank_info.score,
};
rank_db.add(rank_data).then((doc) => {
// id를 콘솔로 확인해볼까요?
console.log(doc.id);
// id를 추가해요!
// current는 여기서 추가해줘야 해요! 그래야 내가 한 것만 하이라이트를 줄 수 있거든요. (db에 current가 true로 들어가면 안됩니다!)
rank_data = { ...rank_data, id: doc.id, current: true };
// 데이터를 추가해줘요!
dispatch(addRank(rank_data));

});
};
}


export const getRankFB = () => {
return function (dispatch){

dispatch(isLoaded(false));

rank_db.get().then((docs) => {
let rank_data = [];

docs.forEach((doc) => {
// console.log(doc.data());
rank_data = [...rank_data, {id: doc.id, ...doc.data()}];
});

dispatch(getRank(rank_data));
dispatch(isLoaded(true));

})
}
}


// Reducer
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
// do reducer stuff
case "rank/ADD_USER_NAME": {
return { ...state, user_name: action.user_name };
}

case "rank/ADD_USER_MESSAGE": {
return { ...state, user_message: action.user_message };
}

case "rank/ADD_RANK": {
return { ...state, ranking: [...state.ranking, action.rank_info] };
}

case "rank/GET_RANK": {

// 리덕스에 있던 데이터에 파이어베이스에서 가져온 데이터를 추가해요! 다만, 같은 값이 있으면 안되겠죠??
// 중복되지 않은 데이터만 추가해줄거예요.
// id가 같은 지 아닌 지로 데이터를 구분해서 추가해볼게요.

// 일단 랭킹 데이터를 담을 변수를 만들고, 기존 리덕스 값을 가져다가 넣어줍니다.
let ranking_data = [...state.ranking];

// 랭킹 데이터의 id 배열을 하나 만들어줍니다.
const rank_ids = state.ranking.map((r, idx) => {
return r.id;
});
// 콘솔로 확인해볼까요! :)
console.log(rank_ids);

// 리덕스에 없는 데이터만 가져오기
const rank_data_fb = action.rank_list.filter((r, idx) => {
// 가지고 온 값의 id가 리덕스에 있는 아이디 배열에 없으면 추가해요!
if(rank_ids.indexOf(r.id) === -1){
// 배열에도 이렇게 스프레드 문법을 사용할 수 있습니다. :) (다른 방법으로 추가하셔도 됩니다.)
ranking_data = [...ranking_data, r];
}
});
// 데이터 확인해보기!
console.log(ranking_data);

return { ...state, ranking: ranking_data };
}

case "rank/IS_LOADED": {
return {...state, is_loaded: action.loaded};
}

default:
return state;
}
}