Etc/기록들

[TIL] 5월 1일 기록

NaDuck 2023. 5. 2. 13:59

오늘의 할 일

  •  📌 어제자 TIL - [내일 할 일] 확인
  •  📌 ~20:00 팀 데일리 미션 출제
  •  📌 ~다음날 06:00 팀 데일리 미션 답변
  •  📌 TIL 작성
  • ✅ 어제자 데일리 미션 답변
  • ✅ week6 미션 셀프 코드 리뷰 & 질문 업로드 (pr 업데이트는 내일!)

 

오늘의 나는 무엇을 잘했을까?

  • 다른 팀원들의 데일리 미션 덕에 라우팅과 제어 컴포넌트에 대해 상세히 공부할 수 있는 기회가 되었다.
  • 오늘 면접 스터디 완료💪
    그리 어려운 문제는 아니었지만 어쨌든 술술 답변하고, 말투 부분에 있어서 많이 신경써서 답변해서, 팀원들의 좋은 피드백도 받을 수 있었다!

 

오늘의 나는 무엇을 배웠을까? ⭐(중요체크)

React로 데이터 다루기

네트워크 로딩 처리하기

데이터를 불러오는 동안에 유저가 행동하지 못하게 막아둘 필요가 있음 → isLoading state를 따로 관리

const [isLoading, setIsLoading] = useState(false)

const handleLoad = async (options) => {
    let result;
    try {
      setIsLoading(true);
      result = await getReviews(options);
    } catch (error) {
      console.error(error);
      return;
    } finally {
      setIsLoading(false);
    }
		...
}

return (
	<button disabled={isLoading} onClick={handleLoadMore}>
	  더 보기
  </button>
);

handleLoad 함수를 호출하는 순간, isLoadingtrue로 설정하여 button의 disabled로 설정되어 버튼을 누르지 못하게 한 다음, 데이터를 로드하는 동안 계속 유지된다.

그러다가 데이터 로드가 끝나고 finally 구문으로 isLoadingfalse로 바꾸어 버튼을 누를 수 있게 한다.

 

네트워크 에러 처리하기

네트워크가 느리거나 통신이 끊겨서 데이터를 받아오는 데 에러가 발생한 경우 → 프로그램 동작이 멈추는 위험성이 있고 또 유저에게도 에러 발생을 알려줄 필요가 있음

export async function getReviews({
  order = 'createdAt',
  offset = 0,
  limit = 6,
}) {
  const query = `order=${order}&offset=${offset}&limit=${limit}`;
  const response = await fetch(
    `https://learn.codeit.kr/api/film-reviews?${query}`
  );
  if (!response.ok) { // 👈
    throw new Error('리뷰를 불러오는데 실패했습니다');
  }
  const body = await response.json();
  return body;
}

response.ok = response가 성공적으로 받아지면 true를 리턴하고 아니라면 false를 리턴함.

!response.ok는 정상적인 주소로 fetch를 보냈는데, 응답코드가 200이 아닌 다른 코드를 받았을 때 실행된다. 즉 잘못된 주소로 fetch를 보내면 응답 자체를 받지 못하기 때문에 이 때는 “failed to fetch” 에러가 발생한다.

const [loadingError, setLoadingError] = useState(null);

const handleLoad = async (options) => {
    let result;
    try {
      setLoadingError(null);
      setIsLoading(true);
      result = await getReviews(options);
    } catch (error) {
      setLoadingError(error); // 👈
      return;
    } finally {
      setIsLoading(false);
    }
	  ...
  };

return (
	<div>
    {loadingError?.message && <span>{loadingError.message}</span>}
  </div>
)

catch문에서 받은 에러를 loadingError state에 설정 → catch문에서 return하기 때문에 finally까지만 실행되고 뒤에 있는 코드는 실행되지 않고 종료한다.

⇒ 즉 fetch 부분에서 throw error를 한 뒤, 컴포넌트 측에선 loadingError state로 네트워크 에러를 처리한다.

 

리액트에선 input의 값을 주로 state로 관리한다.

state 값과 input의 값을 동일하게 만드는 게 핵심! ⇒ 제어 컴포넌트

 

✅ input 관련 이벤트

  • onInput: 사용자가 input에 입력할 때마다 발생
  • onChange: 사용자 입력이 끝났을 때 발생하는 이벤트 ⇒ 리액트에서의 onChange prop은 바닐라 js의 onInput처럼 사용자가 값을 입력할 때마다 onChange 이벤트가 발생함
import { useState } from 'react';

function ReviewForm() {
  const [title, setTitle] = useState('');

  const handleTitleChange = (e) => {
    setTitle(e.target.value);
  };

  return (
    <form className="ReviewForm">
      <input value={title} onChange={handleTitleChange} /> // 👈 
    </form>
  );
}

export default ReviewForm;

JSX에서 input 태그의 onChange 이벤트 핸들러 함수에서 현재 받고 있는 값을 title state에 설정하는 코드

 

form 제출하기 = onSubmit

  1. 제출 버튼에 type=”submit”으로 설정하기
  2. 제출 버튼을 감싸는 form 태그에 onSubmit 이벤트 핸들러 등록하기
const handleSubmit = (e) => {
		// onSubmit의 기본동작은 input value를 가지고 GET 리퀘스트를 보내는 동작임.
		// 만약 이 기본동작을 막고 싶다면 preventDefault 함수 호출하기
    e.preventDefault();
    console.log({
      title,
      rating,
      content,
    });
  };

return (
    <form className="ReviewForm" onSubmit={handleSubmit}>  // 👈
      <input value={title} onChange={handleTitleChange} />
      <input type="number" value={rating} onChange={handleRatingChange} />
      <textarea value={content} onChange={handleContentChange} />
      <button type="submit">확인</button>  // 👈
    </form>
  );

 

하나의 state로 여러 input의 입력값 받아오기

import { useState } from 'react';

function FoodForm() {
  const [values, setValues] = useState({
    title: '',
    calorie: 0,
    content: '',
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(values); 
  };

  // ⭐ 포인트!
  const handleChange = (e) => {
    const {name, value} = e.target;  // 👈 input의 name과 value prop을 이용한다!
    setValues({
			// values의 title, rating, content 중에 name과 겹칠텐데, 이렇게 겹치는 프로퍼티 key는 가장 나중에 선언한 값으로 기존 값이 덮어씌워짐
      ...values, 
      [name]: value,
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" value={values.title} onChange={handleChange} /> // 👈 name, value 프로퍼티 주의깊게 보기!
      <input
        type="number"
        name="calorie"
        value={values.calorie}
        onChange={handleChange}
      />
      <input name="content" value={values.content} onChange={handleChange} />
      <button type="submit">확인</button>
    </form>
  );
}

export default FoodForm;

input이 각각 여러 개 있어도 모두 namevalue 프로퍼티를 갖는다는 공통점을 이용해 하나의 함수로 각 input의 값을 받아와서 state로 설정할 수 있다.

*이 때 state는 모든 input의 값을 받도록 객체 형태로 선언

 

setValues({
  ...values, 
  [name]: value,
});

하나의 input 값만 바꿔도 모든 input 값을 관리하고 있는 객체 values의 값을 바꿔야 하며, 이 때 새로 바뀌는 값을 제외한 기존의 다른 값들은 이전 values에서 갖고와서 spread 구문을 이용한다.

만약 기존 values={title: “alice”, calorie: “100”, content: “마이떵”} 이고, 새로 받은 값이 title: “john”이면 title이 중복되므로 가장 나중에 선언된 값으로 덮어 씌워지므로, 따라서 기존 값에서 현재 새로운 값만 업데이트한 결과를 낳는다. ⇒ values={title: “john”, calorie: “100”, content: “마이떵”}

 

제어 컴포넌트(Controlled Component)

input의 value값을 리액트에서 지정하고 제어

ex) 사용자는 소문자를 입력하지만 대문자로 다 변환되어 입력되어짐

const handleChange = (e) => {
  const nextValue = e.target.value.toUpperCase();
  setValue(nextValue);
};

<input name="title" value={value} onChange={handleChange} />

이게 가능한 이유는 input의 value 프로퍼티에 state를 내려주었고, onChange 프로퍼티에 이벤트 핸들러 함수를 내려주었기 때문

더 정확히는, 리액트의 input 태그는 value prop으로 어떤 read only 데이터를 설정할 수 있다. 따라서 사용자는 실제로 값을 입력할 때 그대로 input 화면에 설정되는 것이 아니라, 리액트 내부적으로 값을 state에 반영 → 그 state를 value prop으로 설정되어 화면에 보여지는 것이다.

 

비제어 컴포넌트(Unconrolled Componenet)

제어 컴포넌트와 달리, value 값을 지정하지 않는 컴포넌트 = 리액트에서 사용하는 값(state)이랑 실제 input 값이랑 다름. 경우에 따라서 필요한 방법!

제어 컴포넌트input의 값과 리액트의 state 값이 항상 일치하기 때문에 동작을 예측하기가 쉽고, input 값을 여러 군데에서 쉽게 바꿀 수 있다. 따라서 주로 권장되는 방법!

ex)

import { useState } from "react";

function MyInput({value, onChange}) { // 👈 부모 컴포에서 받은 value를..
	const handleChange = (e) => {
		const nextValue = e.target.value;
		// 이벤트 핸들러 함수는 부모 컴포에서 선언하고, 실제 호출은 자식 컴포에서 진행
		onChange(nextValue); 
	};

	return <input value={value} onChange={handleChange} />; // 👈 그대로 input vale에 연결
}

function App() {
	const [value, setValue] = useState('');

	const handleClear = () => setValue('');

	return (
		<div>
			<MyInput value={value} onChange={setValue} /> // 👈 setter 함수를 prop으로 전달 가능! 
			<button onClick={handleClear}>지우기</button>
		</div>
	);
}
  • 부모 컴포에서 관리하는 state를 자식 제어 컴포넌트에 전달함으로써 부모에서state의 값을 바꿈으로써 쉽게 자식 제어 컴포넌트의 input value를 바꿀 수 있음
  • 위 코드에서 부모 컴포에서 handleClear 함수는 해당 state의 값을 ''로 설정하는 것이고, 이는 자식 제어 컴포넌트(MyInput)의 input value도 그대로 ''로 설정됨

 

오늘의 나는 무엇이 궁금했나?

week6를 진행하면서 들었던 궁금점들 → 모두 week6 pr에 질문할 예정이다

  • useState 초기값으로 빈 배열 or null 중에 뭘 선택하는게 좋고 그런게 있나요?
  • 실제 서비스 사이트들을 보면 아이콘은 svg 태그로 넣는 것 같은데 이렇게 바꾸는게 좋을까요?
  • useEffect를 하나밖에 쓰지 못하나요? 예를 들어 어떤 state냐에 따라 작업을 달리 하는 경우에 어떻게 하나요?

 

오늘 하루 회고

월요벙 + 온라인 = 혼절

재택 공부는 항상 장단점이 극대화되는 것 같은데, 오늘은 단점이 더 컸던 것 같다😭 일단 집중하기가 많이 힘들어서 딴짓도 많이 하게 된 것 같고, 배우는 것들이 낯설다 보니 이해할 때까지 계속 반복해서 공부해서 지치는 것도 컸던 것 같다

하지만 하루 안에 내가 최소한으로 해야할 것들을 나만의 규칙으로 만들어서 꾸준히 지키고 있는 점은 잘하고 있는 것 같다! 괜히 다음날로 미루어서 하면 오히려 더 하기 싫어지는 것도 있고, 이렇게 최소한의 책임감을 부여하며 공부하는 것이 계속 꾸준히 할 수 있는 원동력이 되고 있다!

그리고 막판엔 집중해서 공부를 많이 할 수 있었는데, 역시 초반 기초를 이해하고 진행하니 강의에서 하는 설명은 잘 이해할 수 있게 됐다. 낼부턴 다시 오프라인 공부니까 그만큼 더 열심히 해야겠다 화이팅!