Table of contents
함수형 컴포넌트
선언하는 방법들
여러 방법중에 arrow function
와 named function declaration
두가지가 많이 사용되어지는 듯하다.
// arrow function
export const FunctionComponent1 = () => {
return (
<div>
<h1>FunctionComponent 1</h1>
</div>
)
}
// named function declaration
export function FunctionComponent2() {
return (
<div>
<h1>FunctionComponent 2</h1>
</div>
)
}
Props를 전달하는 방식
props를 매개변수로 받아와서 사용하는 방식
# 올바른 방식
const FunctionComponent = (props: any) => {
const { name } = props;
return (
<div>
<h1>FunctionComponent1 {name}</h1>
</div>
);
}
# 커스텀 포맷만 받고 싶을때
export const FunctionComponent0 = (props: {name: string}) => {
return (
<div>
<h1>FunctionComponent0 {props.name}</h1>
</div>
)
}
# 타입 지정
export interface FunctionComponentProps {
name: string;
}
export const FunctionComponent = (props: FunctionComponentProps) => {
return (
<div>
<h1>FunctionComponent {props.name}</h1>
</div>
)
}
# 함수 컴포넌트의 기본 구조와 맞지 않아서 이렇게 사용 할 수 없다.
export const FunctionComponent = (name: string) => {
return (
<div>
<h1>FunctionComponent1 {name}</h1>
</div>
)
}
구조 분해 할당을 사용하는 방법
export interface FunctionComponentProps {
name: string;
}
export const FunctionComponent = ({name}: { name: string }) => {
return (
<div>
<h1>FunctionComponent1 {name}</h1>
</div>
)
}
export const FunctionComponent = ({name}: FunctionComponentProps) => {
return (
<div>
<h1>FunctionComponent1 {name}</h1>
</div>
)
}
자식노드 전달
네이밍규칙이 지켜져야한다.
export const FunctionComponent = ({ children }: {children : ReactNode}) => {
return (
<div>
{children} <=={<h1>i'm children</h1>}
</div>
)
}
# 호출
<FunctionComponent>
<h1>i'm children</h1>
</FunctionComponent>
# props 도 추가해보기
export const FunctionComponent = (props: { children: ReactNode, name: string }) => {
return (
<div>
{props.name}
{props.children}
</div>
)
}
# 호출
<FunctionComponent name="Jone">
<h1>i'm children</h1>
</FunctionComponent>
React.FC(권장하지 않음)
기본 사용코드
type Props = {
count: number;
};
const FCComponent: React.FC<Props> = ({ count }) => {
return <div>Parent {count}</div>;
};
export default FCComponent;
권장하지 않는 이유
자식 노드가 필요하지 않는 경우에도 자식에 대한 암시적 정의를 제공함.
React 17버전 이하에서는 children 을 지정하지 않았더라도 에러가 발생하지 않는다.
17버전 이하로 했을때,
18버전으로 했을때,
하위 버전에서는 Child가 optional이므로 컴파일 에러가 발생하지 않는 문제가 있다.
제네릭 이슈
함수형 컴포넌트로 제네릭 사용하는 예시
interface Link {
name: string;
url: string;
}
export const GenericComponent = <T extends Link>({name, url}: T) => {
return (
<div>
<a href={url}>{name} Link</a>
</div>
)
}
React.FC에서 제네릭을 일반화 정의 할 수 없다.
# 이렇게는 사용불가능하다.
const LinkFCoponent: React.FC<T extends Link> = ({name, url}: T) => {
return (
<div>
<a href={url}>{name} Link</a>
</div>
)
}
# 타입을 지정해줘야한다.
export const LinkFComponent: React.FC<Link> = ({name, url}) => {
return (
<div>
<a href={url}>{name} Link</a>
</div>
)
}
default props
optional 타입이 아닌경우 defaultProps를 사용할 수 없다.
interface Link {
name: string;
url: string;
}
export const LinkFComponent: React.FC<Link> = ({name, url}) => {
return (
<div>
<a href={url}>{name} Link</a>
</div>
)
}
LinkFComponent.defaultProps = {
name: "myDefaultLink",
url: "http://localhost:3000"
}
optional을 추가해줘야 사용 할 수 있다.
interface Link {
name?: string;
url?: string;
}
React.FC는 React 컴포넌트와 다소 다르게 작동되는 이슈가 있기때문에 사용을 지양하라고 권장한다.
클래스 컴포넌트
선언하는 방법
기본 코드
export class ClassComponent extends React.Component<any, any> {
constructor(props: any) {
super(props);
}
render() {
return <div>
<h1>Hello</h1>
</div>;
}
}
props 사용하기
export class ClassComponent extends React.Component<any, any> {
render() {
return <div>
<p>Hello, {this.props.name}</p>
</div>;
}
}
<ClassComponent name="Jone"></ClassComponent>
state 사용하기
export class ClassComponent extends React.Component<any, any> {
constructor(props: any) {
super(props);
# 상태선언
this.state = {
firstName: "kapil",
lastName: "sharma"
};
}
render() {
return <div>
<p>Hello, {this.state.firstName} {this.state.lastName}</p>
</div>;
}
}
물론 타입을 지정 할 수 있다.
export class ClassComponent extends React.Component<any, { firstName: string, lastName: string }> {
constructor(props: any) {
super(props);
this.state = {
firstName: "kapil",
lastName: "sharma"
};
}
render() {
return <div>
<p>Hello, {this.state.firstName} {this.state.lastName}</p>
</div>;
}
}
타입 명시하기
type MyProps = any
type MyStatus = {
firstName: string,
lastName: string
}
export class ClassComponent extends React.Component<MyProps, MyStatus> {
constructor(props: any) {
super(props);
this.state = {
firstName: "kapil",
lastName: "sharma"
};
}
render() {
return <div>
<p>Hello, {this.state.firstName} {this.state.lastName}</p>
</div>;
}
}
생명주기 함수들 (React 문서 참고)
interface을 보자
// react/index.ts
// 이것은 실제로 'Lifecycle<P,S>|Descated Lifecycle<P,S>'와 같은 것이어야 합니다,
// 리액트가 새로운 라이프사이클 중 하나라도 사용하지 않는 라이프사이클 방법을 호출할 것이므로
// 방법이 있습니다.
interface ComponentLifecycle<P, S, SS = any> extends NewLifecycle<P, S, SS>, DeprecatedLifecycle<P, S> {
/**
* 구성 요소가 장착된 후 즉시 호출됩니다. 여기에서 상태를 설정하면 재렌더링이 트리거됩니다.
*/
componentDidMount?(): void;
/**
* 소품 및 상태 변경이 재렌더를 트리거할지 여부를 결정하기 위해 호출됩니다.
*
* component는 항상 true로 반환됩니다.
* 'PureComponent'는 소품과 상태를 얕은 비교를 구현하고, 있으면 참으로 돌아옵니다
* 소품 또는 상태가 변경되었습니다.
*
* false가 반환되면 'Component#render', 'ComponentWillUpdate'가 됩니다
* 'componentDidUpdate'가 호출되지 않습니다.
*/
shouldComponentUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: any): boolean;
/**
* 구성 요소가 파괴되기 직전에 호출됩니다. 다음과 같이 이 방법으로 필요한 정리를 수행합니다
* 취소된 네트워크 요청 또는 'componentDidMount'에 생성된 모든 DOM 요소를 정리합니다.
*/
componentWillUnmount?(): void;
/**
* 하위 구성 요소에서 생성된 예외를 캡처합니다. 처리되지 않은 예외는 다음을 야기합니다
* 마운트 해제할 전체 구성 요소 트리.
*/
componentDidCatch?(error: Error, errorInfo: ErrorInfo): void;
}
// 안타깝게도 구성 요소 구성 요소가 이를 구현해야 한다고 선언할 방법이 없습니다
interface StaticLifecycle<P, S> {
getDerivedStateFromProps?: GetDerivedStateFromProps<P, S> | undefined;
getDerivedStateFromError?: GetDerivedStateFromError<P, S> | undefined;
}
interface NewLifecycle<P, S, SS> {
/**
* React는 'render' 결과를 문서에 적용하기 전에 실행합니다
* componentDidUpdate에 제공할 개체를 반환합니다. 저장에 유용합니다
* '스크롤'이 그것에 변화를 일으키기 전의 스크롤 위치와 같은 것들.
*
* 참고: getSnapshotBeforeUpdate가 있으면 권장되지 않는 기능이 없습니다
* 실행 중인 라이프사이클 이벤트.
* getSnapshotBeforeUpdate 메서드를 사용하는 경우에는 React의 다른 라이프사이클 이벤트들이 실행되지 않으므로 주의해야 합니다.
*/
getSnapshotBeforeUpdate?(prevProps: Readonly<P>, prevState: Readonly<S>): SS | null;
/**
* 업데이트가 발생한 후 즉시 호출됩니다. 초기 렌더에 대해 호출되지 않습니다.
*
* 스냅샷은 getSnapshotBeforeUpdate가 있고 null이 아닌 것을 반환하는 경우에만 존재합니다.
*/
componentDidUpdate?(prevProps: Readonly<P>, prevState: Readonly<S>, snapshot?: SS): void;
}
생명 주기 하나씩 파악해보자
componentDidMount
...
/**
* 구성 요소가 장착된 후 즉시 호출됩니다. 여기에서 상태를 설정하면 재렌더링이 트리거됩니다.
*/
componentDidMount() {
console.log("componentDidMount! ", this.unixTime());
}
render() {
return <div>
<h1>Hello</h1>
<p> {this.unixTime()}</p>
</div>;
}
}
componentDidMount 는 최초 한번 마운트시 호출 된다.
언제 사용할까?
네트워크 요청 과 같은 비동기 작업
라이브러리 초기화
이벤트 리스너
# 컴포넌트 방식으로는 어떻게 사용할까?
userEffect를 사용하자.
useEffect 의 두번째 인자 DependencyList
가 빈배열이면 최초 한번만 호출된다.
useEffect(() => {
console.log("ComponentStateCounter componentDidMount! ", unixTime());
}, []);
shouldComponentUpdate
컴포넌트를 업데이트 할지 말지 결정하는 함수
/**
* 소품 및 상태 변경이 재렌더를 트리거할지 여부를 결정하기 위해 호출됩니다.
*/
shouldComponentUpdate(nextProps: Readonly<any>, nextState: Readonly<any>, nextContext: any): boolean {
console.log("shouldComponentUpdate! ", unixTime());
return Math.random() >= 0.5;
}
랜덤으로 true
or false
를 발생시켜 보았다.
countUp() {
this.setState({
count: this.state.count + 1
})
}
...
render() {
return
...
<button onClick={ () => this.countUp() }>증가+1</button>
}
this.countUp()
의 통해 상태를 변경되더라도, shouldComponentUpdate
가 false
를 리턴한다면 render()
가 수행되지 않는다.
함수형 컴포넌트에서는 useMemo
, memo
가 비슷한 역할을 한다고 볼 수 있다.
componentWillUnmount
구성 요소가 파괴되기 직전에 호출된다.
class Button extends React.Component<any, any> {
handleClick = () => {
this.props.onClick();
};
/**
* 구성 요소가 파괴되기 직전에 호출됩니다. 다음과 같이 이 방법으로 필요한 정리를 수행합니다
* 취소된 네트워크 요청 또는 'componentDidMount'에 생성된 모든 DOM 요소를 정리합니다.
*/
componentWillUnmount() {
console.log("componentWillUnmount! ", unixTime());
}
render() {
return (
<button onClick={this.handleClick}>
{this.props.children}
</button>
);
}
}
{this.state.isVisible && (
<Button onClick={this.unvisible}>나를 사라지게</Button>
)}
예시코드와 같이 Button
컴포넌트가 사라질때, componentWillUnmount
호출된다.
함수형 컴포넌트에서 사용하고 싶을때
useEffect
의 리턴 메소드를 정의한다
useEffect(() => {
const timer = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
// 컴포넌트가 언마운트될 때 실행되는 부분
return () => {
clearInterval(timer); // 타이머 정리
};
}, []); // 빈 배열을 전달하면 마운트와 언마운트 시에만 실행
DependencyList를 추가하면?
export const ComponentStateCounter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("NO DI useEFFECT", unixTime());
return () => {
console.log("NO DI useEFFECT RETURN", unixTime());
}
}, []);
useEffect(() => {
console.log("Count useEffect", unixTime());
return () => {
console.log("Count useEffect RETURN Start", unixTime());
for (let i = 0; i < 999999999; i++) {}
console.log("Count useEffect RETURN End", unixTime());
}
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount((prevCount) => prevCount + 1)}>+1</button>
</div>
)
}
+1
버튼을 클릭하고 사라지는 버튼
을 클릭했을때 호출 순서
componentDidCatch
하위 구성 요소에서 생성된 예외를 캡처합니다.
export class ErrorBoundary extends React.Component<any, any> {
constructor(props: {}) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error:any, errorInfo: any) {
// 컴포넌트 내에서 발생한 예외를 처리합니다.
console.error("에러가 발생했습니다:", error);
console.error("에러 정보:", errorInfo);
// 예외를 처리한 후 상태를 업데이트하여 에러 메시지를 렌더링합니다.
this.setState({ hasError: true });
}
render() {
if (this.state.hasError) {
// 에러가 발생한 경우 대체 컨텐츠를 렌더링합니다.
return <div>에러가 발생했습니다. 대체 컨텐츠를 표시합니다.</div>;
}
// 에러가 없는 경우 자식 컴포넌트를 렌더링합니다.
return this.props.children;
}
}
export class ExampleComponent extends React.Component {
render() {
// 에러를 발생시키는 예제 코드
if (Math.random() < 0.5) {
throw new Error("자식 컴포넌트에서 전달하는 에러 메시지!!!!! 랜덤 예외 발생!");
}
return <div>예외가 발생하지 않았습니다.</div>;
}
}
에러가 발생했을때 감지 됨.
정리
React 에서 컴포넌트 정의는 함수형과 클래스형으로 할 수 있다.
React.FC는 과거 버전 or 라이브러리 호환성 문제 와 다소 다르게 작동되는 부분이 있기 때문에 사용을 지양함.
특별한 라이프 사이클을 사용해야 할때는 클래스 컴포넌트를 활용하자.
참고
https://ko.legacy.reactjs.org/docs/state-and-lifecycle.html#adding-lifecycle-methods-to-a-class
https://medium.com/@martin_hotell/10-typescript-pro-tips-patterns-with-or-without-react-5799488d6680
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30695
https://github.com/typescript-cheatsheets/react/issues/87
프로젝트 dependencies
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.46",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
}