๐ ๋ฆฌ์กํธ SPA(Single Page Application) ์ ์
- [ํฐ๋ฏธ๋] npx create-react-app mbti-app
- [ํฐ๋ฏธ๋] npm i redux react-redux @reduxjs/toolkit styled-components prettier
- .prettierrc
{
"semi": true,
"singleQuote": true
}
- /.vscode/settings.json
{
"[javascript]": {
"editor.maxTokenizationLineLength": 2500,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
- ํด๋ ๊ตฌ์กฐ ์ธํ
๐ฉ redux ๊ธฐ์ด ์ธํ
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import rootReducer from './store/index'; // index ์๋ตํด๋ ๋์ผ
const reduxDevTool =
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
const store = configureStore({ reducer: rootReducer }, reduxDevTool);
console.log(store.getState());
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
reportWebVitals();
๐ฉ rootReducer ์ค์
// src/store/index.js
import { combineReducers } from 'redux';
import mbti from './modules/mbti';
// reducer ๋ค์ combine ํด์ export
export default combineReducers({
mbti,
});
๐ฉ mbti store ์ค์
- ์ด๊ธฐ state๋ฅผ ์ค์ : mbti ์ง๋ฌธ ๋ชฉ๋ก, ํ์ฌ ํ์ด์ง ๊ฐ, mbti ์ ์ฒด ๊ฒฐ๊ณผ ๊ฐ, ์ ์ฒด ๊ฒฐ๊ณผ์ ๋ํ ์ค๋ช ๊ฐ, ์ถ๊ฐ ์ด๋ฏธ์ง ์ฃผ์ ๊ฐ
- ์์ง DB ์ฐ๋์ ํ์ง ์์ ๊ฒ์ด๋ฏ๋ก ํ์ํ ๋ฐ์ดํฐ ์ค์
- ์ก์ ํ์ ์ค์
- ์ก์ ํจ์ ์ค์
- ์ค์ ๋ก state ๊ฐ์ ๋ณ๊ฒฝ์์ผ์ค reducer ๋ง๋ค๊ธฐ
// src/store/modules/mbti.js
// ์ด๊ธฐ ์ํ ์ค์
const initState = {
mbtiResult: '',
page: 0, // 0: ์ธํธ๋ก ํ์ด์ง, 1 ~ n: ์ ํ ํ์ด์ง, n+1: ๊ฒฐ๊ณผ ํ์ด์ง
survey: [
{
question:
'ํด๊ทผ ์ง์ ์ ๋๋ฃ๋ก๋ถํฐ ๊ฐ๋ฐ์ ๋ชจ์์ ์ด๋๋ฅผ ๋ฐ์ ๋!!\n\nํด๊ทผ ์๊ฐ์ ๋๋?',
answer: [
{
text: '๊ทธ๋ฐ ๋ชจ์์ ์ ์ด์ ์์ผ ์๋ ค ์ค๊ฑฐ์ผ! ๋น์ฅ ๋ชจ์์ผ๋ก ์ถ๋ฐํ๋ค',
result: 'E',
},
{
text: '1๋
์ ์ ์๋ ค์คฌ์ด๋ ์๊ฐ์ ๊ฑด๋ฐ ๋ญ... ๋ ๋น ๋ฅด๊ฒ ์ง์ผ๋ก ๊ฐ๋ค',
result: 'I',
},
],
},
{
question:
'์๋ก์ด ์๋น์ค ๊ฐ๋ฐ ์ค์, ๋๋ฃ๊ฐ ์๋ก ๋์จ ์ ๊ธฐ์ ์ ์ฐ๋๊ฒ ๋ ํธํ ๊ฑฐ๋ผ๊ณ ์ถ์ฒ์ ํด์ค๋ค!\n\n๋์ ์ ํ์!?',
answer: [
{
text: '๋ญ์๋ฆฌ์ฌ, ๊ทธ๋ฅ ํ๋ ๋๋ก ๊ฐ๋ฐํ๋ฉด ๋๋๊ฑฐ์ง! ๊ธฐ์กด ์๊ฐ๋๋ก ๊ฐ๋ฐํ๋ค',
result: 'S',
},
{
text: '์คํธ? ๊ทธ๋ฐ๊ฒ ์์ด? ์ผ๋จ ๊ตฌ๊ธ์ ์ฐพ์๋ณธ๋ค',
result: 'N',
},
],
},
{
question:
'์๋น์ค ์ถ์ ์ดํ ์ ์ผ๊ทผ ์๊ฐ, ๊ฐ์๊ธฐ ๋๋ฃ๊ฐ ์ด!?๋ฅผ ์ธ์ณค๋ค!\n\n๋์ ์ ํ์?',
answer: [
{
text: '๋ฌด์จ ๋ฒ๊ทธ๊ฐ ๋ฐ์ํ ๊ฑฐ์ง? ์๋ง DB ๊ด๋ จ ๋ฒ๊ทธ๊ฐ ์๋๊น? ๋น ๋ฅด๊ฒ ๋๋ฃ์ ์๋ฆฌ๋ก ๋ฌ๋ ค๊ฐ๋ค',
result: 'T',
},
{
text: '์... ๋ด์ผ๋ ์ผ๊ทผ ๊ฐ์ด๊ตฌ๋ ใ
ใ
! ์ผ๋จ ๋๋ฃ์ ์๋ฆฌ๋ก ๊ฐ ๋ณธ๋ค',
result: 'F',
},
],
},
{
question:
'ํ์ฅ๋์ด xx ์จ ๊ทธ์ ์ ๋งํ ๊ธฐ๋ฅ ๋ด์ผ ์คํ๊น์ง ์๋ฃ ๋ถํํด์๋ผ๊ณ ๋งํ๋ค!\n\n๋์ ์ ํ์?',
answer: [
{
text: '์ผ๋จ ๋น ๋ฅด๊ฒ ๊ฐ๋ฐ ์๋ฃํ๊ณ , ๋๋จธ์ง ์๊ฐ์ ๋
ผ๋ค',
result: 'J',
},
{
text: '๊ทธ๊ฑฐ ๋ด์ผ ์์นจ์ ์์ ๊ฐ๋ฐํด๋ ์ถฉ๋ถ ํ๊ฒ ๋๋ฐ? ์ผ๋จ ๋
ผ๋ค',
result: 'P',
},
],
},
],
explaination: {
ESTJ: {
text: '๋ฌด๋ฆฌํ ๊ฐ๋ฐ ์ผ์ ๋ง ์๋๋ผ๋ฉด ์ผ์ ์ ์ฒ ์ ํ๊ฒ ์งํฌ ๋น์ ์ MBTI๋!',
img: '/images/estj.jpg',
},
ISTJ: {
text: '์ค์ค๋ก ํ๊ณ ์ถ์ ๋ถ์ผ๋ฅผ ๋๊น์ง ํ๊ณ ๋ค์ด์ ๋๋ด ์ฑ๊ณต ์ํฌ ๋น์ ์ MBTI ๋!',
img: '/images/istj.jpg',
},
ENTJ: {
text: '๋ฏธ๋์ ๋ฅ๋ ฅ ์ฉ๋ ๊ฐ๋ฐ ํ์ฅ๋์ผ๋ก ๊ฐ๋ฐํ์ ์ด๋ ๋น์ ์ MBTI ๋!',
img: '/images/entj.jpg',
},
INTJ: {
text: 'ํผ์์ ๋ชจ๋ ๊ฒ์ ๋ค ํด๋ด๋ ์๋งจ ์บ๋ฆฌ์ ํ๋ณธ! ๋น์ ์ MBTI ๋!',
img: '/images/intj.jpg',
},
ESFJ: {
text: '๊ฐ๋ฐํ์ ๋ถ์๊ธฐ ๋ฉ์ด์ปค์ด์ ์์ด๋์ด ๋ฑ
ํฌ๊ฐ ๋ ๋น์ ์ MBTI๋!',
img: '/images/esfj.jpg',
},
ISFJ: {
text: '๊ฐ๋ฐํ์ ๋ง๋ ํ
๋ ์ฌ, ๊ณ ๋ฏผ ์๋ด์ ์ญํ ์ ์์ฒํ๋ ๋น์ ์ MBTI๋!',
img: '/images/isfj.jpg',
},
ENFJ: {
text: '๋น์ ์ด ์๋ ํ์ ์ธ์ ๋ ์ฌ๋ฐ๋ฅธ ๊ณณ์ ํฅํ๊ณ ์์ต๋๋ค! ํ์์ ๋ฌผ๋ก ํ์ ๋ฐฉํฅ์ ์ฑ๊ธฐ๋ ๋น์ ์ MBTI๋!',
img: '/images/enfj.jpg',
},
INFJ: {
text: '์๋ฆฌํ ํต์ฐฐ๋ ฅ์ผ๋ก ๋ชจ๋ ๊ฒ์ ๋ด๋ค๋ณด๋ฉด์ ์๋ฒฝํ๊ฒ ๊ฐ๋ฐ์ ํ ๋น์ ์ MBTI๋!',
img: '/images/infj.jpg',
},
ESTP: {
text: '์ฟจํ๊ฒ ์์ ์ด ํ ๊ฒ์ ํ๋ฉด์ ๋
ผ๋ฆฌ์ ์ธ ๊ฐ๋ฐ์ ํ ๋น์ ์ MBTI๋!',
img: '/images/estp.jpg',
},
ISTP: {
text: '๋จ์๊ฐ์๋ ํจ์จ์ ์ผ๋ก ๊ฐ๋ฐํ์ฌ ๋ชจ๋ ๊ฒ์ ์์ฑํ ๋น์ ์ MBTI๋!',
img: '/images/istp.jpg',
},
ENTP: {
text: '์ค์ค๋ก ํฅ๋ฏธ๋ง ์๊ธด๋ค๋ฉด ๋น์ฅ์ ํ์ด์ค๋ถ๋ ๋ง๋ค์ด ๋ฒ๋ฆด ๋น์ ์ MBTI๋!',
img: '/images/entp.jpg',
},
INTP: {
text: 'ํ์คํ ์ฃผ๊ด๊ณผ ๋ฐ์ด๋ ์ง๋ฅ์ ๋ฐํ์ผ๋ก ๋
ผ๋ฆฌ์ ๊ฐ๋ฐ์ ํ ๋น์ ์ MBTI๋!',
img: '/images/intp.jpg',
},
ESFP: {
text: '๊ฐ๋ฐํ์ ์๋์์ด์ ! ๊ฐ๋ฐํ ํน์ ์ ์๋จนํจ์ ๊นจ๋ ๋น์ ! ๋น์ ์ MBTI๋!',
img: '/images/esfp.jpg',
},
ISFP: {
text: '๋ฐ์ด๋ ํธ๊ธฐ์ฌ๊ณผ ์์ ์ ๊ฐ๊ฐ์ผ๋ก ๊ฐ๋ฐํ์ ๋ถ์กฑํจ์ ์ฑ์๊ฐ ๋น์ ! ๋น์ ์ MBTI๋!',
img: '/images/isfp.jpg',
},
ENFP: {
text: '์์ ๋ก์ด ์ํผ์ผ๋ก ๊ฐ๋ฐํ์ ์คํ์ ๋ฐ ํ๋ ฅ์๊ฐ ๋์ด์ค ๋น์ ์ MBTI๋!',
img: '/images/enfp.jpg',
},
INFP: {
text: '๊ฐ๋ฐํ์ ๊ทธ ์ด๋ค ํธ๋ฌ๋ธ๋ ๋น์ ์์์๋ ์ฌ๋ฅด๋ฅด ๋
น์๋ฟ, ํ์ ๊ทผ๊ฐ์ ๋ค์ ธ์ฃผ๋ ๋น์ ์ MBTI๋!',
img: '/images/infp.jpg',
},
},
};
// ์ก์
ํ์
(๋ฌธ์์ด)
const CHECK = 'mbti/CHECK';
const NEXT = 'mbti/NEXT';
const RESET = 'mbti/RESET';
// ์ก์
์์ฑ ํจ์
export function next() {
return {
type: NEXT,
};
}
export function check(result) {
return {
type: CHECK,
payload: { result },
};
}
export function reset() {
return {
type: RESET,
};
}
// reducer
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;
}
}
* ๋ด์ฉ์ ์์ ๊ณผ ๋๊ฐ์ด ์์ฑํ ๊ฒ์ผ๋ก, mbti์ ๋ํ ์ ํํ ์ง์์ ๊ธฐ๋ฐ์ผ๋ก ํ ๊ฒ์ด ์๋๋๋ค!
๐ฉ ์์ ํ์ด์ง ์ปดํฌ๋ํธ ์ ์
// src/components/Start.js
import styled from 'styled-components';
import OrangeButton from './OrangeButton';
export default function Start() {
return (
<>
<Header>๊ฐ๋ฐ์ MBTI ์กฐ์ฌ</Header>
<MainImg src="/images/main.jpg" alt="๋ฉ์ธ ์ด๋ฏธ์ง" />
<SubHeader>
๊ฐ๋ฐ์๊ฐ ํํ ์ ํ๋ ์ํฉ์ ๋ฐ๋ผ์ MBTI๋ฅผ ์์๋ด
์๋ค!
</SubHeader>
<OrangeButton text="ํ
์คํธ ์์" />
</>
);
}
const MainImg = styled.img`
width: inherit;
`;
const Header = styled.p`
font-size: 3em;
`;
const SubHeader = styled.p`
font-size: 1.5em;
color: #777;
`;
// src/App.js
import styled from 'styled-components';
import Start from './components/Start';
const Main = styled.main`
box-sizing: border-box;
width: 100%;
max-width: 500px;
padding: 0 35px;
margin: auto;
text-align: center;
`;
function App() {
return (
<Main>
<Start />
</Main>
);
}
export default App;
// src/components/Button.js
import styled from 'styled-components';
export default function Button({
text,
clickEvent,
mainColor,
subColor,
hoverColor,
}) {
return (
<MyButton
onClick={clickEvent}
mainColor={mainColor}
subColor={subColor}
hoverColor={hoverColor}
>
{text}
</MyButton>
);
}
const MyButton = styled.a`
position: relative;
display: inline-block;
cursor: pointer;
vertical-align: middle;
text-decoration: none;
line-height: 1.6em;
font-size: 1.2em;
padding: 1.25em 2em;
background-color: ${(props) => props.mainColor};
border: 2px solid ${(props) => props.subColor};
border-radius: 0.75em;
user-select: none; // ๋๋๊ทธ ๋ฐฉ์ง
transition: transform 0.15s ease-out;
transform-style: preserve-3d;
margin-top: 1em;
// ๊ทธ๋ฆผ์
&::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
right: 0;
left: 0;
right: 0;
background: ${(props) => props.subColor};
border-radius: inherit;
box-shadow: 0 0 0 2px ${(props) => props.subColor};
transform: translate3d(0, 0.75em, -1em);
}
&:hover {
background: ${(props) => props.hoverColor};
transform: translateY(0.25em);
}
`;
๐ฉ OrangeButton์ผ๋ก ํน์ํ
// src/components/OrangeButton.js
import Button from './Button';
export default function OrangeButton({ text, clickEvent }) {
return (
<Button
text={text}
clickEvent={clickEvent}
mainColor="#fae243"
subColor="#fa9f1a"
hoverColor="#faf000"
/>
);
}
๐ฉ OrangeButton ์ ์ฉ
// src/components/Start.js
return (
<>
<Header>๊ฐ๋ฐ์ MBTI ์กฐ์ฌ</Header>
<MainImg src="/images/main.jpg" alt="๋ฉ์ธ ์ด๋ฏธ์ง" />
<SubHeader>
๊ฐ๋ฐ์๊ฐ ํํ ์ ํ๋ ์ํฉ์ ๋ฐ๋ผ์ MBTI๋ฅผ ์์๋ด
์๋ค! ์ง๊ธ๊น์ง{'\n\n'}
{counts} ๋ช
์ด ์ฐธ์ฌํด์ฃผ์
จ์ต๋๋ค.
</SubHeader>
<OrangeButton text="ํ
์คํธ ์์" clickEvent={() => dispatch(next())} />
</>
);
๐ฉ styled-components GlobalStyle
- ํ์ด์ง ์ ์ฒด์ ๋ํ ์คํ์ผ์ ์ ์ฉํ ๋ ์ฌ์ฉ
// src/components/GlobalStyle.js
import { createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
@font-face {
font-family: 'HS-Regular';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2201-2@1.0/HS-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
}
body {
font-family: 'HS-Regular', "Arial", sans-serif;
padding-top: 1em;
white-space: pre-wrap;
}
ul, ol {
list-style: none;
padding-left: 0px;
}
`;
export default GlobalStyle;
// src/app.js
return (
<>
<GlobalStyle />
<Main>
<Start />
</Main>
</>
);
๐ฉ ํ์ด์ง ๋ถ๊ธฐ ์ฒ๋ฆฌ
- page๊ฐ 0์ด๋ฉด Start ์ปดํฌ๋ํธ ๋ณด์ฌ์ฃผ๊ธฐ
- page๊ฐ ์ค๋ฌธ์ ๊ธธ์ด์ ๊ฐ์ ๋๊น์ง ์ค๋ฌธ์กฐ์ฌ ์ปดํฌ๋ํธ ๋ณด์ฌ์ฃผ๊ธฐ
- page๊ฐ ์ค๋ฌธ์ ๊ธธ์ด๋ฅผ ๋์ด๊ฐ๋ฉด ๊ฒฐ๊ณผ ์ปดํฌ๋ํธ ๋ณด์ฌ์ฃผ๊ธฐ
// src/App.js
return (
<>
<GlobalStyle />
<Main>
{page === 0 ? (
<Start />
) : page !== survey.length + 1 ? (
<Mbti />
) : (
<Show />
)}
</Main>
</>
);
๐ฉ ์ค๋ฌธ ์ปดํฌ๋ํธ ์ ์, SkyblueButton ํน์ํ
// src/components/Mbti.js
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import SkyBlueButton from './SkyblueButton';
const SurveyQuestion = styled.p`
font-size: 1.5em;
color: #777;
`;
const Vs = styled.p`
font-size: 2em;
padding-top: 1em;
`;
export default function Mbti() {
const survey = useSelector((state) => state.mbti.survey);
const page = useSelector((state) => state.mbti.page);
return (
<>
<SurveyQuestion>{survey[page - 1].question}</SurveyQuestion>
<ul>
{survey[page - 1].answer.map((el, index) => {
return (
<li key={index}>
<SkyBlueButton
text={el.text}
/>
{index === 0 && <Vs>VS</Vs>}
</li>
);
})}
</ul>
</>
);
}
// src/components/SkyblueButton.js
import Button from './Button';
export default function SkyBlueButton({ text, clickEvent }) {
return (
<Button
text={text}
clickEvent={clickEvent}
mainColor="#7EDCFA"
subColor="#3A82E0"
hoverColor="#CFECF2"
/>
);
}
๐ฉ ์ด๋ฒคํธ ํธ๋ค๋ฌ์ ์ก์ ์์ฑ ํจ์ ์ง์
- ํ์ด์ง๋ฅผ ๋๊ธฐ๋ ๊ธฐ๋ฅ์ ํ๋ next()๋ฅผ dispatch๋ฅผ ์ด์ฉํ์ฌ Reducer์ ์ ๋ฌ
- Start ์ปดํฌ๋ํธ์ ํ ์คํธ ์์์ด๋ผ๋ ๋ฒํผ, Mbti ์ปดํฌ๋ํธ์ ์ ํ ๋ฒํผ์ ์ง์
// src/components/Start.js
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import OrangeButton from './OrangeButton';
import { next } from '../store/modules/mbti';
export default function Start() {;
const dispatch = useDispatch();
return (
<>
<Header>๊ฐ๋ฐ์ MBTI ์กฐ์ฌ</Header>
<MainImg src="/images/main.jpg" alt="๋ฉ์ธ ์ด๋ฏธ์ง" />
<SubHeader>
๊ฐ๋ฐ์๊ฐ ํํ ์ ํ๋ ์ํฉ์ ๋ฐ๋ผ์ MBTI๋ฅผ ์์๋ด
์๋ค! ์ง๊ธ๊น์ง{'\n\n'}
{counts} ๋ช
์ด ์ฐธ์ฌํด์ฃผ์
จ์ต๋๋ค.
</SubHeader>
<OrangeButton text="ํ
์คํธ ์์" clickEvent={() => dispatch(next())} />
</>
);
}
const MainImg = styled.img`
width: inherit;
`;
const Header = styled.p`
font-size: 3em;
`;
const SubHeader = styled.p`
font-size: 1.5em;
color: #777;
`;
// src/components/Mbti.js
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import SkyBlueButton from './SkyblueButton';
import { next } from '../store/modules/mbti';
const SurveyQuestion = styled.p`
font-size: 1.5em;
color: #777;
`;
const Vs = styled.p`
font-size: 2em;
padding-top: 1em;
`;
export default function Mbti() {
const survey = useSelector((state) => state.mbti.survey);
const page = useSelector((state) => state.mbti.page);
const dispatch = useDispatch();
return (
<>
<SurveyQuestion>{survey[page - 1].question}</SurveyQuestion>
<ul>
{survey[page - 1].answer.map((el, index) => {
return (
<li key={index}>
<SkyBlueButton
text={el.text}
clickEvent={() => {
dispatch(next());
}}
/>
{index === 0 && <Vs>VS</Vs>}
</li>
);
})}
</ul>
</>
);
}
๐ฉ Progress Bar ๋ง๋ค๊ธฐ, ์ฝ์
- ๊ฐ์ ํ์ฌ page / ์ ์ฒด ์ค๋ฌธ ๋ฐฐ์ด์ ๊ธธ์ด
- Progress Bar์ ๋ฐ๊นฅ ๋ถ๋ถ์ ๋จผ์ ๊ทธ๋ฆฌ๊ณ , ์์ ์์๊ฐ ๋ถ๋ชจ์ ํฌ๊ธฐ๋ฅผ ์์ํ ๋ค์ ์์ ์ ํ์ %๋ก ๊ตฌํ
// src/components/Progress.js
import styled from 'styled-components';
export default function Progress({ page, maxPage }) {
return (
<MyProgress>
<div>
{page} / {maxPage}
</div>
<Fill>
<Gauge percent={(page / maxPage) * 100}></Gauge>
</Fill>
</MyProgress>
);
}
const MyProgress = styled.div`
margin-top: 3em;
`;
const Fill = styled.div`
width: 100%;
height: 10px;
background-color: #777;
margin-top: 1em;
text-align: left;
`;
const Gauge = styled.div`
background-color: skyblue;
display: inline-block;
height: inherit;
position: relative;
top: -4px;
width: ${(props) => props.percent}%;
`;
// src/components/Mbti.js
return (
<>
<SurveyQuestion>{survey[page - 1].question}</SurveyQuestion>
<ul>
{survey[page - 1].answer.map((el, index) => {
return (
<li key={index}>
<SkyBlueButton
text={el.text}
clickEvent={() => {
dispatch(check(el.result));
}}
/>
{index === 0 && <Vs>VS</Vs>}
</li>
);
})}
</ul>
<Progress page={page} maxPage={survey.length} />
</>
);
๐ฉ ๊ฒฐ๊ณผ๋ฅผ ๋ง๋๋ check() ์ก์ ์์ฑ ํจ์ ์ฝ์
- check()๋ ์ ๋ฌ ๋ฐ์ ๊ฒฐ๊ณผ๋ฅผ mbtiResult๋ผ๋ ๋ฌธ์์ด์ ์ถ๊ฐํด์ฃผ๊ธฐ ๋๋ฌธ์ ํจ์๋ฅผ ํธ์ถํ ๋ ์ค๋ฌธ ๊ฐ์ฒด์ ํฌํจ๋ ๊ฒฐ๊ณผ ๋ฌธ์์ด๋ง ์ ๋ฌํ๋ฉด ๋จ
// src/components/Mbti.js
return (
<>
<SurveyQuestion>{survey[page - 1].question}</SurveyQuestion>
<ul>
{survey[page - 1].answer.map((el, index) => {
return (
<li key={index}>
<SkyBlueButton
text={el.text}
clickEvent={() => {
dispatch(check(el.result));
dispatch(next());
}}
/>
{index === 0 && <Vs>VS</Vs>}
</li>
);
})}
</ul>
<Progress page={page} maxPage={survey.length} />
</>
);
๐ฉ redux์ ๋ชจ์ธ ๊ฒฐ๊ณผ ์ถ๋ ฅ, reset() ์ก์ ์์ฑ ํจ์ ์ ๋ฌ
- redux์์ mbtiResult ๊ฐ๊ณผ, ๊ฒฐ๊ณผ ๊ฐ์ ๋ง๋ ์ค๋ช + ์ด๋ฏธ์ง๋ฅผ ์ถ๋ ฅ
- ๋ค์ํ๊ธฐ ๋ฒํผ์ dispatch๋ฅผ ํตํด ํจ์ ์ ๋ฌ
// src/components/Show.js
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { reset } from '../store/modules/mbti';
import OrangeButton from './OrangeButton';
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="ํฉํญ" />
<OrangeButton text="๋ค์ ๊ฒ์ฌํ๊ธฐ" clickEvent={() => dispatch(reset())} />
</>
);
}
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);
`;
'KDT > TIL' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
10/17 TIL : MySQL ์ ๊ทํ, Mbti-App์ MongoDB ์ ์ฉ (0) | 2022.10.17 |
---|---|
10/14 TIL : MySQL ์ ์ฉ (0) | 2022.10.17 |
10/7 TIL : Redux, ๊ฐ๋จํ Todo-list ๋ง๋ค๊ธฐ (0) | 2022.10.14 |
10/5 TIL : React Router (0) | 2022.10.14 |
9/30 TIL : React Hooks(useEffect, useMemo) (0) | 2022.10.14 |