React, DRF API를 이용한 velog 따라 만들어보기 1장

大 炫 ·2020년 9월 9일
2

Velog Clone

목록 보기
1/8
post-thumbnail

구글링으로 참조를 도와주신 모든 개발자분들에게 먼저 감사합니다.

먼저 !

아직 한참 모자라는 내 실력에 Redux, 비동기식방식을 제어하는 async, await 등 일정기간의 공부기간이 필요하다고 판단되어 지금까지 해놓은 것들을 정리하는 과정을 진행하려고한다.
ReactDjango REST framework API를 이용했고 DB는 Django의 내장 DB인 sqlite3을 사용했으니 별도의 설치과정이 필요없겠다.
포스팅을 하는 내 수준은 국비지원의 산업전문학교 4개월차(과정중에 React와 Python은 전혀 다루지 않았다)에 리액트를 독학으로 공부하기 시작한지는 45~50일정도 된 듯 하다.

참고로 내코드는 전혀 리팩토링이 되지않았다

하지만 리팩토링되지 않은 소스들이 오히려 나는 초보자들이 더 보기 쉬울거라고 생각한다.
내가 짠 코드들이 고수가 아닌 포스팅을 찾아보는 React코린이들이 짤만한 소스라고 생각하기때문이지!
그렇다고 리팩토링을 하지않을것은 아니고 지금까지 한것들을 모두 올리고 리팩토링하고 리팩토링한 소스까지 다 올리게되면 일정기간의 수련을 가질 생각이다.

왜 React ? or DRF API ?

아주 기초적인 부분만을 제외하고 JavaScript를 거의 다룰줄도 모르는 내가 왜 React를 선택했으며 DB또한 mySQL찔끔 만져본놈이 벌써 API를 만져봤다. 확실히 누군가가 열광하는 이유가 있다고 생각한다.

1. "직관적"

개인적으로 나에겐 html과 JavaScript를 합쳐놓은듯한 React는 굉장히 편안함을 줬다.
사실 html도 잘 못짜지만 짜놓은 코드의 동적효과를 위해 그 코드들을 데려다가 작성하는 JavaScript는 솔직히 말하자면 정말 어렵게 느껴졌다. this를 남발하는 과정에서도 this가 가르키는 객체가 순간순간 무엇인지 가늠하기 어려웠으니 진입장벽이 조금 높게 느껴졌다.
와중에 React를 접하게되었는데 Function 또는 Component 안에서 함수를 작성하고 onClick, onChange 등 동적효과가 들어가야하는 곳에 함수를 직접 작성하거나 넣기만하면 동작하는 모습은 동적효과를 분석하고 오류를 잡아내는 과정이 나에게 훨씬 더 직관적으로 다가왔다.

2. "인기"

사실 이제 막 공부하기 시작한 내가 하면 좀 웃긴 말 일수도 있겠지만 프로그래밍만큼 변화에 민감한 업계이 몇이나 더 있을까 싶다. 민감한 만큼 누군가가 열광하는 이유는 분명있다고 생각한다. React와 DRF API를 선택한 이유도 비슷한 이유이다. 어차피 혼자 작업했지만 front와 back을 분명하게 나눌 수 있어 열광하는 API.. 난 사람들의 "인기"를 선택했다.
참고사이트 이곳은 내가 REST API 선택의 결정적인 영향을 준 곳인데 읽어보면 좋을 듯 하다!

마지막, 시작하기 앞서

React나 JS의 기초적인 문법은 알고있어야 어느정도 이해할 수 있을 것이고 React와 Django를 연결하는 과정은 정말정말정말 포스팅을 깔끔하게 해주신 여기! 에서 읽고 따라하시면 좋겠다.

시작 !

따라해보시거나 이미 연동하시는 방법을 아셔서 진행해도 된다면

front => header, navi, loginModal

우선 작업물을 담을 폴더안에서 frontend라는 React를 생성하겠습니다.

create-react-app frontend

그리고 개인적으로 logo나 쓸데없는 부분들을 싫어해서 지운 뒤 velog에서 header, navi, loginModal을 만들꺼기때문에
src 폴더 안에 component, css 라는 폴더를 생성하고 각각에 맞는 js파일과 css파일을 만들어줄게요.
만들어 줬다면 frontend의 구성은 다음과 같습니다!

그리고 코드의 내용을 설명하기 전 이번 포스팅이 끝난다면 아래와 같은 front를 완성시킬 수 있습니다!

home으로 이동하게해주는 velog의 로고와 로그인 버튼이 담긴 Header영역
게시물을 볼수있는 최신과 Q&A를 표시해주는 Navi
그리고 로그인이 되지않은 상태에서 로그인버튼을 누른다면 아래와 같은 loginModal로 이동하게됩니다.

회원가입과 로그인을 모두 간단하게 username과 password만 받아서 하기때문에 회원가입 폼 또한 loginModal에 함께 작성해 주었는데 회원가입버튼을 누르면 "로그인" -> "회원가입" 으로 모든 문자열이 변경될 수 있도록 state기본값을 "로그인"이라고 설정했고, 회원가입을 누르면 아래와 같습니다 !

아울러 navi에서 최신과 Q&A를 각각 클릭시 underline이 이동하는 state, 기본값을 left:0%, Q&A 클릭시 left가 50%가되어 underline이 이동하게 되도록 했습니다

frontend/src/App.js

import React from 'react';
import './App.css';
import Navi from './components/Navi';
import Header from './components/Header';
import LoginModal from './components/LoginModal';
import { Route } from 'react-router-dom';

function App() {

  return (
    <>
      <div className="App">
        <div className="auto-margin">
        
          <Route exact path="/">
            <Header modal={modal}/>
          </Route>

          <Route exact path="/">
            <Navi/>
          </Route>

          <Route exact path="/login">
            <LoginModal setModal={setModal}/>
          </Route>
          
      </div>
    </div>
    </>
  );
}

export default App;

우선 전체 컨테이너와 auto-margin을 설정해주기위해 div태그 두개로 감싸주었구요.
그 안에는 각각의 component들이 있는데 Heder와 Navi의 경우 Link를 통해 특정페이지로 이동하는 경우 따라와야하는 경우가 종종 있기때문에 home과 같은 exact path="/" 로 설정해줬고 LoginModal같은 경우는 exact path="/login" 로 설정해주어 /login이 포함된 주소가 아니라면 표시되는 Component들을 제외시킬 수 있도록 했습니다.

frontend/src/component/Header.js

import React from 'react';
import {Link} from 'react-router-dom';
import '../css/Header.css';

function Header(){

  return(
    <>
      <div className="header">
        <div className="header-nav">
          <div className="header-nav-links">
            <Link className="header-logo" to="/">Velog</Link>
              <Link to="/login"><button className="header-btn">로그인</button></Link>
          </div>
        </div>
      </div>
    </>
  )
}

export default Header;

별거 없습니다 . Link태그로 LoginModal로 이동할 수 있도록 경로를 to="/login" 이라고 맞춰놓은 것 밖에없습니다 !

frontend/src/component/frontend/src/component/Navi.js

import React, { useState } from 'react';
import {Link} from 'react-router-dom';
import '../css/Navi.css';

function Navi(){
    let [underline, setUnderline] = useState({left:"0%"})

    return(
        <>
        <div className="navi-container">
            <div className="navi-box">
                <Link className="navi-" to="/" onClick={()=>{
                    setUnderline({left:"0%"})
                }}>
                    <span role = "img" aria-label = "하트">🤞최신</span>
                </Link>
                <Link className="navi-" to="/" onClick={()=>{
                    setUnderline({left:"50%"})
                }}>
                    <span role = "img" aria-label = "질문">🤷‍♂️Q & A</span>
                </Link>
                <div className="navi-underline" style={underline}></div>
            </div>
        </div>
        </>
    )
}
export default Navi;

Navi같은 경우는 처음으로 State값을 먼저 줬는데 이는 className="navi-underline" 해당의 div style이 onClick이 실행될 때 마다 변경되야 하기때문에 underline이라는 state의 초기값을 left:"0%", 클릭시 변경값을 left:"50%" 로 주었습니다.

frontend/src/component/LoginModal.js

import React, { useState } from 'react';
import { useHistory } from 'react-router'
import '../css/LoginModal.css';

function LoginModal(){
  let [joinLoign,setJoinLogin] = useState('로그인')
  const history = useHistory()

  return(
    <>
      <div className="login-container">
        <div className="login-box">
          <div className="exit">
              <button onClick={()=>{ history.goBack() }}>
                <svg stroke="currentColor" fill="currentColor" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path></svg>
              </button>
          </div>
          <span>{joinLoign}</span>
          <form>
            {
              joinLoign === '로그인'
              ?(
                <>
                <input type="text" placeholder="아이디를 입력하세요"/>
                <input type="password" placeholder="비밀번호를 입력하세요"/>
                <button className="JoinLoign-button">{joinLoign}</button>
                </>
              )
              :(
                <>
                <input type="text" placeholder="아이디를 입력하세요"/>
                <input type="password" placeholder="비밀번호를 입력하세요"/>
                <button className="JoinLoign-button">{joinLoign}</button>
                </>
              )
            }
          </form>
          <section className="social-box">
            <h4>소셜 계정으로 {joinLoign}</h4>
            <div className="googlebox">
                  <button>
                    <svg width="20" height="20" fill="none" viewBox="0 0 20 20" className="google-login">
                      <path fill="#4285F4" d="M19.99 10.187c0-.82-.069-1.417-.216-2.037H10.2v3.698h5.62c-.113.92-.725 2.303-2.084 3.233l-.02.124 3.028 2.292.21.02c1.926-1.738 3.037-4.296 3.037-7.33z"></path>
                      <path fill="#34A853" d="M10.2 19.931c2.753 0 5.064-.886 6.753-2.414l-3.218-2.436c-.862.587-2.017.997-3.536.997a6.126 6.126 0 0 1-5.801-4.141l-.12.01-3.148 2.38-.041.112c1.677 3.256 5.122 5.492 9.11 5.492z"></path>
                      <path fill="#FBBC05" d="M4.398 11.937a6.008 6.008 0 0 1-.34-1.971c0-.687.125-1.351.329-1.971l-.006-.132-3.188-2.42-.104.05A9.79 9.79 0 0 0 .001 9.965a9.79 9.79 0 0 0 1.088 4.473l3.309-2.502z"></path>
                      <path fill="#EB4335" d="M10.2 3.853c1.914 0 3.206.809 3.943 1.484l2.878-2.746C15.253.985 12.953 0 10.199 0 6.211 0 2.766 2.237 1.09 5.492l3.297 2.503A6.152 6.152 0 0 1 10.2 3.853z"></path>
                    </svg>
                  </button>
            </div>
          </section>
          <div className="login-foot">
            {
              joinLoign === '회원가입'
              ?
              (
                <>
                <span>이미 회원이신가요  ?</span>
                <div className="foot-link" onClick={()=>{
                setJoinLogin('로그인')
                }}>로그인</div>
                </>
              )
              :
              (
                <>
                <span>아직 회원이 아니신가요 ?</span>
                <div className="foot-link" onClick={()=>{
                setJoinLogin('회원가입')
                }}>회원가입</div>
                </>
              )
            }
          </div>
        </div>
      </div>
    </>
  )
}


export default LoginModal;

LoginModal의 경우는 로그인말고도 회원가입 또한 포함하고 있습니다. joinLoign이라는 state값을 "로그인"이라는 문자열을 담고 setJoinLogin이라는 setState를 통해 "회원가입" 이라는 문자열로 변경시키는 효과로 로그인과 회원가입을 같은 코드에 담아 냈구요.
하지만 회원가입과 로그인은 엄연히 다른곳으로 다른 데이터를 보내줘야하기 때문에 input태그는 완전히 동일한 구조지만 별개의 영역으로 구분해놨습니다.

import { useHistory } from 'react-router'
const history = useHistory()
<button onClick={()=>{ history.goBack() }}>

해당코드는 react-router의 useHistory로서 history라는 객체를 생성해서 exit를 표현한 button클릭시 history.goBack() 뒤로가기(to="/login"에서 to="/")와 같은 효과를 낼 수 있었습니다.

1장 마치며

저는 이번 포스팅도 copy로 진행했지만 copy에 필요한 기능에대해서 구글링의 도움을 받았지 어떤 강의를 보며 클론프로젝트를 진행한 것이 아니었고 이런 copy는 component구성이나 이벤트핸들링을 직접 만들어보며 이해도를 증가시켰고 구글링에 필요한 키워드를 점점 넓은 의미에서 좁은의미로 좁혀가면서 내가 필요한 기능을 정확하고 빠르게 찾아내는 능력을 핵심이라고 생각하며 진행했습니다.

다음시간에는

로그인과 회원가입의 기능을 backend와 연동해서 구현하는 시간을 가져보겠습니다.

profile
대현

4개의 댓글

comment-user-thumbnail
2021년 1월 20일

src\App.js
Line 16:28: 'modal' is not defined no-undef
Line 24:35: 'setModal' is not defined no-undef
Search for the keywords to learn more about each error.
실행을 시키면 에러가 발생합니다....

1개의 답글
comment-user-thumbnail
2021년 3월 28일

src/App.js
Line 17:28: 'modal' is not defined no-undef
Line 25:35: 'setModal' is not defined no-undef

Search for the keywords to learn more about each error.

저도 유사한 에러가 발생하는데, 혹시 이유가 뭘까요? ㅜ

1개의 답글