버전이 다르기 때문에 소스가 강좌와 다를 수 있다.
버전
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)
댓글 남기기