버전이 다르기 때문에 소스가 강좌와 다를 수 있다.
버전
next: 13.0.4
antd: 5.0.1
1. Next.js 실행해보기
강의 :
next 설치
$ npm install next@9
$ npm install react react-dom
$ npm i prop-types
2. page와 레이아웃
강의
:

AppLayout.js
import React from 'react';
import PropTypes from 'prop-types';
const AppLayout = ({ children }) => {
return (
); }; AppLayout.prototype = { children: PropTypes.node.isRequired } export default AppLayout;
index.js
import AppLayout from './../components/AppLayout';
const Index = () => {
return (
Hello.Next
);
};
export default Index;
3. Link와 eslint
$ npm i eslint -D $ npm i eslint-plugin-react -D $ npm i eslint-plugin-import -D $ npm i eslint-plugin-react-hooks -D
"devDependencies": {
"eslint": "^8.28.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-react": "^7.31.11",
"eslint-plugin-react-hooks": "^4.6.0"
}
.eslintrc
{
"parserOptions": {
"ecmaVersion": 2022,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"env":{
"browser":true,
"node":true,
"es6":true
},
"extends":[
"eslint:recommended",
"plugin:react/recommended"
],
"plugins": [
"import",
"react-hooks"
],
"rules": {
}
}
4. Q&A
antd 사용해 SNS 화면 만들기
5. antd와 styled-components
강의 :
$ npm i antd styled-components @ant-design/icons
AppLayout.js
import React from 'react';
import PropTypes from 'prop-types';
import Link from 'next/link'
import { Menu } from 'antd';
const AppLayout = ({ children }) => {
return (
노드버드 프로필 회원가입
{children}); }; AppLayout.prototype = { children: PropTypes.node.isRequired } export default AppLayout;
6. _app.js와 Head
강의 :
pages 디렉토리에 _app.js 파일을 생성하면 넥트스에서 자동적으로 인식한다.
이 파일은 전체 공통적으로 적용할 라이브러리 코드들을 작성하면 된다.
_app.js
import React from 'react';
import PropTypes from 'prop-types';
import 'antd/dist/antd';
import Head from 'next/head';
const NodeBird = ({ Component }) => {
return (
<>
NodeBird ); }; NodeBird.propTypes = { Component: PropTypes.elementType.isRequired, } export default NodeBird;
7. 반응형 그리드 사용하기.
component/AppLayout.js
import React from 'react';
import PropTypes from 'prop-types';
import Link from 'next/link'
import { Menu, Input, Row, Col } from 'antd';
const AppLayout = ({ children }) => {
return (
노드버드 프로필 회원가입
왼쪽 메뉴 {children} Made by macaronics); }; AppLayout.prototype = { children: PropTypes.node.isRequired } export default AppLayout;
8. 로그인 폼 만들기
AppLayout.js
import React, { useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import Link from 'next/link'
import { Menu, Input, Row, Col, MenuProps } from 'antd';
import { useNavigate } from 'react-router-dom';
import UserProfile from './UserProfile';
import LoginForm from './LoginForm';
const items = [
{ label: 노드버드, key: 'item-1' }, { label:
프로필, key: 'item-2' }, { label: , key: 'item-3' }, { label:회원가입, key: 'item-4' }, ]; const AppLayout = ({ children }) => { const [isLoggedIn, setIsLoggedIn] = useState(false); const [current, setCurrent] = useState('item-1'); const onClick = useCallback((e) => { console.log('click ', e); setCurrent(e.key); }, []); return (
{isLoggedIn ? : } {children} Made by macaronics
); }; AppLayout.prototype = { children: PropTypes.node.isRequired } export default AppLayout;
LoginForm.js
import React, { useCallback, useState } from 'react';
import { Form, Input, Button } from 'antd';
import Link from 'next/link'
const LoginForm = () => {
const [id, setId] = useState('');
const [password, setPassword] = useState('');
const onChangeId = useCallback((e) => {
setId(e.target.value);
}, []);
const onChangePassword = useCallback((e) => {
setPassword(e.target.value);
}, []);
return (
회원가입); }; export default LoginForm;
9. 리렌더링 이해하기
AppLayout.js
import styled from 'styled-components';
const SearchInput = styled(Input.Search)`
vertical-align: 'middle' ;
`;
const items = [
{ label: 노드버드, key: 'item-1' }, { label:
프로필, key: 'item-2' }, { label: , key: 'item-3' }, { label:회원가입, key: 'item-4' }, ]; ~
Warning: Prop `className` did not match. when using styled components with semantic-ui-react
오류 메시지가 나오면 next.config.js 파일에 styledComponents: true 추가
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
styledComponents: true
}
module.exports = nextConfig
10. 리렌더링 이해하기
AppLayout.js
<Col xs={24} md={6} >
{isLoggedIn ? <UserProfile setIsLoggedIn={setIsLoggedIn} /> : <LoginForm setIsLoggedIn={setIsLoggedIn} />}
</Col>
LoginForm.js
import React, { useCallback, useState } from 'react';
import { Form, Input, Button } from 'antd';
import styled from 'styled-components';
import Link from 'next/link';
const ButtonWrapper = styled.div`
margin-top:10px
`;
const FormWrapper = styled(Form)`
margin-top: 100px;
padding:10px;
`;
const LoginForm = ({ setIsLoggedIn }) => {
~
const onSubmitForm = useCallback(() => {
console.log(id, password);
setIsLoggedIn(true);
}, [id, password])
return (
<FormWrapper onFinish={onSubmitForm}>
~
UserProfile.js
import React, { useCallback } from 'react';
import { Card, Avatar, Button } from 'antd';
const UserProfile = ({ setIsLoggedIn }) => {
const onLogOut = useCallback(() => {
setIsLoggedIn(false);
}, []);
return (
<Card
actions={[
<div key="twit">짹짹<br />0</div>,
<div key="followings">팔로잉<br />0</div>,
<div key="follower">팔로워<br />0</div>,
]}
>
<Card.Meta
avatar={<Avatar>ZC</Avatar>}
title="Macaronics"
/>
<Button onClick={onLogOut}>로그아웃</Button>
</Card>
);
};
export default UserProfile;
11. 프로필 페이지 만들기
pages/profile.js
import React from 'react';
import AppLayout from './../components/AppLayout';
import Head from 'next/head';
import FollowList from './../components/FollowList';
import NicknameEditForm from './../components/NicknameEditForm';
const Profile = () => {
const followingList = [{ nickname: 'aaaa' }, { nickname: 'bbbb' }, { nickname: 'ccc' }];
const followerList = [{ nickname: 'ddd' }, { nickname: 'eee' }, { nickname: 'fff' }];
return (
<>
<Head>
<title>프로필 | NodeBird</title>
</Head>
<AppLayout>
<NicknameEditForm />
<div style={{ marginBottom: 20 }}></div>
<FollowList header="팔로잉 목록" data={followingList} />
<FollowList header="팔로워 목록" data={followerList} />
</AppLayout>
</>
);
};
export default Profile;
components/FollowList.js
import React from 'react';
import PropTypes from 'prop-types'
import { Button, Card, List } from 'antd';
import { StopOutlined } from '@ant-design/icons'
const FollowList = ({ header, data }) => {
return (
<List
style={{ marginBottom: 20 }}
grid={{ gutter: 4, xs: 2, md: 3 }}
size="small"
header={<div>{header}</div>}
loadMore={<div style={{ textAlign: 'center', margin: '10px 0' }}><Button>더 보기</Button></div>}
bordered
dataSource={data}
renderItem={(item) => (
<List.Item style={{ marginTop: 20 }}>
<Card actions={[<StopOutlined key="stop" />]} >
<Card.Meta description={item.nickname} />
</Card>
</List.Item >
)
}
>
</List >
);
};
FollowList.prototype = {
header: PropTypes.string.isRequired,
data: PropTypes.array.isRequired
}
export default FollowList;
12. 회원가입 페이지 만들기(커스텀 훅)
pages/signup.js
import Head from 'next/head';
import React, { useCallback, useState } from 'react';
import AppLayout from '../components/AppLayout';
import { Form, Input, Checkbox, Button } from 'antd';
import useInput from '../hooks/useInput';
import styled from 'styled-components';
const ErroMessage = styled.div`
color:red;
`;
const SignUp = () => {
const [id, onChangeId] = useInput('');
const [nickName, onChangeNickname] = useInput('');
const [password, onChangePassword] = useInput('');
const [passwordCheck, setPasswordCheck] = useState('');
const [passwordError, setPasswordError] = useState(false);
const [term, setTerm] = useState("");
const [termError, setTermError] = useState(false);
const conChangePasswordCheck = useCallback((e) => {
setPasswordCheck(e.target.value);
setPasswordError(e.target.value !== password);
}, [password]);
const onChangeTerm = useCallback((e) => {
setTerm(e.target.checked);
setTermError(!e.target.checked);
}, [])
const onSubmit = useCallback(() => {
if (password !== passwordCheck) {
return setPasswordError(true);
}
if (!term) {
return setTermError(true);
}
}, [password, passwordCheck, term]);
return (
<>
<AppLayout>
<Head>
<title>회원 가입 | NodeBird</title>
</Head>
<Form onFinish={onSubmit}>
<div>
<lable htmlFor="user-id">아이디</lable>
<br />
<Input name="user-id" value={id} require onChange={onChangeId} />
</div>
<div>
<lable htmlFor="user-nickName">닉네임</lable>
<br />
<Input name="user-nickName" value={nickName} require onChange={onChangeNickname} />
</div>
<div>
<lable htmlFor="user-password">비밀번호</lable>
<br />
<Input name="user-password" type="password" value={password} require onChange={onChangePassword} />
</div>
<div>
<br />
<lable htmlFor="user-passwordCheck">비밀번호</lable>
<Input name="user-passwordCheck" type="password" value={passwordCheck} require onChange={conChangePasswordCheck} />
</div>
{passwordError && <ErroMessage>비밀번호가 일치하지 않습니다.</ErroMessage>}
<Checkbox name="user-term" checked={term} onChange={onChangeTerm}>
동의합니다.
</Checkbox>
{termError && <div style={{ color: 'red' }}>약관에 동의하셔야 합니다.</div>}
<div style={{ marginTop: 10 }}>
<Button type="primary" htmlType='submit'>가입하기</Button>
</div>
</Form>
</AppLayout>
</>
);
};
export default SignUp;
hooks/useInput.js
import { useCallback, useState } from 'react';
export default (initialValue = null) => {
const [value, setValue] = useState(initialValue);
const handler = useCallback((e) => {
setValue(e.target.value);
}, []);
return [value, handler];
}














댓글 ( 4)
댓글 남기기