나와 상대의 별자리, mbti, 12지신, 혈액형을 입력하면 두사람의 궁합에 대해 알려주는 사이트를 만들었다.
화면 뷰는 총4개로 첫화면 / 내 정보 / 상대 정보 / 결과 화면 으로 구성되어있다. 제플린으로 제작된 화면을 그대로 따라서 구현하려고 노력했다.
| 사용 기술 |
| - react(function components) - style component - react redux - axios |
1. 컴포넌트 생성 & 폴더 구조

-> 액션은 삭제해도 되는데 실수로,, 남아있다,,,ㅎㅎ
->인풋 값을 저장할 리덕스 생성
-> 컴포넌트 폴더를 생성해서 필요한 페이지들을 만들어줌
( 폴더에 있는 jsx파일을 RootRouter를 통해 페이지 전환을 해줄 것이다.)
-> 모달창 폴더
-> RootRouter
-> 이미지 소스 폴더
-> 전역에 적용할 스타일 컴포넌트
->폰트
2. 페이지 Router 처리
🔷 페이지 흐름
첫 화면 -> 내 정보 -> 상대 정보 -> 결과 화면
🔷 라우터 돔 설치
$ npm add react-router-dom
🔷 Root Router 파일 생성
🚨 React Router가 v5 ⏩ v6으로 버전이 변경되었다.
가장 큰 변화점은 Switch가 사라진 것 & useHistory가 아닌 useNavigator로 변경 된 것 이다.
프로젝트에 바로 적용을 시켜보았다. 기존 Switch는 여러개의 Route들을 묶어 둔 Routes로 변경되었다.
//Rootrouter.js
import { Routes, Route } from "react-router-dom";
import MeinputPage from "../components/MeinputPage";
import YouinputPage from "../components/YouinputPage";
import ResultPage from "../components/ResultPage";
import InitialScreen from "../components/InitialScreen";
const Rootrouter = () => {
return (
<Routes>
<Route path="/" element={<InitialScreen />} />
<Route path="/mepage" element={<MeinputPage />} />
<Route path="/youpage" element={<YouinputPage />} />
<Route path="/result" element={<ResultPage />} />
</Routes>
);
};
export default Rootrouter;
더 많은 변경 사항을 참고 하고 싶으면 공식문서 링크에서 확인해보면 된다.
리액트 라우터 공식문서)) https://reactrouter.com/docs/en/v6/upgrading/v5
React Router | Upgrading from v5
Declarative routing for React apps at any scale
reactrouter.com
🔷 src index.js 에 BrowserRouter로 App을 감싸주기
//src index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./components/App";
import reportWebVitals from "./reportWebVitals";
ReactDOM.render(
<BrowserRouter>
<App />{" "}
</BrowserRouter>
document.getElementById("root")
);
reportWebVitals();
export 한 Rootrouter를 App.js에 적어주면 라우트 처리가 완료된다. 이제 해당 페이지에서 클릭이벤트가 발생했을 때 useNavigator함수를 사용해서 페이지가 넘어가는 것 처럼 보일 수 있게 만들어 주면 된다. 이부분은 각 컴포넌트 파일에서 다루도록 하겠다.
//App.js
import Rootrouter from "../routers/Rootrouter";
function App() {
return (
<>
<Rootrouter />
</>
);
}
export default App;
3. 글로벌 스타일 설정 - 스타일 컴포넌트 적용
🔷 스타일 컴포넌트 설치
$ npm i styled-components
🔷 GlobalStyle
//GlobalStyle.js
import styled from "styled-components"; //설치된 스타일컴포넌트를 import해준다.
import check from "../images/check.jpg"; //배경으로 사용할 이미지
//전역에서 적용할 스타일 생성해주기
export const GlobalStyle = styled.div`
background-image: url(${check});
width: 400px;
height: 100%; //height는 페이지 내부 컨텐츠 양에 따라 달라질 수 있기 때문에 100% 로 해줌
padding-bottom: 50%; //배경 이미지가 높이가 짧은 파일이라서 padding을 줘서 좀 늘어나게 설정해줬다.
margin: auto; // 페이지 가운데에 올 수 있게 설정
font-family: "MaruBuri"; // 글꼴(적용방법 링크 참조)
`;
폰트 적용하는 방법은 아래 링크 참고
✅ https://velog.io/@lifeisbeautiful/React-%ED%8F%B0%ED%8A%B8%EC%9E%85%ED%98%80%EB%B3%B4%EA%B8%B0
이제 App.js 에서 Rootrouter 외부를 감싸주면 안에있는 컴포넌트에 GlobalStyle이 적용된다.
//App.js
import Rootrouter from "../routers/Rootrouter";
import { GlobalStyle } from "../styleComponent/GlobalStyle"; //글로벌 스타일 임포트하기
import "../App.css";
function App() {
return (
<>
<GlobalStyle>
<Rootrouter />
</GlobalStyle>
</>
);
}
export default App;
4. InitialScreen - 첫 화면 만들기

GlobalStyle을 적용해서 배경과 전체폭, 높이만 정해져 있는 상태에 옆에 보이
는 나머지 내용들을 추가하고 기능을 구현하고자 한다.
<기능 구현>
- i 아이콘 Click ⏩ 출처 정보 담은 모달창 띄우기
- 시작하기 버튼 Click ⏩ 다음페이지 이동
- 테스트 공유 Click ⏩ url 복사 + alert창 띄우기
- 참여인원 API get ⏩ 화면에 띄우기
🔷 i 아이콘 Click ⏩ 출처 정보 담은 모달창 띄우기
1) IconModal component 생성
//IconModal.jsx
import React from "react";
import styled from "styled-components";
let IconModal = (props) => {
const onClick = () => {
props.setInfoIconModal(false);
};
//배경클릭시 InitialScreen의 infoIconModal의 값이 true에서 false로 변경되면서 창 꺼짐 이벤트
return (
<IconModalStyle> //스타일 컴포넌트
<ModalBack> // 클릭시 검은색 반투명 배경 추가 될 것
<div className="modalBack" onClick={onClick}></div>
<div className="IconModal">
<div className="IconModalInfo">
<div className="question">
<strong>문의:</strong> codnjs779@naver.com
</div>
<div className="source">
<strong>출처:</strong>
<div>- MBTI 유형별 궁합</div>
<div>- 네이버 지식백과</div>
<div>- 별자리 궁합표</div>
</div>
</div>
</div>
</ModalBack>
</IconModalStyle>
);
};
2) InitialScreen component 생성
//InitialScreen.jsx
import React from "react";
import IconModal from "../modal/IconModal";
//앞서 만들어둔 모달창을 InitialScreen 에서 사용하기 위해 컴포넌트를 import 해서 사용해줬다.
//fontawesome
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
const InitialScreen = () => {
const [infoIconModal, setInfoIconModal] = useState(false);
const onClick = () => {
setInfoIconModal(!infoIconModal);
};
// 아이콘을 클릭할 때 창이 사라질 수 있는 기능
// 삼항연산자를 사용해서 false일때는 보이지 않고 true일때만 모달이 띄워질 수 있게 해줌
// IconModal component에 setInfoIconModal을 props로 넘겨서 IconModal에서 infoIconModal 값을 조정할 수 있게 함.
return (
<>
{infoIconModal === true ? <IconModal setInfoIconModal={setInfoIconModal} /> : null}
<div className="infoIcon">
<FontAwesomeIcon icon={faInfoCircle} onClick={onClick} />
</div>
</>
);
}
폰트 어썸 적용하는 방법은 아래 링크 참고
🔷 시작하기 버튼 Click ⏩ 다음페이지 이동
//InitialScreen.jsx
import { Link } from "react-router-dom";
<Link to="mepage">
<button className="blackBtn">시작하기</button>
</Link>
Rooter 처리를 해줄 때 "내 정보" 입력하는 페이지의 주소를 -> "/mepage"로 설정해줬다. Link를 import 하고 해당 주소를 Link to 에 써주면 된다. 이 방법 말고 useNavigator로 할 수도 있는데 이건 다음 페이지부터 적용시켰다.
🔷 테스트 공유Click ⏩ url 복사 + alert창 띄우기
$ npm install --save react-copy-to-clipboard
//InitialScreen.jsx
import { CopyToClipboard } from "react-copy-to-clipboard";
const url = "https://chemistry-test.co.kr";
return(
<CopyToClipboard text={url}>
<button className="yellowBtn" onClick={() => {alert("링크 복사가 완료되었습니다!"); }}>
테스트 공유
</button>
</CopyToClipboard>
)
CopyToClipboard를 사용해서 링크를 복사할 수 있는 기능을 손쉽게 구현할 수 있다.
🔷 참여인원 API get ⏩ 화면에 띄우기
$ npm install axios
//InitialScreen.jsx
import React, { useState, useEffect } from "react";
import axios from "axios";
const [data, setData] = useState();
//axios response 값이 들어갈 자리 마련
useEffect(() => {
axios
.get("https://api주소")
.then((response) => {
setData(response.data.result.count);
})
.catch((error) => {
console.log(error);
});
}, []);
//받아온 데이터 활용하기
return(
<div className="countNumber">
현재까지 <span>{data}</span>명이 참여했어요
</div>
)
🚩 스타일 컴포넌트까지 작성한 InitialScreen & IconModal 최종코드
//InitialScreen.jsx
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { CopyToClipboard } from "react-copy-to-clipboard";
import axios from "axios";
//img
import heart from "../images/inheart.png";
//style
import styled from "styled-components";
import IconModal from "../modal/IconModal";
//fontawesome
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
const InitialScreen = () => {
const [infoIconModal, setInfoIconModal] = useState(false);
const [data, setData] = useState();
const url = "https://chemistry-test.co.kr";
const onClick = () => {
setInfoIconModal(!infoIconModal);
};
useEffect(() => {
axios
.get("https://dlsrksrndgkq.link/count")
.then((response) => {
setData(response.data.result.count);
})
.catch((error) => {
console.log(error);
});
}, []);
return (
<InitialContentsBoxStyle>
<div className="initialContentsBox">
{infoIconModal === true ? <IconModal setInfoIconModal={setInfoIconModal} /> : null}
<div className="infoIcon">
<FontAwesomeIcon icon={faInfoCircle} onClick={onClick} />
</div>
<TitleAndcontentsDesign>
<h1 className="title">우리의 모든 것</h1>
<div className="contents">mbti 별자리 띠 혈액형 모든 궁합을 한번에 확인해보세요!</div>
<div className="heartIcon">
<img src={heart} alt="img" />
</div>
<div className="countNumber">
현재까지 <span>{data}</span>명이 참여했어요
</div>
</TitleAndcontentsDesign>
<div className="buttonSet">
<ButtonDesign>
<Link to="mepage">
<button className="blackBtn">시작하기</button>
</Link>
<CopyToClipboard text={url}>
<button
className="yellowBtn"
onClick={() => {
alert("링크 복사가 완료되었습니다!");
}}
>
테스트 공유
</button>
</CopyToClipboard>
</ButtonDesign>
</div>
</div>
</InitialContentsBoxStyle>
);
};
const InitialContentsBoxStyle = styled.div`
.infoIcon {
width: 18pt;
height: 18pt;
cursor: pointer;
margin: 0 auto;
position: relative;
left: 160px;
top: 20px;
}
`;
const TitleAndcontentsDesign = styled.div`
.title {
text-align: center;
font-size: 2.1rem;
width: 230px;
margin: 0 auto;
position: relative;
top: 100px;
}
.contents {
text-align: center;
font-size: 1.1rem;
width: 250px;
margin: 0 auto;
color: rgb(153 153 153);
position: relative;
top: 120px;
}
.heartIcon {
text-align: center;
}
.heartIcon img {
width: 250px;
margin: 0 auto;
position: relative;
top: 160px;
}
.countNumber {
text-align: center;
margin: 0 auto;
position: relative;
top: 170px;
}
span {
font-weight: 800;
}
`;
//버튼 디자인은 계속 재활용 할 예정이라 export 처리해줌
export const ButtonDesign = styled.div`
text-align: center;
display: flex;
position: relative;
top: 230px;
flex-direction: column;
.blackBtn,
.yellowBtn {
width: 330px;
height: 70px;
font-size: 1.3rem;
text-align: center;
line-height: 22pt;
font-weight: bold;
border-radius: 13pt;
border: none;
cursor: pointer;
margin: 0 auto;
}
.blackBtn {
background-color: rgb(34 34 34);
color: rgb(255 255 255);
}
.yellowBtn {
margin-top: 20px;
background-color: rgb(255 234 70);
color: rgb(51 51 51);
}
`;
export default InitialScreen;
//IconModal.jsx
import React from "react";
import styled from "styled-components";
let IconModal = (props) => {
const onClick = () => {
props.setInfoIconModal(false);
};
return (
<IconModalStyle>
<ModalBack>
<div className="modalBack" onClick={onClick}></div>
<div className="IconModal">
<div className="IconModalInfo">
<div className="question">
<strong>문의:</strong> codnjs779@naver.com
</div>
<div className="source">
<strong>출처:</strong>
<div>- MBTI 유형별 궁합</div>
<div>- 네이버 지식백과</div>
<div>- 별자리 궁합표</div>
</div>
</div>
</div>
</ModalBack>
</IconModalStyle>
);
};
const IconModalStyle = styled.div`
.IconModal {
background-color: rgb(255 255 255);
position: absolute;
z-index: 2;
width: 230pt;
height: 150pt;
border-radius: 15pt;
opacity: 1;
margin-left: 10pt;
top: 87pt;
}
.IconModalInfo {
padding: 15pt;
font-size: 15pt;
}
.source {
padding-top: 20pt;
}
`;
export const ModalBack = styled.div`
.modalBack {
position: absolute;
z-index: 1;
width: 400px;
height: 100%;
background-color: #202020;
opacity: 0.5;
margin: auto;
}
`;
export default IconModal;
🚩 InitialScreen 기능 구현 완성

'side project' 카테고리의 다른 글
| [React][side project] mbti궁합 테스트(3) - aws Amplify 배포 & 가비아 도메인 붙이기 (2) | 2021.11.26 |
|---|---|
| [React][side project] mbti궁합 테스트(2) (0) | 2021.11.26 |