React Hook
, React Functional Components
방식이 도입되었습니다. 그전에는 useState, useEffect와 같이 state를 관리하거나 컴포넌트 진행과정을 컨트롤 하기 위해서는 자바스크립트의 class
가 필요했습니다.render
라는 예약어로 JSX
코드를 반환했습니다. 위에서 설명했다시피 클래스 컴포넌트 구성방식에서는 React Hook
을 사용할 수 없습니다. 하지만 두방식 모두 유효하기에 각 기능을 class 기반으로 변환 할수도 있습니다.물론 클래스 컴포넌트 구성방식이 나쁘다는 것도 아니고 잘 돌아가지 않는 것도 아닙니다. 선호도의 차이정도로 여기거나 옛날 코드라고 이야기 하면 됩니다. 함수형 컴포넌트
클래스 컴포넌트
클래스 컴포넌트에서는 class
라는 자바스크립트 구조를 사용합니다. class User 이름을 정의해 놓고 extends Component
라는 리액트 class
를 상속받습니다. 그리고 안에서는 render
라는 예약어로 함수를 정의합니다. 이 함수에서 return
값은 함수형 컴포넌트의 return
과 같습니다.
다만 props
의 취급은 조심해야합니다. this
.props로 모습을 바꿔줍니다. class
는 함수객체입니다. 따라서 this
가 존재하는데 Component
라는 클래스를 상속받아 this.props
도 존재합니다. 이런식으로 상위 컴포넌트의 props를 받아올수 있습니다.
함수형 컴포넌트
클래스 컴포넌트
자바스크립트 문법에서 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함수로 바이딩을 해줍니다.
이제 정상적으로 작동합니다.
함수형 컴포넌트는 훅을 사용할 수 없습니다. 따라서 useEffect
와 같은 개념을 사용할 수 없습니다. 하지만 Lifecycle
이라는 개념이 있습니다.
componentDidMount()
: 컴포넌트가 마운트 될때 실행됩니다. 컴포넌트가 마운트 되었다는 것은 컴포넌트가 실행된다음 렌더링까지 된 상태라는 것을 의미합니다. useEffect(..., [])
이 개념과 동일합니다. 렌더링되고 나서 처음 실행되고 더이상은 실행되지 않습니다.componentDidUpdate()
: 컴포넌트가 업데이트 될때 실행합니다. useEffect(..., [someValue])
개념과 동일합니다. 일부 종속성이 있으면 해당 변수가 변할때마다 실행됩니다. componentWillUnmount()
: 컴포넌트가 제거되기전에 실행됩니다. useEffect(()=>{return ()=> {}},[])
의 클리너 함수와 같은 개념입니다.useEffect
componentDidUpdate
componentDidUpdate
함수의 첫번째 인자는 이전상태의 props
를 두번째 인자는 이전상태의 state
스냅샷을 가져올 수 있습니다. useEffect
의 종속성 배열을 따라해보려면 if구문으로 직접 해당 변수에 접근해서 이전상태와 현재상태를 비교하는 구문을 써줘야 비교가 가능합니다. useEffect에서는 그냥 배열로 넣으면 알아서 비교를 해주는데 하나 하나 if구문으로 넣어야 하다니 비효율적고 코드 가독성이 안좋아집니다.
마찬가지로 componentDidMount
의 경우는 비교할거리가 없기때문에 if구문과 인수 없이 바로 사용하면 되며
componentWillMount
또한 useEffect의 클리너 함수에 해당하는 내용을 넣으면 제대로 기능합니다.
클래스 기반 컴포넌트에서는 훅을 사용하지 못하므로 useContext
를 사용하지 못합니다. 그렇다면 어떻게 Context를 사용할 수 있을까요?
두가지 방법이 있습니다.
context.Consumer 사용하기
이런식으로 context를 사용할 컴포넌트 안에 Consumer를 Wrapping하고 인라인 함수로 store를 인수로 받아 반환 값을 렌더합니다. 이런식으로 context의 종류가 많아지면 계속 덮어씌우게 됩니다. 이는 3개 이상만되어도 가독성이 너무나 떨어집니다.
static 키워드 사용하기
static
키워드는 정적 프로퍼티를 생성합니다. 즉, 위 코드에서 contextType이라는 프로퍼티를 UserFinder class 객체에 직접 할당한 것입니다.
UserFinder.contextType = UserContext
과 같은 뜻입니다. 이렇게 되면 this.context
라는 프로퍼티로 해당 컨텍스트에 접근가능합니다.
하지만 이방법은 제한이 있습니다. 하나의 컨텍스트 밖에 접근할수가 없습니다. 여러개의 컨텍스트에 접근하려면 상단의 방식으로 중첩 wrapping을 하면서 접근해야합니다.
에러 바운더리 라는 개념은 컴포넌트안에서 핸들링 하지 않은 에러가 생성되었을때 에러가 핸들링 될때까지 상위 컴포넌트로 올라가다가 ErrorBoundary 컴포넌트에서 핸들링 되는 개념입니다. 여기에는 componentDidCatch
함수가 사용되기 때문에 에러 바운더리 컴포넌트는 함수형 컴포넌트로 만들수 가 없습니다. 클래스 컴포넌트에서 작성됩니다.
특정 상황에서 에러가 생성되면 componentDidCatch
에서 에러를 감지해서 상태변수를 변경합니다. 변경된 상태변수로 렌더링 구성요소를 바꿉니다. 물론 하위 컴포넌트들이나 다른 컴포넌트들이 함수형 컴포넌트이고 에러바운더리만 class 컴포넌트로 만들수도 있습니다. 컴포넌트는 독립적이기 때문이죠.
하지만 단점이 너무나 명확한것이 context
같이 글로벌 변수를 조정해서 portals
같은 것으로 에러 창을 띄우는 방법이 있습니다. 굳이 Error 바운더리를 사용해서
지엽적으로 Wrapping하거나 핸들링할 필요는 없어보입니다.
하지만 명확한 장점으로서 에러 핸들링을 깔끔하게 할수 있습니다. 놓치는 에러 없이 모든 에러를 잡아내므로 에러핸들링을 구분시키고 최상단 컴포넌트에 ErrorBoundary를 Wrapping한다면 좋은 개념이라고 생각합니다. 실제 코드에서는 에러를 발생시키고 에러 바운더리에서 에러를 잡아낸다음 portals로 에러 확인창을 렌더링 시키면 굉장히 우아하다고 생각됩니다.
❗️ 에러 경계는 다음과 같은 에러는 포착하지 않습니다.
- 이벤트 핸들러 (더 알아보기)
- 비동기적 코드 (예: setTimeout 혹은 requestAnimationFrame 콜백)
- 서버 사이드 렌더링
- 자식에서가 아닌 에러 경계 자체에서 발생하는 에러