react,next 01. 회원가입 폼 만들기

|

react,next 05. 회원가입 폼 만들기

custom Hook과 useCallBack이 사용되었습니다.

signup.js

회원가입페이지에쓰이는 form과 input 코드 특성상 반복되는 코드가 많다.
reactForm라이브러리를 쓰기도하지만, 여기선 custom Hook으로 반복코드를 최소화시켰고 useCallback을 사용해 리소스를 최소화시켰다.

import React, { useState, useCallback } from "react";
import { Form, Input, Checkbox, Button } from "antd";

const signup = () => {
  const [passwordCheck, setPasswordCheck] = useState("");
  const [term, setTerm] = useState(false);
  const [passwordError, setPasswordError] = useState(false);
  const [termError, setTermError] = useState(false);

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

  const [id, onChangeId] = useInput("");
  const [nick, onChangeNick] = useInput("");
  const [password, onChangePassword] = useInput("");

  const onSubmit = useCallback(
    (e) => {
      e.preventDefault();
      if (password !== passwordCheck) {
        return setPasswordError(true);
      }
      if (!term) {
        return setTermError(true);
      }
    },
    [password, passwordCheck, term]
  );

  // 이 함수는 passwordCheck를 칠때마다 발생하는데, password랑 같은지 체크해줌
  const onChangePasswordCheck = useCallback(
    (e) => {
      setPasswordError(e.target.value !== password);
      setPasswordCheck(e.target.value);
    },
    [password]
  );

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

  return (
    <>
      <Form onSubmit={onSubmit} style=>
        <div>
          <label htmlFor="user-id">아이디</label>
          <br />
          <Input name="user-id" value={id} required onChange={onChangeId} />
        </div>
        <div>
          <label htmlFor="user-nick">닉네임</label>
          <br />
          <Input
            name="user-nick"
            value={nick}
            required
            onChange={onChangeNick}
          />
        </div>
        <div>
          <label htmlFor="user-password">비밀번호</label>
          <br />
          <Input
            name="user-password"
            type="password"
            value={password}
            required
            onChange={onChangePassword}
          />
        </div>
        <div>
          <label htmlFor="user-pass">비밀번호체크</label>
          <br />
          <Input
            name="user-pass-check"
            type="password"
            value={passwordCheck}
            required
            onChange={onChangePasswordCheck}
          />
          {passwordError && (
            <div style=>비밀번호가 일치하지 않습니다.</div>
          )}
        </div>
        <div>
          <Checkbox name="user-term" checked={term} onChange={onChangeTerm}>
            제로초 말을 잘 들을 것을 동의합니다.
          </Checkbox>
          {termError && (
            <div style=>약관에 동의하셔야 합니다.</div>
          )}
        </div>
        <div style=>
          {/* antd에선type="submit"하려면 htmlType을 써줘야함 */}
          <Button type="primary" htmlType="submit">
            가입하기
          </Button>
        </div>
      </Form>
    </>
  );
};

export default signup;

커스텀 훅

  • 커스텀 hook을 만들면 컴포넌트 로직을 재사용 가능한 함수로 뽑아낼 수 있다.
  const useInput = (initValue = null) => {
    const [value, setter] = useState(initValue);
    const handler = useCallback((e) => {
      setter(e.target.value);
    }, []);
    return [value, handler];
  };

커스텀 훅을 사용하기전의 코드

import React, { useState, useCallback } from "react";
import { Form, Input, Checkbox, Button } from "antd";

const signup = () => {
  const [passwordCheck, setPasswordCheck] = useState("");
  const [term, setTerm] = useState(false);
  const [passwordError, setPasswordError] = useState(false);
  const [termError, setTermError] = useState(false);

  const [id, setId] = useState("");
  const [nick, setNick] = useState("");
  const [password, setPassword] = useState("");

  const onSubmit = useCallback(
    (e) => {
      e.preventDefault();
      if (password !== passwordCheck) {
        return setPasswordError(true);
      }
      if (!term) {
        return setTermError(true);
      }
    },
    [password, passwordCheck, term]
  );

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

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

  const onChangePassword = (e) => {
    setPassword(e.target.value);
  };

  // 이 함수는 passwordCheck를 칠때마다 발생하는데, password랑 같은지 체크해줌
  const onChangePasswordCheck = useCallback(
    (e) => {
      setPasswordError(e.target.value !== password);
      setPasswordCheck(e.target.value);
    },
    [password]
  );

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

  return (
    <>
      <Head>
        <title>NodeBird</title>
        <link
          rel="stylesheet"
          href="https://cdnjs.cloudflare.com/ajax/libs/antd/3.23.1/antd.css"
        />
      </Head>
      <AppLayout>
        <Form onSubmit={onSubmit} style=>
          <div>
            <label htmlFor="user-id">아이디</label>
            <br />
            <Input name="user-id" value={id} required onChange={onChangeId} />
          </div>
          <div>
            <label htmlFor="user-nick">닉네임</label>
            <br />
            <Input
              name="user-nick"
              value={nick}
              required
              onChange={onChangeNick}
            />
          </div>
          <div>
            <label htmlFor="user-password">비밀번호</label>
            <br />
            <Input
              name="user-password"
              type="password"
              value={password}
              required
              onChange={onChangePassword}
            />
          </div>
          <div>
            <label htmlFor="user-pass">비밀번호체크</label>
            <br />
            <Input
              name="user-pass-check"
              type="password"
              value={passwordCheck}
              required
              onChange={onChangePasswordCheck}
            />
            {passwordError && (
              <div style=>비밀번호가 일치하지 않습니다.</div>
            )}
          </div>
          <div>
            <Checkbox name="user-term" checked={term} onChange={onChangeTerm}>
              제로초 말을 잘 들을 것을 동의합니다.
            </Checkbox>
            {termError && (
              <div style=>약관에 동의하셔야 합니다.</div>
            )}
          </div>
          <div style=>
            {/* antd에선type="submit"하려면 htmlType을 써줘야함 */}
            <Button type="primary" htmlType="submit">
              가입하기
            </Button>
          </div>
        </Form>
      </AppLayout>
    </>
  );
};

export default signup;

커스텀 훅을 사용한후의 코드

훨씬 간결해진 모습

import React, { useState, useCallback } from "react";
import { Form, Input, Checkbox, Button } from "antd";

const signup = () => {
  const [passwordCheck, setPasswordCheck] = useState("");
  const [term, setTerm] = useState(false);
  const [passwordError, setPasswordError] = useState(false);
  const [termError, setTermError] = useState(false);

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

  const [id, onChangeId] = useInput("");
  const [nick, onChangeNick] = useInput("");
  const [password, onChangePassword] = useInput("");

  const onSubmit = useCallback(
    (e) => {
      e.preventDefault();
      if (password !== passwordCheck) {
        return setPasswordError(true);
      }
      if (!term) {
        return setTermError(true);
      }
    },
    [password, passwordCheck, term]
  );

  // 이 함수는 passwordCheck를 칠때마다 발생하는데, password랑 같은지 체크해줌
  const onChangePasswordCheck = useCallback(
    (e) => {
      setPasswordError(e.target.value !== password);
      setPasswordCheck(e.target.value);
    },
    [password]
  );

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

  return (
    <>
      <Form onSubmit={onSubmit} style=>
        <div>
          <label htmlFor="user-id">아이디</label>
          <br />
          <Input name="user-id" value={id} required onChange={onChangeId} />
        </div>
        <div>
          <label htmlFor="user-nick">닉네임</label>
          <br />
          <Input
            name="user-nick"
            value={nick}
            required
            onChange={onChangeNick}
          />
        </div>
        <div>
          <label htmlFor="user-password">비밀번호</label>
          <br />
          <Input
            name="user-password"
            type="password"
            value={password}
            required
            onChange={onChangePassword}
          />
        </div>
        <div>
          <label htmlFor="user-pass">비밀번호체크</label>
          <br />
          <Input
            name="user-pass-check"
            type="password"
            value={passwordCheck}
            required
            onChange={onChangePasswordCheck}
          />
          {passwordError && (
            <div style=>비밀번호가 일치하지 않습니다.</div>
          )}
        </div>
        <div>
          <Checkbox name="user-term" checked={term} onChange={onChangeTerm}>
            제로초 말을 잘 들을 것을 동의합니다.
          </Checkbox>
          {termError && (
            <div style=>약관에 동의하셔야 합니다.</div>
          )}
        </div>
        <div style=>
          {/* antd에선type="submit"하려면 htmlType을 써줘야함 */}
          <Button type="primary" htmlType="submit">
            가입하기
          </Button>
        </div>
      </Form>
    </>
  );
};

export default signup;

useCallBack

  • useCallback은 특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용한다.

    • 객체는 렌더링되면 완전히 새로운 객체가되어버린다. 새로운 객체가만들어지면 리액트는 렌더링을 시킨다.
    • 함수도 객체다.

  • 함수 안에서 사용하는 상태 혹은 props 가 있다면 꼭, deps 배열안에 포함시켜야 된다.

코드 사용예시

  const onChangePasswordCheck = useCallback(
    (e) => {
      setPasswordError(e.target.value !== password);
      setPasswordCheck(e.target.value);
    },
    [password]
  );

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

react,next 01. 기본 페이지틀 생성 및 변경

|

react,next 04. 기본 페이지틀 생성 및 변경

  • pages>index.js ,AppLayout 변경

  • pages>profile.js, pages>signup.js 파일 생성

index.js

import React from "react";
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.23.1/antd.css"
        />
      </Head>
      <AppLayout>
        <div>Hello,Next!</div>
      </AppLayout>
    </>
  );
};

export default Home;

AppLayout.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>노드버드</a>
          </Link>
        </Menu.Item>
        <Menu.Item key="profile">
          <Link href="/profile">
            <a>프로필</a>
          </Link>
        </Menu.Item>
        <Menu.Item key="email">
          <Input.Search enterButton style= />
        </Menu.Item>
      </Menu>

      <Link href="/signup">
        <a>
          <Button>회원가입</Button>
        </a>
      </Link>

      {children}
    </div>
  );
};

export default AppLayout;

profile.js

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

const profile = () => {
  return (
    <>
      <Head>
        <title>NodeBird</title>
        <link
          rel="stylesheet"
          href="https://cdnjs.cloudflare.com/ajax/libs/antd/3.23.1/antd.css"
        />
      </Head>
      <AppLayout>
        <div>프로필</div>
      </AppLayout>
    </>
  );
};

export default profile;

signup.js

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

const signup = () => {
  return (
    <>
      <Head>
        <title>NodeBird</title>
        <link
          rel="stylesheet"
          href="https://cdnjs.cloudflare.com/ajax/libs/antd/3.23.1/antd.css"
        />
      </Head>
      <AppLayout>
        <div>회원가입</div>
      </AppLayout>
    </>
  );
};

export default signup;
  • Head코드가 중복적으로 들어간다.

  • 페이지가 한두개면 상관없지만 수십개,수백개로 불어날경우 유지보수가 힘들다.

  • 회원가입페이지에서 input에 값을 입력시 헤더도 같이 리렌더링된다.

  • 이 문제를 해결하기위해 이후 강좌에서 _app.js로 레이아웃을 분리한다.

_app.js란?

  • next안에 내장된 레이아웃파일이다.

  • pages폴더안에있는 모든 파일들이 _app.js파일을 부모컴포넌트로 인식한다.

  • _app.js 외에도 _document.js와 _error.js이 있다

    • _document.js: html,head,body를 담당(서버사이드 렌더링을 할때 커스텀마이징 해줌)
    • _error.js는 에러발생시 나올것 예정

_app.js사용

next에 내장된 레이아웃 파일인 _app.js를 커스텀마이징 해준것입니다.

  • pages>_app.js
import React from "react";
import Head from "next/head";
import AppLayout from "../components/AppLayout";

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

export default NodeBird;

바뀐 코드들

중복된 레이아웃코드들을 _app.js파일에 집중시켜 클린코드를 만들었다.

  • pages>index.js
import React from "react";

const Home = () => {
  return (
    <>
      <div>Hello,Next!</div>
    </>
  );
};

export default Home;

  • pages>profile.js
import React from "react";

const profile = () => {
  return (
    <>
      <div>프로필</div>
    </>
  );
};

export default profile;

  • pages>signup.js
import React from "react";
import AppLayout from "../components/AppLayout";
import Head from "next/head";

const signup = () => {
  return (
    <>
      <Head>
        <title>NodeBird</title>
        <link
          rel="stylesheet"
          href="https://cdnjs.cloudflare.com/ajax/libs/antd/3.23.1/antd.css"
        />
      </Head>
      <AppLayout>
        <div>회원가입</div>
      </AppLayout>
    </>
  );
};

export default signup;

react,next 01. ant design적용하기

|

react,next 03. ant design적용하기

ant designstyled-components를 이용하여 레이아웃 구성을 합니다.
ant design은 리액트 코드로 되어있고, 그리드와 반응형까지 지원해줍니다.

ant design install

npm i antd를 해버릴경우 나중에 회원가입폼 onSubmit에서 console.log할때 작동을 안한다.
꼭 버전을 맞춰줄것

npm i antd@3.23.1

ant design 활용하여Layout 구성하기

  • front 폴더에 component폴더생성후 안에 AppLayouts.js파일 생성

  • ant design에서 참고하여 소스코드를 가져온다.

  • AppLayouts.js

    여기서 사용된 key 는 Unique ID of the menu item라고 공식홈페이지에 명시되있는데, className이라고 생각하면 될것같다.

import React from "react";
import { Menu, Input } from "antd";

const AppLayout = ({ children }) => {
  return (
    <div>
      <Menu>
        <Menu.Item key="home">노드버드</Menu.Item>
        <Menu.Item key="profile">프로필</Menu.Item>
        <Menu.Item key="email">
          <Input.Search enterButton />
        </Menu.Item>
      </Menu>
      {children}
    </div>
  );
};

export default AppLayout;
  • front>pages>index.js
import React from "react";
import Link from "next/link";
import AppLayout from "../components/AppLayout";

const Home = () => {
  return (
    <>
      <AppLayout>
        <Link href="/about">
          <a>about</a>
        </Link>
        <div>Hello, Next!!</div>
      </AppLayout>
    </>
  );
};

export default Home;

결과

views
css적용이 안되어있다.

next에서 antDesign에 css적용하기

  • head에 css파일을 넣어주어야한다. 다음 코드를 삽입해주자
<Head>
  <title>NodeBird</title>
  <link
    rel="stylesheet"
    href="https://cdnjs.cloudflare.com/ajax/libs/antd/3.23.1/antd.css"
  />
</Head>
  • front>pages>index.js
import React from "react";
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.23.1/antd.css"
        />
      </Head>
      <AppLayout>
        <Link href="/about">
          <a>about</a>
        </Link>
        <div>Hello,Next!</div>
      </AppLayout>
    </>
  );
};

export default Home;

결과

views
css적용완료. 사진은 mode를 써서 검색창의 위치를 변화시켜준 모습임

ant grid 적용해보기

xs:모바일, sm: 작은 화면, md: 중간 화면,lg:큰 화면
화면 폭에 따라서 화면 레이아웃을 설정할수 있습니다.
gutter={}는 Col간의 간격을 말 합니다.

<Row gutter={8}>
  <Col xs={24} md={6}>
    {/* 유저정보 */}
  </Col>
  <Col xs={24} md={12}>
    {children}
  </Col>
  <Col xs={24} md={6}>
    세번째
  </Col>
</Row>

react,next 01. scripts 설정 및 next 라우팅 시스템

|

react,next 02. scripts 설정 및 next 라우팅 시스템

package.json 설정

front폴더의 package.json 변경

  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  • npm run dev 하면 프론트서버 실행됨

라우팅

next는 pages폴더가 라우터역할을 한다.
이 pages에서 서버사이드렌더링,코드스플리팅을 알아서 해준다.

views
  • pages>index.js는 ‘/’ 메인경로라고 생각하면 된다.

  • localhost:3000/about 경로로 가면 pages폴더의 about.js파일이 실행된다.

  • localhost:3000/user/create 경로로 가면 pages폴더의 user폴더안의 user.js파일이 실행된다.

import React from "react";
import Link from "next/link";

const Home = () => {
  return (
    <>
      <Link href="/about">
        <a>about</a>
      </Link>
      <div>Hello, Next!!</div>
    </>
  );
};

export default Home;

react,next 01. next와 eslint 설치하기

|

react,next 01. next와 eslint 설치하기

React로 NodeBird SNS만들기

npm init

npm init은 package.json을 편하게 만들기위한 명령어다.

mkdir react-nodebird
cd react-nodebird
mkdir front
mkdir back
cd front
npm init

install

프로젝트에 필요한 모듈들을 설치해줍니다.
next와 styled-components는 꼭 버전을 명시해줘야합니다.(강의에서의 버전과 현재 최신버전이 달라서..)

 npm i react react-dom next@8

 npm i -D nodemon webpack

 npm i -D eslint

 npm i styled-components@4

 npm i redux react-redux

 npm i next-redux-wrapper@5

eslint

.eslintrc파일을 front 폴더에 만들어준다.

{
  "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "env": {
    "browser": true,
    "node": true
  },
  "extends": ["eslint:recommended", "plugin:react/recommended"],
  "plugins": ["import", "react-hooks"]
}

  • plugin을 install 해준다.
npm i -D eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks