리액트 공부 #4

min Mt·2020년 4월 24일
0
post-thumbnail

얼마전 완성한 리액트 프로젝트를 회고 하면서 다음 리액트 프로젝트를 시작 하기 전,
리액트를 깊이있게 공부하고 싶어 적는 나의 리액트 공부.log

Reactjs.org의 공식 문서 중 MAIN CONCEPTS 를 다시 한번 천천히 살펴보자.

Higher-Order Components

Concretely, a higher-order component is a function that takes a component and returns a new component.

const EnhancedComponent = higherOrderComponent(WrappedComponent);

redux의 connect가 바로 이 HOC이다.

HOC는 function 이고 컴포넌트를 반환하기 때문에 다음과 같은 구조를 갖는다고 외우자.

function withSomething(WrappedComponent, data){
  //감싸진 컴포넌트를 받아서 새로운 컴포넌트를 반환함.
  return class extends React.Component {
    constructor(props){
      this.state = {
        data: modifyData(data), //받은 데이터를 원하는대로 수정
      }
    }
    render(){
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }      
  }  
}

즉, 반복되는 코드들을 모아서 하나의 HOC로 만들고, HOC로 컴포넌트들을 감싸서 반복을 줄일 수 있다.

Caveats

  1. Don’t Use HOCs Inside the render Method
  2. Static Methods Must Be Copied Over
  3. Refs Aren’t Passed Through

Hooks

16.8에 추가된 Hooks를 알아보자.
Hooks는 클래스없이도 state를 사용가능하게 만들어준다.

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0); //여기서 0 은 기본값이다.

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Hooks는 기존에 React가 가지고 있던 다음과 같은 문제를 해결하기 위해 고안되었다고 한다

1. It's hard to reuse stateful logic between components
컴포넌트가 state를 사용하는 로직을 재사용하는게 쉽지 않다. 하지만 Hooks에서는 이게 쉽다. state를 원하는 로직에 따라 변경되도록 하는 OwnHooks를 만들어 얼마든지 재사용이 가능하다. Hooks는 보면 함수형 컴포넌트를 지향하는 듯 하다.

2. Complex components become hard to understand.
Class Component를 사용하면 Life Cycle함수를 이용할 수 있다는 장점이 있다. 이게 장점이기도 하지만, 단점은 복잡해질 수 있다는 것이다. 하지만 Hooks를 사용하면 Functional Component로 대체할 수 있게 되기 때문에 더 단순하게 나눌 수 있게된다.

3. Classes confuse both people and machines.
클래스가 진입 장벽도 높고 이벤트 핸들러를 bind하는 것 등 어려운 문제가 많지만,
Functional component는 구조도 단순하고 배우기도 쉽다. 그리고 Functional Component를 Class없이 사용할 수 있도록 도와준다.

useState

function ExampleWithManyStates() {  
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);

배열 구조 분해로 변수를 할당하고, 첫번째 값은 state, 두번째 값은 setState라고 보면된다.
클래스 컴포넌트와 마찬가지로 setState함수가 호출되고 나면 변경된 state에 맞춰 렌더링을 다시 한다.

useEffect

useState로 state를 변경하고 출력되는 UI도 변경했다. 그런데 변경된 값을 console.log로 찍어보고 싶거나 할때는 어떻게 할까? Class Component에서는 ComponentDidUpdate()같은 라이프사이클을 이용하면 됐던거를 useEffect를 사용하면 된다. 공식 문서에 따르면 useEffect()는 렌더링 이후 매번 수행된다고 한다.

Clean-up

재밌는 것은 기존 Class Component에서는 ComponentWillUnmount 에서 구독해지하는 과정을 통해 메모리 누수를 막는 작업을 하게 되있었는데, 이 과정을 더 쉽고 또 더 간결하게 useEffect를 통해 가능하다는 것이다.

useEffect()가 구독해지하는 함수를 리턴하게 된다면, 다음 useEffect()가 호출될 때, 기존 구독을 해지 하고 다시 구독을 하게 된다.
다음 예를 봐보자

function FriendStatus(props) {
  // ...
  useEffect(() => {
    // ...
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

구독 해지함수를 리턴하게 되면 useEffect()가 호출될때마다 해지 -> 구독의 과정을 반복하게 된다.

// { friend: { id: 100 } } state을 사용하여 마운트합니다.
ChatAPI.subscribeToFriendStatus(100, handleStatusChange);     // 첫번째 effect가 작동합니다.

// { friend: { id: 200 } } state로 업데이트합니다.
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 이전의 effect를 정리(clean-up)합니다.
ChatAPI.subscribeToFriendStatus(200, handleStatusChange);     // 다음 effect가 작동합니다.

// { friend: { id: 300 } } state로 업데이트합니다.
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 이전의 effect를 정리(clean-up)합니다.
ChatAPI.subscribeToFriendStatus(300, handleStatusChange);     // 다음 effect가 작동합니다.

// 마운트를 해제합니다.
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 마지막 effect를 정리(clean-up)합니다.

이렇게 매 번 리렌더링 될때마다 구독 해지 하는 과정을 반복하게 된다면, 관리 및 버그 발생가능성도 낮아지게 될텐데 문제는 성능이다. 이 때문에 useEffect의 두번째 인자로 값을 넣어 해당 값이 변할때만 useEffect()가 호출되도록 할 수 있다.

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // count가 바뀔 때만 effect를 재실행합니다.

//Tip 빈 배열을 인자로 넣으면 useEffect가 첫 렌더링 시에만 호출된다.

나중에 확인할 사항!
useEffect()가 그럼 컴포넌트가 Update가 아닌 Unmount 될 때 해지 하도록 하려면 어떻게 해야 하지?
정리하다 보니, cleanup 함수는 ComponeneWillUnmount 보다는 ComponentDidUpdate를 대체하는 용도에 가깝다는 생각이 든다.
나중에 더 알아보자.
아시는 분께서 댓글로 알려주시면 편할텐데..

결론

Hooks는 참 좋은 것 같다. 무엇보다 Hooks를 기존 방식과 100% 호환하면서 사용할 수 있다는 것이 정말 좋다. 다음 프로젝트에서도 여전히 Class Component를 사용하면서 새롭게 배운 패턴에 익숙해져야 겠지만, 계속 사용할만한 로직들은 Hooks로 만들어 놓고 함수형 컴포넌트로 사용해야겠다.

profile
안녕하세요!

0개의 댓글