이 블로그는 "한입 크기로 잘라먹는 리액트" 강의를 보고 복습하고자 작성되었습니다!
목차
1. useRef로 DOM 조작하기
DOM(Document Object Model)이란 문서 객체 모델이라는 뜻으로, 웹 페이지의 요소들을 구조화시켜 접근, 조작할 수 있도록 API를 제공해주는 인터페이스입니다.
const handleSubmit = () => {
if (state.author.length < 1) {
alert('작성자는 최소 1글자 이상 입력해주세요.');
return;
}
if (state.contents.length < 5) {
alert('일기 본문은 최소 5글자 이상 입력해주세요.');
return;
}
alert('저장 성공');
};
이 코드는 alert를 사용해 form 요소들의 글자수를 체크하는 코드입니다.
다이어리 폼을 제출할 때 칸이 비워져 있으면 간단하게 alert를 띄울 수도 있으나,
alert는 좋은 UX가 아닙니다.
alert처럼 단순 텍스트가 아니라, 실시간으로 화면에서 보여주는 것이 사용자에게 더 잘 보이기 때문입니다.
가리키고 싶은 특정 DOM 요소에다 focus를 주어야 하는데, 이때 사용할 수 있는 게 useRef입니다.
그럼 저번에 작업하던 다이어리 프로그램에서 useRef 기능을 이용해,
author나 contents를 안 썼을 때 해당 DOM(textInput, textarea)에 focus를 줍시다.
1. useRef 임포트하기
import { useRef, useState } from 'react';
2. textInput에 focus 주기
2-1. useRef() 함수 호출, authorInput에 넣기
const authorInput = useRef();
그럼 authorInput에 React.MutableRefObject 자료형이 들어갑니다.
MutableRefObject는 html, 즉 DOM 요소에 접근하는 기능을 합니다.
2-2. return 속 input 태그에 속성으로 ref={ authorInput } 넣기
ref={authorInput}
2-3. 위에서 handleSubmit 함수에서 author 부분 alert를 지우고, 아래 코드 넣기
authorInput.current.focus();
현재 가리키는 authorInput 부분에 focus를 넣는다는 뜻입니다.
3. 같은 방법으로 textArea에 focus 주기
const handleSubmit = () => {
if (state.author.length < 1) {
// focus
authorInput.current.focus();
return;
}
if (state.contents.length < 5) {
// focus
contentsInput.current.focus();
return;
}
alert('저장 성공');
};
handleSubmit 함수의 전체 코드입니다.
<div>
<input
ref={authorInput}
name='author'
value={state.author}
onChange={handleChangeState}
placeholder='작성자'
type='text'
/>
</div>
<div>
<textarea
ref={contentsInput}
name='contents'
value={state.contents}
onChange={handleChangeState}
placeholder='일기'
type='text'
/>
</div>
return에서 author과 contents 입력 부분입니다.
2. 일기 리스트 렌더링 (조회)
일기 작성 부분 밑에 내가 작성한 일기 리스트를 올려봅시다.
1. DiaryList.js 파일 생성, 초기설정
DiaryList.js
const DiaryList = () => {
return (
<div>
<h2>일기 리스트</h2>
</div>
);
};
export default DiaryList;
이렇게 작성한 후, App.js에서 임포트하고 return 부분에 컴포넌트 태그를 추가합니다.
import DiaryList from './DiaryList';
return (
<div className='App'>
<DiaryEditor />
<DiaryList diaryList={dummyList} />
</div>
);
2. App.js에 dummy list 생성 후 태그로 전달
const dummyList = [
{
id: 1,
author: '최명빈',
contents: "Show it to the world we blossomin' some more",
emotion: 5,
created_date: new Date().getTime(),
},
]
이런 더미 데이터를 여러 개 만들어 dummyList 리스트로 만든 후,
DiaryList 컴포넌트에 태그로 전달합니다.
<DiaryList diaryList={dummyList} />
DiaryList 컴포넌트에서는 prop으로 dummyList를 받아올 수 있습니다.
const DiaryList = ({ diaryList }) => {
return (
<div>
<h2>일기 리스트</h2>
<h4>{diaryList.length}개의 일가 있습니다.</h4>
<div>
{diaryList.map((it) => (
<div>일기 아이템</div>
))}
</div>
</div>
);
};
DiaryList.defaultProps = {
diaryList: [],
};
export default DiaryList;
diaryList에 빈 리스트를 받았을 경우를 대비해, defaultProps도 설정해줍니다.
하지만, 이렇게 쓰면 브라우저 콘솔에 경고가 뜹니다.
Each child in a list should have a unique "key" prop.
이 문제는 일기 리스트에서 최상위 div에 id를 붙여 해결이 가능합니다.
{
diaryList.map((it) => (
<div key={it.id}>
<div>작성자: {it.author}</div>
<div>내용: {it.contents}</div>
<div>감정: {it.emotion}</div>
<div>작성시간: {it.created_date}</div>
</div>
));
}
map 안에 쓰인 화살표함수 반환값의 최상위 div에서 key로 it.id를 각각 매기도록 작성했습니다.
만약 id가 없는 객체 리스트일 경우, map에서 it와 함께 idx를 함께 전달하면, 각 항목에 자동으로 idx가 붙습니다.
{diaryList.map((it, idx) => (
하지만 idx를 쓸 경우, 데이터가 수정되면 idx가 바뀌어 리액트에서 문제가 발생할 수 있습니다.
이 이유로 여기서는 id만 쓰도록 하겠습니다.
3. 컴포넌트 분할
DiaryList에 수정, 삭제 기능까지 넣으면, 이 컴포넌트의 return이 너무 커져서 코드가 길어지고 에러가 발생하기 쉽습니다.
그래서 각 Diary 아이템을 별도의 컴포넌트로 분할할 것입니다.
1. DiaryItem.js 생성
const DiaryItem = () => {
return <div className='DiaryItem'></div>;
};
export default DiaryItem;
이제 컴포넌트를 만드는 게 점점 익숙해지고 있습니다.
2. DiaryList의 리턴에는 diaryList 항목을 매핑하고, key를 넣어, DiaryItem 컴포넌트로 전달
return (
<div>
<h2>일기 리스트</h2>
<h4>{diaryList.length}개의 일가 있습니다.</h4>
<div>
{diaryList.map((it) => (
<DiaryItem key={it.id} {...it} />
))}
</div>
</div>
);
이렇게 DiaryItem 컴포넌트를 불러오면 해당 컴포넌트가 자동으로 임포트됩니다.
3. DiaryItems 컴포넌트에 들어가 props로 받아옴
const DiaryItem = ({ id, author, contents, emotion, created_date }) => {
};
더미 데이터마다 속성들을 객체로 불러옵니다.
4. 렌더링
return (
<div className='DiaryItem'>
<div className='info'>
<span>
작성자: {author} | 감정 점수: {emotion}
</span>
<span className='date'>{new Date(created_date).toLocaleString()}</span>
</div>
<div className='contents'>{contents}</div>
</div>
);
불러온 데이터를 화면에 띄웁니다.
여기서 created_date는 ms단위라서
Date 객체로 새로 만든 뒤, toLocaleString 함수를 먹여서 한글로 시간이 나오게 고쳤습니다.
참고) 전체 코드
DiaryList.js
import DiaryItem from './DiaryItem';
const DiaryList = ({ diaryList }) => {
return (
<div>
<h2>일기 리스트</h2>
<h4>{diaryList.length}개의 일기가 있습니다.</h4>
<div>
{diaryList.map((it) => (
<DiaryItem key={it.id} {...it} />
))}
</div>
</div>
);
};
DiaryList.defaultProps = {
diaryList: [],
};
export default DiaryList;
DiaryItem.js
const DiaryItem = ({ id, author, contents, emotion, created_date }) => {
return (
<div className='DiaryItem'>
<div className='info'>
<span>
작성자: {author} | 감정 점수: {emotion}
</span>
<span className='date'>{new Date(created_date).toLocaleString()}</span>
</div>
<div className='contents'>{contents}</div>
</div>
);
};
export default DiaryItem;
DiaryEditor.js
import { useRef, useState } from 'react';
const DiaryEditor = () => {
const authorInput = useRef();
const contentsInput = useRef();
const [state, setState] = useState({
author: '',
contents: '',
emotion: 1,
});
const handleChangeState = (e) => {
setState({
...state,
[e.target.name]: e.target.value,
});
};
const handleSubmit = () => {
if (state.author.length < 1) {
// focus
authorInput.current.focus();
return;
}
if (state.contents.length < 5) {
// focus
contentsInput.current.focus();
return;
}
alert('저장 성공');
};
return (
<div className='DiaryEditor'>
<h2>오늘의 일기</h2>
<div>
<input
ref={authorInput}
name='author'
value={state.author}
onChange={handleChangeState}
placeholder='작성자'
type='text'
/>
</div>
<div>
<textarea
ref={contentsInput}
name='contents'
value={state.contents}
onChange={handleChangeState}
placeholder='일기'
type='text'
/>
</div>
<div>
<span>오늘의 감정점수: </span>
<select
name='emotion'
value={state.emotion}
onChange={handleChangeState}>
<option value={1}>1</option>
<option value={2}>2</option>
<option value={3}>3</option>
<option value={4}>4</option>
<option value={5}>5</option>
</select>
</div>
<div>
<button onClick={handleSubmit}>일기 저장</button>
</div>
</div>
);
};
export default DiaryEditor;
App.js
import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';
const dummyList = [
{
id: 1,
author: '밍비',
contents: "Show it to the world we blossomin' some more",
emotion: 5,
created_date: new Date().getTime(),
},
{
id: 2,
author: '밍비',
contents: "Show it to the world we blossomin' some more",
emotion: 5,
created_date: new Date().getTime(),
},
{
id: 3,
author: '밍비',
contents: "Show it to the world we blossomin' some more",
emotion: 5,
created_date: new Date().getTime(),
},
{
id: 4,
author: '밍비',
contents: "Show it to the world we blossomin' some more",
emotion: 5,
created_date: new Date().getTime(),
},
];
function App() {
return (
<div className='App'>
<DiaryEditor />
<DiaryList diaryList={dummyList} />
</div>
);
}
export default App;
App.css
.DiaryEditor {
border: 1px solid gray;
text-align: center;
padding: 20px;
}
.DiaryEditor input,
textarea {
margin-bottom: 20px;
width: 500px;
padding: 10px;
}
.DiaryEditor textarea {
height: 150px;
}
.DiaryEditor select {
width: 300px;
padding: 10px;
margin-bottom: 20px;
}
.DiaryEditor button {
width: 500px;
padding: 10px;
cursor: pointer;
}
/* List */
.DiaryList {
border: 1px solid gray;
padding: 20px;
margin-top: 20px;
}
.DiaryList h2 {
text-align: center;
}
/* Item */
.DiaryItem {
background-color: rgb(240, 240, 240);
margin-bottom: 10px;
padding: 20px;
}
.DiaryItem .info {
border-bottom: 1px solid gray;
padding-bottom: 10px;
margin-bottom: 10px;
}
.DiaryItem .date {
color: gray;
}
.DiaryItem .contents {
font-weight: bold;
margin-bottom: 30px;
margin-top: 30px;
}
'Udemy' 카테고리의 다른 글
[React] 데이터 삭제하기, 데이터 수정하기 (0) | 2023.04.17 |
---|---|
[React] 데이터 추가 기능 (0) | 2023.04.16 |
[React] 리액트로 프로젝트 만들기 (0) | 2023.04.15 |
[React] React 입문 (0) | 2023.04.09 |
[Node.js] Node.js 기초, 실행환경 구성 (0) | 2023.04.09 |