프론트엔드 - 19

송현섭 ·2023년 4월 17일

프론트엔드

목록 보기
19/24
post-thumbnail

비회원 장바구니 기능 구현 (HOF, localStorage 활용)



  • localStorage 저장 기능을 활용해서 비회원이라도 장바구니에 상품을 담고 해당 데이터를 로컬에서 저장하여 조회가능하도록 함

  • fetchBoards 로 data 를 받아오고, 이를 map 함수로 뿌린 다음 버튼을 클릭하면 해당 요소 (el) 를 받을 수 있는 버튼 onClickBasket 함수를 만듬


// 비회원 장바구니에 클릭한 게시글을 넣어주는 함수
const onClickBasket = (el: IBoard) => () => {
  const baskets = JSON.parse(localStorage.getItem("baskets") || "[]");
	const temp = baskets.filter((basketEl: IBoard) => basketEl._id === el._id);
	if (temp.length === 1) {
	  alert("이미 담으신 물품입니다!!!");
	  return;
	}

  const { __typename, ...newEl } = el;
  baskets.push(newEl);
  localStorage.setItem("baskets", JSON.stringify(baskets));
};
  • 버튼을 누르면 해당하는 (el) 이 인자로 들어옴
  • 기존에 로컬스토리지에 저장된 데이터가 있다면 (장바구니에 담은 적이 있다면) 해당 데이터를 불러오고, 그렇지 않으면 빈 배열("[ ]")을 불러옴

  • if 조건문을 통해 filter로 현재 선택한 (el)의 id와 배열 안에 들어있는 el들의 id 값을 비교하여 같은 것이 있다면(이미 선택한 적이 있어서 데이터에 저장되어 있다면) 알림창을 띄우고 함수 종료

  • rest 메소드를 활용해 받아오는 (el) 에서 필요없는 key와 value를 제거해 줌


  • 이후 push 로 정리된 newEl을 baskets 안에 넣어주고 해당 baskets을 localstorage.setItem 으로 로컬스토리지에 저장
    *localstorage 에는 문자열만 저장되기 때문에 배열, 객체로 들어오는 값은 꼭 JSON.stringify, JSON.parse 사용해서 변환해줘야 함!!






+a) 비회원 장바구니 조회 기능 구현

import { useEffect, useState } from "react";
import { IBoard } from "../../src/commons/types/generated/types";

export default function BasketLoggedInPage() {
  const [basketItems, setBasketItems] = useState([]);

  useEffect(() => {
    const baskets = JSON.parse(localStorage.getItem("baskets") || "[]");
    setBasketItems(baskets);
  }, []);

  return (
    <div>
      <h1>나만의 장바구니(비회원전용!!)</h1>
      {basketItems.map((el: IBoard) => (
        <div key={el._id}>
          <span>{el.writer}</span> | 
					<span>{el.title}</span>
        </div>
      ))}
    </div>
  );
}
  • localStorage.getItem 으로 불러와서 변수에 담은다음 map 함수로 화면에 뿌려줄 수 있음

    *이 때 그냥 렌더링 시 화면에 표출되게 하면 프론트 서버에서 프리렌더링 하는 동안 localStorage를 읽지 못해 [undefined] 에러가 발생함으로 useEffect 함수 안에 넣어서 렌더링이 완료된 이후 불러올 수 있도록 함!!









폼 라이브러리 (react-hook-form)


  • react-hook-form 을 사용하면 함수를 일일히 만들어 바인딩해 줄 필요 없이 간편하게 코딩 가능 (노가다성 코딩 필요 X)
    ex. 기존에 setState를 이용해 각 input의 입력값을 받아오던 것을 라이브러리로 한 번에 해결 가능

  • 폼 라이브러리 종류 = [react-form, redux-form, react-hook-form, formik...]





react-hook-form 사용하기


  • 기존 방식은 setState를 사용하기 때문에 state가 변경될 때마다 렌더링이 되어 불필요한 렌더링으로 인해 비효율적 (렌더링으로 속도 저하)

  • 반면에 react-hook-form 은 각 값을 입력받고 등록된 함수가 실행 될 때 한 번에 처리하기 때문에 불필요한 렌더링이 없고 효율적임
    *이렇게 한 번에 처리하는 방식을 비제어 컴포넌트라고 함

    *다만 react-hook-form 의 default 가 비제어 방식일 뿐, 설정에서 mode : "onChange"로 변경하여 제어 컴포넌트 방식으로 사용할 수도 있음





비제어 컴포넌트 vs 제어 컴포넌트



[비제어 컴포넌트] = useRef 처럼 submit 함수가 실행되면 ref가 부여된 input의 값을 한 번에 변경
복잡한 폼 만들 시 유용 (ex. Pw 입력 시 입력값이 ""로 표시되게 하는 기능)


[제어 컴포넌트] = 사용자 입력을 기반으로 state를 실시간으로 관리 (입력될 때마다 state 값이 실시간으로 변경 됨)
*단순한 폼 제작에 유용


따라서 기본적인 기능은 비제어 컴포넌트, 복잡한 것에만 제어 컴포넌트 활용하는 것이 BEST!!







react-hook-form 설치


yarn add react-hook-form




react-hook-form 사용

const ReactHookForm = ()=>{
	// react-hook-form 에서 useForm을 제공합니다.
	const {register , handleSubmit} = useForm()

	// 등록하기 함수 -> handleSubmit이 조종해주는 함수 입니다.
	const onClickSubmit = (data)=>{
		console.log(data)
	}

	return(
		<form onSubmit={handleSubmit(onClickSubmit)}>
			<input type="text" {...register("writer")}/>
			<input type="text" {...register("title")}/>
			<input type="text" {...register("contents")}/>
			<button type="reset"> 등록하기 </button>
		</form>
	)
}
export default ReactHookForm
  • useForm 에서 register, handleSubmit 을 꺼내와서 input에 입력되는 값을 받아올 수 있음

  • input 태그에 {...register("writer")] 를 넣어서 입력되는 값을 받아서 후에 data로 뽑아올 수 있는데 이 때 ("writer") 는 해당 data 객체의 key값이 됨


  • 위의 방식은 form 태그의 속성 중 onSubmit 을 활용해서 입력값을 data에 담는 방식
    *form 태그는 하위 자식요소들을 하나로 묶어주는 기능이 있는데 이 때 onSubmit 속성을 부여하면 form 태그가 품고있는 input의 입력값들을 onSubmit에 바인딩 된 함수 안으로 보내주는(제출) 기능이 있음




  • HOF 로 인해 onSubmit으로 제출되는 data는 onClickSubmit 함수가 아닌 해당 함수를 인자로 받는 handleSubmit 함수에 받아짐

    [주의!] - 위 코드는 실제 handleSubmit 함수 내부 구조가 아님!! (예시자료)


    -예시로 들어 설명하자면, input의 입력값들은 ...register 기능으로 각각 key값을 부여받고, onSubmit으로 handleSubmit 함수에 전달되며, 이 전달된 data는 함수 내부에 formData에 객체 형태로 저장 됨


    -이후 인자로 받은 onSubmit 함수 안의 인자로 해당 data 객체를 다시 집어넣어서 그대로 보냄


    -onSubmit 함수는 data 매개변수에 해당 data객체를 인자로 받아 이를 조회해 볼 수 있게 됨





+a) form 태그 내부의 button 의 type

  • type = "reset" = form 내부의 input 값 모두 삭제

  • type = "submit" = form 내부의 input 값을 보냄(제출) - default 값

  • type = "button" = form 내부에 영향을 주지 않는 개별적ㅇ로 작동하는 기능을 만들고 싶을 때 사용
    *form 내부의 button의 타입이 submit인 경우, 해당 버튼에 다른 함수를 바인딩하게 되면 예상치 못한 오류 발생 가능!!
    [다른 기능을 주고 싶다면 꼭 type="button"을 주도록 할 것!!]









검증 라이브러리 (yup)


  • 기존의 까다롭게 조건문을 반복하여 작성하던 검증과정을 yup 이라는 라이브러리로 간단하게 대체 가능




yup 사용하기


설치

yarn add @hookform/resolvers yup




react-hook-form 과 연결 및 사용

import * as yup from 'yup'
import {useForm} from 'react-hook-form'
import {yupResolver} from '@hookform/resolvers/yup'


// yup 에러메세지 생성해주기 -> 제어 컴포넌트 형태로 사용해야 합니다.
const schema = yup.object().shape({
	myWriter : yup.string()
								.email('이메일 형식이 적합하지 않습니다.')
								.required('필수 입력값입니다.')
	myPassword : yup.string()
									.min(4,'비밀번호는 최소 4자리 이상입니다.')
									.max(15,'비밀번호는 최대15자리 입니다.')
									.required('필수 입력값 입니다.') 
})

const ReactHookForm = ()=>{
	//formState에서 에러메세지들을 받아오게 됩니다.
	const {register , handleSubmit, formState} = useForm({
		// schema는 위에서 만들어 둔 schema입니다.
		resolver : yupResolver(schema),
		mode : "onChange"
	})

	// 등록하기 함수 -> handleSubmit이 조종해주는 함수 입니다.
	const onClickSubmit = (data)=>{
		console.log(data)
	}

	return(
		<form onSubmit={handleSubmit(onClickSubmit)}>
			이메일 : <input type="text" {...register("myEmail")}/>
				{ /* 우리가 생성한 yup의 에러메세지는 항상 errors에 담기는데 이 에러는 있을때도 있고 없을 때도 있기 때문에 옵셔널 체이닝을 붙여야 합니다. */}
				<div> {formState.errors.myEmail?.message}</div>
			비밀번호 : <input type="text" {...register("myPassword")}/>
				<div> {formState.errors.myPassword?.message}</div>
			<button styled={{ backgroundColor: formState.isValid ? "yellow" : "" }}> 등록하기 </button>
		</form>
	)
}
export default ReactHookForm
  • yup 같은 검증라이브러리는 보통 form 과 많이 사용하기 때문에 react-hook-form 과 연결해서 사용가능


  • useForm 안에 resolver로 yupResolver를 넣고 미리 설정해 둔 schema 구조도 넣어 줌

  • schema에는 form 에서 제출되는 각 input 입력값의 검증 조건들을 설정할 수 있음

    -.string( ) = 문자열만 입력가능
    -.email( ) = 이메일 형식에 맞는지 체크
    -.required( ) = 반드시 값이 입력되어야 함


  • 이후 return 문 안에서 formState.errors.myEmail?.message 형식으로 요소를 만들어서 조건부렌더링에 따라 조건에 맞지 않다면 해당 에러메시지가 출력되도록 설정

  • formState.isValid 로 지정한 모든 조건에 부합한다면 true를 반환하는 기능을 활용해 버튼 비활성화 여부 결정






+a) useform - formstate의 각 기능들

  • formstate.dirty
 <button type="button" onClick={onSave} disabled={!formState.dirty}>
        Save
      </button>

   사용자가 form을 수정했다면 값은 true. (이를 활용해 값의 변경이 있을 때에만 버튼을 활성화하는 식으로 활용 가능)







  • formstate.isSubmitted
 {formState.isSubmitted && <span>Form submitted!</span>}
 form이 제출되었는지 여부를 체크
 





  • formstate.isValid
      <button type="submit" disabled={!formState.isValid}>Submit</button>

     form이 유효한  (에러가 없는지) 여부를 체크






  • formstate.isValidating
   {formState.isValidating && <span>Validating...</span>}
  
    form의 유효성 검사가 진행 중인 경우 이 값은 true. (이를 이용해 유효성 검사가 진행중인 동안 로딩바 등의 UI 요소 추가하여 표시 가능)
       






  • formstate.errors
  {formState.errors.email && <span>Please enter a valid email address</span>}

  form 에 에러가 있을 경우 ```formState.errors.email.message``` 로 에러메시지 표시
profile
막 발걸음을 뗀 신입

0개의 댓글