프로젝트 시작 시 기초 구조 구현하기
1. npx create-react-app 프로젝트명
2. npm i -D prettier eslint
3. npx eslint --init
4. .vscode 폴더 > settings.json
5. 폴더 최상단 > .prettierrc, .eslintrc.js
6. npm i -S @reduxjs/toolkit react-redux
7. npm i -S styled-components
8. npm i -S react-router-dom
MBTI 심리테스트 구현
- 리액트 SPA 제작
- Styled Components 활용 - Global Style / Component
- Redux 활용, 조건부 렌더링
기초 세팅
1. 필요 모듈 설치 npm i -S @reduxjs/toolkit (redux 대체, react-redux 는 필요함)
2. 폴더 구조 세팅
- modules 폴더 안에 mbti.js 로 리덕스 모듈용 파일 생성
3. 리덕스 기초 세팅
- src 폴더의 최상위 index.js 파일의 세팅
- rootReducer 를 import 해주고, Provider 임포트 후 store 설정
import rootReducer from './store/index.js';
- 이제부터 createStore (옛날 기술) 가 아닌, configureStore를 사용한다.
configureStore({ reducer : 리듀서 }, 리덕스 개발자 도구) 형태로 작성
const store = configureStore({ reducer: rootReducer }, reduxDevTool);
- 리덕스 개발자 도구 사용을 위한 코드를 추가해준다.
const reduxDevTool =
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
const reduxDevTool =
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
const store = configureStore({ reducer: rootReducer }, reduxDevTool);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
);
mbti store 설정
초기 State 를 설정 (더미 데이터)
Action type 설정
Action 함수 설정
Reducer 생성
초기 상태 설정
const initState = {
mbtiResult: '', // mbti 전체 결과 값
page: 0, // 현재 페이지 값
survey: [질문 목록 배열 객체], // mbti 질문 목록
explanation: {결과에 대한 설명 객체}, // 전체 결과에 대한 설명 값 + 추가 이미지 주소값
};
Action type 설정
페이지를 다음으로 넘기는 기능 -> 전달 값이 필요 없음
선택에 따른 결과를 반영하는 기능 -> 선택에 따른 결과 값 전달 필요
마지막 페이지에서 결과를 리셋하는 기능 -> 전달값이 필요 없음
// Action 타입 설정 (문자열)
const CHECK = 'mbti/CHECK';
const NEXT = 'mbti/NEXT';
const RESET = 'mbti/RESET';
Action 생성 함수 설정
외부에서 store 의 내부 함수 구조를 알 필요가 없으므로 -> 외부에서 원하는 액션에 따른 기능을 Dispatch 를 통하여 전달할 action 함수를 설정한다.
액션 타입에 맞는 액션 생성 함수를 설정해준다.
외부에서 사용해주므로 export 설정이 필요하다.
type은 반드시 전달해줘야 하며 데이터가 필요한 경우 payload 에 담아서 전달해준다.
// Action 생성 함수 설정
export function check(result) {
return {
type: CHECK,
payload: { result },
};
}
export function next() {
return {
type: NEXT,
};
}
export function reset() {
return {
type: RESET,
};
}
Reducer 생성
실제적으로 state 변경 관리하는 reducer 를 만든다.
dispatch 에 의해 전달 받은 action 의 type 값에 따라 원하는 기능을 수행해주면 된다.
리듀서가 export default 가 되며 type 구분은 switch 를 통해서 한다.
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++,
};
case RESET:
return {
...state,
page: 0,
mbtiResult: '',
};
default:
return state;
}
}
시작 페이지 제작
pages 폴더 안에 첫 페이지로 Start.jsx 페이지를 제작
버튼은 리로딩을 막기 위해 <a> 태그로 구현해줌
export default function Start() {
const Header = styled.p`
font-size: 3em;
`;
const SubHeader = styled.p`
font-size: 1.5em;
color: #777;
`;
const MainImg = styled.img`
width: inherit;
`;
return (
<>
<Header>개발자 MBTI 조사</Header>
<MainImg src="/images/main.jpg" alt="메인 이미지" />
<SubHeader>
개발자가 흔히 접하는 상황에 따라서 MBTI를 알아봅시다.
</SubHeader>
<a>테스트 시작</a>
</>
);
}
////
function App() {
const Main = styled.main`
box-sizing: border-box;
width: 100%;
max-width: 500px;
padding: 0 35px;
margin: auto;
text-align: center;
`;
return (
<Main>
<Start />
</Main>
);
}
버튼 컴포넌트 제작
테스트 시작, mbti 선택지 선택, 다시 하기 등등 다양한 곳에서 재사용된다.
리액트의 특수화 개념을 사용하여 기초 스타일인 button 컴포넌트를 제작하고 해당 컴포넌트를 이용하여 색과 가능을 가진 버튼으로 만들어 사용한다.
porps 로부터 받아야와야 할 값이 버튼의 텍스트 / 이벤트 핸들러 / 메인 색상 / 서브 색상 / Hover 시 색상의 값 이다.
Styled-components 는 현재 컴포넌트에서 전달한 props 를 받아서 처리가 가능하며 ${ }, sass 문법을 기본적으로 사용이 가능하다.
버튼의 기본 구조와 디자인
export default function Button({
text,
clickEvent,
mainColor,
subColor,
hoverColor,
}) {
const MyButton = styled.a`
position: relative;
display: inline-block;
cursor: pointer;
text-decoration: none;
vertical-align: middle;
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;
bottom: 0;
background-color: ${(props) => props.subColor};
border-radius: inherit;
box-shadow: 0 0 0 2px ${(props) => props.subColor};
transform: translate3d(0, 0.75em, -1em);
}
&:hover {
background-color: ${(props) => props.hoverColor};
transform: translateY(0.25em);
}
`;
return (
<MyButton
onClick={clickEvent}
mainColor={mainColor}
subColor={subColor}
hoverColor={hoverColor}
>
{text}
</MyButton>
);
}
user-select 는 드래그와 클릭 여부
버튼의 특수화 작업
매번 버튼을 만들 때마다 props 를 입력해줄 수 없으므로
원하는 텍스트와 색상값을 props 로 전달해서 <Button>을 색상별 컴포넌트로 만들어준다.
export default function OrangeButton({ text, clickEvent }) {
return (
<Button
text={text}
clickEvent={clickEvent}
mainColor="#fae243"
subColor="#fa9f1a"
hoverColor="#faf000"
/>
);
}
글로벌 스타일 적용
SPA --> App.css 에 글로벌 스타일 적용
MPA --> 전체를 감싸는 최종 컴포넌트에 스타일을 적용
다만 styled-components 의 경우는 자체 기능을 통해 createGlobalStyle 함수를 사용해서 지정해준다.
특히 웹 폰트의 경우 @font-face 부분을 가져와서 지정해준다.
const GlobalStyle = createGlobalStyle`
@font-face {
font-family: 'Tenada';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2210-2@1.0/Tenada.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
body {
font-family: 'Tenada';
padding-top: 1em;
white-space: pre-wrap;
}
ul, ol {
list-style: none;
padding-left: 0;
}
`;
export default GlobalStyle;
return (
<>
<GlobalStyle />
<Main>
<Start />
</Main>
</>
);
페이지 분기 처리
페이지에 따라 보여줘야 하는 부분이 다르므로 page : 0 -> Start 페이지
page 가 설문의 길이와 같을 때 -> 설문 조사 페이지 보여주기
page 가 설문의 길이를 넘으면 -> 결과 페이지 보여주기
즉 페이지의 상태에 따라 각각의 페이지를 렌더링하는 방식으로 분기 처리해준다.
리액트의 경우 라우팅 보단 조건부 렌더링 또는 3항 연산자, if 문 처리가 편하다.
function App() {
const page = useSelector((state) => state.mbti.page);
return (
<>
<GlobalStyle />
<Main>{page === 0 ? <Start /> : <Mbti />}</Main>
</>
);
}
액션 생성 함수 지정
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((e, idx) => {
return (
<li key={idx}>
<SkyblueButton
text={e.text}
clickEvent={() => {
dispatch(next());
}}
/>
{idx === 0 && <Vs>VS</Vs>}
</li>
);
})}
</ul>
</>
);
}
프로그래스바 생성
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}%;
`;
export default function Progress({ page, maxPage }) {
return (
<MyProgress>
<div>
{page} / {maxPage}
</div>
<Fill>
<Gauge percent={(page / maxPage) * 100} />
</Fill>
</MyProgress>
);
}
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((e, idx) => {
return (
<li key={idx}>
<SkyblueButton
text={e.text}
clickEvent={() => {
dispatch(next());
}}
/>
{idx === 0 && <Vs>VS</Vs>}
</li>
);
})}
</ul>
<Progress page={page} maxPage={survey.length} />
</>
);
'학원에서 배운 것 > React' 카테고리의 다른 글
KDT 5th 웹개발자 입문 수업 ... 마지막 팀 프로젝트 시작! (0) | 2023.04.08 |
---|---|
KDT 5th 웹개발자 입문 수업 44일차 (0) | 2023.04.06 |
KDT 5th 웹개발자 입문 수업 43일차 - 1 (0) | 2023.04.05 |
KDT 5th 웹개발자 입문 수업 42일차 (0) | 2023.04.04 |
KDT 5th 웹개발자 입문 수업 41일차 (0) | 2023.04.03 |