리다기 스터디 8. Hooks

설영환·2020년 8월 30일
0

react-study

목록 보기
10/11

1. 훅스란?

React 16.8버전부터 새로 추가된 class를 작성할 필요없이 상태값과 여러 React의 기능들을 사용하기 위해서 만든 api입니다.

1.1 훅스를 만들게 된 동기

  • class형은 컴포넌트에서 State와 관련된 로직을 재사용하기 어렵습니다.(store에 component연결등)

  • 복잡한 컴포넌트들은 다양한 메서드로 인하여 이해하기 힘듦니다. (useEffect를 이용하면 간단함)

  • class형은 사람과 컴퓨터의 이해를 느리게합니다.
    클래스형은 재사용성과 코드구성이 더 어렵고, js에서의 this의 이해도, 불안정한 문법이 없다면 코드가 길어집니다. 그리고 class형은 최근 tool과도 많은 문제를 일으킵니다.

    하지만 class형은 계속 남길 예정이라고 밝히긴했습니다. 둘중에 권장은 함수형을 권장하여 hook를 권장하지만 class형으로 제작된 React에서도 계속 지원하겠다는 이야기로 들립니다.

1.2 Hook의 종류


위 사진과 같이 기본훅스3종류와 추가 훅스 그리고 커스텀으로 훅스까지 만들수 있습니다.
여기서는 기본 Hook 3가지 모두와 추가 hooks 몇가지를 설명해보려합니다. (최대한으로 설명하겠지만.. 조사의 한계때문에 몇개는 잘릴거 같습니다.)

2. 기본 Hooks

2.1 useState

useState라는 hook은 State(상태)를 생성하고, 그것을 관리하는 함수를 만들기위해 필요한 기본 hook입니다.

2.1.1 useState와 class형의 State 선언 비교

  • 함수형(useState)

    import React,{ useState } from "react";
    const Example = () =>{
    	const [stateName,setStateName] =useState("초기값");

    위의 세번째줄이 state선언의 끝입니다. useState()는 return으로 length가 2인 배열을 return합니다. 그것을 앞에서 구조분해 할당으로 받아오는방법 입니다.
    그중 앞 stateName에 해당되는 부분이 state의 이름이 되는것입니다. 두개 이상의 state를 생성하고 싶다면 원하는 만큼 useState를 사용하면됩니다.
    물론 초기값에 객체나 배열로 들어가도 되지만 call by value인 JS에서는 reference값이 들어가므로 안의 내용을 임의로 바꿔도 되긴하지만.. 추천하진 않습니다.

  • class형 (constructor)

    import React, { Component } from "react"
    class Example extends Component{
    	constructor(props){
       		super(props);
       		this.state ={
               		stateName = "초기값",
               	};
           };
     }

    class형은 위의 코드처럼 state를 선언하기 위해서는 (props이용을 위해서도) 최소 5줄 이상이 사용됩니다.

2.1.2 state(상태) 사용하기

  • 함수형
    <p> 상태값의 초기값은 {stateName} 입니다 <p>
  • class형
    <p> 상태값의 초기값은 {this.state.stateName}입니다</p>

위의 두 호출을 봐서도 함수형쪽에서 훨씬 더 쉽게 state를 호출하여 사용할수 있습니다.

2.1.3 state(상태) 변경/갱신 하기

  • 함수형
    this를 호출하지 않고 useState의 두번째 인자를 함수처럼 사용해도 됩니다.

    setStateName("변경할값");

    위처럼 바로 사용하면 stateName이라는 state가 "변경할값"이라는 string으로 변경되게 됩니다.
    태그 안에서도 마찬가지로

    <button onClick={()=>setStateName("변경할값")}> </button>

    위의 코드처럼 사용한다면 버튼을 누른다면 stateName의 값이 "변경할값"으로 변하게 됩니다.

  • class 형
    클래스형은 각자의 변경 함수는 존재하지 않습니다. setState하나로 모든 변경이 가능하나 this를 사용하여 scope를 확실히 알고 사용해야하는 불편함은 있습니다.

    this.setState({
    	stateName:"변경할값"
       });  

    으로 사용해서 stateName이라는 값을 변경해줘야됩니다.

    함수형과 마찬가지로 tag안에서는

    <button onClick={()=>{this.setState({
    	stateName: "변경할값",
    })}></button>

    이렇게 써주어야 stateName이라는 state가 함수형과 마찬가지로 변형되게 되는것입니다.

2.2 useEffect

useEffect가 하는일은 렌더링 이후에 어떠한 일을 수행해야되는지 알려주는 역할을 합니다. DOM 업데이트 이우헤 나타나므로 Document를 가져올수 있지만 사용을 권장하지는 않습니다.
effect훅을 사용한다면 함수컴포넌트에서 side effect(클래스형의 라이프사이클 메서드)를 수행할수 있습니다.
class형의 Lifecycle 메서드중 componentDidMount와 componentDidUpdate, componentWillUnmount의 세가지의 메서드를 대체할수 있습니다.

여기서 보통 componentDidMount와 componentWillUpdate를 사용하는 경우는 다른 외부데이터를 subscribe(구독)하는 경우에 사용한다고 공홈에 나와있습니다. 그 예제로 대체합니다.

2.2.1 비교용 코드

  • 클래스형에서의 Lifecycle 메서드(Update 제외)
class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }

  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}
  • 위의 코드를 useEffct를 사용했을때의 변화
import React, { useState, useEffect } from 'react';

const FriendStatus = (props) => {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // effect 이후에 어떻게 정리(clean-up)할 것인지 표시합니다.
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

이렇게 바뀌게 됩니다. 코드가 이해가 안되시는 분들은 아래 메서드별로 나누어 놓을테니 걱정말고 넘어가시면 됩니다.
다만 위의 코드 줄수의 변화나 같은부분을 어떻게 줄였는지 여부는 한번 보시고 넘어가시면 좋을거같습니다.
(여기서 Update까지 있다면 class형은 더 많이 늘어나지만 함수형은 한두줄 늘어나거나 늘어나지 않습니다.)

2.2.2 Mount될때만 사용하고 싶을때

다시 리다기로 예제로 하겠습니다.

  • 함수형(useEffect)사용
import React, { useState, useEffect } from 'react';

const Info =() => {
    const [name, setName] = useState("");
    const [nickName, setNickName] = useState("");
    useEffect(()=>{
    	...함수내용
    ,[]}
    ...
}

위의 코드에서 함수내용에 관련된 내용을 넣으면 Mount될때만 사용되게 됩니다. (useEffect 두번째 인자를 빈 배열로 넣을 때)

  • class 형(componentDidMount) 사용
import React, {Component} from "react";
class Info extends Component{
    constructor(props){
    super(props);
        this.state ={
            name:"",
            nickName:"",
        }
    }
    componentDidMount(){
    	함수내용...
    }
}

위의 내용으로 보면 state선언을 제외하고는 별로 차이는 없어보입니다.(메서드 이름이 긴거빼고..?)
하지만 componentDidUpdate와 WillUnmount랑 같이쓰면 useEffect는 Line수가 몇줄 안늘어나지만(아예 안늘어날수도 있습니다.) class형은 함수 내용 만큼 똑같이 늘어납니다.

2.2.3 Update시 사용하고 싶을때

class형은 함수내용만 같고 componentDidUpdate만 사용하면 되기때문에 따로 예제는 적지 않겠습니다.

  • 함수형(useEffect)
import React,{ useState, useEffect} from "react";

const Info = () =>{
    const [name, setName] = useState("");
    const [nickName, setNickName] = useState("");
    useEffect(()=>{
    	...함수내용
    ,[name,nickName]}
    ...
}

위의 코드에서 Mount시와 변화된것은 useEffect의 두번째 인자로 배열이 들어갔다는 점입니다.
이렇게 되면 name이나, nickName이 update될때마다 useEffect를 실행시키게 됩니다.

두번째 인자를 넣을거처럼 ","하고 배열을 넣지 않는다면 무한 실행이됩니다. 한번만 실행시키고 싶다면 빈배열을 넣어주세요.. 그리고 두번째 인자를 아예 안넣는다면 매 리랜더링마다 실행됩니다.

2.2.4 Unmount시 실행시키고 싶을때

useEffect 의 return의 형태는 항상 함수형태이어야됩니다. 이는 unmount될때 이 함수를 콜백처럼 실행시게 됩니다.

useEffect(()=>{
    return() =>{
    	console.log("unMount시 실행되는 함수내용");
        };
}

2.2.5 위의 모든 메서드 합친 useEffect

useEffect(()=>{
    ...mount,update시 함수내용
    return () =>{
    	... unMount시 함수내용
    }
,[...update될 state]}

위의 코드가 다입니다
당연히 ... 부분은 줄인 부분이지만 클래스형을 쓴다면 훨씬 더 긴 코드가 됩니다.

2.3 useContext

useContext는 뒤에 나오는 contextAPI에서 나오므로 이 장에서는 다루지 않습니다. 추후에 제가 시간이 난다면 여기에 추가해 놓겠습니다.

3. 추가 Hooks

3.1 useReducer

3.1.1 상태관리

리듀서의 사용을 이해하려면 state와 props에 대해 잠깐 이야기해야됩니다. 부모 컴포넌트에서 내려준 props가 변하면 하위 컴포넌트는 다 변하게 됩니다. 하지만 굳이 중간에 props가 필요 없고 depth가 깊은 쪽에서 얕은쪽의 props를 받아야한다면 가운데를 거쳐가야될 필요가 없습니다. 때문에 상태관리가 생겨납니다. 필요없는 state를 굳이 중간중간 거쳐서 모든 component가 rendering이 일어나게 하는 일을 안일어나게 하고자 상태관리를 해야됩니다. 이후에 17장에서 상태관리를 redux나 mobX로 하게되므로 많이쓰이지는 않지만 상태관리의 개념을 잡고가시는편이 좋을거같습니다. 이해가 안되시더라도.. 17장에서 다시나오니 패스하셔도 됩니다.

3.1.2 reducer 인자

  • 첫번째 input 값
    reducer가 실행될 첫번째 함수가 인풋 첫번째 인자로 들어가야됩니다. 여기서 action에 따른 상태의 변화를 넣어주고 return을 state로 해야됩니다. 이 함수의 첫번째 인풋인자로는 상태가 들어가고 두번째 인자로는 action이 들어갑니다. 이를 이용하여 상태를 return하는 함수를 만드시면 됩니다.

  • 두번째 인풋값
    상태의 값이 들어가야되며 초기값을 설정해줘야됩니다.

  • return
    return값은 length가 2인 배열로 나오며 배열의 첫번째 에는 그때그때 바뀐 상태가 들어가게 됩니다.
    두번재에는 dispatch함수가 들어가게되어 이 함수의 인자에 action값을 넣으면 action에 의해 첫번째 return인자의 상태가 변하게 됩니다.

const [상태이름,디스패치함수이름] = 
useReducer(리듀서_함수이름,상태초기값);
//리듀서 함수에관련해서는 예제에서 자세히

3.1.3 reducer 예제

리듀서는 상태관리를 더 용이하게 하기위해 action값을 전달받아 새로운 state를 반환하는 함수입니다. 리듀서 함수를 만들때는 불변성을 지켜야됩니다.

const reducer(state,action){
	action에 관련된 함수내용
return {새로운 상태}// 불변성을 지키며 새로운 상태 반환

//액션값 타입입력 - 여기서 굳이 타입일필요는 없습니다.  문자열이어도 됩니다.
{
    type: "INCREMENT"
}

counter component를 reducer로 구현해본다면

import React, {useReducer} from 'react';

const reducer(state,action){ // 리듀서 함수(첫번째 input인자)
    switch(action.type){
        case "INCREMENT":
        return { value : state:value +1};
        case "DECREMENT":
        return { value : state:value +1};
        default:
        return state;
    }
}
const Counter = () =>{// component
    const [state, dispatch] = useReducer(reducer, {value:0});
    
    return(
    <div>
      <p>
        현재 카운터 값은 <b>{state.value}</b>입니다.
      </p>
      <button onClick={() => dispatch({ type: ‘INCREMENT‘ })}>+1</button>
      <button onClick={() => dispatch({ type: ‘DECREMENT‘ })}>-1</button>
    </div>
   );
};

이렇게 된다면 버튼을 누를때마다 +1과 -1이 될것입니다.

추가적인 내용은 추후 17장에서 다루겠습니다.

3.2 useMemo, useCallback

3.2.1 Memoization

  • 메모이제이션이란
    이전 계산 또는 연산의 결과를 기억해놨다가 다시 계산또는 연산을 하지 않도록 하는 방법
  • 예제
    하노이의 탑이나 피보나찌수열구하기
  • 피보나찌 수열에서의 메모이제이션
    1번째 항이 1, 2번째 항도 1 이고 3번재 항부터는 이전 두개의 항을 더한값이라고 할때 95번째 항의 값은?
    -> 컴퓨터에게 93번째를 구하고 94번째를 구하게 해서 더한다면 연산이 엄청 많게 됩니다. 이를 배열이같은 메모이제이션한다면 굳이 처음부터 계산할 필요가 없습니다.

3.2.2 useMemo, useCallback의 인자

  • input & return
    유즈메모의 input의 첫번째 인자로는 함수가 들어옵니다.
    그리고 두번째 인자로는 memoization을 해야되는 값을 가져오게 됩니다.
    두번째인자의 값이 변할때 함수를 실행심켜서 결과값을 가지고 있게 됩니다.

    여기서 useCallBack이랑 차이점은 useCallBack은 함수의 return값을 가져오느냐 참조값을 가져오느냐 차이입니다. 리랜더링이 되면 함수가 새로생성되기때문에 공홈에서 찾지 못했지만 useCallback을 좀더 권장하는거 같습니다.

3.3 useRef

useRef는 createRef랑 같은 역할을 합니다. 그래서 함수형에서 쉽게 ref를 참조할수 있도록 해주는 역할을 합니다.

ref의 안의 값이 바뀌어도 렌더링 되지 않습니다. 그래서 렌더링과 관련없는것들만 이러한 방식으로 작성하세요

const ref = useRef("초기값");

여기서 초기값은 부모에서받은 참조값으로 비어있어도 됩니다.

3.4 custom Hooks

useReducer로 커스텀 훅을 만들수 있습니다.

import { useReducer } from ‘react‘;
 
function reducer(state, action) {
  return {
    …state,
    [action.name]: action.value
  };
}
 
export default function useInputs(initialForm) {
  const [state, dispatch] = useReducer(reducer, initialForm);
  const onChange = e => {
    dispatch(e.target);
  };
  return [state, onChange];
}

이렇게한다면 useInput이라는 훅스를 만들게 됩니다.
이름은 use가 안들어가도 되지만 훅스라는것을 알려주는 편이 좋아서 use를 넣었던거 같습니다.

profile
Frontend 를 목표로합니다.

0개의 댓글