동행큐브
725
2021-01-10 01:16:39
6
603

리액트에서 상태값을 효율적으로 관리할 수 있는 방법을 여쭤보고 싶습니다.


안녕하세요. 리액트 초짜 개발자입니다. 현재 회사에서 진행하는 프로젝트를 리액트로 진행하고 있습니다만 제가 코드와 데이터를 효율적으로 다루지 못하는 느낌이 많이 들어 질문을 드립니다. 

현재 개발하고 있는 페이지의 대략적인 구조입니다. 


이 구조를 계층 구조를 나타내면 다음과 같습니다. 




Sidebar는 Users와 같은 계층의 컴포넌트입니다. 

일단 여기에서 가장 많이 쓰이는 데이터가 검색조건, 그리드 컬럼, 그리드 데이터, 그리드 페이지 정보(페이지당 항목, 페이지 번호, 마지막 페이지, 총 항목 개수 등)가 있습니다. 

이 중 검색조건은 Search.js에서 hook-form으로 구현했으나 나머지 데이터는 Users에서 useState를 이용하여 관리하다보니 그리드 데이터가 갱신될때마다 Users의 하위 컴포넌트들이 재렌더링 되므로 비효율적으로 자원을 사용할 것으로 생각이 듭니다. 


원래는 Grid.js에 그리드 데이터와 그리드 컬럼, 페이지 정보를 넣어주려 했으나, 페이지가 로드되거나 Search에 있는 조회 버튼을 클릭 시 Search 내부 함수에서 그리드 데이터(그리드 데이터, 페이지 정보)를 가져옵니다. Search 컴포넌트는 해당 데이터를 Search 컴포넌트를 호출할 때 Users로부터 전달받은 setGridData() 함수를 실행합니다.


제 핵심 질문은 이것입니다. 


1. 어떻게 해야 그리드 데이터를 좀더 효율적으로 다루고 아울러 컴포넌트를 더 효율적으로 개선할 수 있을까요?


2. 이건 위 질문과 다른 질문인데, 아래 코드는 itemsPerPage가 있으면 그 값으로 검색을 하는 코드입니다. Test.js가 최상위 컴포넌트입니다. 

import React, from "react";
import TestSearch from "./TestSearch";
import {size} from "./TestInfo";

const Test = () => {
    const itemsPerPage = size;

    return (
        <TestSearch
            itemsPerPage={itemsPerPage}
        />
    )
}

export default React;



import React, {useState, useEffect} from "react";
import axios from "axios";

const TestSearch = ({itemsPerPage}) => {
    const [data, setData] = useState({})

    useEffect(() => {
        let param = '';
        if(itemsPerPage !== undefined) param = '?size='+itemsPerPage;
        //2번 검색이 됨(/users 한번, /users?size=5 한번)
        axios('/users'+param)
            .then((data) => {
                setData(data)
            })
    }, [])
    return <div>{data}</div>

}

export default TestSearch;

전 분명히 가장 위 코드에 데이터를 불러오는 코드를 작성했는데 검색이 2번이 됩니다. 파라미터 없이 한번, 파라미터 추가해서 한번. 그래서 서버에 요청을 2번이나 하게 되는데 제가 리액트의 렌더링 과정에 대해 아직 모르는게 있는지 왜 이런 상황이 발생하는지 모르겠습니다. 가르침을 주신다면 감사히 받겠습니다. 

0
  • 답변 6

  • 킁킁탐정
    844
    2021-01-10 02:05:45 작성 2021-01-10 02:06:38 수정됨

    1. 컴포넌트 효율이 좋은지는 다음의 주소에서 받을 수있는 React Developer Tools 크롬 확장을 설치하고
        크롬 콘솔의 Components 탭 설정의 Highlight updates when components render 체크하면
        상태 변화나 이벤트로 인해 다시 렌더링되는 부분을 시각적으로 확인 가능합니다.

        https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi

        관리할 상태가 많아지고 너무 높은곳에서 너무 깊은 아래로 다양하고 많은 데이터와 이벤트를
        전달한다면 상태 관리 라이브러리를 알아보시는게 좋습니다.

        컴포넌트 최적화는 리터럴 값을 사용하는 방식부터 useCallback, useMemo 그리고 서버로부터
        받은 데이터를 캐쉬하는등의 다양한 방법이 존재합니다.


    2. react 특징상 단편적인 코드만 봐서는 확인할 수 있는게 많지 않습니다.
        의심가는 부분이 하나 있다면 Strict Mode입니다. 이것을 한번 Off 해보시기를 권해드립니다.

        https://ko.reactjs.org/docs/strict-mode.html

  • zerosugar
    361
    2021-01-10 02:14:00

    1. 상태관리 라이브러리를 찾아보세요. redux, mobx 이외에도 여러가지 있습니다.

    2. 복잡한 계층구조를 가진 컴포넌트들을 다루는데 사용되는 Context API 라는 것도 있습니다.

    3. 왜 두번 렌더링되는지 모르면 component lifecycle을 이해하지 못하고 있는 상태이신것 같습니다. 한번 더 읽어보시고 어떤 state 또는 props가 변화되는지 관찰해보세요.


  • 브로콜리먹기
    179
    2021-01-10 10:59:27

    상태관리를 위해 redux가 꽤 많이 쓰였는데 이게 좀 복잡하고 부담된다면 apollo client의 local storage도 추천합니다

  • 동행큐브
    725
    2021-01-10 15:11:36

    redux의 경우 사용해봤지만 페이지를 이동해도 페이지 번호, 검색조건 등이 그대로 유지되어 안쓰려고 했습니다. 페이지마다 초기화하는 코드를 넣자니 코드의 중복때문에 꺼려지게 되더군요... 일단 추천해주신 상태관리 라이브러리 한번 공부해보고 써봐야겠습니다. 답변 감사합니다!

  • 애아빠
    1k
    2021-01-18 01:24:57 작성 2021-01-18 13:56:08 수정됨

    개발할때 역할모델에 기반한 고민을 많이 하셔요.


    모듈을 구조화 할때 어떤 모듈이 어떤 역할을 하게 할 것인지에 대해서 가장 많은 고민이 필요합니다.

    역할을 잘 나누는 것이 가장 첫번째 이고 그 이후 구현단계에서 말씀하신 부분들을 고민하시면 될 거 같아요.

    제가 볼때 글로 남겨주신 모듈의 역할은 다음처럼 변경하면 좀 더 효율적일 수 있을 거 같아요.




    예를 들어 보죠


    일단 개별 구성요소 컴포넌트는 최소한의 필요한 역할만 부여한채로 구성해봅니다.


    Search component

    - props

        - onSearch

    - 역할

        - 키워드 입력 input 제공

        - 조회 버튼 제공

        - 조회 버튼 클릭시 onSearch(keyword) 호출


    Grid component

    - props

        - users (데이터)

    - 역할

        - users 데이터를 렌더링


    Pagination component

    - props

        - totalPageCount

        - currentPage

        - pagePerBlock

        - onClick

    - 역할

        - 페이지 번호 노출

        - currentPage 강조 노출

        - 이전/다음 버튼 등 제공

        - 페이지 번호 클릭시 onClick(pageNumber) 호출


    그리고 이 개별 구성요소를 조합하여 기능을 구현할 User component의 역할은 이렇게 부여해보죠.


    User component 의 역할 (각 모듈들을 활용하여 기능 구현)

    - useState 사용

        - keyword data (setKeyword)

        - page data (setPage)

        - users data (setUserData)

    - 내부 역할

        - useCallback으로 구현(deps는 keyword, page state를 설정하여 그 두 값이 변경되면 수행되도록 함)

            - keyword, page를 활용하여 api call

            - api 응답 성공시

                - setUserData(response data)

    - 개별 구성요소에 props 전달

        - <Search onChange={setKeyword} />

        - <Grid users={users} />

        - <Pagination  onClick={setPage} totalPageCount={} currentPage={} pagePerBlock={} />


    이렇게 구성을 하게되면

    1. 각 컴포넌트의 역할이 단순해지고 그로 인해 각 컴포넌트간의 의존성이 최소화

        - Search 컴포넌트는 조회 버튼 클릭시 사용자가 입력한 키워드를 전달만 해주면 됨

        - Pagination 컴포넌트는 페이지 번호를 표현하고 페이지 번호 클리시 클릭된 페이지 번호를 전달만 하면 됨

        - Grid 컴포넌트는 Users 데이터를 렌더링만 함(페이지당 몇개인지 등과 같은 정보에 관심 없음)

            - 고도화 해서 필드정보도 같이 받아서 users 데이터의 포멧에 의존적이지 않도록 구성할 수 있음

    2. 각 개별 컴포넌트를 조합하여 기능을 구현하는 User 컴포넌트에 비즈니스 로직 집중화

        - 데이터를 한곳에서 핸들링 할 수 있음

    3. React.memo 등을 활용한 컴포넌트 re-render 최적화

        - React.memo는 props가 변경되지 않으면 컴포넌트를 re-render하지 않도록 하는 memoization 헬퍼

        - 즉 User 컴포넌트의 users 데이터가 바뀌어서 User 컴포넌트가 re-render 되어도 Search/Pagination 컴포넌트는 re-render 되지 않도록 Search/Pagination에 React.memo 적용

            

  • 동행큐브
    725
    2021-01-19 18:17:53
    오... 감사합니다. 구조가 진짜 깔끔하네요 앞으로는 컴포넌트 작성할때 이렇게 정리하고 작성해야겠네요 ㅠㅠ 큰 도움이 되었어요 정말 감사합니다!
  • 로그인을 하시면 답변을 등록할 수 있습니다.