함수형 컴포넌트
선언하는 방법들
여러 방법중에 arrow function와named function declaration 두가지가 많이 사용되어지는 듯하다.
// arrow function
export const FunctionComponent1 = () => {
return (
FunctionComponent 1
)
}
// named function declaration
export function FunctionComponent2() {
return (
FunctionComponent 2
)
}
Props를 전달하는 방식
props를 매개변수로 받아와서 사용하는 방식
import React from 'react';
// 1. 기본적인 방식: props를 통해 값 받아오기
const FunctionComponent = (props: any) => {
const { name } = props;
return (
<div>FunctionComponent {name}</div>
);
}
// 2. 특정한 props 타입만 받고 싶을 때: 객체 리터럴을 이용해 타입 지정
export const FunctionComponent0 = (props: {name: string}) => {
return (
<div>FunctionComponent0 {props.name}</div>
)
}
// 3. TypeScript 인터페이스를 사용하여 props 타입 지정
export interface FunctionComponentProps {
name: string;
}
export const FunctionComponent1 = (props: FunctionComponentProps) => {
return (
<div>FunctionComponent1 {props.name}</div>
)
}
export const FunctionComponent2 = (props: {name: string}) => {
return (
<div>FunctionComponent2 {props.name}</div>
)
}
구조 분해 할당을 사용하는 방법
export interface FunctionComponentProps {
name: string;
}
export const FunctionComponent = ({name}: { name: string }) => {
return (
FunctionComponent1 {name}
)
)}
)
}
export const FunctionComponent = ({name}: FunctionComponentProps) => {
return (
FunctionComponent1 {name}
)
}
자식노드 전달
// 자식 노드만 받는 컴포넌트
export const FunctionComponent = ({ children }: { children: ReactNode }) => {
return <div>{children}</div>;
};
// 사용 예시
const App = () => {
return (
<FunctionComponent>
<p>im children</p>
</FunctionComponent>
);
};
// props와 자식 노드를 함께 받는 컴포넌트
export const FunctionComponentWithProps = ({
children,
name,
}: {
children: ReactNode;
name: string;
}) => {
return (
<div>
<h1>{name}</h1>
{children}
</div>
);
};
// 사용 예시
const AppWithProps = () => {
return (
<FunctionComponentWithProps name="Example">
<p>im children</p>
</FunctionComponentWithProps>
);
};
React.FC(권장하지 않음)
기본 사용코드
import React from 'react';
// Props 타입 정의
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 = ({name, url}: T) => {
return (
{name} Link
)
}
React.FC에서 제네릭을 일반화 정의 할 수 없다.
# 이렇게는 사용불가능하다.
const LinkFCoponent: React.FC = ({name, url}: T) => {
return (
{name} Link
)
}
# 타입을 지정해줘야한다.
export const LinkFComponent: React.FC = ({name, url}) => {
return (
{name} Link
)
}
default props
optional 타입이 아닌경우 defaultProps를 사용할 수 없다.
import React from 'react';
// Link 인터페이스 정의
interface Link {
name: string;
url: string;
}
// Link 인터페이스를 사용하여 props 타입 지정
const LinkFComponent: React.FC<Link> = ({ name, url }) => {
return (
<div>
<a href={url}>{name} Link</a>
</div>
);
};
// 기본 props 설정
LinkFComponent.defaultProps = {
name: "myDefaultLink",
url: "http://localhost:3000"
};
export default LinkFComponent;
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 Hello;
}
}
# props 사용하기
export class ClassComponent extends React.Component<any, any> {
render() {
return Hello, {this.props.name}
}
}
state 사용하기
export class ClassComponent extends React.Component<any, any> {
constructor(props: any) {
super(props);
# 상태선언
this.state = {
firstName: "kapil",
lastName: "sharma"
};
}
render() {
return
Hello, {this.state.firstName} {this.state.lastName};
}
}
# 물론 타입을 지정 할 수 있다.
export class ClassComponent extends React.Component<any, { firstName: string, lastName: string }> {
constructor(props: any) {
super(props);
this.state = {
firstName: "kapil",
lastName: "sharma"
};
}
render() {
return Hello, {this.state.firstName} {this.state.lastName} ;
}
}
타입 명시하기
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 Hello, {this.state.firstName} {this.state.lastName};
}
}
생명주기 함수들 (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, nextState: Readonly, 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
...
this.countUp() }>증가+1
}
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 (
{this.props.children}
);
}
}
{this.state.isVisible && (
나를 사라지게
)}
예시코드와 같이 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>
Count: {count}
<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 ; // 에러가 발생했습니다. 대체 컨텐츠를 표시합니다.
}
// 에러가 없는 경우 자식 컴포넌트를 렌더링합니다.
return this.props.children;
}
}
export class ExampleComponent extends React.Component {
render() {
// 에러를 발생시키는 예제 코드
if (Math.random() < 0.5) {
throw new Error("자식 컴포넌트에서 전달하는 에러 메시지!!!!! 랜덤 예외 발생!");
}
return ;
}
}
에러가 발생했을때 감지 됨.
정리
- 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"
}
'프로그래밍 > React' 카테고리의 다른 글
Golang Slice(슬라이스) 동작원리와 사용법 (0) | 2024.07.31 |
---|---|
React Props,State (0) | 2024.07.29 |
React Virtual DOM (0) | 2024.07.29 |
React Composition (0) | 2024.07.29 |