프론트엔드 개발자라면 중요한 클로저 개념...
클로저 예시 찾아보면 모두 class 형밖에 없다
Q: 그럼 함수형에는 클로저 개념이 없는 걸까?
A :아님
함수형 클로저
function out (a:string){
let outV = "밖";
function inner(){
console.log("inner", outV)
console.log("props",a)
}
return inner
}
const instance1 = out("인스턴스단 변수1");
const instance2 = out("인스턴스단 변수2");
const instance3 = out("인스턴스단 변수3");
instance1()
instance2()
instance3()
/**
'inner' '밖'
'props' '인스턴스단 변수1'
'inner' '밖'
'props' '인스턴스단 변수2'
'inner' '밖'
'props' '인스턴스단 변수3'
*/
코드만 보면 당연한 이야기인 거 같다,,,,
out 함수 내부에 변수 넣어놓고 그 안에서 다시 함수(inner)를 선언해서 return 하면
out 함수 외부에서 inner 접근이 가능함
외부에서는 결국 out 함수를 거치기 때문에 inner는 outV 변수의 값을 사용할 수 있다
이걸 어려운 말로 하자면
클로저는 함수가 선언될 때(instance1,2,3)
렉시컬 환경(lexical environment)을 기억하고 함수가 그 환경 밖에서 호출되더라도
그 환경에 접근할 수 있는 기능
이라고 하는 듯
-여기서 렉시컬 환경은 functon out 환경을 이야기 한다 (outV)-
여기서 고안된 리액트 use 훅
const useOut = (props:string) => {
const test = "1234";
function a (){
console.log(111,test + "A" + props)
}
function b (){
console.log(222, test + "B" + props)
}
return {a,b}
}
const {a,b} = useOut("예시");
a();
b();
구조 분해 할당한 거 말고 다른 점을 못 찾겠움...
여기서 a()나 b()가 어떤 변수를 받는다고 해도 test 변수 값은 사용 가능하다
클로저 그 자체
정리된 다른 예시 코드
이건 멋진 말로하면 hof 라고 한다
const handleOrder = (food:string) =>
(drink:string) => console.log("음식 :", food, ", 음료 :", drink)
const orderInstance1 = handleOrder("밥")("물")
orderInstance1
const order2 = handleOrder("한식")
order2("콜라")
order2("콜라빵")
간단한 예시만 봐도 컴포넌트 재활용시키기 딱 좋아보이는 개념
리액트 hook이 없던 시절 이 점을 이용해서 관심사 분리나 컴포넌트 구조를 짰던 거 같다
생각해보니 hof 에서 단순 jsx 문법의 컴포넌트를 인자로 받고 return 하는 것뿐인 듯...
이게 바로 고차 컴포넌트(HOC)
1. 로딩처리를 예시로 들 때
import React from 'react';
const DataComponent = ({ data }) => (
<div>
<h1>Data</h1>
{data.id}
</div>
);
export default DataComponent;
컴포넌트 자체를 인자로 받는 HOC 컴포넌트
import React from 'react';
const withLoading = (WrappedComponent) => {
return function WithLoadingComponent({ isLoading, ...props }) {
if (isLoading) {
return <div>Loading...</div>;
}
return <WrappedComponent {...props} />;
};
};
export default withLoading;
import React, { useState, useEffect } from 'react';
import DataComponent from './DataComponent';
import withLoading from './withLoading';
/** 여기서 인스턴스 만들고 */
const DataComponentWithLoading = withLoading(DataComponent);
const App = () => {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState(null);
//..........//
return (
<div>
/** 여기서 씀, 내부 인자 받음 */
<DataComponentWithLoading isLoading={isLoading} data={data} />
</div>
);
};
export default App;
가장 간단한 예시
근데 지금 당장 이 기능을 구현하라면
난 그냥 hook 썼을 거 같음
2. 합성 컴포넌트 개념
import React from 'react';
// HOC to add a title
const withTitle = (WrappedComponent, title) => {
return function WithTitleComponent(props) {
return (
<div>
<h1>{title}</h1>
<WrappedComponent {...props} />
</div>
);
};
};
// HOC to add a footer
const withFooter = (WrappedComponent, footer) => {
return function WithFooterComponent(props) {
return (
<div>
<WrappedComponent {...props} />
<footer>{footer}</footer>
</div>
);
};
};
// Basic component
const Content = ({ text }) => <div>{text}</div>;
// Composed component
const ContentWithHeaderAndFooter = withFooter(withTitle(Content, 'Header'), 'Footer');
const App = () => <ContentWithHeaderAndFooter text="This is the content" />;
export default App;
withFooter(withTitle(Content, 'Header'), 'Footer')
이게 좀 힘들긴 한데... 나쁘진 않은 거 같다...
근데 이런 형식이라면 그냥 compound 패턴 쓸 거 같다
compound 예시
import React from 'react';
const Header = ({ title }) => <h1>{title}</h1>;
const Footer = ({ footer }) => <footer>{footer}</footer>;
const Content = ({ text }) => <div>{text}</div>;
const CompoundComponent = ({ children }) => {
return <div>{children}</div>;
};
CompoundComponent.Header = Header;
CompoundComponent.Footer = Footer;
CompoundComponent.Content = Content;
import React from 'react';
const App = () => (
<CompoundComponent>
<CompoundComponent.Header title="Header" />
<CompoundComponent.Content text="This is the content" />
<CompoundComponent.Footer footer="Footer" />
</CompoundComponent>
);
export default App;
깔꼼....
카카오에서 진행한 합성 컴포넌트 개념도 있긴 한데
https://fe-developers.kakaoent.com/2022/220731-composition-component/
=> 이건... 예전에 써 본 결과... 너무 힘들었다...
chilren 여러개를 받다보니 단계별 props 관리가 너무 하드 코딩이었음...
chilren에서 map 들리고.. 거기서 조건 넣어서 분기 나누고...
개인적으로는 컴파운드가 짱인 거 같다
3.Props 조작 및 주입
import React from 'react';
// HOC to inject additional props
const withUser = (WrappedComponent) => {
return function WithUserComponent(props) {
const user = { name: 'John Doe', age: 30 }; // 예시로 사용자 정보 추가
return <WrappedComponent {...props} user={user} />;
};
};
const UserProfile = ({ user }) => (
<div>
<p>Name: {user.name}</p>
<p>Age: {user.age}</p>
</div>
);
const UserProfileWithUser = withUser(UserProfile);
const App = () => <UserProfileWithUser />;
그냥 함수나 hook으로 만들어도 될 것 같지만...
이런 방법도 있다
4. 조건부 렌더링
import React from 'react';
// HOC to conditionally render a component based on a prop
const withAuthorization = (WrappedComponent) => {
return function WithAuthorizationComponent({ isAuthorized, ...props }) {
if (!isAuthorized) {
return <div>Unauthorized</div>;
}
return <WrappedComponent {...props} />;
};
};
const SecretComponent = () => <div>Secret Information</div>;
const SecretComponentWithAuth = withAuthorization(SecretComponent);
const App = () => (
<div>
<SecretComponentWithAuth isAuthorized={true} />
<SecretComponentWithAuth isAuthorized={false} />
</div>
);
export default App;
JSX를 리턴해야하는 경우 좀 더 깔끔해보이는 것 같기도 하고....
이런 경우 있을 거 같다
탠스택 쿼리 로딩, 에러 처리는 대부분 비슷하니까....
좀 더 관심사가 잘 모인다
import React from 'react';
const withDataFetching = (WrappedComponent) => {
return function WithDataFetchingComponent({ isLoading, error, data, ...props }) {
if (isLoading) {
return <div>Loading...</div>;
}
if (error) {
return <div>Error: {error.message}</div>;
}
return <WrappedComponent data={data} {...props} />;
};
};
export default withDataFetching;
const SecretComponentWithData = withDataFetching(SecretComponent);
const App = () => {
const { data, error, isLoading } = useFetchData('secretData', fetchSecretData);
return (
<div>
<SecretComponentWithData isLoading={isLoading} error={error} data={data} />
</div>
);
};
export default App;
이렇듯 살펴보면 굳이 class형을 써야하는가... 에 대한 의문이 생긴다
함수형으로도 충분히 할 수 있을 것 같은데 굳이 왜 class를 써야하는가
class와 함수형은 뭐가 다른지...
1. 상속
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} barks.`);
}
}
const dog = new Dog('Rex');
dog.speak(); // Rex barks.
class 에는 상속 관련 extends 문법이 있다
2. 캡슐화 기능 (private)
class Counter {
#count = 0; // Private field
increment() {
this.#count++;
console.log(this.#count);
}
decrement() {
this.#count--;
console.log(this.#count);
}
}
const counter = new Counter();
counter.increment(); // 1
counter.decrement(); // 0
// console.log(counter.#count); // Error: Private field '#count' must be declared in an enclosing class
앞에 #붙이면 아예 접근이 안 됨
타입스크립트에는 as const 나 readOnly 기능이 있지만 좀더 강력한 기능임
- 불변성을 보장하려면 as const와 readonly를 사용
- 접근 제어를 위해 클래스 내부 상태를 보호하려면 class #private를 사용
느낀점...
리액트는 이미 hook으로 함수형에 넘어왔기 때문에 class는 크게 쓰일 일이 없는 거 같다...(아직은)
가끔 fetch 관련, indexdb 사용할 때 class 코드는 본 거 같은데(근데 다 싱글톤 썼음) 함수형으로 모두 쓸 수 있는 거 같다
음...아직 리액트를 쓰면서 내가 어디에 쓸진 모르지만
기존 디자인 패턴들을 보면 모두 oop 형식이기도하고...
클래스로 작성된 코드들이 그래도 꽤 있어서 읽을 줄은 알아야하는 것 같다
내가 아직 모르는 세계가 있을 수도 있기도 하고....움......
'TIL' 카테고리의 다른 글
타입스크립트 오버로딩 (0) | 2024.08.11 |
---|---|
싱글톤 (0) | 2024.08.04 |
es5은 어떻게 브라우저에서 동작했을까 & UMD (0) | 2024.07.28 |
tanstack Query v5 (0) | 2024.07.28 |
타입스크립트 never, 컨디셔널 타입 (0) | 2024.07.27 |
댓글