본문 바로가기
TIL

클로저 - hof - 리액트 - hoc - class

by 은지:) 2024. 8. 4.
728x90
반응형

 

 

프론트엔드 개발자라면 중요한 클로저 개념...

클로저 예시 찾아보면 모두 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 형식이기도하고...

 

클래스로 작성된 코드들이 그래도 꽤 있어서 읽을 줄은 알아야하는 것 같다

내가 아직 모르는 세계가 있을 수도 있기도 하고....움......

 

 

 

728x90
반응형

'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

댓글