https://www.inflearn.com/course/%EC%B2%98%EC%9D%8C-%EB%A7%8C%EB%82%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8
강의를 참고했습니다.
React에서의 State는 React Component의 상태를 저장하는 JS 객체입니다.
클래스 컴포넌트에서만 state를 사용할 수 있었지만, React 16.8부터 Hooks을 이용해 함수 컴포넌트에서도 state를 사용할 수 있게 해줍니다.

class LikeButton extends React.Component {
constructor(props) {
super(props);
this.state = {
liked: false
};
}
}
super(props)의 역할
Props의 초기화: 부모 클래스인 React.Component의 생성자를 통해 전달받은 props를 컴포넌트의 인스턴스에 바인딩합니다. 이를 통해 this.props를 사용하여 부모 컴포넌트로부터 전달받은 데이터에 접근할 수 있습니다.
생명주기 메소드의 사용: React.Component에 정의된 생명주기 메소드를 사용할 수 있게 됩니다. 이 메소드들은 컴포넌트가 렌더링되기 전후, 업데이트되기 전후, 그리고 언마운트되기 전에 호출되는 메소드들입니다.
컴포넌트 상태의 설정: this.state를 사용하여 컴포넌트의 내부 상태를 설정하고 관리할 수 있습니다.
클래스 컴포넌트에서 this.state와 this.setState를 사용하는 것과 유사한 기능을 제공하지만, 훨씬 간결하게 사용할 수 있습니다.
렌더링 단계에서 실행됩니다.
const [변수명, set함수명] = useState(초기값);
function Counter() {
// useState를 사용하여 count라는 상태 변수와 이를 업데이트하는 setCount 함수를 선언합니다.
const [count, setCount] = useState(0);
return (
<div>
<p>현재 카운터 값은 {count} 입니다.</p>
<button onClick={() => setCount(count + 1)}>
카운트 증가
</button>
</div>
);
}
Side effect 를 수행하기 위한 Hook 입니다.
컴포넌트의 렌더링이 완료된 후에 실행됩니다.
Side effect : 렌더링이 완료된 이후에 실행되어야 하는 작업들클래스 컴포넌트의 componentDidMount, componentDidUpdate, componentWillUnmount와 비슷한 역할을 합니다.
useEffect 는 컴포넌트가 렌더링될 때마다 특정 작업을 실행할 수 있도록 합니다. 기본적으로는 컴포넌트가 처음 렌더링될 때와 업데이트될 때마다 실행되지만, 의존성 배열(dependency array)을 이용하여 실행 시점을 조절할 수 있습니다.
useEffect(이펙트 함수, 의존성 배열);
[]을 가질 때 코드useEffect(() => {
// 이곳에 Side effect를 위한 코드를 작성합니다.
console.log('컴포넌트가 화면에 나타났습니다.');
return () => {
// 이곳에 정리(clean-up) 코드를 작성합니다.
// 주로 구독 해제, 타이머 제거 등의 작업을 수행합니다.
console.log('컴포넌트가 화면에서 사라졌습니다.');
};
}, []);
위 코드에서 useEffect는 빈 의존성 배열[]을 가지고 있으므로, 처음 렌더링 될 때만 실행되고
useEffect안에 return문이 있으므로 컴포넌트가 제거될 때 return문 안의 함수가 실행되어 정리 작업을 수행합니다.
생략했을 때 코드function Counter() {
// useState를 사용하여 count라는 상태 변수와 이를 업데이트하는 setCount 함수를 선언합니다.
const [count, setCount] = useState(0);
// componentDidMount, componentDidUpdate와 비슷하게 작동합니다.
useEffect(() => {
// 브라우저에서 페이지를 열었을 때 창에 표시되는 문자열
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>현재 카운터 값은 {count} 입니다.</p>
<button onClick={() => setCount(count + 1)}>
카운트 증가
</button>
</div>
);
}
의존성 배열을 생략하면 처음 랜더링 될 때, 컴포넌트가 업데이트될 때마다 매번 실행됩니다.
만약 의존성 배열에 특정 값을 넣는다면, 그 값이 변할 때마다 useEffect 안의 코드가 실행됩니다.
의존성 배열에 상관없이 useEffect 안에 return문이 있으면 컴포넌트가 제거될 때 return문 안의 함수가 실행됩니다. 즉, componentWillUnmount와 비슷한 역할을 합니다.
또한, 의존성 배열에 상관없이 컴포넌트가 처음 렌더링(마운트)될 때 useEffect가 항상 실행됩니다.
useEffect Hook은 하나의 컴포넌트에 여러 개를 사용할 수 있습니다.
useEffect(() => {
// 컴포넌트가 마운트 된 이후,
// 의존성 배열에 있는 변수들 중 하나라도 값이 변경되었을 때 실행됨
// 의존성 배열에 빈 배열([])을 넣으면 마운트와 언마운트시에 단 한 번씩만 실행됨
// 의존성 배열 생략 시 마운트와 업데이트시에 실행됨
...
return () => {
// 컴포넌트가 마운트 해제되기 전에 실행됨
...
};
}, [의존성 변수1, 의존성 변수2, ...]);
Memoized value를 리턴하는 Hook입니다.
(메모를 해두었다가 나중에 다시 사용한다고 생각하면 됨)
렌더링 단계에서 실행됩니다.
const memoizedValue = useMemo(() => {
// 복잡한 연산
return computeExpensiveValue(a, b);
}, [a, b]);
위 코드에서 computeExpensiveValue(a, b)는 복잡한 연산을 수행하는 함수이며, [a, b]는 의존성 배열입니다.
a나 b값이 변경될 때만 computeExpensiveValue(a, b)함수가 다시 실행되고, 그렇지 않은 경우 이전에 계산된 Memoized value를 재사용 합니다.
useMemo와 다르게 값이 아닌 함수를 반환합니다.
렌더링 단계에서 실행됩니다.
컴포넌트가 렌더링 될 때마다 매번 함수를 새로 정의하는 것이 아니라 의존성 배열의 값이 바뀐 경우에만 함수를 새로 정의해서 리턴해주는 것입니다.
useCallback에서는 파라미터로 받는 함수를 callback이라 부릅니다.
Reference를 사용하기 위한 Hook입니다.
렌더링 단계에서 실행됩니다.
Reference란?
- DOM 요소나 컴포넌트에 직접적으로 접근할 수 있는 방법을 제공합니다.
ref를 사용하면 React 데이터 흐름(상태와 속성을 통한 데이터 흐름) 외부에서 DOM 요소에 직접적으로 작업을 할 수 있습니다.- 폼 요소에 포커스를 주거나, 특정 DOM 요소의 크기나 위치를 직접 읽어야 하는 경우와 같이 순수
JavaScript에서document.getElementById()또는document.querySelector()를 사용하는 것과 유사한 작업을 할 때ref를 사용합니다.
function MyComponent() {
const myInputRef = useRef();
useEffect(() => {
// 컴포넌트가 마운트된 후 input 요소에 포커스를 줍니다.
myInputRef.current.focus();
}, []);
return <input ref={myInputRef} />;
}
위 코드에서 useRef는 .current(ref가 부착된 DOM요소를 가르킴) 프로퍼티를 가진 객체를 반환하여 focus() 메소드를 호출 하고 있습니다.
myInputRef.current의 값이 변경되더라도 (예를 들어 다른 요소를 가리키도록 바꾸어도) MyComponent 컴포넌트는 재렌더링되지 않습니다.
Custom Hook의 이름은 꼭 use로 시작해야합니다.
// truthy
true
{} (empty object)
[] (empty array)
42 (number, not zero)
"0", "false" (string, not empty)
// falsy
false
0, -0 (zero, minus zero)
0n (BigInt zero)
'', "", `` (empty string)
null
undefined
NaN (not a number)
{isLoggedIn && <span style={styles.greeting}>환영합니다!</span> }
{isLoggedIn ? (
<button onClick={onClickLogout}>로그아웃</button>
) : (
<button onClick={onClickLogin}>로그인</button>
)}
const doubled = numbers.map((number) => number * 2);
// numbers 배열에 있는 각 숫자에 2를 곱한 값이 들어간 doubled 배열을 생성하는 코드
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
)
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
function NumberList(props) {
const { numbers } = props;
const listItems = number.map((number) =>
<li>{number}</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers = { numbers }/>
document.getElementById('root')
);
Element를 사용하면서 map 함수를 쓸 때 key 값을 꼭 넣어야합니다.
Key 값으로 id를 사용하는 경우const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
)
Key 값으로 index를 사용하는 경우const todoItems = todos.map((todo) =>
// 아이템들의 고유한 id가 없을 경우에만 사용해야 함
<li key={index}>
{todo.text}
</li>
)
배열에서 아이템의 순서가 바뀔 수 있는 경우에는 Key값으로 index를 사용하는 것은 좋지 않습니다.
React에서는 Key를 명시적으로 넣어주지 않는다면 기본적으로 index 값을 key값으로 사용합니다.
// input 태그
<input type="text" value={value} onChange={handleChange} />
// textarea 태그
<textarea value={value} onChange={handleChange} />
// select 태그
<select value={value} onChange={handleChange}>
<option value="apple">사과</option>
<option value="banana">바나나</option>
<option value="grape">포도</option>
<option value="watermelon">수박</option>
</select>
하위 컴포넌트가 공통된 부모 컴포넌트의 state를 공유하여 사용하는 것을 말합니다.
function WelcomeDialog() {
return (
<Dialog>
<h1>환영합니다!</h1>
<p>React 세계로 오신 것을 환영합니다!</p>
</Dialog>
);
}
function Dialog(props) {
return (
<div style={{border: '2px solid blue'}}>
{props.children}
</div>
);
}
Inheritance(상속)은 React에서 권장하지 않는 방식입니다.
Context는 React 컴포넌트들 사이에서 데이터를 props를 통해 전달하는 방식 대신 컴포넌트 트리를 통해 곧바로 컴포넌트로 전달하는 새로운 방식입니다.
props 방식context 방식
Context 생성const MyContext = React.createContext(기본값);
React에서 렌더링이 일어날 때 Context 객체를 구독하는 하위 컴포넌트가 나오면 Context 값을 가장 가까이에 있는 상위 레벨의 provider로부터 받아옵니다.
만약 상위 레벨에 매칭되는 provider가 없으면 기본값이 사용됩니다.
Context.Provider 사용<MyContext.Provider value={/* value */}>
provider 컴포넌트에는 value라는 props가 있으며 provider 컴포넌트 하위에 있는 컴포넌트들에게 전달됩니다.
context 값을 사용하는 하위 컴포넌트들을 Consumer 컴포넌트라 부릅니다.
Provider에서 Context값이 변경되면, 그 값을 사용하고 있는 Consumer 컴포넌트들은 새로운 값을 받아서 자동으로 다시 렌더링(업데이트)됩니다.
상위 컴포넌트가 변경되지 않아도, Context를 구독하고 있는 Consumer 컴포넌트들은 새로운 Context값에 따라 업데이트가 이루어집니다.
<MyContext.Provider value={{ something: 'something' }}>
{/* 이 안에 있는 모든 컴포넌트들은 { something: 'something' } 값을
Context로부터 받을 수 있습니다. */}
</MyContext.Provider>
이렇게 useState를 사용하지 않고 컴포넌트 내에서 객체 리터럴을 직접 value prop으로 전달하는 경우 provider 컴포넌트가 재렌더링 되면 value prop에 새로운 객체를 다시 생성하게 되어서 모든 하위 컴포넌트가 새로운 value prop를 받을려고 불필요하게 재렌더링됩니다. (하위 컴포넌트가 실제로 필요로 하는 값이 변경되지 않았음에도 발생합니다.)
const [state, setState] = useState({ something: 'something' });
<MyContext.Provider value={state}>
{/* 컴포넌트 트리 */}
</MyContext.Provider>
Context.Consumer (구독) 사용<MyContext.Consumer>
{value => {
// 여기서 value는 현재 context의 값입니다.
// 이 값을 사용하여 UI를 동적으로 렌더링할 수 있습니다.
return <button theme={value}>버튼</button>;
}}
</MyContext.Consumer>
function MyComponent(props){
const value = useContext(MyContext);
return ();
}