React - SOPT 해커톤(IN-CYWORLD) (2)

김정욱·2020년 11월 26일
1

React

목록 보기
18/22

QuestionPage

: 모든 질문 페이지는 동등한 구조
( 싸이월드 이미지 / 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 -> 열 번째 질문 페이지

[ Route + props ]

: 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 정리 ]

: 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)

[ 답 보기 버튼 onClickHandler ]

: 답안은 항상 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는 사실 비동기적으로 동작한다? ]

: 분명 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
    });
  }

[ 최종 결과물 ]

✨ 2등 ✨

profile
Developer & PhotoGrapher

0개의 댓글