: 모든 질문 페이지는 동등한 구조
( 싸이월드 이미지 / Text2개 / Image1개 / Button4개 )[ 핵심 ]
- 질문의 idx마다 배경 이미지가 바뀌어야 한다
- App.js에 배열 State를 생성한 뒤 사용자가 입력할 때마다 해당 배열에 값을 넣어준다
- 마지막 질문을 대답하면 최종 10개의 답이 들어있는 배열을 서버에 보내서 score와 scoreRate를 받아야 한다
(ResultPage에서 사용)
[ 배경 이미지 넣기 ]
: 뜻밖으로 시간이 많이 소요된 부분이다 (
김정욱 멍청이)
실제 해커톤 당시 방법을 찾지 못해 임시로 구현하고 끝난 뒤 수정했다.
(배경 아래에 컴포넌트를 넣고 position:absolute로 강제로 올림; 어휴ㅠ)
- 이렇게 생긴 10개의 이미지를 각 번호마다 출력해서 배경에 자리잡게 해야 한다
- 처음에는 해당 요소를 img태그로 넣고 {children}으로 넘겨주면 될 것이라 생각했다
--> img태그는 하위 요소를 둘 수 없어서 불가능하다- 결국 답은 div에서 background-img와 {children}을 통해서 구현해야 했다
(그리고 계속 width와 height를 조정하는데 안되서 찾아보니 background-img는
반드시 background-size를 통해서 크기조정을 해야 한다!!!!! )
import React from 'react' import styled, {css} from 'styled-components'; const BackgroundTemplate = styled.div` position: relative; ${props => css` background-image: url('/images/Question_${props.num}.png'); ` } background-size: 100% 100%; padding: 9.1%; `; function Background({children, num}) { return ( <BackgroundTemplate num={num}> {children} </BackgroundTemplate> ) } export default Background
- 중요한 핵심
1) div에 background-img로 삽입한 경우 background-size로 크기 조정
2) padding을 조절하면서 어느정도 크기를 맞춰주어야 한다
3) props에 따라 배경 이미지가 바뀌게 하였다 (조건부 스타일링)
4) 내부에 다른 컴포넌트들이 와야하므로 {children} 처리를 해야한다
[ 페이지 로드 방식 ]
: 총 10개의 질문이 있고 모두다 형식은 같으니까
/quesition/:idx
경로에서idx
로 페이지를 구분했다.(다른 테스트 페이지들을 보면 params 말고 다르게 구현했는데 방법을 못찾았음 /
어떤 테스트는 페이지마다 넘어가는 transition도 보이는걸 보니 커다란 이미지 슬라이더로 한것같기도하고..)/question/1 -> 첫 번째 질문 페이지 /question/2 -> 두 번째 질문 페이지 ... /question/10 -> 열 번째 질문 페이지
: react-router-dom에서 Route로 라우팅을 해주었는데
이 때 동시에 props를 전달하는 방법은 다음과 같다import React, {useState, useEffect} from 'react'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import LandingPage from './pages/LandingPage'; import ResultPage from './pages/ResultPage'; import QuestionPage from './pages/QuestionPage'; ... return ( <Router> <Switch> <Route exact path='/' render={(props)=>(<LandingPage onBirthHandler={onBirthHandler} props={props}/>)}></Route> { object.levelNum && <Route exact path='/result' render={(props)=>(<ResultPage props={props} onResetAns={onResetAns} object={object}/>)}></Route> } <Route exact path='/question/:idx' render={(props)=>(<QuestionPage onAnswerSubmit={onAnswerSubmit} onAnsHandler={onAnsHandler} /* props={props} */ question={question}/>)}></Route> <Route path='/*'>404 NOT FOUND</Route> </Switch> </Router> );
: Redirect할 때에 보통 history를 이용해서 하는데 경우가 나뉜다.
return ( <Router> <Switch> <Route exact path='/' render={(props)=>(<LandingPage onBirthHandler={onBirthHandler} props={props}/>)}></Route> { object.levelNum && <Route exact path='/result' render={(props)=>(<ResultPage props={props} onResetAns={onResetAns} object={object}/>)}></Route> } <Route exact path='/question/:idx' render={(props)=>(<QuestionPage onAnswerSubmit={onAnswerSubmit} onAnsHandler={onAnsHandler} /* props={props} */ question={question}/>)}></Route> <Route path='/*'>404 NOT FOUND</Route> </Switch> </Router> );
- 직접 라우팅 되는 컴포넌트 -> LandingPage / QuestionPage / ResultPage
- 직접 라우팅 되지 않는 컴포넌트 -> LandingPage 내부에서 사용되는 컴포넌트
1) 직접 Route를 통해 라우팅 된 컴포넌트
: 직접 Route를 통해 라우팅이 되면 기본적으로
props를 통해 history, match, location을 사용할 수 있으므로
props.history.push()로 redirect하면 된다2) 직접 Route되지 않은 컴포넌트
: 직접 라우팅 되지 않으면 props에 history가 없으므로 받아야 한다
이 때, 상위컴포넌트에게 props로 받거나 / withRouter를 사용하면 된다
(withRouter가 더 깔끔해 보임)😄 사용법은 이전 게시글 참조 😁
(https://velog.io/@neity16/React-Redux-Middleware-react-router-dom-react-thunk)
: 답안은 항상 4개로 주어지고 각 버튼이 누를 때 마다 해당 값을 넘겨주어야 한다.
이 때, 각 버튼마다 name을 지정하여 하나의 Callback함수로 처리할 수 있다.function QuestionPage({question, match, history, onAnsHandler, onAnswerSubmit}) { const inputIdx = Number(match.params.idx); const onClickHandler = (e)=>{ /* 이벤트 버블링 때문에 e.target.attributes.id.value 하면 가끔 undefinded가 뜬다! */ //onAnsHandler(Number(e.currentTarget.getAttribute('id'))); onAnsHandler(inputIdx, Number(e.currentTarget.attributes.id.value)); if(inputIdx !== 10){ history.push(`/question/${inputIdx+1}`); }else{ //onAnswerSubmit(); history.push('/result'); } } ... <ButtonBox> <Button id="1" onClick={onClickHandler} > <ButtonImg><i class="fas fa-check"></i></ButtonImg> <ButtonDesc>{question[inputIdx-1].bogi[0]}</ButtonDesc> </Button> <Button id="2" onClick={onClickHandler}> <ButtonImg><i class="fas fa-check"></i></ButtonImg> <ButtonDesc>{question[inputIdx-1].bogi[1]}</ButtonDesc> </Button> <Button id="3" onClick={onClickHandler}> <ButtonImg><i class="fas fa-check"></i></ButtonImg> <ButtonDesc>{question[inputIdx-1].bogi[2]}</ButtonDesc> </Button> <Button id="4" onClick={onClickHandler}> <ButtonImg><i class="fas fa-check"></i></ButtonImg> <ButtonDesc >{question[inputIdx-1].bogi[3]}</ButtonDesc> </Button> </ButtonBox>
- 평소 우리가
e.target.value
를 가져온 것은 input 태그의 경우 value속성이 있기 때문에 가능했던 것
--> div태그에서는e.target.value
를 사용할 수 없음- 따라서
e.target.attributes
으로 해당 dom에 접근할 수 있다.- 또 하나 주의해야 할 것은 현재 dom에서 가져오게 하기 위해
e.target.attributes
가 아니라e.currentTarget.attributes
로 접근해야 한다
(이렇게 가져오지 않으면 이벤트 버블링 때문에 가끔 undefined가 가져와진다 ㅠㅠ)
: 분명 setState로 State값을 바꿔주었으나 console.log()로 찍었을 때 바뀌지 않는 경우가 있다
그래서 찾아보니 setState는 비동기적으로 동작한다고 한다
: change와 ans는 값이 동일해야 하는데 그렇지 않음을 알 수 있다
- 문제는 사용자가 선택한 답안이 바로바로 저장되지 않아서 마지막 문제 클릭시 답안이 저장되지
않은 상태로 서버로 넘어가게 된다
- setState를 비동기적으로 제어하는 방법을 찾거나,
useEffect로 ans값이 변경된 다음 서버에 통신해서 값을 넘기면 문제가 해결된다.
- 일단은 useEffect로 해결하였다
/* 원래는 QuestionPage에서 바로 onAnswerSubmit()을 호출해야 하는데. State값 변경보다 호출이 먼저되서 문제가 발생함 따라서, useEffect로 호출하는 부분을 확실히 Ans값 변경 후로 조치함 */ useEffect(()=>{ if(ans[9] !== 0){ onAnswerSubmit(); // } },[ans]); ... const onAnswerSubmit = async() => { const object = { birthYear: birthYear, answers: ans } console.log(ans); const result = await postAnswerAPI(object); SetObject({ score: result.score, scoreRate: result.scoreRate, levelNum: result.levelNum }); }