ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React] 클래스 컴포넌트와 함수 컴포넌트
    간단정리 2020. 12. 4. 17:27

    1. 개요


    1-1. 클래스 컴포넌트와 함수 컴포넌트

    - React에서는 두 가지의 컴포넌트 형태가 존재한다.

    - 흔히 한국에서는 클래스형, 함수형 컴포넌트라고 말하지만 공식 문서에 따르면 클래스, 함수 컴포넌트가 옳바른 표현이다. (함수형 이라고 하면 오해가 생길 수 있기 때문이다.)

    // 안녕 난 클래스 컴포넌트
    class Welcome extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }
    // 안녕 난 함수 컴포넌트
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }

     

    1-2. Hooks의 점진적 업데이트

     React 공식 문서를 확인 해보면 페이스북 또한, class컴포넌트를 제거할 생각은 없지만 function컴포넌트의 점진적 업데이트를 통하여 사용 범위를 늘려갈 것이고 이를 권장한다고 말하고 있다.

     

    Hook의 개요 – React

    A JavaScript library for building user interfaces

    ko.reactjs.org

     

    1-3. 그래서 말이야..

    이 두 가지의 컴포넌트의 차이는 무엇이고, 각자 어떤 강점과 약점을 가지고 있으며 점진적 업데이트라는 키워드를 사용하면서 마치 "미래는 함수 컴포넌트다!"라는 느낌을 주는 이유는 무엇일까? 그것을 알아보자.

     

    2. 두 가지의 컴포넌트


     개요에서 언급한 것 처럼 React에서는 두가지의 컴포넌트 형태가 존재 한다. 또, React에서는 함수 컴포넌트에 힘을 싣는 모습을 볼 수 있다. 두 가지의 컴포넌트를 조금 더 알아보자.

     

    2-1. 각 컴포넌트의 특징

    클래스 컴포넌트

    - React Life Cycle과 State를 사용할 수 있다.

    함수 컴포넌트

    - 클래스 컴포넌트 보다 메모리를 덜 사용한다.

     

    2-2. React Hooks의 등장

     함수 컴포넌트는 메모리 효율이 좋았다. 하지만 이는 상태를 사용하지 않고 Life Cycle을 사용할 수도 없기 때문에 어떻게 보면 당연한 결과였다.

     이런 함수 컴포넌트의 단점? 기능을 보완하기 위하여 2019년 초 React v16.8에 React Hooks가 업데이트 되었다. Hooks의 간단한 개념은 함수 컴포넌트에서도 React Life Cycle, State를 사용할 수 있게 한다는 것이다.

     

    Hooks 탄생의 목적은 아래와 같다.

    - 클래스 컴포넌트는 복잡하다.

    - 클래스 컴포넌트는 재사용이 힘들다.

    시간이 있을 때, 한 번 보는것을 추천한다.
     

    pomber/react-conf-2018-hooks-demo

    A copy of Dan's intro to hooks using Code Surfer v3 - pomber/react-conf-2018-hooks-demo

    github.com

     

    2-3. 내가 생각하는 Hooks의 탄생

     Hooks가 의미하는 것은 재사용이라고 생각한다. 지난 주에 진행했던 Naver Deview에서도 재사용성과 확장성을 개발 부채에 관련하여 설명했었다. '똑같은 것을 또 만들 수는 없잖아.'라는 개발자스러운 생각과 이전 작업자들의 코드를 보며 욕을 했던 대부분의 개발자의 경험으로 Hooks의 탄생을 설명할 수 있을 것 같다.

     웹 개발을 처음 배우게 됬을 때부터 지금까지 제대로 사용은 못하고 있지만 항상 버리지 않으려고 하는 UI 개발 방법론? Atomic Design처럼 기능 또한 최소한으로 나누어 조립하여 사용할 필요가 있다.

     

    2-4. 강력한 Custom Hook

     난 이전까지 상태관리 라이브러리도 MobX를 채택하였다.

    - 러닝커브가 낮다.

    - 사용이 간편하다.

    굉장히 단순한 이유로 MobX를 사용하였는데. 항상 불편했던 것이 있었다.

    MobX Input 예제

     여러가지 Input들이 존재해야할 때, observable 형태의 변수를 선언하고 set함수를 선언해야했다. 또, 난 코드의 통일성을 위해 computed형태의 get함수를 만들었다. 만약 Input태그가 수 십개 존재했다면 난 변수, set, get 함수를 수 십번의 복사하고 붙여놓았을 것이다. 물론, MobX를 사용하면서도 이런 무의미한 작업을 단축시킬 수 있겠지만 나의 지식의 한계 였다.

    @observable idValue = "";
    @observable pwValue = "";
    
    @action
    setidValue = (id: string) => {
    	this.idValue = id;
    }
    
    @action
    setpwValue = (pw: string) => {
    	this.pwValue = pw;
    }
    
    // 필요에 따라
    @computed get IDValue() {
    	return idValue;
    }

    Hook 예제

    - useState만 단순하게 사용하면 왜 이것만으로는 왜 다들 "hook~! hook!" 소리치는지 공감이 되지 않았다.

    const [idValue, setIdValue] = useState("");
    const [pwValue, setPwValue] = useState("");
    
    function handleIdChange(e) {
    	setIdValue(e);
    }
    
    function handlePwChange(e) {
    	setPwValue(e);
    }
    
    // ...
    <input value={idValue} onChange={handleIdChange} />
    <input value={pwValue} onChange={handlePwChange} />

    하지만 Custom Hook을 사용하게 되면 Hook은 강력해진다. 각 상태에 관한 기능을 정의하고 이를 재사용할 수 있다. 프로젝트가 커질 수록 효율은 극대화 될 것이다.

    - 더 강력한 것은 이런 Custom Hook 또한 라이브러리로 존재한 다는 것이다.

    - 다운로드 수를 보니 나 빼고 다 사용하고 있었던 느낌이다.

     

    react-hook-form

    Performant, flexible and extensible forms library for React Hooks

    www.npmjs.com

    function useFormInput(initialValue) {
    	const [value, setValue] = useState(initialValue);
    
    	function handleChange(e) {
    		setValue(e);
    	}
    
    	return {
    		value,
    		onChange: handleChange
    	};
    }
    
    const idValue = useFormInput("");
    const pwValue = useFormInput("");
    
    
    // ...
    <input {...idValue} />
    <input {...pwValue } />

     

    3. 잘 못쓰면 훅간다.


     함수형 프로그래밍, Atomic Design등 대부분의 개발자들은 이런 재사용 가능하고, 확장 가능한 모듈화를 꿈꾼다. 하지만, 프로젝트 끝에 도달하면 처음에 추구 했던 '함수형'은 온데간데 없고 순수 함수들은 이미 오염 되어있을 것이다.

     React에서도 분명 함수 컴포넌트 보다 클래스 컴포넌트가 사용이 쉽다고 생각한다. 위의 예제처럼 코드를 복사 붙여넣기 할 지언정 더 직관적이기 때문에 분명 초기 공수에서는 클래스 컴포넌트가 우위에 선다고 믿는다. 당연한 이야기이겠지만 함수 컴포넌트를 잘 활용한다면 장기적인 관점에서는 당연 함수 컴포넌트가 좋을 것이다.

    하지만, '잘 활용한다.'를 어디까지 감당할 수 있을 것이며 이후 모듈화 된 코드를 얼마나 써먹을 수 있을 것인가?의 문제인 것 같다.

     

    3-1. 최상위에서만 Hook을 호출 (참고)

    - 동일한 순서로 Hook이 호출 되는 것을 보장 하기 위해서 반복문, 조건문 혹은 중첩된 함수 내에서는 Hook을 호출 하면 안된다.

     

    잘 활용되고 있는 예제

    function Form() {
      // 1. name이라는 state 변수를 사용하세요.
      const [name, setName] = useState('Mary');
    
      // 2. Effect를 사용해 폼 데이터를 저장하세요.
      useEffect(function persistForm() {
        localStorage.setItem('formData', name);
      });
    
      // 3. surname이라는 state 변수를 사용하세요.
      const [surname, setSurname] = useState('Poppins');
    
      // 4. Effect를 사용해서 제목을 업데이트합니다.
      useEffect(function updateTitle() {
        document.title = name + ' ' + surname;
      });
    
      // ...
    }
    // ------------
    // 첫 번째 렌더링
    // ------------
    useState('Mary')           // 1. 'Mary'라는 name state 변수를 선언합니다.
    useEffect(persistForm)     // 2. 폼 데이터를 저장하기 위한 effect를 추가합니다.
    useState('Poppins')        // 3. 'Poppins'라는 surname state 변수를 선언합니다.
    useEffect(updateTitle)     // 4. 제목을 업데이트하기 위한 effect를 추가합니다.
    
    // -------------
    // 두 번째 렌더링
    // -------------
    useState('Mary')           // 1. name state 변수를 읽습니다.(인자는 무시됩니다)
    useEffect(persistForm)     // 2. 폼 데이터를 저장하기 위한 effect가 대체됩니다.
    useState('Poppins')        // 3. surname state 변수를 읽습니다.(인자는 무시됩니다)
    useEffect(updateTitle)     // 4. 제목을 업데이트하기 위한 effect가 대체됩니다.
    
    // ...

    조건문 사용으로 규칙을 깼을 때

    - React는 Hook의 호출 순서에 의존한다.

     React는 이전 렌더링 때처럼 persistForm가 effect와 일치할 것으로 예상한다. 하지만 건너뛴 Hook 다음에 호출 되는 Hook들의 순서가 하나씩 밀리게 되어 오류가 발생 된다.

    이는 lint를 통하여 빌드 이전에 오류를 잡을 수 있지만 알아두자.

    // 🔴 조건문에 Hook을 사용함으로써 첫 번째 규칙을 깼습니다
      if (name !== '') {
        useEffect(function persistForm() {
          localStorage.setItem('formData', name);
        });
      }
    useState('Mary')           // 1. name state 변수를 읽습니다. (인자는 무시됩니다)
    // useEffect(persistForm)  // 🔴 Hook을 건너뛰었습니다!
    useState('Poppins')        // 🔴 2 (3이었던). surname state 변수를 읽는 데 실패했습니다.
    useEffect(updateTitle)     // 🔴 3 (4였던). 제목을 업데이트하기 위한 effect가 대체되는 데 실패했습니다.

    - 내부에 조건문을 넣어 해결 할 수 있다.

    useEffect(function persistForm() {
        // 👍 더 이상 첫 번째 규칙을 어기지 않습니다
        if (name !== '') {
          localStorage.setItem('formData', name);
        }
      });

     

    3-2. 미지원 기능

     getSnapshotBeforeUpdate, getDerivedStateFromError, componentDidCatch 기능은 아직 업데이트 전이다. 사실 클래스 컴포넌트를 사용할 때에도 써본적 없는 기능들이다.

     

    Hooks FAQ – React

    A JavaScript library for building user interfaces

    ko.reactjs.org

    결론


     처음 글을 작성할 때는 클래스 컴포넌트와 함수 컴포넌트의 차이를 파악하고 React Hooks의 단점을 파악하고 싶었다. 하지만 생각보다 Hooks의 단점이 없었고 간단한 테스트 코드에서 조차 확실한 강점을 느낄 수 있었다.

     

     Hook이 존재하지 않았을 때, 함수 컴포넌트는 그저 state나 lifecycle을 활용하지 않는 컴포넌트 선에서 '메모리를 아끼기 위하여' 사용 되었다고 생각한다. 하지만 이제 함수 컴포넌트에서도 state, lifecycle을 사용하고 있고, 지속적인 업데이트와 recoil의 등장으로 함수 컴포넌트는 많은 지지를 받고 있는 것 같다. 언제나 그렇듯 이상적인 설계와 현실은 많이 다르지만 Hooks는 충분히 써볼 가치가 있다고 생각한다.

     

     

     

     

    댓글

Developer RyuK