NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    JavaScript 1부 - React의 Hooks에 대해 알아보자!

    페이지 정보

    본문

    안녕하세요. 엔지엠소프트웨어입니다. 회사 프로젝트로 반도체나 화학, 철강등등... 제조 공장에서 품질을 개선하기 위한 활동들을 모니터링하고 수율 하락 요인이 어떤 공정의 장비에서 발생하는지를 지표로 알려주는 소프트웨어를 만들고 있습니다. 보통 MES와 연계하는 하위 시스템으로 EES로 이런 활동들을 수행합니다. 다니던 회사가 Exit했기 때문에 새로운 제품(EES Product)을 만들어야 하는 상황입니다. 기존에 Java Spring + Angluar + Typescript로 만든 제품을 다른 회사에 라이센스를 팔았기 때문에 재사용할 수 있는 소스도 없군요. 아무튼, 이번에는 Java Spring + React + Typescript를 사용하기로 방향을 정했습니다. 원래는 Vue를 사용하려고 했으나... 제 의견은 무시되었고 React가 선택되었습니다. 그래서~ 공부해야 합니다. 나이 50에 다시 새로운걸 공부하려니... 머리가 아프군요ㅠㅠ;

    QjzSKWi.png

     

     

    이 글을 읽으시는 분들은 아마도 개발자분들일겁니다. 그래서, 아주~ 기초적인 내용은 건너뛰고 진행하도록 하겠습니다. 우선, Visual Studio Code(vscode)를 실행하고, 작업 위치(Work space)를 선택하세요. 그리고, 터미널을 열고, 아래 명령으로 리액트 훅 예제를 설치하세요. npm이 안되면 yarn을 사용하시면 됩니다.

    PS D:\MyWorkspace\react> npm create react-app hooks-tutorial

    kZYj8SW.png

     

     

    잠시 기다리면 설치가 완료되고, 프로그램을 실행해보라고 명령을 알려줍니다. cd로 hooks-tutorial 폴더로 이동한 후 npm start로 app을 실행합니다.

    We suggest that you begin by typing:
    
      cd hooks-tutorial
      npm start
    
    Happy hacking!
    Done in 181.56s.
    PS D:\MyWorkspace\react>

     

    아래 그림처럼 hooks-tutorial 폴더로 이동한 후 React App을 실행하세요.

    soaON1I.png

     

     

    잠시 기다리면 웹브라우저가 실행되면서 리액트 앱이 실행됩니다. 혹시라도 아래와 같은 메세지가 표시되면 Y를 입력하세요. 저는 이미 3000번 포트를 사용중이기 때문에 아래 메세지가 표시되었고, Y를 입력하면 자동으로 다음 포트를 할당 받아서 앱을 실행해줍니다.

    ? Something is already running on port 3000.
    
    Would you like to run the app on another port instead? » (Y/n)

     

    1. useState (setState)

    useState는 가장 기본적인 리액트의 Hook 함수입니다. 가변적인 상태를 가질 수 있도록 해줍니다. 컴포넌트의 상태를 관리하려면 useState 훅을 사용하면 됩니다. 가장 기본이 되는 예제는 카운터 구현입니다. src 안에 새 파일을 생성하고, Counter.js로 파일명을 만드세요. 그리고, 아래 코드를 입력해줍니다.

    import React, { useState } from 'react';
    
    const Counter = () => {
      const [value, setValue] = useState(0);
      return (
        <div>
          <p>
            현재 카운터 값은 <b>{value}</b> 입니다.
          </p>
          <button onClick={() => setValue(value + 1)}>+1</button>
          <button onClick={() => setValue(value - 1)}>-1</button>
        </div>
      );
    };
    
    export default Counter;

     

    useState를 사용할 땐 import로 패키지를 불러와야 합니다. 아래는 여러가지 복잡하게 추가되어 있지만, 이 예제에서는 useState 하나만 있으면 됩니다.

    import { SettingOutlined, ReloadOutlined } from '@ant-design/icons';
    import { useCallback, useEffect, useState } from 'react';

     

    useState는 배열 비구조화 할당 문법을 사용합니다. 저도 리액트를 하면서 처음 접해보는 문법이라... 처음에 잘 이해가 안갔습니다. 아래 예제는 좀 더 쉽게 확인할 수 있는 코드입니다. useState를 할당해주면, 두번째 파라메터가 함수화되어 첫번째 변수에 데이타를 넣을 수 있습니다.

    const array = ['dog', 'cat', 'sheep'];
    const [first, second] = array;
    console.log(first, second); // dog cat

     

    useState 훅을 이용하면 이 함수의 파라메터는 상태의 기본 값을 넣어줍니다. 현재 0을 넣어줬습니다.

    PzYouLV.png

     

     

    이 함수가 호출되고 나면 배열을 반환합니다. 배열의 첫번째 변수는 상태값이고 두번째 변수는 상태를 설정하는 함수가 들어갑니다. 이 함수에 파라메터를 넣어서 호출하게되면 전달받은 파라메터로 값이 바뀌게 되고 컴포넌트는 정상적으로 리랜더링됩니다. 이 예제를 확인하기 위해 App.js 에서 아래와 같이 카운터 컴포넌트를 불러오세요. 다른 언어들의 void main이 Angular나 React에서는 App가 됩니다. 몇가지만 외워두면... 대부분은 전통적인 HTML, CSS, Javascript와 비슷해서 배우는데 크게 어려움은 없을거 같습니다.

    import './App.css';
    import Counter from './Counter.js';
    
    function App() {
      return (
        <>
          <div style={{ marginLeft: 20, marginBottom: 20 }}><Counter /></div>
          <div style={{ marginLeft: 20, marginBottom: 20 }}><a href="http://ngmsoftware.com">엔지엠소프트웨어</a></div>
        </>
      );
    }
    
    export default App;
    

     

    +1과 -1 버튼을 클릭 해보세요. 카운터가 잘 동작하죠? 함수형 콤포넌트에서 상태 관리를 위해 굳이 클래스 형태로 변환할 필요가 없어서 사용하기가 편리합니다. 리액트에서는 콤포넌트가 클래스와 함수 2종류입니다. 클래스보다 함수형 콤포넌트가 다양한 기능을 제공해서 대부분 이렇게 많이 사용합니다.

     

     

    만약 컴포넌트에서 관리해야 할 상태가 여러개라면 useState를 여러개 사용하면 됩니다. useState는 하나의 상태만 관리할 수 있습니다. 이번에는 src 폴더에 Info.js 파일을 생성하고 아래 코드를 붙여넣기 하세요.

    import React, { useState } from 'react';
    
    const Info = () => {
      const [name, setName] = useState('');
      const [nickname, setNickname] = useState('');
    
      const onChangeName = e => {
        setName(e.target.value);
      };
    
      const onChangeNickname = e => {
        setNickname(e.target.value);
      };
    
      return (
        <div>
          <div>
            <input value={name} onChange={onChangeName} />
            <input value={nickname} onChange={onChangeNickname} />
          </div>
          <div>
            <div>
              <b>이름:</b> {name}
            </div>
            <div>
              <b>닉네임: </b>
              {nickname}
            </div>
          </div>
        </div>
      );
    };
    
    export default Info;

     

    App.js에 Info 컴포넌트를 추가 해줍니다.

    import './App.css';
    import Counter from './Counter.js';
    import Info from './Info.js';
    
    function App() {
      return (
        <>
          <div style={{ marginLeft: 20, marginBottom: 20 }}><Counter /></div>
          <div style={{ marginLeft: 20, marginBottom: 20 }}><Info /></div>
          <div style={{ marginLeft: 20, marginBottom: 20 }}><a href="http://ngmsoftware.com">엔지엠소프트웨어</a></div>
        </>
      );
    }
    
    export default App;
    

     

    Ctrl+S를 눌러서 저장하면 웹브라우저에서 새로고침되면서 바로 반영됩니다.

     

     

    관리해야 할 useState가 많아지면 코드가 복잡해지고, 분석이 어려워집니다. 그래서, 컴포넌트의 상태와 각각의 콘트롤들의 상태를 #region을 그룹화 할 수 있습니다.

      //#region Date Ragne Picker state
      const [isShowTime, setIsShowTime] = useState<Boolean>(props.isShowTime);
      const [selectTime, setConfigTime] = useState<any>([
        dayjs().add(-7, 'd'),
        dayjs(),
      ]);
      const [start, setStart] = useState<any>(dayjs().add(-7, 'd'));
      const [end, setEnd] = useState<any>(dayjs());
      //#endregion
    
      //#region Select config state
      const [selectedOptionItems, setSelectedOptionItems] = useState<any>({});
      const [selectLabelWidth, setSelectLabelWidth] = useState<any>(
        props.selectLabelWidth
      );
      const [isMultiple, setIsMultiple] = useState<Boolean>(props.isMultiple);
      const [selectOptionItems, setSelectOptionItems] = useState<any>({});
      const [optionItems, setOptionItems] = useState<any>({});
      //#endregion

     

    #region을 축소시키면 관리가 용이합니다. 다른 언어에서 사용했던 것처럼 리액트에서도 동일하게 리전을 사용할 수 있습니다. 언어 특성이라기 보다는 Visual Studio Code와 같은 IDE가 제공해주는 기능입니다. 전처리 기호라고 부르는데 리전뿐만 아니라 빌드 조건이나 변수등등... 다양하게 사용할 수 있습니다.

    6RvqaWY.png

     

     

    2. useEffect

    useEffect는 리액트 콤포넌트가 렌더링될 때 특정 작업을 수행하도록 설정할 수 있는 Hook입니다. 클래스형 콤포넌트의 componentDidMount와 componentDidUpdate를 합친 형태라고 생각해도 됩니다. 위의 Info 콤포넌트에 useEffect를 적용 해보겠습니다. 아래 코드를 복사해서 덮어쓰기 하세요.

    import React, { useState, useEffect } from 'react';
    
    const Info = () => {
      const [name, setName] = useState('');
      const [nickname, setNickname] = useState('');
    
      useEffect(() => {
        console.log('렌더링이 완료되었습니다!');
        console.log({
          name,
          nickname
        });
      });
    
      const onChangeName = e => {
        setName(e.target.value);
      };
    
      const onChangeNickname = e => {
        setNickname(e.target.value);
      };
    
      return (
        <div>
          <div>
            <input value={name} onChange={onChangeName} />
            <input value={nickname} onChange={onChangeNickname} />
          </div>
          <div>
            <div>
              <b>이름:</b> {name}
            </div>
            <div>
              <b>닉네임: </b>
              {nickname}
            </div>
          </div>
        </div>
      );
    };
    
    export default Info;

     

    개발자 도구(F12)를 열고, 텍스트박스(input)에 내용을 입력 해보세요. 텍스트를 입력할 때마다 렌더링이 발생되는걸 알 수 있습니다.

     

     

    만약, 콤포넌트가 화면에 처음 렌더링될 때만 실행되고, 업데이트할 경우에는 실행하지 않으려면 함수의 두번째 파라메터로 빈 배열을 넣어주면 됩니다. 너무 잦은 렌더링은 복잡한 SPA 앱에서 부담을 주기 때문에 가능하면 최소한으로 사용하는게 좋습니다. 기존의 useEffect코드를 아래와 같이 변경해보세요.

      useEffect(() => {
        console.log('마운트될 때만 실행합니다!');
        }, []);
      };

     

    개발자 모드에서는 useEffect에 로그가 두번찍힙니다. 이는 React App이 Strict Mode로 동작하고 있기 때문입니다. index.js에서 Strict Mode를 삭제하면 한번만 실행됩니다.

    hv2mKC1.png

     

     

    Strict Mode는 리액트에서 제공하는 하위 노드까지 검사해주는 도구입니다. 모든 윈도우 프로그램, 웹 프로그램들은 클래스, 컴포넌트, 뷰, 콘트롤등등... 생명주기라는 걸 가집니다. 보통 Life-cycle이라고 부르는데요. 안전하지 않는 라이프 사이클을 가진 컴포넌트가 있거나 상호참조등등... 문제가 발생할 수 있는 요인들을 미리 검토해주는 모드입니다. 개발할 때 문제가 발생하면 로직을 다시 검토 해보는게 좋습니다.

    2im8CE9.png

     

     

    다시 원점으로 돌아와서 useEffect의 두번째 인자로 빈 배열을 주면, 상태가 업데이트 되지 않는걸 확인할 수 있습니다.

     

     

    위의 예제에서는 이름(name)과 별명(nickname) 2개의 콘트롤에 상태를 업데이트 하도록 했습니다. 만약, name이 변경되는 경우에만 업데이트를 실행하고 싶으면 어떻게 해야 할까요? 만약, 클래스형 콤포넌트라면 아래와 같이 작성해야 할겁니다.

    componentDidUpdate(prevProps, prevState) {
      if (prevProps.value !== this.props.value) {
        doSomething();  
      }
    }

     

    하지만, 함수형 컴포넌트에서는 useEffect의 두번째 인자에 배열로 업데이트할 변수를 넣어주면 됩니다.

      useEffect(() => {
        console.log(name);
        console.log(nickname);
        }, [name]);

     

    앱을 실행하고, name과 nickname에 각각 내용을 입력 해보세요.

     

     

    useEffect는 기본적으로 렌더링 되고난 직후부터 실행되며, 두번째 파라메터는 배열에 무엇을 넣느냐에 따라 실행되는 조건이 달라집니다. 만약, 컴포넌트가 언마운트되기 전이나 업데이트 되기 직전에 어떤 작업을 수행하고 싶다면 useEffect에서 cleanup 함수를 반환해줘야 합니다. useEffect 함수를 아래와 같이 변경 해주세요.

      useEffect(() => {
        console.log('effect');
        console.log(name);
        return () => {
          console.log('cleanup');
          console.log(name);
        };
      });

     

    App.js에서 컴포넌트를 추가하고, 가시성을 바꿀 수 있게 코드를 변경 해보세요. 그리고, 이 상태도 useState로 관리하도록 합니다.

    import './App.css';
    import React, { useState } from 'react';
    import Counter from './Counter.js';
    import Info from './Info.js';
    
    function App() {
      const [visible, setVisible] = useState(false);
    
      return (
        <>
          <div>
            <button
              onClick={() => {
                setVisible(!visible);
              }}
            >
              {visible ? '숨기기' : '보이기'}
            </button>
            <hr />
            {visible && (
              <>
                <div style={{ marginLeft: 20, marginBottom: 20 }}><Counter /></div>
                <div style={{ marginLeft: 20, marginBottom: 20 }}><Info /></div>
                <div style={{ marginLeft: 20, marginBottom: 20 }}><a href="http://ngmsoftware.com">엔지엠소프트웨어</a></div>
              </>)}
          </div>
        </>
      );
    }
    
    export default App;
    

     

    수정된 코드를 모두 저장하고 웹브라우저를 보면 상단에 보이기 / 숨기기 버튼이 표시됩니다. 버튼을 클릭하면서 개발자 도구의 콘솔창을 확인 해보세요.

     

     

    컴포넌트가 나타날 때 콘솔에 effect가 보이고, 사라질 때 cleanup이 보여집니다. 그리고, 텍스트박스에 이름을 적어보고 콘솔에 어떤 결과가 나타나는지 확인 해보세요. 그리고, 다시 숨기기를 누르고 보이기를 눌러보세요. 텍스트박스에 입력했던 내용들이 사라진것을 확인할 수 있습니다. 렌더링이 될 때마다 뒷정리 함수가 계속 보여지고 처리되는 것을 알 수 있습니다. 만약, 언마운트 될 때만 뒷정리 함수를 호출하고 싶으시다면 useEffect 함수의 두번째 인자에 비어있는 배열을 넣으면 됩니다.

     

    [ 2부 - 리액트 Hook에 대해 알아보기 ]

     

    개발자에게 후원하기

    MGtdv7r.png

     

    추천, 구독, 홍보 꼭~ 부탁드립니다.

    여러분의 후원이 빠른 귀농을 가능하게 해줍니다~ 답답한 도시를 벗어나 귀농하고 싶은 개발자~

    감사합니다~

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.