antd 사용해 SNS 화면 만들기 - 2

ktw2378·2021년 10월 4일
0

ReactBirdSNS

목록 보기
3/5
post-thumbnail

4. antd로 프론트 디자인하기 - 3

4-1. 크롬 확장 프로그램

chrome 웹 스토어에서 React와 Redux 개발을 편리하게끔 해주는 아래 툴들을 다운 받자.

(1) React Developer Tools

(2) Redux dev Tools

React Dev Tools의 Profiler 부분에서 Dark 모드 & Highlight를 켜주는 것이 좋다


4-2. 프로필 페이지 만들기

localhost:3000/profile 즉, 프로필 페이지를 제작해보자.

// pages/profile.js

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

const Profile = () => {
    const followerList = [{ nickname: '강태웅'}, { nickname: '안녕'}, {nickname: '노드버드'}];
    const followingList = [{ nickname: '강태웅'}, { nickname: '안녕'}, {nickname: '노드버드'}];

    return (
        <>
            <Head>
                <title>내 프로필 | NodeBird</title>
            </Head>
            <AppLayout>
                <NicknameEditForm />
                <FollowList header="팔로잉 목록" data={followingList} />
                <FollowList header="팔로워 목록" data={followerList} />
            </AppLayout>
        </>
    );
};

export default Profile;

현재는 서버 & 데이터베이스가 존재하지 않기 때문에 가짜 데이터를 생성해준다. 위 코드에서 볼 수 있는 followerList와 followingList가 바로 그것이다. 또, NicknameEditForm(닉네임 수정) 컴포넌트와 FollowList(팔로우 & 팔로잉) 컴포넌트를 불러와서 localhost:3000/profile 에서 닉네임을 수정하는 포멧팔로우 & 팔로잉 목록을 볼 수 있게끔 할 것이다. 이렇게 미리 만들 컴포넌트를 정해서 UI를 구상한 이후에 컴포넌트를 제작하는 것이 좋다.

components/NicknameEditForm.js

import React, { useMemo } from 'react';
import { Form, Input } from 'antd';

const NicknameEditForm = () => {
    const style = useMemo(() => ({ marginBottom: '20px', border: 
																	'1px solid #d9d9d9', padding: '20px'
																}));

    return (
        <Form style={style}>
            <Input.Search addonBefore="닉네임" enterButton="수정" />
        </Form>
    );
};

export default NicknameEditForm;
components/FollowList.js

import React from 'react';
import { List, Button, Card } from 'antd';
import PropTypes from 'prop-types';
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>
            )}
        />
    );
};

FollowList.propTypes = {
    header: PropTypes.string.isRequired,
    data: PropTypes.array.isRequired,
}

export default FollowList;

아래와 같이 닉네임을 수정하는 부분과 가짜 더미 데이터로 만들어준 팔로잉 / 팔로워 목록이 적절하게 만들어진 것을 볼 수 있다. 그리고 처음부터 styled-component 혹은 useMemo로 모든 인라인 스타일을 빼주기는 힘들기 때문에 일단 인라인 스타일로 작성해놓은 뒤 이후에 배포할 때 바꿔주는 것도 괜찮다.


4-3. 회원가입 페이지 만들기(커스텀 훅)

코드를 작성하다보면 아래와 같은 코드가 비슷비슷하면서 계속 반복되는 부분이 있다고 느낄텐데, 이는 커스텀 훅으로 줄여줄 수 있다.

// components/LoginForm.js

...

const [id, setId] = useState('');
const [password, setPassword] = useState('');

const onChangeId = useCallback((e) => {
	setId(e.target.value);
}, []);

const onChangePassword = useCallback((e) => {
	setPassword(e.target.value);
}, []);

...


hooks 폴더 안에 useInput.js 파일을 생성해준 뒤 아래와 같은 코드로 커스텀 훅을 만들어준다.

// hooks/useInput.js
// 커스텀 훅

import { useState, useCallback } from "react";

export default (initialValue = null) => {
    const [value, setValue] = useState(initialValue);
    const handler = useCallback((e) => {
        setValue(e.target.value);
    }, []);
    return [value, handler];
}

위의 initialValue나 value, handler 등은 임의로 정해준 이름이다. 일종의 매개변수와 같다고 볼 수 있다. 다른 이름으로 해도 아무 상관 없다. 이렇게 만들어준 커스텀 훅은 아래와 같이 사용할 수 있다.

// components/LoginForm.js

...

import useInput from '../hooks/useInput';

// const [id, setId] = useState('');
// const onChangeId = useCallback((e) => {
// 	setId(e.target.value);
// }, []);

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

// const [password, setPassword] = useState('');
// const onChangePassword = useCallback((e) => {
// 	setPassword(e.target.value);
// }, []);

const [password, onChangePassword] = useInput('');

...

위의 코드를 보면 알 수 있듯이 매우 길었던 반복되는 코드를 단 2줄로 줄여줄 수 있었다. 이를 참고해서 signup 페이지 (회원가입 페이지)를 마저 제작해보자.

// pages/signup.js

...
import useInput from '../hooks/useInput';

const Signup = () => {    
	
		// 커스텀 훅으로 줄여준 부분
    const [id, onChangeId] = useInput('');
    const [password, onChangePassword] = useInput('');
    const [nickname, onChangeNickname] = useInput('');

    // 비밀번호 체크는 조금 다른 부분이 있어 커스텀 훅으로 줄이지 못한다
    const [passwordCheck, setPasswordCheck] = useState('');
    const [passwordError, setPasswordError] = useState(false);
    const onChangePasswordCheck = useCallback((e) => {
        setPasswordCheck(e.target.value);   // 여기까지였으면 커스텀 훅으로 줄일 수 있었다.
        setPasswordError(e.target.value !== password);
				// 비밀번호와 비밀번호 확인이 일치하는지 확인한다.
        // 둘이 일치하지 않으면 passwordError가 true가 되고
        // 둘이 일치하면 passwordError가 false가 된다.
        // 따라서 passwordError가 true가 되면 에러를 표시해주면 된다.
    }, [password]);
    
    const [term, setTerm] = useState('');
    const [termError, setTermError] = useState(false);
    const onChangeTerm = useCallback((e) => {
        setTerm(e.target.checked);
        setTermError(false);
    }, []);

    const onSubmit = useCallback(() => {
        if(password !== passwordCheck) {
            return setPasswordError(true);
        }
        if(!term) {
            return setTermError(true);
        }
        console.log(id, nickname, password);
    }, [password, passwordCheck.anchor, term]);
    // 비밀번호 일치 여부의 경우에는 위에서 체크해줬지만 제출할 때 한번 더 체크해준 것

    return (
        <AppLayout>
            <Head>
                <title>회원가입 | Nodebird</title>
            </Head>
            <Form onFinish={onSubmit}>
                <div>
                    <label htmlFor="user-id">아이디</label>
                    <br />
                    <Input 
                        name="user-id" 
                        value={id} 
                        required 
                        onChange={onChangeId}
                    />
                </div>
                <div>
                    <label htmlFor="user-nickname">닉네임</label>
                    <br />
                    <Input 
                        name="user-nickname" 
                        value={nickname} 
                        required 
                        onChange={onChangeNickname}
                    />
                </div>
                <div>
                    <label htmlFor="user-password">비밀번호</label>
                    <br />
                    <Input 
                        name="user-password" 
                        type="password"
                        value={password} 
                        required 
                        onChange={onChangePassword}
                    />
                </div>
                <div>
                    <label htmlFor="user-password-check">비밀번호 체크</label>
                    <br />
                    <Input 
                        name="user-password-check" 
                        type="password"
                        value={passwordCheck} 
                        required 
                        onChange={onChangePasswordCheck}
                    />
										{passwordError && <ErrorMessage>비밀번호가 일치하지 않습니다.</ErrorMessage>}
                </div>
                <div>
                    <Checkbox name="user-term" checked={term} onChange={onChangeTerm}>위 사항에 대해 동의합니다.</Checkbox>
                    {termError && <ErrorMessage>약관에 동의하셔야합니다.</ErrorMessage>}
                </div>
                <div style={{ marginTop: 10}}>
                    <Button type="primary" htmlType="submit">가입하기</Button>
                </div>
            </Form>
        </AppLayout>
    );
};

export default Signup;

코드가 매우 길기 때문에 조금씩 잘라서 해석하자.

// pages/signup.js

...
import useInput from '../hooks/useInput';

const Signup = () => {    
	
		// 커스텀 훅으로 줄여준 부분
    const [id, onChangeId] = useInput('');
    const [password, onChangePassword] = useInput('');
    const [nickname, onChangeNickname] = useInput('');

    // 비밀번호 체크는 조금 다른 부분이 있어 커스텀 훅으로 줄이지 못한다
    const [passwordCheck, setPasswordCheck] = useState('');
    const [passwordError, setPasswordError] = useState(false);
    const onChangePasswordCheck = useCallback((e) => {
        setPasswordCheck(e.target.value);
        setPasswordError(e.target.value !== password);
    }, [password]);
    
    const [term, setTerm] = useState('');
    const [termError, setTermError] = useState(false);
    const onChangeTerm = useCallback((e) => {
        setTerm(e.target.checked);
        setTermError(false);
    }, []);

    const onSubmit = useCallback(() => {
        if(password !== passwordCheck) {
            return setPasswordError(true);
        }
        if(!term) {
            return setTermError(true);
        }
        console.log(id, nickname, password);
    }, [password, passwordCheck.anchor, term]);
    // 비밀번호 일치 여부의 경우에는 위에서 체크해줬지만 제출할 때 한번 더 체크해준 것
  • 비밀번호 체크 나머지는 앞서 설명했던 커스텀 훅과 거의 유사한데, 조금 신경써야 할 부분은 비밀번호 체크 부분과 약관 동의 부분이다. 비밀번호 체크 부분의 경우에는 setPasswordError(e.target.value !== password); 코드가 추가되면서 커스텀 훅을 이용할 수 없다. 따라서 그냥 해체해서 전체를 써줘야한다.
    const ErrorMessage = styled.div`
        color: red;        
    `;
    
    ...
    
    const [passwordCheck, setPasswordCheck] = useState('');
        const [passwordError, setPasswordError] = useState(false);
        const onChangePasswordCheck = useCallback((e) => {
            setPasswordCheck(e.target.value);
            setPasswordError(e.target.value !== password);
        }, [password]);
    
    ...
    
    {passwordError && <ErrorMessage>비밀번호가 일치하지 않습니다.</ErrorMessage>}
    setPasswordError(e.target.value !== password); 는 비밀번호 체크 부분에 입력한 비밀번호가 내가 앞서 입력했던 비밀번호와 다르면 'true', 같으면 'false'로 passwordError 부분을 바꾸라는 뜻이다. 따라서 passwordError가 true, 즉 두 비밀번호가 일치하지 않으면 비밀번호가 일치하지 않는다는 ErrorMessage를 표시해주면 된다. styled-components를 이용해 빨간색으로 경고 메세지를 표현하게끔 코드를 작성했다.
  • 약관 동의 & 가입버튼
    const [term, setTerm] = useState('');
        const [termError, setTermError] = useState(false);
        const onChangeTerm = useCallback((e) => {
            setTerm(e.target.checked);
            setTermError(false);
        }, []);
    
    const onSubmit = useCallback(() => {
        if(password !== passwordCheck) {
            return setPasswordError(true);
        }
        if(!term) {
            return setTermError(true);
        }
        console.log(id, nickname, password);
    }, [password, passwordCheck.anchor, term]);
    
    ...
    
    <div>
        <Checkbox name="user-term" checked={term} onChange={onChangeTerm}>
    			위 사항에 대해 동의합니다.
    		</Checkbox>
        {termError && <ErrorMessage>약관에 동의하셔야합니다.</ErrorMessage>}
    </div>
    <div style={{ marginTop: 10}}>
        <Button type="primary" htmlType="submit">가입하기</Button>
    </div>
    처음 상태 = 체크 박스가 채워져있지 않은 상태이다. 따라서 체크 박스에 변화가 생겼다는 것은 체크 박스에 체크가 됐다는 것이며 setTermError(false)가 동작한다. termError가 true여야 약관에 동의하여야한다는 ErrorMessage가 출력된다. 만약 가입하기 버튼을 눌러서 submit이 되면 Form 안에 onFinish={onSubmit}이 동작하고, 비밀번호와 체크 비밀번호가 일치하지 않으면 비밀번호가 일치하지 않는다는 문구를 출력하고, !term, 즉 체크 박스가 비어있으면 setTermError(true)를 함으로써 약관에 동의해야한다는 ErrorMessage를 출력한다.
profile
풀스택 개발자를 꿈꾸는 강태웅입니다 :)

0개의 댓글