섹션 6 State and Lifecycle

State 와 Lifecycle의 정리⭐

state

  • state 란 리액트 Componenet의 상태를 의미한다. 여기서 상태란 정상 / 비정상의 의미 보다는 데이터라는 쪽에 가까운 의미를 내포하고 있다.
  • 즉, state 는 리엑트 컴포넌트에서 변경 가능한 데이터를 의미한다.
  • 여기서 state를 사용하는 가는 각 개발자의 정의에 따라 움직인다고 보면된다.
  • 렌더링이나 데이터 흐름에 사용되는 값만 state 에 포함시켜야 한다.
    • 왜냐하면 결국 state란 데이터의 변경과 함께 렌더링이 발생하기 때문에 불필요한 데이터를 등록했다간,
  • 리엑트의 state는 JavaScript 객체라고 봐도 무방하다.
class LikjeButton extends React.Component {
	constructor(props){
		super(props)
		
		this.state = {   // state 를 정의함 (클래스 컴포넌트는 이방식, 함수 컴포넌트는 훅을 사용함)
			liked: false
		};
	}

	...
}
  • state 는 직접 수정할 순 없다. (하면 안된다) 왜냐하면 렌더링과 연관된 값들이기 때문에 개발자의 의도한 렌더링과는 다르게 나오게 만들 수도 있기 때문이다.
// state를 직접 수정(잘못된 방법)
this.state = {
	name: 'Inje'
};

// state를 setState함수를 사용한 수정(정상적인 사용법)
this.setState({
	name: 'Inje'
});

Lifecycle

  • 생명주기
  • 리엑트 컴포넌트가 생성되는 시점, 사라지는 시점이 정해져 있다.

20240523180045

  • mount : 컴포넌트 생성단계
  • update : 새로운 props를 받거나, 적절하게 수정되거나, 강제로 수정하거나
  • unmount : 상위 컴포넌트에서 해당하는 컴포넌트를 더 이상 표시 하지 않을 때, 모든 과정을 거쳐 컴포넌트는 사용되지 않게된다.
  • 위의 update되는 state와 관련된 함수는 3가지만 배우게 되었지만, 이 밖에도 다양한 방식으로 가능은 하나 배우진 않음.
  • 핵심은 컴포넌트가 시간의 흐름에 따라 생성과 갱신, 이후 사망하여 종결되는 그러한 구조를 갖고 있다는 것을 명확히 인지하는 것이다.

(실습) state 사용하기

섹션 7 Hooks

Hooks 의 개념과 useState, useEffect

20240523212344 20240523212501

  • 기본적으로 앞전에 배운 클래스 컴포넌트는 state를 자유 자재로 다루기 쉽도록 메서드를 제공한다.
  • 이에 반해 함수형 컴포넌트는 그렇지 못하고, 이에 따라 라이프사이클, state 사용이 가능케 하는 역할을 Hooks가 대신해준다.
  • Hook 이란 영어 단어 갈고리 처럼 어떤 특정 영역, 순간에 컴포넌트의 생명주기가 시작하여 끝나거나 갱신될 수 있도록 하는 것을 의미한다. 그리하여 이때 실행되는 함수가 Hook 이라고 설정한 것이다.

20240523222723

  • 이러한 훅은 기본적으로 접미사가 use로 시작한다. 커스텀 훅은 이름을 마음대로 작성이 가능하나, 이 역시 기본적인 법칙을 지켜주는게 당연히 유지보수에 효과적이다.

useState() Hook

  • state 를 사용하기 위한 Hook 이다.
  • 함수 컴포넌트는 기본적으로 class 컴포넌트와는 달리 state를 따로 작성하거나 하여 사용이 안된다고 한다.
import React, { useState } from "react";

function Counter(props) {
	var count = 0;

	return (
		<div>
			<p>{count}번 클릭했습니다.</p>
			<button onClick={() => count++}>
				클릭
			</ button>
		</ div>
	);
}
  • 위의 예시처럼 만들게 되면, Count 값을 증가 시키는 것은 가능하다. 재 랜더링은 일어나지 않아 카운트 값이 화면에 나타나지는 않게 된다. => 이런 경우에 따라 재 랜더링을 useState 훅을 사용하면 된다.
//useState 사용방법
const [변수명, set함수명] = useState(초기값);
import React, { useState } from "react";

function Counter(props) {
	const [count, setCount] = useState(0);

	return (
		<div>
			<p>{count}번 클릭했습니다.</p>
			<button onClick={() => setCount(count + 1)}>
				클릭
			</ button>
		</ div>
	);
}
  • useState 구문을 사용하고, 카운트 값이 변경되면 setCount와 함께 재 랜더링 과정을 보여준다.
  • 클래스 컴포넌트에서 하던 setState를 실행하거나 마찬가지이다.
  • 그러나 특이사항은 useState 의 경우 변수 각각에 대해 set 함수가 따로 존재하게 만들어야 한다는 점에서 차이가 발생한다.

useEffect()

  • side Effect 를 수행하기 위한 Hook 이다.
  • side effect 란? 부정적인 의미는 없고, 효과, 혹은 영향이라는 단어로 쓰인 것이며 서버에서나 돔에서 수동으로 데이터를 받아오는 경우 같은 것들이 포함된다.
  • 여기에 effect 라는 이름이 붙는 이유는, 다른 컴포넌트에 영향을 미칠 수 있으며, 렌더링 중에는 작업이 완료될 수 없기 때문이다.
  • 즉, 이러한 상황에서 리액트의 함수 컴포넌트에서 side에서 effect 가 발생한다는 이름에서 side Effect 라는 개념이 생긴 것이며, 이를 실행할 수 있게 해주는 Hook 을 의미한다.
  • 클래스 컴포넌트가 사용될 때의 생명주기 함수의 mount, update, unmount를 하나로 묶어둔 효과를 지닌다.

useEffect() 사용법

useEffect(이펙트 함수, 의존성 배열);
  • 의존성 배열 : 해당 이팩트가 의존성을 갖고 있는 배열이다. 배열 안에 값들이 변경이 발생하면 이펙트 함수가 실행된다.
  • 본 함수는 처음 컴포넌트가 렌더링 된 이후나, 업데이트 이후 재 랜더링 된 이후에 실행된다.
    • mount, unmount 단 한 번 씩만 실행되게 하고 싶다면 의존성 배열을 빈 배열을 넣어주면 되다.
    • 의존성 배열을 아예 생략하면 컴포넌트가 업데이트 될 때 호출이 된다.
improt React, { useState, useEffect } from react;

function Counter(props) {
	const [count, setCount] = useState(0);

	// componentDidMount, componentDidUpdate 와 비슷하게 동작한다. 
	useEffect(() => {
		// 브라우저 API를 사용해서 document 의 타이틀 업데이틀 한다. 
		// 실행 자체가 컴포넌트 안에서 접근하기 때문에, 함수 컴포넌트 내부의 state, props에도 접근이 가능해진다. 
		document.title = `You clicked ${count} times`;
	});

	return (
		<div>
			<p>{count}번 클릭했습니다.</p>
			<button onClick={() => setCount(count + 1)}>
				클릭
			</ button>
		</ div>
	);
}
// componentDidUnmount와 동일한 기능을 사용하는 예시 
improt React, { useState, useEffect } from react;

function UserStatus(props) {
	const [isOnline, setIsOnline] = useState(null);

	function handleStatusChange(status) {
		setIsOnline(status.isOnline);
	}

	useEffect(() => {
		ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
		// 컴포넌트가 언마운트 될때 하는 행동을 동일하게 return 하면서 진행함 
		return () => {
		
			ServerAPI.unsubscribeUserStatus(props.user.id, handleSatatusChange);
		};
	});

	if (isOneline == null) {
		return '대기중...';	
	}
	return  isOnline ? '온라인'; '오프라인';
}
// useEffect 훅을 여러개 사용하는 예시 코드 
function UserStatusWithCounter(props) {
	const [count, setCount] = useStatus(0);

	useEffet(() => {
		document.title = `${count}번 클릭했습니다.`;
	});

	const [isOnline, setIsOnline] = useState(null);
	useEffect(() => {
		ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
		// 컴포넌트가 언마운트 될때 하는 행동을 동일하게 return 하면서 진행함 
		return () => {
			ServerAPI.unsubscribeUserStatus(props.user.id, handleSatatusChange);
		};
	});

	function handleStatusChange(status) {
		setIsOnline(status.isOnline);
	}
	// . . .
}

useEffect 사용법 정리

useEffect (() => {
	// 컴포넌트가 마운트 된 이후,
	// 의존성 배열에 있는 변수들 중 하나라도 값이 변경되었을 때 실행됨
	// 의존성 배열에 빈 배열([])을 넣으면 마운트와 언마운트 시 단 한 번 씩만 실행됨
	// 의존성 배열 생략 시 컴포넌트 업데이트 시마다 실행됨
	
	return () => {
		// 컴포넌트가 언마운트 되기 전에 실행됨
	}
}, [의존성 변수1, 변수2, 변수3, ... ]);

useMemo, useCallback, useRef

useMemo

  • Memoized value 를 리턴하는 Hook 이다.

Memoization

  • 최적화를 위한 일종의 방식으로, 연산량이 높은 연산 결과 등이 존재 할 경우, 해당 상황을 저장해두고 같은 연산이 필요할 경우 재사용하는 방식을 의미한다.
  • 자원의 불필요한 사용, 재연산 등을 막을 수 있음.

useMemo 사용 방법

const memoizedValue = useMemo(
	() => {
		// 연산량이 높은 작업을 수행하여 결과를 반환
		return computeExpensiveValue(의존성 변수1, 의존성 변수2);
	},
	[의존성 변수1, 의존성 변수2]
);
  • 연산량이 높은 렌더링의 반복을 대응할 수 있다.
  • 알아둬야 할 점은 useMemo에 연결되는 함수는 렌더링 도중에 일어난다는 점이다.
    • 이 말은 useMemo를 사용할 수 없다. 대표적인 경우가 side effect 를 위한 useEffect 같은 것이 그러하고, 이런 경우 useEffect 훅을 사용해야 한다.
  • 의존성 배열을 넣지 않음 = 매 렌더링마다 함수가 실행됨. 이러면 다른 useState와 아무런 차이가 없다.
const memoizedValue = useMemo(
	() => {
	return computeExpensiveValue(a, b)
	}
);
  • 의존성 배열이 빈 배열일 경우, 컴포넌트 마운트 시에만 호출됨
const memoizedValue = useMemo(
	() => { 
		return computeExpensiveValue(a, b)
	}, []
);

useCallback() Hook

  • useMemo() 와 유사하지만 값이 아닌 함수를 반환해준다.
  • 의존성 배열이 바뀌면 함수를 리턴해서 무언갈 동작하게 만드는 것이다.

useCallback() 사용법

const memoizedCallback = useCallback(
	() => {
		doSomething(의존성 변수1, 의존성 변수2);
	},
	[의존성 변수1, 의존성 변수2]
);
  • useMemo() 처럼 의존성변수의 변화에 따라 움직이며, 메모훅과 동일한 해동 패턴을 보여준다.

동일한 역할을 하는 두 줄의 코드

useCallback(함수, 의존성 배열);
useMemo(() => 함수, 의존성 배열);

useRef()

  • Reference를 사용하기 위한 훅을 의미한다.

Reference란?

  • 특정 컴포넌트에 접근할수 있는 객체를 의미한다.
  • 즉, useRef() 훅은 Reference를 반환하는 것으로 볼 수 있다.
  • refObject.current = 현재 참조하고 있는 Element 를 의미한다.

useRef() 훅 사용법

const refContainer = useRef(초깃값);
// 이렇게 reference 값으로 감싸지게 되면, 
// 해당 값은 라이프타임 전체에 걸쳐 유지되게 된다.
// 컴포넌트를 언마운트 전까진 계속 유지되도록 만드는 것이라고 볼 수 있다. 
function TextInputWithFocusButton(props) {
	const inputElem = useRef(null);

	const onButtonClick = () => {
		// `current` 는 마운트된 input element 를 카리킴
		inputElem.current.focus();
	};

	return (
		<>
			<input ref={inputElem} type="text" />
			<button onClick={onButtonClick }>
			Focus the input
			</button>
		</ >
	)
}
  • useRef 훅의 특징은, 렌더링 될 때마다 매번 같은 reference를 반환한다는 점이다.
  • useRef() 훅은 내부의 데이터가 변경되었을 때 별도로 알리지 않는다. 따라서 어떤 변화를 인지하고 행동을 발생시키고 싶다면 Callback Ref를 사용해야 한다.

Callback ref

  • 리엑트는 ref가 다른 노드에 연결 될 때마다 callback 을 호출한다.
function MeasureExample(props) {
	const [height, setHeight] = useState(0);
	const measuredRef = useCallback(node => {
		if (node !== null) {
			setHeight(node.getBoundingClientRect().height)'
		}
	}, []);

	return (
		<>
			<h1 ref={measuredRef}>안녕, 리액트</h1>
			<h2>위 헤더의 높이는 {Math.rount(height)}px 입니다.</h2>
		</>
	);
}

Hook의 규칙과 Custom Hook 만들기

Hook

  • Hook은 무조건 리액트 컴포넌트 최상위 레벨에서만 호출해야 한다.
  • Hook은 컴포넌트가 렌더링 될 때마다 매번 같은 순서로 호출되어야 한다.

잘못된 Hook 사용법

function MyComponent(props) {
	const [name, setName] = useState('haryu');

	if (name !== '') {
		useEffect(() => {
			// 생략
		});
	}
	// 생략 
}
  • 이 코드는 name에 따라 조건문의 결과에 따라 useEffect가 동작한다.
  • Hook이 항상 같은 순서로 렌더링 되는 것을 보장하지 않음. 그렇기에 문제가 있음.

리액트 함수 컴포넌트에서만 Hook 호출 가능하다

개발에 도움이 되는 패키지

  • eslint-plugin-react-hooks : hook 의 규칙을 따르도록 강제해준다. hook의 규칙을 따르는지 자동으로 경고를 띄우며, 수정 방안도 보여주는 유용한 패키지이다.

Custom Hook

  • 반복적으로 사용되는 로직을 재사용하기 위한 도구.

Custom Hook 을 만들어야 하는 상황

import React, { useState, useEffect } from 'react';

function UserStatus(props) {
	const [isOnline, setIsOnline] = useState(null);

	useEffect(() => {
		function handleStatusChange(status) {
			setIsOnline(status.isOnline);
		}

		ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
		return () => {
			ServierAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
		};
	});

	if (isOnline === null) {
		return '대기중...';
	}
	return isOnline ? '온라인' : '오프라인';
}
import React, { useState, useEffect } from 'react';

function UserListItem(props) {
	const [isOnline, setIsOnline] = useState(null);

	useEffect(() => {
		function handleStatusChange(status) {
			setIsOnline(status.isOnline);
		}

		ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
		return () => {
			ServierAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
		};
	});

	return (
		<li style={{ color: isOnline ? 'green' : 'black' }}>
			{props.user.name}
		</li>
	)
}
  • 중복되는 코드가 보여진다.
  • 이러한 경우 커스텀 훅으로 추출이 가능하다.
  • 기본적으로 함수와 구성이나 사용법은 동일하나, use라는 키워드는 켜져 있어야 한다.
  • Hook은 무조건 리액트 컴포넌트 최상위 레벨에서만 호출해야 한다.
  • Hook은 컴포넌트가 렌더링 될 때마다 매번 같은 순서로 호출되어야 한다.

Custom Hook 추출하기

  • 원칙 : 이름이 use로 시하고 내부에서 다른 Hook을 호출하는 하나의 자바 스크립트 함수다.
import React, { useState, useEffect } from 'react';

function useUserStatus(userId) {
	const [isOnline, setIsOnline] = useState(null);

	useEffect(() => {
		function handleStatusChange(status) {
			setIsOnline(status.isOnline);
		}

		ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
		return () => {
			ServierAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
		};
	});

	return isOnline
}
  • 커스텀 훅은 단순 함수와 동일하고, 그렇기에 값을 받고, 반환하는 것에 자유롭게 설정이 가능하다. 기본적으론 자유지만 규칙을 따르게 해서 구분하고 있다.

Custom Hook 사용하기

  • 추출한 내용을 적용하면 다음과 같이 바뀐다.
function UserStatus(props) {
	const isOnline = useUserStatus(props.user.id)

	if (isOnline === null) {
		return '대기중...';
	}
	return isOnline ? '온라인' : '오프라인';
}

function UserListItem(props) {
	const isOnline = useUserStatus(props.user.id)

	return (
		<li style={{ color: isOnline ? 'green' : 'black' }}>
			{props.user.name}
		</li>
	)
}
  • 이때 중요한 것이 여러개의 컴포넌트에서 하나의 Custom Hook 을 사용해도 모든 컴포넌트 내부 state, effects 는 전부 분리되어 있다.
  • 각 Custom Hook의 호출 또한 완전히 독립적이다.
  • 독립적이다보니 Hook 사이에서 데이터 공유 방법이 따로 있다.
const userList = {
	{ id: 1, name: }
	{ id: 2, name: }
	{ id: 3, name: }
};

function ChatUserSelector(props) {
	const [userId, setUserId] = usestate(1);
	const isUserOnline = useUserStatus(userId);

	return (
		<>
			<Circle color={isUserOnline ? 'green' :'red'} />
			<selctr
				value={userId}
				onChange=(event => setuserId((Numbler(event.target.value))
			/>
				{userList.map(user => (
					<option key={user.id} value={user.id}>
						{user.name}
					</option>
				))}
			</selector>
	:;
}

(실습) Hooks 사용해보기