함수형 컴포넌트는 임의의 객체를 props로서 인수로 가지며, JSX.Element 타입의 값을 반환하는 함수가 됨.
props로 들어오는 값을 type을 통해 제한 가능
children을 가질때는 React.ReactNode로 타입을 지정함.
클래스형 컴포넌트는 render 메소드에서 ReactNode를 리턴합니다. 함수형 컴포넌트는 ReactElement를 리턴합니다.
type ReactFragment = Iterable<ReactNode>; // ReactNode의 이터러블 형태
interface ReactPortal extends ReactElement {
key: Key | null;
children: ReactNode;
} // Portal은 key값을 가진 ReactNode
type ReactNode =
ReactElement| string | number |
ReactFragment | ReactPortal | boolean | null | undefined;
ReactNode가 ReactElement를 포괄하는 개념
interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
type: T;
props: P;
key: Key | null;
}
JSX.Element는 React.Element의 특정타입임
jsx => babel을 통해 트랜스파일 되면, React.createElement 메서드를 호출.
메서드의 return type은 React.Element임
//jsx
<div>Hello {this.props.toWhat}</div>
<Hello toWhat="World" />
// transpile
React.createElement('div', null, `Hello ${this.props.toWhat}`);
React.createElement(Hello, {toWhat: 'World'}, null);
function createElement<P extends {}>(
type: FunctionComponent<P> | ComponentClass<P> | string,
props?: Attributes & P | null,
...children: ReactNode[]): ReactElement<P>;
// 마지막 override type임
declare global {
namespace JSX {
interface Element extends React.ReactElement<any, any> { }
interface ElementClass extends React.Component<any> {
render(): React.ReactNode;
}
Function Component, Void Function Component의 약자(children 여부에 따라 갈렸음)
18버전부터는 FC를 사용하고 children을 사용할때는 prop type안에서 정의해야함.
mount, update, unmount 될때 실행됨 => 컴포넌트 렌더링 이후 useEffect안에 코드가 실행되는 형태임(비동기적으로 동작한다의 의미!!)
mount시 : 컴포넌트 렌더링이 끝난 이후 useEffect 코드 실행
update시 (컴포넌트 리렌더링) => 컴포넌트 리렌더링(재평가) 먼저 실행, 이후에 useEffect가 unmount 되면서 cleanup 함수 실행 => 이후 update 되면서 mount 실행
만약 아래코드에서 input값을 변경한다면? => state가 변하기 때문에 리렌더링 발생함.
의존성 배열 (deps)가 빈값이라면? 컴포넌트가 umount시에만 cleanup이 실행됨.
import './App.css';
import { useEffect, useState } from 'react';
function App() {
const [inputText, setInputText] = useState('');
console.log('first');
useEffect(() => {
console.log('mount');
return () => {console.log('unmount');}
}, [input]);
console.log('second');
return (
<div className='App'>
<input
value={input}
onChange={(e) => {
setInput(e.target.value);
}}></input>
</div>
);
}
// 정답순서
/*
first
second
unmount
mount
*/
컴포넌트 렌더링이 완료된 시점에 useEffect는 실행된다.
중첩구조에서 Inner 컴포넌트부터 렌더링이 완료된 시점에 useEffect가 바깥방향으로 실행됨.
병렬구조에서는 Outer1, Outer2 => App 순서대로 실행
<App>
<Outer>
<Inner/>
</Outer>
</App>
<App>
<Outer1/>
<Outer2/>
</App>
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, [count]); // count가 변할때마다 useEffect의 값이 clear되고 재등록되는 과정을 반복
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(id); // 초기에만 useEffect를 등록
}, []);
useEffect의 클린업 함수는 컴포넌트가 마운트되는 순간 뿐만아니라 모든 리렌더링 시에 cleanup 이후 새로운 effect를 등록함.
deps를 통해 이를 최적화가능함.
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
}, [props.friend.id]); // props.friend.id가 바뀔 때만 재구독합니다.
function FriendStatus(props) {
// ...
useEffect(() => {
// ...
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// { friend: { id: 100 } } state을 사용하여 마운트합니다.
ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // 첫번째 effect가 작동합니다.
// { friend: { id: 200 } } state로 업데이트합니다.
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 이전의 effect를 정리(clean-up)합니다.
ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // 다음 effect가 작동합니다.
// { friend: { id: 300 } } state로 업데이트합니다.
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 이전의 effect를 정리(clean-up)합니다.
ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // 다음 effect가 작동합니다.
// 마운트를 해제합니다.
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 마지막 effect를 정리(clean-up)합니다.
localStorage에 관한 작업(setItem, getItem) 은 동기적으로 실행됨. => 읽기 쓰기 데이터가 커짐에 따라 시간이 걸립니다.
함수형 컴포넌트 안에 직접 전달하기 보단 useEffect안에서 의존성이 변했을 때만 호출하도록 하는게 바람직함.
query가 변할때마다 호출이 되야하지만, useEffect에서 deps에서 누락됨.
useEffect 블록안에 해당 Effect에서 사용하는 함수를 정의하는게 직관적임.
공통으로 사용하는 함수라면, 컴포넌트 바깥 or 안쪽에 useCallback 사용가능
function SearchResults() {
const [query, setQuery] = useState('react');
// 이 함수가 길다고 상상해 봅시다
function getFetchUrl() {
return 'https://hn.algolia.com/api/v1/search?query=' + query;
}
// 이 함수도 길다고 상상해 봅시다
async function fetchData() {
const result = await axios(getFetchUrl());
setData(result.data);
}
useEffect(() => {
fetchData();
}, []);
function SearchResults() {
const [query, setQuery] = useState('react');
useEffect(() => {
function getFetchUrl() {
return 'https://hn.algolia.com/api/v1/search?query=' + query;
}
async function fetchData() {
const result = await axios(getFetchUrl());
setData(result.data);
}
fetchData();
}, [query]); //
// ...
}
ref)
useEffect : https://choyeon-dev.tistory.com/entry/React-useEffect-%EC%8B%A4%ED%96%89-%EC%8B%9C%EC%A0%90-%EC%A7%9A%EA%B3%A0-%EA%B0%80%EA%B8%B0
https://simsimjae.tistory.com/401
https://ko.legacy.reactjs.org/docs/hooks-effect.html
출처 : https://nookpi.tistory.com/141
이벤트핸들러를 다룰때 => data가 변하면 결국에 handleClick이라는 이벤트 핸들러가 리렌더링됨 => 메모이제이션 불가
deps를 비워둔다면? 항상 초기의 data값을 참조하게됨.
해결책 : Context API + useReducer를 사용해서 콜백 함수를 외부로 분리하고, 컴포넌트 내부에서는 dispatch로 해당 콜백 함수를 호출하는 방법 (dispatch 함수는 렌더링시 변하지 않기 때문)
=> 단점 : 관리할 코드가 너무 많아짐... 최적화때문에 굳이...?
=> useEvent라는 훅이 개발되다가 중단됨.(현재 다시 진행중이라나..)
렌더링이 진행되기전 동기적으로 실행되는 hook임. => 보통 초기값 설정할때 많이 사용함. (CSR에서 Fetching하는 부분이 존재하기 때문에, 화면깜빡임 방지하는 용도)
useEffect로 사용 후 문제가 생길 시 사용하는 것을 권장함
서버 렌더링을 사용하는 경우, js가 다운로드 되기 전까지는 useLayoutEffect useEffect 어느것도 실행되지 않음.
useRef의 활용 => dom접근 + 비제어컴포넌트
useImperativeHandle hook => 컴포넌트에 ref가 전달될 때, 부모의 ref에 대입될 값을 설정 할 때 사용함.
Child는 forwardRef로 Wrapping 되어 부모컴포넌트로부터 ref값을 전달받음.
useImperativeHandle hook 에서 부모의 ref로부터 참조 할 수 있는 값을 지정함. 즉 자식에서 정의된 함수를 부모로부터 호출 가능함. (자식에서 부모 ref를 설정가능함)
deps 정의가능함.
import React, { useState, useRef, useImperativeHandle } from 'react'
const Child = React.forwardRef((props, ref) => {
const [message, setMessage] = useState<string | null>(null)
// useImperativeHandle에서 부모의 ref로부터 참조할 수 있는 값을 지정
useImperativeHandle(ref, () => ({
showMessage: () => {
const date = new Date()
const message = `Hello, it's ${date.toLocaleString()} now`
setMessage(message)
},
}))
return <div>{message !== null ? <p>{message}</p> : null}</div>
})
const Parent = () => {
const childRef = useRef<{ showMessage: () => void }>(null)
const onClick = () => {
if (childRef.current !== null) {
// 자녀의 useImperativeHandle에서 지정한 값을 참조
childRef.current.showMessage()
}
}
return (
<div>
<button onClick={onClick}>Show Message</button>
<Child ref={childRef} />
</div>
)
}
export default Parent
ref) 공식문서 : https://react.dev/reference/react/useImperativeHandle
디버깅할때 사용하는 훅, React Developer Tools 와 함께 사용함
개발자도구의 Components 탭에 표시됨, format지정가능함.
useDebugValue(value, format?)