React 컴포넌트를 구성하는 방식

  1. 함수형 컴포넌트 구성방식
    함수형 컴포넌트 구성방식은 현재 리액트에서 가장 기본적이고 현대적인 방식입니다. 대부분의 사람들은 리액트에서 함수형 컴포넌트를 사용합니다. 간단하기 때문입니다. 이전에는 오른쪽과 같은 class 컴포넌트 구성방식으로 구성했습니다. ( React 16.8 ) 이후로 React Hook, React Functional Components 방식이 도입되었습니다. 그전에는 useState, useEffect와 같이 state를 관리하거나 컴포넌트 진행과정을 컨트롤 하기 위해서는 자바스크립트의 class 가 필요했습니다.
  2. 클래스 컴포넌트 구성방식
    클래스 컴포넌트는 리액트 문법이 아닙니다. class라는 자바스크립트 문법으로 컴포넌트를 구성했는데 render라는 예약어로 JSX 코드를 반환했습니다. 위에서 설명했다시피 클래스 컴포넌트 구성방식에서는 React Hook을 사용할 수 없습니다. 하지만 두방식 모두 유효하기에 각 기능을 class 기반으로 변환 할수도 있습니다.물론 클래스 컴포넌트 구성방식이 나쁘다는 것도 아니고 잘 돌아가지 않는 것도 아닙니다. 선호도의 차이정도로 여기거나 옛날 코드라고 이야기 하면 됩니다.
  • 두 컴포넌트를 섞어서 사용해도 됩니다. 함수형 컴포넌트가 클래스 컴포넌트를 호출해서 반환해도 React는 문제없이 받아들입니다. 즉, 이들은 어디까지나 컴포넌트의 본연의 역할에 충실합니다. 하지만 보통 한가지 경우만 고집해서 사용합니다. React에서는 클래스 컴포넌트에서 함수형 컴포넌트로 단계적으로 마이그레이션하는 것을 추천하고 있습니다.

기본적인 구성차이

함수형 컴포넌트

클래스 컴포넌트

클래스 컴포넌트에서는 class라는 자바스크립트 구조를 사용합니다. class User 이름을 정의해 놓고 extends Component 라는 리액트 class 를 상속받습니다. 그리고 안에서는 render 라는 예약어로 함수를 정의합니다. 이 함수에서 return 값은 함수형 컴포넌트의 return과 같습니다.

다만 props의 취급은 조심해야합니다. this.props로 모습을 바꿔줍니다. class 는 함수객체입니다. 따라서 this가 존재하는데 Component 라는 클래스를 상속받아 this.props도 존재합니다. 이런식으로 상위 컴포넌트의 props를 받아올수 있습니다.

state 관리 차이

함수형 컴포넌트

클래스 컴포넌트

자바스크립트 문법에서 contructor 함수 프로퍼티는 함수 자기 자신을 가르킵니다. 따라서 class 가 실행되고 나서 constructor() 안에 super() 함수를 실행시키면 상속된 Component class에서 this 값들을 모두 받아옵니다. 상속된 클래스 안에서 생성자 함수를 호출할때는 반드시 super() 함수를 호출해야합니다.

class형 컴포넌트에서는 상태관리를 모두 한객체에서 관리합니다. state라는 프로피터 객체로 관리되며 setState 메서드로 관리됩니다. 반드시 객체로 관리됩니다.
생성자함수 안에서 this.state 에 대한 초기 설정을 합니다.그리고 렌더링 될 부분에서는 this.state.[상태변수명]으로 접근합니다.

객체에서 함수를 정의할때 프로퍼티 메소드로 정의 하기 때문에 함수명 () {} 형태로 정의합니다. 물론 함수명 : () {} 이렇게 정의해도 되지만 React에서는 구문 에러가 납니다.

setState를 호출할때도 this를 이용해서 호출하고 인라인 함수를 사용하는 이유는 useState와 같습니다. 한개의 상태변수가 있지만 여러개의 상태변수를 가지고 있어도 해당하는 상태변수만 명시합니다. 업데이트 될때 기존 상태의 스냅샷 객체와 리턴된 객체가 병합되기 때문입니다.

마지막으로 JSX에서 함수를 호출 할때에도 this.toggleUserHanlder 이렇게 사용하면 안됩니다. 해당함수안의 this는 비어있기 때문에 해당함수안의 this는 현재 클래스의 this라는 것을 명시하기위해 bind함수로 바이딩을 해줍니다.

이제 정상적으로 작동합니다.

Class-based Component Lifecycle

함수형 컴포넌트는 훅을 사용할 수 없습니다. 따라서 useEffect와 같은 개념을 사용할 수 없습니다. 하지만 Lifecycle 이라는 개념이 있습니다.

  • componentDidMount() : 컴포넌트가 마운트 될때 실행됩니다. 컴포넌트가 마운트 되었다는 것은 컴포넌트가 실행된다음 렌더링까지 된 상태라는 것을 의미합니다. useEffect(..., []) 이 개념과 동일합니다. 렌더링되고 나서 처음 실행되고 더이상은 실행되지 않습니다.
  • componentDidUpdate() : 컴포넌트가 업데이트 될때 실행합니다. useEffect(..., [someValue]) 개념과 동일합니다. 일부 종속성이 있으면 해당 변수가 변할때마다 실행됩니다.
  • componentWillUnmount(): 컴포넌트가 제거되기전에 실행됩니다. useEffect(()=>{return ()=> {}},[]) 의 클리너 함수와 같은 개념입니다.

useEffect

componentDidUpdate

  • componentDidUpdate 함수의 첫번째 인자는 이전상태의 props를 두번째 인자는 이전상태의 state 스냅샷을 가져올 수 있습니다. useEffect의 종속성 배열을 따라해보려면 if구문으로 직접 해당 변수에 접근해서 이전상태와 현재상태를 비교하는 구문을 써줘야 비교가 가능합니다. useEffect에서는 그냥 배열로 넣으면 알아서 비교를 해주는데 하나 하나 if구문으로 넣어야 하다니 비효율적고 코드 가독성이 안좋아집니다.

  • 마찬가지로 componentDidMount의 경우는 비교할거리가 없기때문에 if구문과 인수 없이 바로 사용하면 되며

  • componentWillMount또한 useEffect의 클리너 함수에 해당하는 내용을 넣으면 제대로 기능합니다.

Class-based Component And Context

클래스 기반 컴포넌트에서는 훅을 사용하지 못하므로 useContext를 사용하지 못합니다. 그렇다면 어떻게 Context를 사용할 수 있을까요?
두가지 방법이 있습니다.

context.Consumer 사용하기

이런식으로 context를 사용할 컴포넌트 안에 Consumer를 Wrapping하고 인라인 함수로 store를 인수로 받아 반환 값을 렌더합니다. 이런식으로 context의 종류가 많아지면 계속 덮어씌우게 됩니다. 이는 3개 이상만되어도 가독성이 너무나 떨어집니다.

static 키워드 사용하기

static 키워드는 정적 프로퍼티를 생성합니다. 즉, 위 코드에서 contextType이라는 프로퍼티를 UserFinder class 객체에 직접 할당한 것입니다.
UserFinder.contextType = UserContext 과 같은 뜻입니다. 이렇게 되면 this.context 라는 프로퍼티로 해당 컨텍스트에 접근가능합니다.

하지만 이방법은 제한이 있습니다. 하나의 컨텍스트 밖에 접근할수가 없습니다. 여러개의 컨텍스트에 접근하려면 상단의 방식으로 중첩 wrapping을 하면서 접근해야합니다.

Error Boundaries

에러 바운더리 라는 개념은 컴포넌트안에서 핸들링 하지 않은 에러가 생성되었을때 에러가 핸들링 될때까지 상위 컴포넌트로 올라가다가 ErrorBoundary 컴포넌트에서 핸들링 되는 개념입니다. 여기에는 componentDidCatch 함수가 사용되기 때문에 에러 바운더리 컴포넌트는 함수형 컴포넌트로 만들수 가 없습니다. 클래스 컴포넌트에서 작성됩니다.




특정 상황에서 에러가 생성되면 componentDidCatch에서 에러를 감지해서 상태변수를 변경합니다. 변경된 상태변수로 렌더링 구성요소를 바꿉니다. 물론 하위 컴포넌트들이나 다른 컴포넌트들이 함수형 컴포넌트이고 에러바운더리만 class 컴포넌트로 만들수도 있습니다. 컴포넌트는 독립적이기 때문이죠.

하지만 단점이 너무나 명확한것이 context 같이 글로벌 변수를 조정해서 portals같은 것으로 에러 창을 띄우는 방법이 있습니다. 굳이 Error 바운더리를 사용해서
지엽적으로 Wrapping하거나 핸들링할 필요는 없어보입니다.

하지만 명확한 장점으로서 에러 핸들링을 깔끔하게 할수 있습니다. 놓치는 에러 없이 모든 에러를 잡아내므로 에러핸들링을 구분시키고 최상단 컴포넌트에 ErrorBoundary를 Wrapping한다면 좋은 개념이라고 생각합니다. 실제 코드에서는 에러를 발생시키고 에러 바운더리에서 에러를 잡아낸다음 portals로 에러 확인창을 렌더링 시키면 굉장히 우아하다고 생각됩니다.

❗️ 에러 경계는 다음과 같은 에러는 포착하지 않습니다.

  • 이벤트 핸들러 (더 알아보기)
  • 비동기적 코드 (예: setTimeout 혹은 requestAnimationFrame 콜백)
  • 서버 사이드 렌더링
  • 자식에서가 아닌 에러 경계 자체에서 발생하는 에러
profile
일상을 기록하는 삶을 사는 개발자 ✒️ #front_end 💻

0개의 댓글