본문 바로가기
TIL/엘리스 SW 엔지니어 트랙 2기

엘리스 코딩 TIL (리액트, 20220624)

by VANAV 2022. 6. 30.

03. 유연하게 state 변경하기

 

Object를 갖는 State를 만들 때 주의사항

// 잘못된 예시

const [user, setUser] =
useState({name: "민수", grade:1})
setUser((current) => {
	current.grade = 2;
    return current;
})
// 좋은 예시

const [user, setUser] =
useState({name: "민수", grade:1})
setUser((current) => {
	const newUser = { ...current }
	newUser.grade = 2;
    return newUser
})
  • object 내의 값만 바꾸게 되면 object 그 자체는 바뀌지 않기 때문에 리액트가 알아차리지 못해 컴포넌트가 재 랜더링되지 않는다.
  • 새로운 object를 만들고 기존 오브젝트로부터 값을 복사해온 뒤 안에 있는 값을 변경해서 반환해야 한다.
  • JSX 문법에서 style 속성을 쓸 때 "(숫자) px" 표기 팁
    • style={{ borderRadius : "16px" === borderRadius : 16 }} 은 같다
    • 단위를 생략하고 string이 아닌 number로 값을 입력하면 자동으로 px 단위로 들어간다.
  • 가상선택자 - ::after 의 content
    • content는 가상element의 내용을 채워줄 텍스트를 입력하는 곳
    • 아무것도 입력 안하는 이유 = 텍스트를 추가할 필요가 없을 때
    • content를 써주지 않으면 after 자체가 적용이 안되기 때문에 빈값이라도 입력을 해주는 게 좋다.
  • input DOM Element 의 disabled Attribute
    • <input disabled="true" /> 가 되게 되면 input에 아무것도 입력을 할 수 없게 된다.
    • "disabled 값을 컴포넌트 내부의 input에 그대로 전달해줍니다" === <input disabled={disabled} /> 처럼 적어주라는 말이었음
  • Effect Hook
const App = () => {
	useEffect(EffectCallback, Deps?)
    
    // Deps = 변경을 감지할 변수들의 집합(배열)
    // EffectCallback = Deps에 지정된 변수가 변경될 때 실행할 함수
}
const App = () => {
	useEffect(() => {
    	console.log("컴포넌트 생성");
        
        return () => {
        	console.log("컴포넌트 소멸");
        }
    }, []);
    return <div></div>
}

// 이처럼 Deps에 빈 배열을 넣어주면 오직 컴포넌트의 생성과 소멸시에만 Effect Callback 함수가 호출되게 됩니다.

// Effect Callback 함수 내에서 return 해주는 또다른 함수가 Effect의 종료 시에 호출되기 때문에 
// 이는 곧 컴포넌트의 소멸 단계에 호출이 되는 함수라고 할 수 있겠습니다.
  • Effect Hook 을 이용하면 컴포넌트 내의 State나 Props의 변화를 감지해 원하는 로직을 실행할 수 있다.
  • Effect Hook 을 이용하면 함수 컴포넌트에서 side effect를 수행할 수 있다.
  • 컴포넌트가 최초로 랜더링 될 때, 지정한 State나 Prop가 변경될 때마다 이펙트 콜백 함수가 호출된다.

 

  • Hook의 종류와 그 설명(추가 설명)
    • useRef 는 함수를 메모이제이션하기 위해 사용되는 Hook이 아니다(useRef는 컴포넌트 생애주기 내에서 유지할 ref 객체를 반환하는 Hook이다 / 함수를 메모이제이션하기 위해 사용되는 Hook은 useCallback 이다)
    • useMemo 는 지정한 State나 Prop가 변경될 때 해당 값을 사용하는 다른 값을 저장하기 위해 사용한다.

 

const [count, setCount] = useState(0);
  • 위와 같이 선언된 count의 값을 변경하는 방법
    • setCount의 인자로 선언한 함수를 전달한다.
    • setCount의 인자로 익명 함수를 전달한다.
    • setCount의 인자로 변경할 값을 직접 전달한다.
    • ** count의 값을 직접 수정하면 안된다!! **

 

  • Hook 요약정리
    • Hook 이란? = Hook은 기존 함수형 컴포넌트에서도 클래스형 컴포넌트의 기능을 사용할 수 있게 하는 기능(즉, 함수형 컴포넌트에서 React state와 생명주기 기능(lifecycle features)을 연동(hook into) 할 수 있게 해부는 것이 바로 Hook)
    • 왜 사용하나요?
      • 기존의 react의 문제점
        1. 컴포넌트 사이에서 상태와 관련된 로직 재사용이 어려웠음
        2. 복잡한 컴포넌트들을 이해하기 어려웠음
        3. 클래스 - 사람과 기계를 혼동시킴(코드의 재사용성과 코드 구성을 어렵게 만들 뿐 아니라 React를 배울 때 큰 진입장벽)
      • React 16.8에서 Hook이 나오기 전엔 컴포넌트를 사용하던 중 state를 추가하고 싶을 때 클래스 컴포넌트로 바꾸곤 했을 겁니다. 이제 Hook을 사용하면서 손쉽게 함수형 컴포넌트로도 state를 사용할 수 있습니다! 즉, 클래스 없이 React를 사용할 수 있게 해주는 것입니다.
        • 물론 그렇다고 클래스형 컴포넌트가 사라지는 것은 아닙니다! 리액트는 기존에 사용하던 것들의 하위 호환성을 중요시하기 때문에 앞으로 클래스에 대한 업데이트도 염두에 둔다고 합니다.
    • Hook의 장점
      • 컴포넌트의 함수가 많아질 때 클래스 구성 요소로 리팩토링할 필요가 없다.
        • 일반적으로 React 컴포넌트가 함수 컴포넌트로 시작하는 경우가 있는데, 함수 컴포넌트에서 클래스 컴포넌트로 변경하려면 컴포넌트 요소가 얼마나 복잡한지에 따라 약간의 리팩토링이 필요합니다. React hooks를 사용하면 함수 구성 요소로만 상태 관리를 할 수 있기 때문에 리팩토링 노력이 최소화됩니다.
      • UI에서 로직을 더 쉽게 분리하여 두 가지 모두 재사용 가능하다.
        • Hook 및 UI를 사용하면 분리하기가 더 쉽습니다. 즉 코드를 재사용하기 위한 로직을 쉽게 만들 수 있습니다. Hook은 더 적은 상용구와 더 직관적인 UI 및 논리 구성으로 더 세련되게 구현할 수 있습니다. 코드의 재사용은 전체적으로 작성해야 할 코드의 양을 줄어들게 합니다. 전체적인 코드의 양이 줄어들면 코드의 가독성 또한 좋아집니다.
      • 기존의 코드를 다시 작성할 필요 없이 일부의 컴포넌트들 안에서 Hook을 사용할 수 있다.
        • Hook이 등장했다고 해서 기존의 모든 코드를 다시 작성해야 하는 것은 아닙니다. 기존의 코드와 잘 호환이 되기 때문에 필요한 곳에서 Hook을 사용하면 됩니다.
      • Hook을 사용하면 컴포넌트로부터 상태 관련 로직을 추상화가 가능하다.
        • Hook을 이용하면 컴포넌트별로 독립적인 테스트와 재사용이 가능합니다. Hook은 컴포넌트 간 계층 변화 없이 상태 관련 로직을 재사용할 수 있도록 도와줍니다.
    • Hook의 종류
      • State Hook
      • Effect Hook
    • Hook의 규칙 - 자바스크립트의 함수인 Hook을 사용하기 위한 두 가지 큰 규칙에 대해 알아봅시다.
      1. 최상위(at the Top Level)에서만 Hook을 호출해야 합니다.
        • 반복문(while), 조건문 (if) 혹은 중첩된 함수 내에 Hook을 호출하면 안 됩니다. Hook은 렌더링 시 항상 동일한 순서로 호출이 되어야 하며 그렇지 않을 경우 버그가 발생합니다.
        • 따라서 React가 useState() useEffect()가 여러 번 호출되는 중에도 Hook의 상태를 올바르게 유지할 수 있도록 해줘야 합니다. Hook은 React 함수의 최상위(at the top level)에서 호출되는 규칙을 따르면 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 Hook이 호출되는 것이 보장할 수 있습니다.
      2. 오직 React 함수 내에서 Hook을 호출해야 합니다.
        • Hook을 일반적인 자바스크립트 함수에서 호출하면 안 됩니다. Hook을 아래 규칙을 지켜서 호출해야만 컴포넌트의 모든 상태 관련 로직을 소스 코드에서 명확하게 볼 수 있습니다.
          • React 함수 컴포넌트에서 Hook 호출
          • 나만의 Hook에서 Hook 호출 
      3. 규칙을 지켜야 하는 이유
        • name !== '' 조건은 첫 번째 렌더링에서 참이기 때문에 Hook은 동작합니다. 하지만 사용자가 그다음 렌더링에서 폼을 초기화하면서 조건을 거짓으로 만들 수 있습니다. 렌더링 간에 Hook을 건너뛰기 때문에 Hook 호출 순서는 달라집니다. (변경된 name을 Hook에서 알 수가 없게 됩니다)
        • React의 특정 state가 어떤 useState()에서 호출되었는지 알 수 있는 이유는 React가 Hook이 호출되는 순서에 의존하기 때문입니다. 즉, 모든 렌더링에서 Hook의 호출 순서는 같아서 Hook이 올바르게 동작할 수 있는 것입니다.
        • 하지만 Hook을 조건문 안에서 호출한다면 Hook을 건너뛰는 경우가 생길 수 있고 Hook을 호출하는 순서가 달라집니다. 이에 따라 건너뛴 Hook 다음에 호출되는 Hook의 순서가 하나씩 밀리면서 버그를 발생시킵니다. 이것이 컴포넌트 최상위에서만 Hook을 호출하는 이유입니다.
        • 만약 조건부로 side effect를 실행하길 원한다면, 조건문을 Hook 내부에 넣으면 됩니다.
// 조건문 안에서 Hook을 사용함으로써 1번 규칙을 깼습니다.
if (name !== ''){
	useEffect(function persistForm(){
    	localStorage.setItem('formData', name);
    });
}
// 만약 조건부로 side Effect를 실행하길 원한다면, 조건문을 Hook 내부에 넣으면 됩니다.

useEffect(function persistForm() {
	// 더 이상 규칙을 어기지 않습니다.
    if(name !== ''){
    	localStorage.setItem('formData', name);
    }
});

 

댓글