As i wish

[React SNS] React SNS 만들기 - 1 본문

React JS

[React SNS] React SNS 만들기 - 1

어면태 2019. 7. 29. 16:20

안녕하세요. 엄티입니다.

오늘은 React 를 사용하여 간단한 SNS 를 만들어 볼껀데요.

 

제 포스팅은 '제로초' 님의 강의를 바탕으로 복습용으로 작성됩니다.

자세한 설명을 원하면 밑에 강의를 참조해 주세요.

'제로초' 님의 React NodeBird SNS

 

React로 NodeBird SNS 만들기 - 인프런

리액트&넥스트&리덕스&리덕스사가&익스프레스 스택으로 트위터와 유사한 SNS 서비스를 만들어봅니다. 끝으로 검색엔진 최적화 후 AWS에 배포합니다. 중급 웹 개발 프레임워크 및 라이브러리 서비스 개발 Front End Back End Javascript React 온라인 강의

www.inflearn.com

무튼 기본적으로 Front/Back 을 구성하고요. 

Front 는 React, Redux, Next 등을 사용할 예정 입니다.

 

먼저 기본적으로 front/back DIR을 만들어 줍니다. 그리고 front부터 구성 할 것인데요.

 

front/package.json

{
  "name": "react-nodebird-front",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "author": "TeiEom",
  "license": "ISC",
  "dependencies": {
    "antd": "^3.20.5",
    "next": "^9.0.2",
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  },
  "devDependencies": {
    "eslint": "^6.1.0",
    "eslint-plugin-import": "^2.18.2",
    "eslint-plugin-react": "^7.14.3",
    "eslint-plugin-react-hooks": "^1.6.1",
    "nodemon": "^1.19.1",
    "webpack": "^4.36.1"
  }
}

 

프론트의 package.json은 다음과 같이 구성 합니다. 기본적인 react, react-dom 을 깔아주시고 사용하기로한 next를 깔아줍니다.

$ npm install // 설치
$ npm run dev // 실행

순으로 하면 아마 localhost:3000 에서 확인해 보실 수 있습니다. (상황에 따라 포트가 바뀔 수도 있습니다.)

next 는 SSR 을 할 수 있게 해주는 모듈인데 저도 이번에 처음 사용해 봤습니다. 

 

ssr 이란?

 

서버 사이드 렌더링(SSR), 클라이언트 사이드 렌더링(CSR)

SSR 이란? Server Side Rendering 의 약어로써 단어 그대로 서버에서 렌더링을 작업합니다. 기존에 존재하던 방식으로 사용자가 웹페이지에 접근할 때, 서버에 페이지에 대한 요청을 하며 서버에서는 html, view..

brownbears.tistory.com

 

그 외의 next의 장점이 몇가지 있는데요. 다음과 같습니다.

1. 기존 웹팩에서 사용했던 hot-loader 같은것들도 한번에 적용됩니다.

2. react-router 를 사용하지 않아도 기본적인 router 시스템이 적용되어서 아주 편했습니다.

3. 주소체계를 만들어 주어서 파일 구조 안에 "pages" 란 폴더 를 만들고 그 안에 파일을 만들어 주면 자동으로 주소 체계가 잡히더라고요. 예를 들어 pages/about.js 또는 pages/user/create.js 를 각각 만들어 주면 실제 주소에서도 /about -> pages/about.js 가 실행 되고, /user/create -> pages/user/create.js 가 실행되는것을 보실 수 있습니다.

4. React code 상단에 import React from 'react'를 하지 않아도 알아서 처리해주는 아주 똑똑한 친구더군요.

5. 그 외에도 SSR 뿐만 아니라 code spolit 까지 도와주어서 SSR 를 적용하기에 아주 좋은 모듈이죠.

폴더 구조

위의 폴더 구조 처럼 pages 안에서 각각 필요한 화면을 만들어주고 자연스럽게 주소 체계로 사용하면 됩니다.

 

여기 까지 간단한 Next JS 에 대하여 알아봤고요 코드로 들어가보겠습니다.

 

완성된 화면

일단 완성된 화면은 다음과 같은데요.

헤더에 NodeBird, Profile, 검색창 이 있고 SignUp 버튼이 있습니다.

그 밑에 이제 content  (Hello, Next!) 가 보이겠죠?

 

일단 페이지는 총 3페이지이니 각각 index.js, profile.js, signup.js 로 구성했습니다.

 

pages/index.js

import React from 'react'; // eslint 에서 react 쓰면 import 하라고 명시됨
import Link from 'next/link';
import Head from 'next/head';
import AppLayout from '../components/AppLayout';

const Home = () => {
  return (
    <>
      <Head>
        <title>NodeBird</title>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/antd/3.20.5/antd.css"/>
      </Head>
      <AppLayout>
        <div>
          Hello, Next!
        </div>
      </AppLayout>
    </>
  );
};

export default Home;

 

pages/profile.js

import React from 'react';
import Head from 'next/head';
import AppLayout from '../components/AppLayout';


const Profile = () => {
  return (
    <>
      <Head>
        <title>NodeBird</title>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/antd/3.20.5/antd.css"/>
      </Head>
      <AppLayout>
        <div>
          Profile!
          </div>
      </AppLayout>
    </>
  );
};

export default Profile;

 

pages/signup.js

import React, { useState } from 'react';
import Head from 'next/head';
import AppLayout from '../components/AppLayout';
import { Form, Input, Checkbox, Button } from 'antd';

const Signup = () => {
  // const [id, setId] = useState(''); // Using custom hook in bottom
  // const [nick, setNick] = useState(''); // Using custom hook in bottom
  const [pass, setPass] = useState('');
  const [passChk, setPassChk] = useState('');
  const [term, setTerm] = useState('');

  const [passError, setPassError] = useState(false);
  const [termError, setTermError] = useState(false);

  const onSubmit = (e) => {
    e.preventDefault();

    if (pass !== passChk) {
      return setPassError(true);
    }

    if (!term) {
      return setTermError(true);
    }

    console.log({
      id, nick, pass, passChk, term
    })
  };

  // const onChangeId = (e) => { // Using custom hook in bottom
  //   setId(e.target.value);
  // };

  // const onChangedNick = (e) => {
  //   setNick(e.target.value);
  // };

  const onChangePass = (e) => {
    setPass(e.target.value);
  };

  const onChangePassChk = (e) => {
    setPassError(e.target.value !== pass);
    setPassChk(e.target.value);
  };

  const onChangeTerm = (e) => {
    setTermError(false);
    setTerm(e.target.checked);
  };

  // Custom hook
  const useInput = (initValue = null) => {
    const [value, setter] = useState(initValue);
    const handler = (e) => {
      setter(e.target.value);
    };

    return [value, handler];
  }

  const [id, onChangeId] = useInput('');
  const [nick, onChangedNick] = useInput('');

  return (
    <>
      <Head>
        <title>NodeBird</title>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/antd/3.20.5/antd.css"/>
      </Head>
      <AppLayout>
        <Form onSubmit={onSubmit} style={{ padding: 10 }}>
          <div>
            <label htmlFor="user-id">ID</label>
            <br/>
            <Input name="user-id" required value={id} onChange={onChangeId}/>
          </div>
          <div>
            <label htmlFor="user-nick">Nickname</label>
            <br/>
            <Input name="user-nick" required value={nick} onChange={onChangedNick}/>
          </div>
          <div>
            <label htmlFor="user-pass">Password</label>
            <br/>
            <Input name="user-pass" type="password" required value={pass} onChange={onChangePass}/>
          </div>
          <div>
            <label htmlFor="user-pass-chk">Password Check</label>
            <br/>
            <Input name="user-pass-chk" type="password" required value={passChk} onChange={onChangePassChk}/>
            {passError && <div style={{ color: 'red' }}>Please check password</div>}
          </div>
          <div>
            <Checkbox name="user-term" value={term} onChange={onChangeTerm}>
              Are you agree this term?
            </Checkbox>
            {termError && <div style={{ color: 'red' }}>You must agrre term</div>}
          </div>
          <div>
            <Button type="primary" htmlType="submit">Register</Button>
          </div>
        </Form>
      </AppLayout>
    </>
  );
};

export default Signup;

 

특히 pages/signup.js 에서는 custumHook을 사용하였는데 참고 부탁드립니다. 기본적인것이긴 한데 Input tag는 항상 onChange 와 value 가 한 짝을 이루어서 value state에 event listener를 통하여 상태가 변할 수 있도록 도와주는 것이 좋습니다.

 

커스텀 훅은 사용자가 훅을 만드는 것으로 코드를 조금 더 짧게 만들어주고 반복작업을 줄여주요.

 

커스텀 훅 예제

const [id, setId] = useState('');
const [nick, setNick] = useState('');

const onChangeId = (e) => {
  setId(e.target.value);
}

const onChangedNick = (e) => {
  setNick(e.target.value);
}
// 이렇게 사용해야할 훅을 커스텀 훅으로 사용하게 되면 아주 간단해 집니다.

// 먼저 커스텀 훅을 만듭니다.
const useInput = (initValue = null) => {
  const [value, setter] = useState(initValue);
  const handler = (e) => {
    setter(e.target.value);
  };

  return [value, handler];
}

// 그 다음 사용합니다.
// 아주 간단해 졌죠??
const [id, onChangeId] = useInput('');
const [nick, onChangedNick] = useInput('')

 

또한 각각의 코드의 <Head></Head>  부분이 있는데 이는 실제 html <head></head> 부분에 속하는 것이고 추후에 정리 하도록 하겠습니다.

또한 각각의 내용들을 <AppLayout></AppLayout> 으로 감싸주었는데 이는 Component 를 만들어서 자식들을 받아서 components/AppLayout.js 에서 띄워주도록 하였습니다.

 

components/Applyaout.js

import React from 'react';
import Link from 'next/link';
import { Menu, Input, Button } from 'antd';

const AppLayout = ({ children }) => {
  return (
    <div>
      <Menu mode="horizontal">
        <Menu.Item key="home">
          <Link href="/">
            <a>NodeBird</a>
          </Link>
        </Menu.Item>
        <Menu.Item key="profile">
          <Link href="/profile">
            <a>Profile</a>
          </Link>
        </Menu.Item>
        <Menu.Item key="mail">
          <Input.Search enterButton style={{ verticalAlign: 'middle' }}/>
        </Menu.Item>
      </Menu>
      <Link href="/signup">
        <Button>SignUp</Button>
      </Link>
      {children}
    </div>
  )
};

export default AppLayout;

 

위 처럼 components/AppLayout.js 에서 children을 받고 헤더를 구성 하다음 밑에 {children} 을 통하여 content 를 띄워주도록 합니다. 여기서 children은 각각 pages/index.js, profile.js, signup.js 에 <AppLayout></AppLayout> 사이에 있는것 들이 되겠죠?

components/AppLayout.js 에서 'next/link'에 Link 를 사용하여 각각의 페이지로 이동 할 수 있게 구성한것도 보실 수 있습니다.

 

자세한 설명은 각각 코드의 주석으로 처리되어있습니다.

 

마지막으로 antd 를 쓰는것을 볼 수 있는데 이는 bootstrap 과 거의 똑같다고 보시면 됩니다.

bootstrap은 개발자로 하여금 간단한 스타일링을 해주는 뭐 그런건데 antd 도 비슷하더군요.

디자이너가 있으면 달라지겠지만 없는 상황에서는 간편하게 디자인하는것도 나쁘지 않다고 생각합니다.

 

ant.design

 

Ant Design - A UI Design Language

 

ant.design

 

이상 회원가입 페이지 만 동작하는 1강 포스팅을 마치겠습니다.

궁금 하신 점은 댓글로 달아주시고 전체 폴더 구조를 보여주고 포스팅을 줄이겠습니다.

 

 

코드 확인

 

eomttt/react-sns

React sns test by react-nodebird(zeroCho). Contribute to eomttt/react-sns development by creating an account on GitHub.

github.com

 

Comments