- 깊은 복사를 수행해주는 라이브러리
- dependencies 없음 (immer를 다운받으면 함께 다운되는 라이브러리 없음)
- npm i immer 로 다운로드 받기 가능
- vite폴더 상위에 설치하면 된다!
produce 함수 : 새로운 상태를 만들어서 반환해준다.
변경된 draft를 원래의 baseState와 비교를 하는 것이다.
다른 부분을 nextState에 저장해줌
immer 사용
[깊은복사 immer 미사용]
const handleAddressChange = e => {
//값이 변했으면~ 새로 target.name 적용해서 newAddressBook에 복사
const newAddressBook = user.extra.addressBook.map(address => {
if(address.id === Number(e.target.name)){
return { ...address, value: e.target.value}; //value 뺴고 복사
}else{
return {...address};
}
});
const newState = {
...user,
extra: {
...user.extra,
addressBook : newAddressBook
}
}
[깊은복사 immer 사용 - produce]
*기존 객체는 건들이지 않고, 새로운 객체만 변경해주는 것!
const newState = produce(user, draft => {
//배열에서 id값 일치하는 배열 추출
//인자값에서 넘어오는 객체(draft)를 통해 작업해주면 된다.
const address = draft.extra.addressBook.find(address => address.id === Number(e.target.name));
//벨류값을 address에 넣는다.
address.value = e.target.value;
})
draft=>{} 는 함수인데, 여기서 전달된 draft는
이모님이 새로운 객체를 기반으로 기존 객체를 복사해서 리턴해준다.
- produce의 첫 번째 매개변수는 : 불변할 객체 (지켜줄 객체)
- produce의 두 번째 매개변수는 변화할 객
const newItemList = produce(itemList, draft => { //첫번째 객체는
const item = draft.find(item => item._id === _id);
item.done = !item.done;
})
console.log(itemList, newItemList);
setItemList(newItemList);
PropTypes 모듈
import {useState} from "react";
import PropTypes from 'prop-types';
function TodoInput({addItem}){
const [nextId, setNextId] = useState(4);
const [title, setTitle] = useState('');
const handleAdd = () => {
if(title.trim() !== ''){
const item = {_id: nextId, title, done: false};
setNextId(nextId + 1);
addItem(item);
setTitle('');
}
};
const handleKeyUp = event => {
if(event.key === 'Enter') handleAdd();
};
return (
<div className="todoinput">
<input type="text" value={ title } onChange={ event => setTitle(event.target.value) } onKeyUp={ handleKeyUp } autoFocus />
<button type="button" onClick={ handleAdd }>추가</button>
</div>
);
}
TodoInput.propTypes = {
addItem: PropTypes.func //addItem은 함수라고 정의하는 것과 같다.
}; // 빨간줄 나는거 없애기
//TOdoInput에 propTypes라는 속성을 하나 추가하고,
export default TodoInput;
TypeScript를 사용하면 내부에서 체크가 가능하여 별로 사용할 일이 없다.
일반 React는 PropTypes를 따로 import하여 사용하여야 한다.
* 표기법이 조금씩 다르니 주의할 것! (카멜, 파스칼케이스)
[PropTypes 예시]
import PropTypes from 'prop-types';
function TodoItem({ item, toggleDone, deleteItem }){
return (
<li>
<span>{ item._id }</span>
<span onClick={ () => toggleDone(item._id) }>{ item.done ? <s>{ item.title }</s> : item.title }</span>
<button type="button" onClick={ () => deleteItem(item._id) }>삭제</button>
</li>
);
}
TodoItem.propTypes = {
item: PropTypes.object.isRequired,
toggleDone: PropTypes.func.isRequired,
deleteItem: PropTypes.func.isRequired,
}
export default TodoItem;
이렇게 하면 eslint의 감시에서 해방된다.
불변성 지키기
[배열] : 새로운 배열을 리턴하는 그런 방식으로 진행한다.
concat -> 두가지 배열을 합쳐 새로운 배열을 리턴
단방향 데이터 바인딩
변경되어야하는 값이 있다 ? -> 이벤트가 발생하면 변경 (이벤트 함수를 생성) -> 해당 함수 안에서 setState하는 함수를 불러와서 설정
회원가입 form 실습
const handleChange = e =>{
const newUser = {...user, [e.target.name] : e.target.value};
setUser(newUser)
}
[e.target.name] 을 대괄호로 묶어야 한다. -> computed property name 이기 때문,
객체의 키 값을 변수로 사용할 때에는 꼭 대괄호 묶어주어야 한다.
const onSubmit = (e) => {
e.preventDefault();
let newErrors;
// 필수 입력 체크
if(user.name.trim() === ''){
newErrors = {
name: { message: '이름을 입력하세요.' }
};
}else if(user.name.trim().length < 2){
newErrors = {
name: { message: '이름을 2글자 이상 입력하세요.' }
};
}else if(user.email.trim() === ''){
newErrors = {
email: { message: '이메일을 입력하세요.' }
};
}else if(user.cellphone.trim() === ''){
newErrors = {
cellphone: { message: '휴대폰 번호를 입력하세요.' }
};
}
if(newErrors){ // 검증 실패
setErrors(newErrors);
}else{ // 검증 통과
setErrors({});
console.log('서버에 전송...', user);
}
}
submit 함수에서는 trim()을 사용해서 공백인지 확인해줬다,
여기서 newErrors를 객체로 사용한 이유는, name email cellphone 을 구분하여, App단에서 오류를 알려주는 div를 호출할 때 구분할 수 있도록 하기 위해서이다.
return (
<>
<h1>회원 가입</h1>
<form onSubmit={ onSubmit }>
<label htmlFor="name">이름</label>
<input
id="name"
name="name"
value={ user.name }
onChange={ handleChange }
/><br/>
<div>{ errors.name?.message }</div>
<label htmlFor="email">이메일</label>
<input
id="email"
name="email"
value={ user.email }
onChange={ handleChange }
/><br/>
<div>{ errors.email?.message }</div>
<label htmlFor="cellphone">휴대폰</label>
<input
id="cellphone"
name="cellphone"
value={ user.cellphone }
onChange={ handleChange }
/><br/>
<div>{ errors.cellphone?.message }</div>
<button type="submit">가입</button>
</form>
<p>
이름: { user.name } {' '}
이메일: { user.email } {' '}
휴대폰: { user.cellphone } {' '}
</p>
</>
);
App 컴포넌트에서는 위에 언급했던 div의 errors 객체 내부에 있는 해당하는 name, email, cellphone이 있는지
옵셔널 체이닝으로 확인 후 message를 출력해준다.
immer실습
이벤트 handle 함수의 사용
const newState = produce(user, draft => {
const address = draft.extra.addressBook.find(address => address.id === Number(e.target.name));
address.value = e.target.value;
});
immer을 사용하면, 첫 번째 매개변수 -> 불변할 상태변수
두 번째 매개변수 -> 변화할 매개변수로 , find를 돌려주면 id가 동일한 요소가 튀어 나온다.
그 요소의 value 값에다가 변화한 타겟의 value 값을 넣어줬다.