본문 바로가기

React

20210609 React07 : HOC (고차 컴포넌트), Controlled vs Uncontrolled Component, Hooks(useState, useEffect)

React 07



HOC (Higher-Oder Components)

  • 고차 컴포넌트
  • Hook이 나오면서 사용성이 줄어들고 있음
  • 컴포넌트를 재사용 할수 있는 기술
  • React 에서만 있는 기술이 아닌 일종의 패턴 개념임
  • HOC는 컴포넌트를 인자로 받아 새로운 컴포넌트를 리턴하는 함수

컴포넌트 vs HOC

  • input : props -> 컴포넌트 -> output : UI
  • input : 컴포넌트 -> HOC -> output : 새로운 컴포넌트

사용법

  • cross-cutting concerns (횡단 관심사)로 사용함
    • 여러 페이지에서 지속적으로 일어 날수 있는 일 (ex. 로그인, 인증, 로깅)
    • 그런 일을 전문적으로 하나로 분리하여 관리하여 여러 페이지에서 사용함
  • HOC에 인자로 들어가는 컴포넌트를 변경하지 말아야 함 -> 컴포지션을 사용
    • React : composition vs inheritance : react에서는 상속보다는 강력한 합성 방법을 추천함
    • Composition (컴포지션, 합성) : 컴포넌트를 인자로 해서 다른 컴포넌트에 집어 넣어 return하는 방식
    • 어떤 자식 엘리먼트가 들어올지 모르는 컴포넌트의 경우 children prop을 사용하여 자식 엘리먼트를 출력 그대로 전달 가능
  • 우리가 넣는 props (unrelated props)와 HOC가 만든 props가 오염되지 않도록, 우리가 넣는 props는 JSX에 제대로 전달 해야함
  • Composability (조합하는 것을) 최대화 해라
  • 쉬운 디버깅을 위해서 새로 만들어진 컴포넌트(인자X)에 HOC 임을 알려주는 컴포넌트 Display Name을 꼭 넣어주어라

한계

  • HOC에 대해서는 아직 잘 감이 오질 않는다. 필요한 상태에서 사용하는게 가장 이해가 잘되는듯 한데 아직인듯 하다.



Controlled Component vs Uncontrolled Component

  • React의 원칙 : 신뢰 가능한 단일 소스로 내부의 상태를 관리
    • 자식 컴포넌트가 data가 필요할 경우, 해당 data는 가장 가까운 공통 부모 컴포넌트에게서만 props의 형태로 전달받아서 사용해야 한다.
    • -> form 관련한 태그들(input, textarea, select)은 정보를 가지기 때문에 내부적인 state에 의해 정보가 관리되지 않고 real Dom에 의해서 관리됨

  • Controlled Component : 엘리먼트의 상태를 컴포넌트가 관리 (PUSH 방식)
    • event에 의해서 받아오는 정보를 state에 저장하여 변경하고 해당 state 값을 다시 컴포넌트의 value prop으로 전달 -> dom element의 value를 변경 (state값이 변경될 때마다 Rerendering)

import React from "react";
class ControlledComponent extends React.Component {
  state = {
    value: "",
  };
  render() {
    const { value } = this.state;
    return (
      <div>
        <input value={value} onChange={this.change} />
        <button onClick={this.click}>전송</button>
      </div>
    );
  }
  change = (e) => {
    this.setState({ value: e.target.value });
  };

  click = () => {
    console.log(this.state.value);
  };
}

export default ControlledComponent;

  • Uncontrolled Component : 엘리먼트의 상태를 관리 하지 않고, 엘리먼트의 참조만 컴포넌트가 소유 (PULL 방식)
    • 실제 DOM의 Ref 속성을 참조하여 엘리먼트에 있는 정보를 사용함
    • 컴포넌트에서 실시간으로 정보를 관리하지 않아 실시간 작업은 부적합함
import React from "react";
class UncontrolledComponent extends React.Component {
  inputRef = React.createRef();

  render() {
    console.log("initial render", this.inputRef);
    return (
      <div>
        <input ref={this.inputRef} />
        <button onClick={this.click}>전송</button>
      </div>
    );
  }
  componentDidMount() {
    console.log("componentDidMount", this.inputRef);
  }
  click = () => {
    // input 엘리먼트의 현재상태 값 value 꺼내서 전송
    // const input = document.querySelector('#my-input');
    // console.log(input.value);
    // real dom의 값을 읽어 들이는 것을 지양함
    console.log(this.inputRef.current.value);
  };
}

export default UncontrolledComponent;




Hooks

  • function component도 Life Cycle, State를 사용가능케 해줌
  • Component 의 State를 재사용 가능케 함
  • 16.8버전 부터 지원

  • Hooks를 사용하여 function component를 만든 이유
    • class Component는 컴포넌트 사이에서 상태와 관련된 로직을 재사용하기 어려움
      • 컨테이너 방식(props를 이용해서 재사용하는 방식) 말고, 상태와 관련된 로직
    • 복잡한 컴포넌트 이해가 어려움
    • Class는 컴파일 단계에서 코드 최적화를 어렵게함
    • this.state는 로직에서 레퍼런스를 공유하기 때문에 문제가 발생 할 수 있음
      • 즉, Life cycle 사이에 this.state를 공유함 (반대로 function component는 render, Life cycle사이에 공유하지 않음)


Basic Hooks

  • ussState
    • state를 대체 할 수 있음 (동등하지는 않음)
  • useEffect
    • 라이프 사이클 훅을 대체 할 수 있음
    • componentDidMount
    • componentDidUpdate
    • componentWillUnmount
  • useContext

useState


기존의 Class Component 사용 방식

import React from "react";

export default class Example1 extends React.Component {
  state = { count: 0 };
  render() {
    const { count } = this.state;
    return (
      <div>
        <p>Class : You clicked {count} times</p>
        <button onClick={this.click}>Click Me! : classComponent</button>
      </div>
    );
  }
  click = () => {
    this.setState({ count: this.state.count + 1 });
  };
}

Function component + useState 사용 방식01

  • state에 하나의 값을 가지는 경우
import React from "react";

export default function Example2() {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <p>Hooks : You clicked {count} times</p>
      <button onClick={click}>Click Me! : Hook</button>
    </div>
  );

  function click() {
    setCount(count + 1);
  }
}

Function component + uesState 사용 방식02

  • state에 객체로 여러 값을 가지는 경우
  • setState 내부에 함수를 사용하면, 해당 state를 인자로 받기 때문에 state 명에 의존적이지 않고 사용할 수 있음
import React from "react";
// useState => count
// useState => {count: 0}
export default function Example3() {
  const [state, setState] = React.useState({ count: 0, count1: 1 });

  return (
    <div>
      <p>Count + 1 : You clicked {state.count} times</p>
      <p>Count + 1 : You clicked {state.count1} times</p>
      <button onClick={click}>Click Me! : Hook</button>
    </div>
  );

  function click() {
    // setState({ count: state.count + 1 });

    // 기존의 외부 state의 이름을 몰라도 state를 사용하고자 할때 -> 함수를 많이 사용
    // 인자로 해당 state를 들고 오기 때문에
    setState((state) => {
      return {
        count: state.count + 1,
        count1: state.count1 + 1,
      };
    });
  }
}



useEffect


  • Rinae's devlog : UseEffect 완벽 가이드
  • useEffect는 class Component의 Life cycle 에서 componentDidMount, componentDidUpdate 훅의 기능을 대체하고 있음, 또한 componentWillUnmount의 기능도 대체 됨
  • 그래서 최초 render 후, state 및 props Update 후에 실행할 명령을 지정하여 때가 되면 실행시켜준다.

class Component 방식

import React from "react";

export default class Example4 extends React.Component {
  state = { count: 0 };
  render() {
    const { count } = this.state;
    return (
      <div>
        <p>Class DidMount,Update: You clicked {count} times</p>
        <button onClick={this.click}>Click Me! : classComponent</button>
      </div>
    );
  }

  componentDidMount() {
    console.log("componentDidMount", this.state.count);
  }
  componentDidUpdate() {
    console.log("componentDidUpdate", this.state.count);
  }

  click = () => {
    this.setState({ count: this.state.count + 1 });
  };
}

function component + useEffect

  • 기본 구조
    • function component에서 JSX return 전에 useEffect 함수안에 callback을 넣어 사용함
import React from "react";

export default function Example5() {
  const [count, setCount] = React.useState(0);

  React.useEffect(() => {
    console.log("componentDidMount & componentDidUpdate", count);
  });

  return (
    <div>
      <p>Hooks : You clicked {count} times</p>
      <button onClick={click}>Click Me! : Hook</button>
    </div>
  );

  function click() {
    setCount(count + 1);
  }
}
  • 뒤에 state 옵션을 사용하는 방식에 따라 useEffect가 작동하는 방식이 달라진다.


useEffect : [] 가 없는 경우

  • 최초 Mount가 된 후 그리고 Update가 된 후 마다 useEffect를 실행
// 최초 그리고 update때 모두 실행
React.useEffect(() => {
  console.log("componentDidMount & componentDidUpdate", count);
});


useEffect : [] 가 있고 비어 있는 경우

  • 최초 Mount가 된 후에 만 useEffect를 실행 (Update시에는 useEffect가 실행 되지 않음)
  • 원래는 useEffect callback 안에 state를 사용하게 되면 당연히 update되면 update시켜서 render 시켜주는 것이 원칙이기에 []안에 count를 써주는 것이 마땅하다. (의도치 않으면 mount시에만 실행시키도록 하는 것도 상관은 없지만, 통일성을 저해한다.)
React.useEffect(() => {
  console.log("componentDidMount & componentDidUpdate", count);
}, []);


useEffect : [state] 가 있는 경우

  • 최초 Mount가 된 후 그리고 특정 state만 update되면 useEffect를 실행
React.useEffect(() => {
  console.log("componentDidMount & componentDidUpdate", count);
},[count]]);


useEffect : ComponentWillUnmout 대체 기능 (cleanup) 등..


state 옵션이 없어 최초 실행만 되는 useEffect의 경우

  • useEffect의 callback 함수의 return 값에 함수를 지정하면 해당 컴포넌트가 완전히 없어지기 전 useEffect의 callback의 return에 있는 함수를 실행킨다.
React.useEffect(() => {
  console.log("componentDidMount");

  return () => {
    // cleanup
    // componentWillUnmount
  };
}, []);

state 옵션이 있어 mount, update 후 모두 실행되는 uesEffect 경우

  • return 부분의 명령은 component가 없어지기 전에 실행도 되고, 또한 update가 되어 rendor 후 uesEffect 실행되기 전에 Update 이전 state 값을 이용하여 명령을 실행한다.
  • render -> DidMount(state 무관 useEffect) -> state update(버튼 클릭) -> render -> cleanUp(state 있는 useEffect return) -> DidUpdate(state 있는 useEffect)
import React from "react";

// component 정의
export default function Example5() {
  // state 정의
  const [count, setCount] = React.useState(0);

  // 최초 실행 : state 옵션 없는 경우
  React.useEffect(() => {
    console.log("DidMount");

    return () => {
      // cleanUp
      // componentWillUnmount
      console.log("WillUnmount");
    };
  }, []);

  // 최초 실행 및 update 마다 : state 옵션 있는 경우
  React.useEffect(() => {
    console.log("DidMount & DidUpdate", count);

    return () => {
      // cleanUp (update 이전 count 값)
      console.log("cleanup by count", count);
    };
  }, [count]);

  // JSX render
  return (
    <div>
      {console.log("render")}
      <p>Hooks : You clicked {count} times</p>
      <button onClick={click}>Click Me! : Hook</button>
    </div>
  );

  // state update
  function click() {
    setCount(count + 1);
  }
}