npm i antd styled-components @ant-design/icons
AppLayout.js
<Menu mode="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>
하지만 여기서 antd이 버전 업 됨에 따라 아래와 같이 수정해주어야 warning
이 없어진다.const menuItems = [
{
label: <Link href="/"><a>노드버드</a></Link>,
key: "home",
},
{
label: <Link href="/profile"><a>프로필</a></Link>,
key: "profile",
},
{
label: <SearchInput enterButton />,
key: "search",
},
{
label: <Link href="/signup"><a>회원가입</a></Link>,
key: "signup",
},
]
...
<Menu mode="horizontal" items={menuItems}/>
error 등장 ❗️
TypeError _interopRequireDefault is not a function
@babel/runtime
을 설치해주면 해결된다_app.js
import Head from 'next/head'
const NodeBird = ({ Component }) => {
return (
<>
<Head>
<meta charSet='utf--8' />
<title>koeunseo_nodebird</title>
</Head>
...
</>
)
}
화면이 늘어남에 따라 컴포넌트 등이 재배치되면서, 모바일 환경 → 태블릿 환경 → 데스크탑 환경으로 바꿀 수 있음
원칙
AppLauout.js
내 반응형 추가
<Row gutter={8}>
<Col xs={24} md={6}>
왼쪽 메뉴
</Col>
<Col xs={24} md={6}>
{children}
</Col>
<Col xs={24} md={6}>
<a href="https://github.com/Koeunseooooo" target="_blank" rel="noreferrer noopener">Made by koeunseo</a>
</Col>
</Row>
💡왜 여기서는 Link 태그를 사용하지 않을까?
→ csr할 링크는 Link, 그 외에는 a태그만 사용한다. 여기서는 외부페이지로 이동하므로 a태그를 사용
💡Link 컴포넌트의 이점은?
→ 페이지 리렌더링 없이 이동시켜주는 SPA 동작을 담당하며, a 태그로 전환되어 SEO에 적합하거나 다음 페이지를 prefetch 하는 등의 장점이 있음
LoginForm.js
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>
<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"
type="password"
value={password}
onChange={onChangePassword}
required
/>
</div>
<div>
<Button type="primary" htmlType="submit" loading={false}>로그인</Button>
<Link href="/signup"><a><Button>회원가입</Button></a></Link>
</div>
</div>
</Form>
)
}
<div style={{ marginTop:10 }}>
why?{} === {} //false
import { useMemo } from 'react' //값을 캐싱하는 useMemo
const style = useMemo(()=>({marginTop:10}),[]);
<div style={style}>이런식으로 사용하면 인라인을 사용해도 리렌더링 최적화 가능</div>
AppLayout.js
import styled from 'styled-components'
const ButtonWrapper = styled.div`// div 태그 컴포넌트가 됨
margin-top: 10px;
`
(...)
<ButtonWrapper>
<Button />
<Button />
</ButtonWrapper>
import styled from 'styled-components'
const SearchInput = styled(Input.Search)` // 컴포넌트 커스텀도 가능
vertical-align: middle
`
(...)
<SearchInput enterButton />
크롬 web store
components
탭 jquery와 react, vue를 같이 쓰면 안되는 궁극적인 이유
profile.js
const Profile = () => {
const followerList=[{nickname:"고은서"},{nickname:"고은서부계"},{nickname:"고은서부계2"},]
const followingList=[{nickname:"고은서"},{nickname:"고은서부계"},{nickname:"고은서부계2"},]
return (
<>
...
<AppLayout>
<NicknameEditForm />
<FollowList header="팔로잉 목록" data={followingList} />
<FollowList header="팔로워 목록" data={followerList} />
</AppLayout>
</>
);
}
NicknameEditForm, FollowList
로 컴포넌트 분리NicknamEditForm.js
const NicknameEditForm = () => {
const style=useMemo(()=>({
marginBottom:'20px',
border: '1px solid #d9d9d9',
padding: '20px'
}),[])
return (
<Form style={style}>
<Input.Search addonBefore="닉네임" enterButton="수정" />
</Form>
)
}
form
제작은 사실 일일이 작성하기에 비효율적이다FollowList.js
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>
)}
/>
)
}
📌 굳이 공식문서에 찾아보면 다 나와있는 것들을 아깝게 암기하려 하지 말자!
대신 서버사이드렌더링의 동작방식, 클라이언트 렌더링 동작방식, 코드 spliting, next의 기본 원리 등 흐름을 파악해야 하는 것은 설명할 수 있을 정도로 암기 및 이해해두는 것이 좋다.
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)
},[])
signup.js
const [id, setId] = useState('')
const [password, setPassword] = useState('')
const [nickname, setNickname] = useState('')
const onChangeId = useCallback((e) => {
setId(e.target.value)
},[])
const onChangePassword = useCallback((e) => {
setPassword(e.target.value)
},[])
const onChangeNickname = useCallback((e) => {
setNickname(e.target.value)
},[])
useinput.js
# 커스텀 훅들은 hooks라는 폴더 내에서 따로 관리합니다
📦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]
}
signup.js
const [id, onChangeId]=useinput('')
const [nickname, onChangeNickname]=useinput('')
const [password, onChangePassword]=useinput('')
그러나 비슷해보이더라도 동일한 패턴이 아닐 경우에는 원래대로 useState를 사용한다
(...)
const [passwordCheck, setPasswordCheck]=useState('')
const [passwordError, setPasswordError]=useState(false)
const onChangePasswordCheck = useCallback((e)=>{
setPasswordCheck(e.target.value)
setPasswordError(e.target.value !== password)
},[password])
(...)