// AppLayout.js
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import Link from 'next/link';
import { Menu, Input, Row, Col } from 'antd';
import 'antd/dist/antd.css';
import UserProfile from '../components/UserProfile';
import LoginForm from '../components/LoginForm';
const AppLayout = ({ children }) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<div>
<Menu mod="horizontal">
<Menu.Item>
<Link href="/"><a>노드버드</a></Link>
</Menu.Item>
<Menu.Item>
<Link href="/profile"><a>프로필</a></Link>
</Menu.Item>
<Menu.Item>
<Input.Search enterButton style={{ verticalAlign: 'middle' }} />
</Menu.Item>
<Menu.Item>
<Link href="signup"><a>회원가입</a></Link>
</Menu.Item>
</Menu>
<Row gutter={8}>
<Col xs={24} md={6} >
{isLoggedIn ? <UserProfile /> : <LoginForm />}
</Col>
<Col xs={24} md={12}>
{children}
</Col>
<Col xs={24} md={6}>
<a href="https://www.zerocho.com" target="_blank" rel="noreferrer noopener">Made by Zerocho</a>
</Col>
</Row>
</div>
);
};
AppLayout.propTypes = {
children: PropTypes.node.isRequired,
}
export default AppLayout;
// LoginForm.js
import React, { useState, useCallback } 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 (
<Form>
<div>
<label htmlFor="user-id">아이디</label>
<br />
<Input
name="user-id"
value={id}
onChange={onChangeId}
required
/>
</div>
<div>
<label htmlFor="user-password">비밀번호</label>
<br />
<Input
name="user-password"
value={password}
onChange={onChangePassword}
required
/>
</div>
<div style={{ marginTop: 10 }}>
<Button type="primary" htmlType="submit" loading={false}>로그인</Button>
<Link href="/signup"><a><Button>회원가입</Button></a></Link>
</div>
</Form>
);
}
export default LoginForm;
style={{}} 라는 식으로 객체를 넣어준다. 실제로 사용할 수 있는 inline style이다. 그렇지만 이렇게 사용하는건 지양해야한다.<div style={{ marginTop: 10 }}>
<Button type="primary" htmlType="submit" loading={false}>로그인</Button>
<Link href="/signup"><a><Button>회원가입</Button></a></Link>
</div>
styled-components 를 사용하면 된다.// LoginForm.js
import React, { useState, useCallback } from 'react';
import { Form, Input, Button } from 'antd';
import Link from 'next/link';
import styled from 'styled-components';
const ButtonWrapper = styled.div`
margin-top: 10px;
`;
const LoginForm = () => {
const [id, setId] = useState('');
const [password, setPassword] = useState('');
const onChangeId = useCallback((e) => {
setId(e.target.value);
}, []); // 컴포넌트에 props로 넘겨주는 함수는 useCallback을 써줘야 최적화가 된다.
const onChangePassword = useCallback((e) => {
setPassword(e.target.value);
}, []);
return (
<Form>
<div>
<label htmlFor="user-id">아이디</label>
<br />
<Input
name="user-id"
value={id}
onChange={onChangeId}
required
/>
</div>
<div>
<label htmlFor="user-password">비밀번호</label>
<br />
<Input
name="user-password"
value={password}
onChange={onChangePassword}
required
/>
</div>
<ButtonWrapper>
<Button type="primary" htmlType="submit" loading={false}>로그인</Button>
<Link href="/signup"><a><Button>회원가입</Button></a></Link>
</ButtonWrapper>
</Form>
);
}
export default LoginForm;
물론 성능에 영향이 없다면 크게 집착할 필요는 없다. 하지만 앱이 커질수록 렌더링을 최적화하는 게 필수적이므로 평소에 습관을 들여놓자.
AppLayout.js 에도 인라인 스타일로 작성된 부분이 있다. 같이 고쳐주자.// AppLayout.js
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import Link from 'next/link';
import { Menu, Input, Row, Col } from 'antd';
import 'antd/dist/antd.css';
import styled from 'styled-components';
import UserProfile from '../components/UserProfile';
import LoginForm from '../components/LoginForm';
const SearchInput = styled(Input.Search)`
vertical-align: middle;
`;
const AppLayout = ({ children }) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<div>
<Menu mod="horizontal">
<Menu.Item>
<Link href="/"><a>노드버드</a></Link>
</Menu.Item>
<Menu.Item>
<Link href="/profile"><a>프로필</a></Link>
</Menu.Item>
<Menu.Item>
<SearchInput enterButton />
</Menu.Item>
<Menu.Item>
<Link href="signup"><a>회원가입</a></Link>
</Menu.Item>
</Menu>
<Row gutter={8}>
<Col xs={24} md={6} >
{isLoggedIn ? <UserProfile /> : <LoginForm />}
</Col>
<Col xs={24} md={12}>
{children}
</Col>
<Col xs={24} md={6}>
<a href="https://www.zerocho.com" target="_blank" rel="noreferrer noopener">Made by Zerocho</a>
</Col>
</Row>
</div>
);
};
AppLayout.propTypes = {
children: PropTypes.node.isRequired,
}
export default AppLayout;
useMemo : 값을 캐싱
useCallback : 함수를 캐싱
// LoginForm.js
...
const style = useMemo(() => ({ marginTop: 10 }), []);
...
<ButtonWrapper style={style}>
<Button type="primary" htmlType="submit" loading={false}>로그인</Button>
<Link href="/signup"><a><Button>회원가입</Button></a></Link>
</ButtonWrapper>
리렌더링 될 때 모든 부분이 다시 그려지나?
그건 아니다. 함수형 컴포넌트는 일단 내부 코드가 전부 재시작되는 것은 맞다. (useCallback이나 useMemo는 일단 이전과 같은거라고 취급한다.) 그리고 return 부분에서 바뀐 부분만 다시 그리게 된다.
쉽게 설명해서 return 부분이 virtual dom이다. 일단 맨 처음에는 한 번 그려주고 그 이후 리렌더링이 뙬 때 이전 virtual dom과 현재 virtual dom을 비교하고 바뀐 부분만 다시 그리게 되는 것이다.
onFinish={함수} 를 넣으면 e.preventDefault() 같은걸 자동으로 처리해준다.setIsLoggedIn 을 props로 내려주자.// AppLayout.js
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { Menu, Input, Row, Col } from 'antd';
import 'antd/dist/antd.css';
import styled from 'styled-components';
import UserProfile from './UserProfile';
import LoginForm from './LoginForm';
const SearchInput = styled(Input.Search)`
vertical-align: middle;
`;
const AppLayout = ({ children }) => {
const router = useRouter();
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<div>
<Menu mode="horizontal" selectedKeys={[router.pathname]}>
<Menu.Item key="/">
<Link href="/"><a>노드버드</a></Link>
</Menu.Item>
<Menu.Item key="/profile">
<Link href="/profile"><a>프로필</a></Link>
</Menu.Item>
<Menu.Item key="/search">
<SearchInput enterButton />
</Menu.Item>
<Menu.Item key="signup">
<Link href="signup"><a>회원가입</a></Link>
</Menu.Item>
</Menu>
<Row gutter={8}>
<Col xs={24} md={6} >
{isLoggedIn ? <UserProfile setIsLoggedIn={setIsLoggedIn} /> : <LoginForm setIsLoggedIn={setIsLoggedIn} />}
</Col>
<Col xs={24} md={12}>
{children}
</Col>
<Col xs={24} md={6}>
<a href="https://www.zerocho.com" target="_blank" rel="noreferrer noopener">Made by Zerocho</a>
</Col>
</Row>
</div>
);
};
AppLayout.propTypes = {
children: PropTypes.node.isRequired,
}
export default AppLayout;
// LoginForm.js
import React, { useState, useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';
import { Form, Input, Button } from 'antd';
import Link from 'next/link';
import styled from 'styled-components';
const ButtonWrapper = styled.div`
margin-top: 10px;
`;
const FormWrapper = styled(Form)`
padding: 10px;
`;
const LoginForm = ({ setIsLoggedIn }) => {
const [id, setId] = useState('');
const [password, setPassword] = useState('');
const onChangeId = useCallback((e) => {
setId(e.target.value);
}, []); // 컴포넌트에 props로 넘겨주는 함수는 useCallback을 써줘야 최적화가 된다.
const onChangePassword = useCallback((e) => {
setPassword(e.target.value);
}, []);
const onSubmitForm = useCallback(() => {
console.log(id, password);
setIsLoggedIn(true);
}, [id, password]);
const style = useMemo(() => ({ marginTop: 10 }), []);
return (
<FormWrapper onFinish={onSubmitForm}>
<div>
<label htmlFor="user-id">아이디</label>
<br />
<Input
name="user-id"
value={id}
onChange={onChangeId}
required
/>
</div>
<div>
<label htmlFor="user-password">비밀번호</label>
<br />
<Input
name="user-password"
value={password}
onChange={onChangePassword}
required
/>
</div>
<ButtonWrapper style={style}>
<Button type="primary" htmlType="submit" loading={false}>로그인</Button>
<Link href="/signup"><a><Button>회원가입</Button></a></Link>
</ButtonWrapper>
</FormWrapper>
);
}
LoginForm.propTypes = {
setIsLoggedIn: PropTypes.func,
}
export default LoginForm;
rel="noreferrer noopener" 를 붙여줘야한다.