본문 바로가기
FE/React & RN

[ React ] Hook 정리

by Chars4785 2019. 8. 30.

@ 어디서 궁금했는가?

리엑트 프로젝트를 하고 있다보니까  useState, useReducer, useContext 등은 잘 사용했지만 계속 되는 오류로 건들지 못하고 어디서 본 걸로 사용을 하고 에러가 뜨면 더이상 건들지 않는 경우가 많았다. 그래서 제대로 좀 정리해 보고 이것 저것 실험을 해보면서 공부를 하기 위해서 작성하게 되었다.


@ 공부

## Hook 이란?

Hooks 는 리액트 v16.8 에 새로 도입된 기능으로서, 함수형 컴포넌트에서도 상태 관리를 할 수 있는 useState, 그리고 렌더링 직후 작업을 설정하는 useEffect 등의 기능등을 제공하여 기존의 함수형 컴포넌트에서 할 수 없었던 다양한 작업을 할 수 있게 해줍니다.

 

## 우선 Hook 에 대한 규칙

Hooks 규칙
1. 리액트 함수의 제일 상단에 작성해야합니다. 
- 조건문, 내부 함수, 반복문 안에 사용하지 않습니다.

2. 리액트 함수 안에서만 사용합니다. 
- custom 함수 안에서는 예외적으로 호출 할 수 있습니다. 

ex)
const [name, setName] = useState("");

useEffect(()=>{
	document.title = `search search search my ${name}`;
});

주의

React 16.8.0은 Hook를 지원하는 첫 번째 배포입니다. 업그레이드 할 때 React DOM을 포함한 모든 패키지를 업데이트 하는 것을 잊지 마세요. React Native는 v0.59부터 Hook을 지원합니다.

 

## LifeCycle 

import React from "react";

class Content extends React.Component {
  constructor(props) {
    super(props);
  }
  componentWillMount() {
    console.log("Component WILL MOUNT!");
  }
  componentDidMount() {
    console.log("Component DID MOUNT!");
  }
  componentWillReceiveProps(nextProps) {
    console.log("Component WILL RECIEVE PROPS!");
  }
  shouldComponentUpdate(nextProps, nextState) {
    return true;
  }
  componentWillUpdate(nextProps, nextState) {
    console.log("Component WILL UPDATE!");
  }
  componentDidUpdate(prevProps, prevState) {
    console.log("Component DID UPDATE!");
  }
  componentWillUnmount() {
    console.log("Component WILL UNMOUNT!");
  }
  render() {
    return (
      <div>
        <h1>{this.props.sentDigit}</h1>
      </div>
    );
  }
}

export default Content;

LifeCycle을 알기 좋은 예시다.

 

주의할 점은, componentWillMount에서는 props나 state를 바꾸면 안 됩니다. Mount 중이기 때문이죠. 그리고 아직 DOM에 render하지 않았기 때문에 DOM에도 접근할 수 없습니다. componentDidMount에서는 DOM에 접근할 수 있습니다. 그래서 여기에서는 주로 AJAX 요청을 하거나, setTimeout, setInterval같은 행동을 합니다

 

 

## Mount vs Render

마운트 : 나타난다.

https://medium.com/@nancydo7/understanding-react-16-4-component-lifecycle-methods-e376710e5157

 

## useState

> 비동기 처리를 한다.

const onchange = e =>{
    console.log(e.target.checked);
    const{name,checked} = e.target;
    if(checked){
      setComs([...coms,name]);
    }else{
      setComs(coms.filter(t => t !== name));
    }
    // console.log(coms)
  }
  // console.log(coms)

console.log 위치에 따라서 coms 가 변화하는 것이 다르다.

값이 변화 하는 위치가 다르기 때문이다. 함수가 다 끝난 후에 값에 대한 변화가 일어 난다.

 

> 왜 setState는 비동기로 처리 되는가?

https://medium.com/@Dongmin_Jang/reactjs-setstate-%EC%99%9C-%EB%B9%84%EB%8F%99%EA%B8%B0%EB%A1%9C-%EC%B2%98%EB%A6%AC%EB%90%98%EB%8A%94%EA%B0%80-8197d707ca6a

 

## useEffect

 

- If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

 

- If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument

 

- 상태가 바뀔때, 업데이트 전에도, 리렌더링 될때 마다도 작업을 할 수 있다.

 

 useEffect([]) 이 있다면 마운트 될때 만 작동

 

componentWillMount - render - componentDidMount 합친 과정이라고 생각하면 된다.

 

- useEffect() 라면 렌더링 될때만 작동한다.

 

 

1. componentDidMount를 useEffect를 사용하여 대체할 수 있나요?

useEffect(fn, []) 를 대체하여 사용할 수는 있지만, 그렇다고componentDidMount와 useEffect가 완전히 똑같은 것은 아닙니다. useEffect는 componentDidMount와 달리 props와 state를 capture 할 수 있고, 그 안에 있는 callback에서는 초기 props값과 초기 state값을 볼 수 있습니다.

예를 들어, 만약 당신이 맨 마지막에 들어오는 값을 필요로 할 때, 당신은 ref 를 사용하여 코드를 작성할 수 있습니다.

하지만, 그럴 필요 없습니다. 더 간단하게 코드를 구조화 할 수 있는 방법이 있기 때문이죠. 여기서 이 문제를 해결하기 위해 당신이 꼭 명심해야 할 것은effect의 기본적인 모델은 componentDidMount,그 이외의 lifecycle의 기본적인 모델과 다르다는 것 입니다. 결론적으로 그 둘 사이의 정확히 일치하는 공통점을 찾으려고 하는 것은 당신이 혼란스러워 할 수 있는 일이 될 수 있다는 이야기입니다.
따라서, 생산적인 코드를 작성하려면, "think in effects"하세요.

effects의 기본 모델은 Lifecycle 이벤트에 대응하는 것보다 동기화를 구현하는데 가까운 모델이라고 할 수 있습니다.

2. useEffect 안에서 데이터를 어떻게 올바르게 fetch 할 수 있나요? 그리고 [] 이건 뭔가요?

이 글 useEffect를 사용하여 데이터를 fetch 하는데 좋은 발판이 될 수 있을 것입니다. 이 글을 확실히 끝까지 읽으세요! 제가 지금 번역하고 있는 이 글보다 길지 않습니다.

[] 이 빈 대괄호의 뜻은 effect가 React 앱 안에서 데이터 흐름을 구성하고 있는 어떤 값을 아무것도 사용하고 있지 않다는 의미입니다. 그래서 궁금하면 그냥 한 번 써봐도 괜찮습니다. 하지만 이것은 흔하게 버그를 일으킬 수 있는 코드 이기도 합니다.

여러분은 다른 전략(? strategies)를 공부해봐도 좋을 것 같습니다. (주로 useReducer나 useCallback)
that can remove the need for a dependency instead of incorrectly omitting it. (한국어로 어떤 느낌으로 번역해야 할 지 모르겠습니다..)

 

3. 함수를 effect에 의존되게 작성해야 할까요?

추천하는 방법으로는, props나 state가 필요하지 않는 함수들은 컴포넌트 외부에서 호이스팅 하고, effect 안에서는 그 effect 안에서만 사용되는 것들로만 구성해야 합니다.

만약, 여러분이 만든 effect가 render 범위 안에서 계속 사용된다면, effect들이 정의된 곳에서 useCallback으로 감싼 뒤, 계속 진행하세요.

 

4. 왜 가끔가다 refetching loop에 걸리는 것 일 까요?

이것은 두번째 인자를 넣지 않고 effect를 사용하여 데이터를 fetch 할 때 일어날 수 있는 일 입니다. 두번째 인자가 없다면, render가 모두 마친 뒤에 effect가 실행되며, state를 설정하면 다시 effect는 트리거 될 것 입니다. 또한, 계속해서 바뀌는 값을 array에 넣어도 infinite loop가 발생합니다. 아마 하나 하나씩 제거하면서 찾아야 어떤 것이 문제였는지 알 수 있을 것 입니다.

하지만, 그렇게 하나하나씩 제거하는 것은 때때로 잘못된 수정방법이 될 수 있습니다. 그 방법 대신 근본적인 부분부터 고쳐나가세요.

예를 들어, 먼저 A 함수가 그 문제를 일으킨다면, 그 A 함수를 effects 안에 넣거나, 호이스팅 하거나, 또는 useCallback으로 감싸는 것이 도움이 될 수 있을 것 입니다.

object를 새로 재생성 하지 않으려면, useMemo를 유사한 용도로 사용할 수 있습니다.

 

https://velog.io/@jepjap93/useEffect%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A7%A7%EC%9D%80-%EA%B0%80%EC%9D%B4%EB%93%9C

 

import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';

function LifecycleDemo() {
  // It takes a function
  useEffect(() => {
    // This gets called after every render, by default
    // (the first one, and every one after that)
    console.log('render!');

    // If you want to implement componentWillUnmount,
    // return a function from here, and React will call
    // it prior to unmounting.
    return () => console.log('unmounting...');
  })

  return "I'm a lifecycle demo";
}

function App() {
  // Set up a piece of state, just so that we have
  // a way to trigger a re-render.
  const [random, setRandom] = useState(Math.random());
  console.log(random);
  // Set up another piece of state to keep track of
  // whether the LifecycleDemo is shown or hidden
  const [mounted, setMounted] = useState(true);

  // This function will change the random number,
  // and trigger a re-render (in the console,
  // you'll see a "render!" from LifecycleDemo)
  const reRender = () => setRandom(Math.random());

  // This function will unmount and re-mount the
  // LifecycleDemo, so you can see its cleanup function
  // being called.
  const toggle = () => setMounted(!mounted);

  return (
    <>
      <button onClick={reRender}>Re-render</button>
      <button onClick={toggle}>Show/Hide LifecycleDemo</button>
      {mounted && <LifecycleDemo/>}
    </>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

## useCallBack

useCallback 은 useMemo와 상당히 비슷한 함수입니다. 주로 렌더링 성능을 최적화해야 하는 상황에서 사용하는데요, 이 Hook을 사용하면 이벤트 핸들러 함수를 필요할 때만 생성 할 수 있습니다.
우리가 방금 구현한 Average 컴포넌트를 보면, onChange 와 onInsert 라는 함수를 선언해주었습니다. 이렇게 선언을 하게 되면 컴포넌트가 리렌더링 될 때마다 이 함수들이 새로 생성됩니다. 대부분의 경우에는 이러한 방식이 문제가 되지 않지만, 컴포넌트의 렌더링이 자주 발생하거나, 렌더링 해야 할 컴포넌트의 개수가 많아진다면 이 부분을 최적화 해주시는 것이 좋습니다.

 

useCallback 의 첫번째 파라미터에는 우리가 생성해주고 싶은 함수를 넣어주고, 두번째 파라미터에는 배열을 넣어주면 되는데 이 배열에는 어떤 값이 바뀌었을 때 함수를 새로 생성해주어야 하는지 명시해주어야 합니다.

만약에 onChange 처럼 비어있는 배열을 넣게 되면 컴포넌트가 렌더링 될 때 단 한번만 함수가 생성되며, onInsert 처럼 배열 안에 number 와 list 를 넣게 되면 인풋 내용이 바뀌거나 새로운 항목이 추가 될 때마다 함수가 생성됩니다.

 

const changeDate = useCallback( ( e ) => {
    const{ name,value } = e.target;
    if( name === "to" && new Date(range.from) > new Date(value) ){
      alert("날짜가 앞섭니다.");
    }
    setRange({  
      ...range,
      [ name ]:value
    })
  }, [ range ]);
  // 그냥 [] 으로 하게 되면 처음 렌더링 될 때만 함수 생성이 된다. 
  // range 를 적게 되면

## useRef

컴포넌트 안의 변수 만들기 그런데 변수를 만들면 리렌더링 하게 되면 그 변수 값은 초기화 값이 된다. 그래서 계속 유지하기 위해서 useState 를 사용하게 된다. 그런데 state 를 바꾸면 component가 리렌더링 하게 된다.

근데 굳이 리렌더링 할때 마다 할 이유기 없는 값 > setTimeout, setInterval 의 id , 외부라이브러리를 사용하여 생성된 인스턴스,

 

> useRef는 바뀌어도  component가 리렌더링 되지 않는다. 

  const [users,setUsers] = useState([
    {
      id:1,
      username: 'lee1',
      email:'test@name'
    },
    {
      id:2,
      username: 'lee2',
      email:'test2@name'
    },
    {
      id:3,
      username: 'lee3',
      email:'test3@name'
    }
  ]);

  const nextId = useRef(4);
  //useRef 4가 바뀐다고 컴퍼넌트가 리렌더링 되지 않는다.

  const onCreate =() =>{
    const user ={
      id:nextId.current,
      username,
      email,
    }
    ...( 중략 )

 

 


 

 import _ from 'lodash';
  const onchange = useCallback( ( e ) => {
    const { name,checked } = e.target;

    if( !checked ){
        const clonning = _.cloneDeep( coms )
      _.remove( clonning, ( clone ) => clone === name );
      setComs( clonning );

    }else{
      setComs([ ...coms, name ]);
    }
    
  }, [ coms ] );
  useEffect(() => {
    console.log("useEffect");
    return 
  })
  useCallback(()=>{
    console.log("useCallback")
  })

## [] 부분 정리

const [ num, setNum ] = useState(0)
  let bb = 0;
  console.log(num);

  useEffect(()=>{
    console.log("mount");
    return console.log("unMount")
  },[])
  
  const onPress = useCallback(() =>{
    console.log("useCallBack")
    bb++;
    setNum( num + bb );
    console.log(num)
  },[])

result

> 버튼을 눌렀을 때

useCallBack
App.js:21 0
App.js:10 1
App.js:18 useCallBack
App.js:21 0
App.js:10 2
App.js:18 useCallBack
App.js:21 0
App.js:10 3

결국 [] 하게 된다면 onPress() 는 mount 될 당시에 한번만 생성되게 된다. 그렇게 된다면 num 라는 값은 onPress에 종속되어 있는 값으로 설정되게 된다. 그래서 onPress() 에서의 num 과 useState() 안에 있는 num 을 같게 보면 안된다. class component 에서 this 로 구분 지었던 것과 같은 것이라고 생각하면 된다. 그렇기 때문에 [ num ] 을 쓰게 된다면 num 은 새로운 값으로 갱신 된다. 새로운 변수가 생긴는 것이라고 생각해도 좋지만 ( let _num = num 은 아니다 )

 

[ num ] 을 하게 되면 함수를 새롭게 만들게 된다.

 

  const onPress = useCallback(() =>{
    console.log("useCallBack")
    //bb++;
    //setNum( num + bb );
    console.log(num)
  },[])

 

App.js:18 useCallBack
App.js:21 0
App.js:18 useCallBack
App.js:21 0

결국 state 가 변화하지 않는다면 렌더링은 되지 않는다.

 

useEffect(()=>{
    console.log("mount");
    return console.log("unMount")
  })

> 리렌더링 될때 호출 된다.

 

 

### 결론

 

[] 비어있다면 연관되어 있는 값이 없기 때문에 처음 마운트 될때 생성되고 존재한다.

[]가 없다면 리렌더링 될때 마다 새로 호출하게 된다.

[ value ] 가 있다면  value 값이 변할 때 마다 새로 함수가 생성된다.

 

함수 안에 있는 값은 

function testStore(){
  const [ num, setNum ] = useState(0);

  console.log("check");
  // rerendering 이 될때만 실행 하게 된다.

  const onClick = () => {
      //setNum( num + 1 );
  }

  return(
      <div> 
          <h1>hi</h1>
          <button onClick={onClick} />
      </div>
  )
}

console.log( "check")  값은 리렌더링 될때 다시 실행 하게 된다.

 

 

## useRef

const idReference = document.getElementById('id')

와 같이 특정 태그에 접근 하기 위해서 사용하는 경우가 있다. 많은 태그가 존재할 때 관리하기 힘들수 있다. 그래서 useRef() 훅을 통해서 쉬게 접근 할 수 있다. current를 통해서 해당 값에 접근 할 수 있습니다.

function App() {
  let [name, setName] = useState("Nate");

  let nameRef = useRef(null);

  const submitButton = () => {
    setName(nameRef.current.value);
  };

  return (
    <div className="App">
      <p>{name}</p>

      <div>
        <input ref={nameRef} type="text" />
        <button type="button" onClick={submitButton}>
          Submit
        </button>
      </div>
    </div>
  );
}

useRef(null) 로 설정하는 이유 

> Ref.current is null because the ref is not set till after the function returns and the content is rendered

const refContainer = useRef(initialValue);

How can I measure a DOM node?

In order to measure the position or size of a DOM node, you can use a callback ref. React will call that callback whenever the ref gets attached to a different node. Here is a small demo:

> 참고

https://ko.reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node

function MeasureExample() {
  const [height, setHeight] = useState(0);

  const measuredRef = useCallback(node => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height);
    }
  }, []);

  return (
    <>
      <h1 ref={measuredRef}>Hello, world</h1>
      <h2>The above header is {Math.round(height)}px tall</h2>
    </>
  );
}

 

'FE > React & RN' 카테고리의 다른 글

[ React-Native ] 시작  (0) 2019.10.21
[ React ] Redux  (0) 2019.09.04
[ React ] Context API  (0) 2019.08.18
[React] 억음 표시 ( grave accent ) `  (0) 2019.08.03
[React] 조건부 렌더링  (0) 2019.08.03

댓글