state & LifeCycle은 주로 클래스 컴포넌트와 관련됨.
리액트의 핵심 중 핵심!!!

최근에는 클래스 컴포넌트를 거의 사용하지 않기 때문에 이런 개념이 있다는 것 정도만 알면 됨.

import React from "react";
const styles = {
wrapper: {
margin: 8,
padding: 8,
display: 'flex',
flexDirection: 'row',
border: '1px solid grey',
borderRadius: 16,
},
messageText: {
color: 'black',
fontSize: 16,
},
};
class Notification extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
console.log(`${this.props.id} componentDidMount() called.`);
}
componentDidUpdate() {
console.log(`${this.props.id} componentDidUpdate() called.`);
}
componentWillUnmount() {
console.log(`${this.props.id} componentWillUnmount() called.`);
}
render() {
return (
<div style={styles.wrapper}>
<span style={styles.messageText}>{this.props.message}</span>
</div>
);
}
}
export default Notification;
import React from "react";
import Notification from "./Notification";
const reservedNotifications = [
{
id: 1,
message: "안녕하세요, 오늘 일정을 알려드립니다.",
},
{
id: 2,
message: "점심식사 시간입니다.",
},
{
id: 3,
message: "이제 곧 미팅이 시작됩니다.",
},
];
var timer;
class NotificationList extends React.Component {
constructor(props) {
super(props);
this.state = {
notifications: [],
};
}
componentDidMount() {
const { notifications } = this.state;
timer = setInterval(() => {
if (notifications.length < reservedNotifications.length) {
const index = notifications.length;
notifications.push(reservedNotifications[index]);
this.setState({
notifications: notifications,
});
} else {
this.setState({
notifications: [],
});
clearInterval(timer);
}
}, 1000);
}
componentWillUnmount() {
clearInterval(timer);
}
render() {
return (
<div>
{this.state.notifications.map((notification) => {
return <Notification key={notification.id} message={notification.message} />;
})}
</div>
);
}
}
export default NotificationList;
import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import Library from './chapter_03/Library';
import Clock from './chapter_04/Clock';
import CommentList from './chapter_05/CommentList';
import NotificationList from './chapter_06/NotificationList';
createRoot(document.getElementById('root')).render(
<React.StrictMode>
<NotificationList />
</React.StrictMode>
);
reportWebVitals();



첫 번째는 state로 선언된 변수, 두 번째는 해당 state의 set함수이다.






return () => { Server ~};하는데 해당 함수 안에는 구독을 해지하는 API를 호출하도록 되어 있음. useEffect에서 리턴하는 함수는 컴포넌트가 unmount될 때 호출됨.


메모를 해두었다가 나중에 다시 사용한다.


callback이라고 부름. 의존성 배열에 있는 변수 중 하나라도 변경되면 Memoization된 callback함수를 반환함.



현재 참조하고 있는 Element를 의미함.


단순한 JavaScript 함수지만 두 가지 지켜야 할 규칙이 있다.
1. Hook은 무조건 최상위 레벨(React함수 컴포넌트)에서만 호출해야 한다.
따라서 반복문, 조건문 또는 중첩된 함수 안에서 Hook을 호출하면 안 된다.
이 규칙에 따라 Hook은 컴포넌트가 렌더링될 때마다 매번 같은 순서로 호출돼야 한다. 그래야 React가 다수의 useState Hook과 useEffect Hook에 호출해서 컴포넌트의 state를 올바르게 관리할 수 있다.

리액트 함수 컴포넌트에서만 Hook을 호출해야 한다.eslint-plugin-react-hooks
목적 : 반복적으로 사용되는 로직을 Hook으로 만들어 재사용하기 위함.
<Custom Hook을 만들어야 하는 상황>


use로 시작하고 내부에서 다른 Hook을 호출하는 하나의 자바스크립트 함수다. 

반드시 use로 시작해야 한다.
1) useCounter() Custom Hook 만들기
import React, {useState} from "react";
function useCounter(initialValue) {
const [count, setCount] = useState(initialValue);
const increaseCount = () => setCount((count) => count + 1);
const decreaseCount = () => setCount((count) => Math.max(count - 1), 0);
return [count, increaseCount, decreaseCount];
}
export default useCounter;
// useCounter Hook은 초기 카운트 값을 파라미터로 받아 카운트라는 이름의
// state를 생성하여 값을 제공하고 카운트 증가 및 감소를 편리하게 할 수 있도록
// 함수를 제공하는 훅이다.
2) Accommodate 컴포넌트 만들기
useCounter Hook을 사용하는 Accommodate 함수 컴포넌트 만들기.
이 컴포넌트를 사람을 수용하는 시설에서 사용한다고 가정.
import React, {useState, useEffect} from "react";
import useCounter from "./useCounter";
const MAX_CAPACITY = 10;
function Accommodate(props) {
const [isFull, setIsFull] = useState(false);
const [count, increaseCount, decreaseCount] = useCounter(0);
useEffect(() => {
console.log("=================");
console.log("useEffect() is called.");
console.log(`isFull: ${isFull}`);
});
useEffect(() => {
setIsFull(count >= MAX_CAPACITY);
console.log(`Current count value: ${count}`);
}, [count]);
return (
<div style={{ padding: 16 }}>
<p>{`총 ${count}명 수용했습니다.`}</p>
<button onClick={increaseCount} disabled={isFull}>
입장
</button>
<button onClick={decreaseCount}>퇴장</button>
</div>
);
}
export default Accommodate;
// useCounter Hook을 사용해 카운트를 관리함.
// 최대 카운트 개수는 MAX_CAPACITY라는 이름의 상수로 정의.
// 카운트 개수가 최대 용량을 초과하면 경고 문구가 표시되며 더 이상 입장 불가.
// useEffect Hook 작동 방식 확인 위해 일부러 두 개의 useEffect Hook 사용
// 1) 의존성 배열 없는 형태 : 컴포넌트 마운트 된 직후 호출. 이후 컴포넌트 업데이트 될 때마다 호출.
// 2) 의존성 배열 있는 형태 : 컴포넌트가 마운트 된 직후 호출. 이후 카운트 값 바뀔 때마다 호출.
// 이때 용량 가득 찼는지 아닌지의 상태를 isFull state에 저장.
import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import Library from './chapter_03/Library';
import Clock from './chapter_04/Clock';
import CommentList from './chapter_05/CommentList';
import NotificationList from './chapter_06/NotificationList';
import Accommodate from './chapter_07/Accommodate';
createRoot(document.getElementById('root')).render(
<React.StrictMode>
<Accommodate />
</React.StrictMode>
);
reportWebVitals();
입장을 누르면 두 개의 useEffec Hook이 호출되는데 카운트 값은 1 증가함.
여기서 의존성 배열이 없는 useEffect Hook이 호출된 이유는 컴포넌트가 업데이트 됐기 때문. 의존성 배열이 있는 useEffect Hook이 호출된 이유는 카운트 값이 변경되었기 때문.

정원이 가득차면 isFull의 값이 true가 되기 때문에 입장 버튼이 비활성화 되어 더 이상 누를 수 없게 됨.
로그를 보면 카운트 값이 10이 된 이후에는 더 이상 변하지 않기 때문에 카운트를 의존성 배열에 갖고 있는 useEffect Hook은 호출되지 않음.

퇴장 버튼을 누르면 다시 2개의 useEffect Hook이 호출되고 카운트 값이 줄어듦.

useConter Hook에서 Math.max함수를 사용하여 카운트 값이 0 아래로 내려갈 수 없게 만들었기 때문에 값이 0이 되면 더 이상 useEffect Hook도 호출되지 않음
