본문 바로가기
학원에서 배운 것/React

KDT 5th 웹개발자 입문 수업 44일차

by 쿠리의일상 2023. 4. 6.

MBTI 구현 마저하기

Check 액션 생성 함수 삽입

mbti 조사 결과 값을 만들어주기 위해 선택한 값을 바탕으로 Action 시켜준다.

export function check(result) {
  return {
    type: CHECK,
    payload: { result },
  };
}
export default function mbti(state = initState, action) {
  switch (action.type) {
    case CHECK:
      return {
        ...state,
        mbtiResult: state.mbtiResult + action.payload.result,
      };
    case NEXT:
      return {
        ...state,
        page: state.page + 1,
      };
    case RESET:
      return {
        ...state,
        page: 0,
        mbtiResult: '',
      };
    default:
      return state;
  }
}

 

dispatch() 함수에 check() 를 넘겨주되, survey 안에 존재하는 result(엠비티아이 문자열) 를 넘겨준다.

  return (
    <>
      <SurveyQuestion>{survey[page - 1].question}</SurveyQuestion>
      <ul>
        {survey[page - 1].answer.map((e, idx) => {
          return (
            <li key={idx}>
              <SkyblueButton
                text={e.text}
                clickEvent={() => {
                  dispatch(check(e.result));
                  dispatch(next());
                }}
              />
              {idx === 0 && <Vs>VS</Vs>}
            </li>
          );
        })}
      </ul>
      <Progress page={page} maxPage={survey.length} />
    </>
  );

설정하고 리덕스 개발툴을 활용하여

변화값을 확인할 수 있다.

 

결과를 출력하는 페이지

const Header = styled.p`
  font-size: 3em;
`;
const Explanation = styled.p`
  font-size: 1.5em;
  color: #777;
`;
const Result = styled.p`
  font-size: 3em;
  color: dodgerblue;
`;
const Additional = styled.p`
  font-size: 2em;
  color: orange;
`;
const AdditionalImg = styled.img`
  width: 500px;
  transform: translateX(-35px);
`;

export default function Show() {
  const result = useSelector((state) => state.mbti.mbtiResult);
  const explanation = useSelector((state) => state.mbti.explanation[result]);
  const dispatch = useDispatch();
  return (
    <>
      <Header>당신의 개발자 MBTI 결과는?</Header>
      <Explanation>{explanation.text}</Explanation>
      <Result>{result}</Result>
      <Additional>이건 재미로 읽어보세요</Additional>
      <AdditionalImg src={explanation.img} alt="팩폭"></AdditionalImg>
      <PinkButton text="다시 검사하기" clickEvent={() => dispatch(reset())} />
    </>
  );
}

 

결과화면 분기 처리

function App() {
  const page = useSelector((state) => state.mbti.page);
  const survey = useSelector((state) => state.mbti.survey);

  return (
    <>
      <GlobalStyle />
      <Main>
        {page === 0 ? <Start /> : page > survey.length ? <Show /> : <Mbti />}
      </Main>
    </>
  );
}

 

일단 리액트로 구현할 수 있는 부분은 끝났다!

 

 

MongoDB와 연동하여 응용하기

데이터가 객체형태나 배열형태인 경우 MySQL 보다 좋다.

 

백엔드 시작 전 기초 세팅하기

// .env 파일
PORT = 4000

MONGO_DB_URI = "mongodb+srv://아이디:비밀번호@cluster0.y4isdtc.mongodb.net/?retryWrites=true&w=majority"
1. npm i -S express core dotenv mongodb 설치
2. MongoDB 클라우드 -> Connect 부분 카피해서 몽고DB와 접속 모듈 생성
const { MongoClient, ServerApiVersion } = require('mongodb');
const { MONGO_DB_URI } = process.env;
const client = new MongoClient(MONGO_DB_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  serverApi: ServerApiVersion.v1,
});

module.exports = client;
3. 아틀라스 클라우드에 Database 생성 후 counts 로 방문자수 컬렉션을 만들어준다.

4. controller 와 routes 기본 설정

// dataController.js 데이터 관련된 컨트롤러
const mongoClient = require('./mongoConnect');

const initState = {
  // 넣어줄 데이터
};

const setData = async (req, res) => {
  try {
    const client = await mongoClient.connect();
    const data = client.db('mbti').collection('data');

    await data.insertOne(initState);
    res.status(200).json('데이터 추가 성공');
  } catch (err) {
    console.log(err);
    res.status(500).json('데이터 삽입 실패, 알 수 없는 문제 발생');
  }
};

module.exports = {
  setData,
};

몽고클라이언트를 연결하고 이미 존재하는 data 를 post 형식으로 몽고DB에 insertOne() 해준다. 

// 서버 라우터 data.js
const express = require('express');
const router = express.Router();

const { setData } = require('../controllers/dataController');

router.post('/setdata', setData);

module.exports = router;

 

// 메인 서버에 라우터 연결
const express = require('express');
const cors = require('cors');
require('dotenv').config();

const PORT = process.env.PORT;
const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cors());

const dataRouter = require('./routes/data');
app.use('/data', dataRouter);

app.listen(PORT, () => {
  console.log(`서버가 ${PORT}번에서 작동 중입니다.`);
});

POSTMAN 으로 요청 보내기

컨트롤러 기능 추가

  • 방문자 수를 가져오는 getCounts
const getCount = async (req, res) => {
  try {
    const client = await mongoClient.connect();
    const countsDB = client.db('mbti').collection('counts');
    const counts = await countsDB.findOne({ id: 1 });
    res.status(200).json(counts);
  } catch (err) {
    console.error(err);
    res.status(500).json('데이터 가져오기 실패, 알 수 없는 문제 발생');
  }
};
  • 방문자 수를 증가시키는 incCount
const getCount = async (req, res) => {
  try {
    const client = await mongoClient.connect();
    const countsDB = client.db('mbti').collection('counts');
    const counts = await countsDB.findOne({ id: 1 });
    res.status(200).json(counts);
  } catch (err) {
    console.error(err);
    res.status(500).json('데이터 가져오기 실패, 알 수 없는 문제 발생');
  }
};

const incCount = async (req, res) => {
  try {
    const client = await mongoClient.connect();
    const countsDB = client.db('mbti').collection('counts');
    await countsDB.upDateOne(
      { id: 1 },
      {
        $inc: { counts: +1 },
      },
    );
    res.status(200).json('방문자수 업데이트 성공');
  } catch (err) {
    console.error(err);
    res.status(500).json('방문자수 가져오기 실패, 알 수 없는 문제 발생');
  }
};
  • 데이터를 받아오는 getData
const getData = async (req, res) => {
  try {
    const client = await mongoClient.connect();
    const data = client.db('mbti').collection('data');

    const mbtiData = await data.find({}).toArray();
    res.status(200).json(mbtiData);
  } catch (err) {
    console.error(err);
    res.status(500).json('데이터 가져오기 실패, 알 수 없는 문제 발생');
  }
};

 

데이터 라우팅  추가해주기

const express = require('express');
const router = express.Router();

const {
  setData,
  getData,
  incCount,
  getCount,
} = require('../controllers/dataController');

router.post('/setdata', setData);
router.get('/getdata', getData);

router.get('/getcount', getCount);
router.post('/inccount', incCount);

module.exports = router;

 

리액트와 몽고DB 연동

리액트에서 몽고DB의 데이터를 받는 함수 작성하기

위 백엔드에서 작성해준 DB 데이터를 받아주려면 fetch() 로 받아오고,

가장 처음 마운트 될 때 데이터를 받아와야 하므로, Start 컴포넌트 안 useEffect() 로 fetchData() 함수를 작성해준다.

export default function Start() {
  const dispatch = useDispatch();

  async function fetchData() {
  // 백엔드 쪽에서 데이터 요청하기
    const resMbtiData = await fetch('http://localhost:4000/data/getdata');
    if (resMbtiData.status === 200) {
      const data = await resMbtiData.json();
      console.log(data);
    } else {
      // throw new Error('통신 오류');
      console.log(await resMbtiData.json());
    }
  }

// Mount 될 때 실행
  useEffect(() => {
    fetchData();
  }, []);

  return (
    <>
      <Header>개발자 MBTI 조사</Header>
      <MainImg src="/images/main.jpg" alt="메인 이미지" />
      <SubHeader>
        개발자가 흔히 접하는 상황에 따라서 MBTI를 알아봅시다.
      </SubHeader>
      <OrangeButton text="테스트 시작" clickEvent={() => dispatch(next())} />
    </>
  );
}

 

받아온DB 정보를 활용한 리덕스 구조 변경

원래의 더미데이터를 없애고 빈 state 를 만들어준다.

const initStateEmpty = {
  mbtiResult: '',
  page: 0,
  survey: [],
  explanation: {},
};
// Action 타입 설정
const INIT = 'mbti/INIT';
// Action 생성 함수 설정
export function init(data) {
  return {
    type: INIT,
    payload: data,
  };
}

리듀서도 수정해줌

export default function mbti(state = initStateEmpty, action) {
  switch (action.type) {
    case INIT:
      return {
        ...state,
        survey: action.payload.survey,
        explanation: action.payload.explanation,
      };
    case CHECK:
      return {
        ...state,
        mbtiResult: state.mbtiResult + action.payload.result,
      };
    case NEXT:
      return {
        ...state,
        page: state.page + 1,
      };
    case RESET:
      return {
        ...state,
        page: 0,
        mbtiResult: '',
      };
    default:
      return state;
  }

 

읽어온 DB를 다시 리덕스 안에 넣어서 출력해주기

  async function fetchData() {
    const resMbtiData = await fetch('http://localhost:4000/data/getdata');
    if (resMbtiData.status === 200) {
      const data = await resMbtiData.json();
      if (data[0]) dispatch(init(data[0]));
      console.log(data);
    } else {
      // throw new Error('통신 오류');
      console.log(await resMbtiData.json());
    }
  }

받아온 data 는 결국 큰 구조상 배열의 첫번째 요소이므로 data[0] 으로 접근해야 하는 것이다.

기본 INIT 이 되고, 서버에서 읽어온 데이터를 다시 INIT 해줘서 총 2번 실행됨..

방문자수 값을 첫 화면(Start)에 전달

counts 는 값을 새로 받으면 Start 컴포넌트의 방문자 수를 Update 해줘야 하므로 useState 로 값을 선언해준다.

State 값을 변경하는 Hook에 원하는 값을 전달한다.

  async function fetchCounts() {
    const resCountsData = await fetch('http://localhost:4000/data/getcount');
    if (resCountsData.status === 200) {
      const countsData = await resCountsData.json();
      console.log(countsData);
      if (countsData.counts !== undefined) setCounts(countsData.counts);
    } else {
      console.log(resCountsData.json());
    }
  }

마지막 페이지에서 Counts+1 해주기

  const incCount = async () => {
    const resInc = await fetch('http://localhost:4000/data/inccount', {
      method: 'post',
    });
    if (resInc.status === 200) {
      console.log(await resInc.json());
    } else {
      // throw new Error('통신 이상');
      console.log(await resInc.json());
    }
  };

마지막 페이지인 Show 컴포넌트가 나온다는 것은 테스트를 마친 것이므로 방문자수를 여기서 useEffect() 로 늘려주면 된다.