본문 바로가기

React Native

React #4 - immer, State, 회원가입 form 실습

  • 깊은 복사를 수행해주는 라이브러리
  • 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 값을 넣어줬다.