티스토리 뷰

728x90
반응형
21.09.03 오탈자 수정 & flowchart 재첨부

 

 

개발한 웹 앱의 메인페이지

🤔 React에 대하여

시작 전에 React에 대한 이야기. 사실 React를 배웠었던 가장 주된 이유는 인턴을 했던 회사에서 React를 사용했다. 그게 가장 컸다... 이제 와서 React에 대해서 다시 공부하게 된 이유도 익숙해서가 제일 컸다.

현재까지도 가장 큰 커뮤니티를 가지고 있고, 사용되는 곳이 많다. Vue.js나 Angular.js는 사용을 안 해봤기에 정확한 체감 비교까지 할 수 없지만, 모두 SPA(Single Page Application)인 웹 앱을 만드는데 큰 도움이 되는 도구들이다.

최근에 와서는 "Vue.js가 뜨고 있다. React는 지는해다." 말이 많다. 그럼 나 같은 신입 개발자를 준비하는 사람들은 '어? 그럼 Vue.js를 해야 되는 건가..?' 생각을 하게 된다. 하지만 언제나 그렇듯 개발자가 실력이 좋으면 해결이 되는 문제다. 개발 실력을 높이면 그게 어떤 프레임워크든 정답이지 않을까라는 개인적인 생각이 든다. 이것저것 다 경험해보고 싶다.

왜 React를 쓰나요?

Vue.js/React/입문자도 이해하기 쉬운 명쾌한 비교

프로젝트 시작 (21.08.02)

Astronaut JS 프로젝트에 이어 React를 활용한 쇼핑몰 웹 앱을 만들기로 했다. 기존의 쇼핑몰들을 떠올리며 기능을 구현하는데 최선을 다하려고 한다. 어떻게 코드를 알아보기 쉽게 짜고 줄일 수 있는지 고민해보고, 어떻게 하면 렌더링 속도를 높일 수 있는지 고민해보고 해결하고자 한다.

벌써부터 무수히 많은 오류들에 부딪히고 있고 개념들이 헷갈려서 아찔하다. JS에 이어서 예전에 React를 배울 때를 기억을 떠올리며 새로 배운다는 생각으로 프로젝트를 진행하고 있다. 이전보다 더 깊이 파고들며 내가 이해가 안가는 부분들은 최대한 해소시키며 진행하고 있다.

 

😃 UI 구성

내 모든 오류들과 고민했던 부분들을 Notion에 메모하면서 진행을 하고 있다. 알게된 것들이 정말 많고 개발자로서 성장하고 있다는 느낌도 많이 받는다. 하지만 그만큼 시행착오들이 많기에, 정리해서 글을 올리는 것이 생각보다 시간이 오래 걸리고 힘든 작업이다. 모든 내용을 다루기도 힘들 것 같다. 그래서 내가 많이 고민하고 어려웠던 부분을 어떻게 해결했는지를 중점으로 기록하려 한다. React가 미숙해 엉뚱한 방법으로 코딩하기도 했고, 지금 생각하면 너무 간단한데 머리 쥐어뜯던 것들을 기록하고자 한다.

처음엔 무작정 내가 생각하는 쇼핑몰의 메인페이지를 구성했다. 화면에 메인 화면과 상품을 띄었다. UI 구성은 ant-design, react-bootstrap 두개를 모두 사용했다. ant-design은 UI가 굉장히 깔끔하고 이쁜데, react-bootstrap만큼 다양한 컴포넌트가 있지는 않은 것 같다. 각각의 장단점이 있기에 2가지 모두를 활용했다. 현재 화면 구성은 다음과 같다. 처음부터 생각하고 짠 것은 아니지만 만들다 보니 만들어진 UI.

DB가 없지만 내가 쇼핑몰에 올릴 상품들의 데이터를 가진 JS 파일을 대신 만들어 import했다. 추가적으로 사용자들에게 보여줄 데이터들도 json파일로 만들었다. 이미지는 github에 업로드 후, url주소를 데이터 객체에 포함시켰다.

json에 포함되는 데이터 형식은 아래와 같다.

[
    {
        "id": 0,
        "title": "NIKE ",
        "description":
            "NIKE 짱!",
        "price": 70000,
        "imageUrl":
            "MY_URL"
        "재고":100
    },
    ]

 

😃 파일 구조

전체적으로 반복되는 코드들은 UI만 가진 ./component디렉터리에 저장했다. 그리고 최소 단위의 컴포넌트들로 UI를 직접적으로 구성하는 JS파일을 ./container에 작성했다.

코드가 반복되는 모습을 보기가 힘들다! 그래서 점점 단순화시키고 싶은 욕심이 생겼다. 너무 길고 반복되는 코드들은 컴포넌트화 하거나 함수로 만들었다. ShoesItem이라는 컴포넌트를 만들었고, App이 가지고 있는 데이터를 props로 넘겨줬다. map함수를 이용해 데이터의 개수만큼 ShoesItem컴포넌트들을 자동적으로 생성했다. 아래와 같다.

(./App.js)  //초기 접속시 메인페이지의 UI)
return (
    <>
            <div className="welcomePage">
                <h1>Hello Astronaut!</h1>
                <h2>We are Astronaut in Universe.</h2>
                <Button className="welcomePage__btn" type="primary">
                    About Astronaut
                </Button>
            </div>

            <div className="container">
                <div className="row">
                    {shoes.map((item, i) => {
                        /*ShoesItem    호출부 */
                        return <ShoesItem shoes={item} num={i} key={i}></ShoesItem>;
                    })}
                </div>
            </div>

    </>
);

그런데 메인 페이지에 신발 상품이 들어가는 것이 별로 이쁘진 않았다. 그리고 메인 페이지에서 Navbar를 만들어 각각의 상품 카테고리별로 라우팅하게 했고, 해당 데이터를 ShoesItem이 전달받게 했다.

Men's Shoes, Women's Shoes about 그림과 같이 3개의 페이지를 만들었다.

App.js 파일을 살펴보면 아래와 같다:

(App.js)
const App = () => {
    return (
        <div className="App">
            <div class="no-display">화면이 너무 작습니다. </div>
            <Switch>
                <Route exact path="/">
                    <MainPage></MainPage>
                </Route>
                <Route path="/menshoes">
                    <ShoesList></ShoesList>
                </Route>
                <Route path="/womenshoes">
                    <ShoesList></ShoesList>
                </Route>
                <Route path="/about">
                    <ShoesList></ShoesList>
                </Route>
            </Switch>
        </div>
    );
};

 

😃데이터 넘겨주기

그런데 이렇게 나눠버리니 남성용 상품, 여성용 상품을 어떻게 구분해서 ShoesList에 넘겨야될지 고민됐다. 통합된 Data라면 filter를 사용해 성별에 따라 경우로 나눌 수도 있을 것 같다. 하지만 그냥 남자, 여자 상품 정보를 나누는 것이 훨씬 효율적이라 생각했다.

데이터 자체를 남성용 여성용으로 나눠서 저장했다. 어떻게 데이터를 나누는 것이 DB에서 효율적인지 정확하게는 모르겠다. 하지만 DB에서 '성별'로 구분해서 전달해준다고 가정해서 데이터를 나눠서 진행했다.

ShoesList가 화면을 구성하는 컨테이너라서 <ShoesItem/> 컴포넌트를 호출한다. 남성용인지 여성용인지 ShoesList도 정보를 가지고 있다.

그래서 데이터 흐름이 아래와 같다.

 

😂 잘못된 코딩 시작

그래서 남자일 경우 '0' 여자일 경우 '1'을 props로 넘겨줬다. 그러면 ShoesList에선 이 정보를 가지고 남성용 상품을 표시할지, 여성용 상품을 표시할지 각각의 컴포넌트에 해당하는 Data를 ShoesItem에 보낼 수 있었다.

(App.js)
<Route path="/menshoes">
    <ShoesList num={0}></ShoesList>
</Route>
<Route path="/womenshoes">
    <ShoesList num={1}></ShoesList>
</Route>

num이 1인 경우엔 <Woman/> 화면을 보여주고 아닌 경우엔 <Man/>을 보여준다. 각각의 화면에선 ShoesItem 에 상품의 요소 정보인 item을 props로 전달해준다. 마지막에 num이 1이라면 을 호출하고, 0이면 을 호출한다.

(./container/ShoesList.js)
//남성용 상품 List 화면
const Man = () => {
    return (
        <div className="row">
            {shoes.map((item, i) => {
                return <ShoesItem shoes={item} num={i} key={i}></ShoesItem>;
            })}
        </div>
    );
};
//여성용 상품 List 화면
const Woman = () => {
    return (
        <div className="row">
            {wshoes.map((item, i) => {
                return <ShoesItem shoes={item} num={i} key={i}></ShoesItem>;
            })}
        </div>
    );
};

// UI렌더링 되는 부분, 삼항연산자를 활용
{props.num === 1 ? <Woman></Woman> : <Man></Man>}

 

그런데 위와 같이 수정하면 Data, Data2를 App에서 뿌릴 필요가 없어진다. 그래서 UI에서 Data 흐름을 굳이 App에서 뿌리지않고 자식 컴포넌트에서 뿌리도록 수정했다.

(21.08.06) 깨달음😂

Data를 성별로 미리 나눴으니 props로 {0}, {1}을 넘기는 것이 아니라 이미 구분된 {남자데이터}, {여자데이터}를 넘기면 됐다. 이때는 이걸 props로 넘기는 게 헷갈려서 놓쳤다.

ShoesList를 기준으로 생각하기도 했고, 최상단에서 데이터를 흐르게 해야 된다는 사실도 잘 몰랐어서 헤맸던 것 같다. 기능을 점점 구현하면서 전달해야 되는 정보는 최상단이 가지고 있어야 한다는 것을 당연하게 이해하게 됐다... Apps에서부터 Data를 직접 넘기게 되면 어떤 데이터인지 ShoesList에서 고민할 필요가 없었다. 전달받은 props로 UI를 그려주기만 하면 끝나는 문제를 어렵게 만들었다.

 

데이터 더보기 버튼 구현부 수정

위의 문제를 해결하고자 데이터는 그대로 넘겨주되, 여자인지 남자인지에 대한 숫자 정보인 {0}, {1}도 같이 넘겨주는 것으로 했다. 그 이유는 다음과 같다.

이미 구현한 더보기 버튼이 있는데, 이 버튼을 누르면 json에 다른 상품들을 렌더링 해준다. json파일의 구분을 {0}, {1}로 한다. 나열되는 상품이 총 상품의 데이터 개수를 넘어간다면, 버튼은 비활성화되는 기능을 한다. 하지만 남자 카테고리, 여자 카테고리가 이 한 가지 버튼의 State를 공유하고 있었다. 그래서 2개의 버튼 State를 만들고, 그 버튼을 {0},{1}로 구분하기로 했다.

그래서 다시 처음처럼 App에서 데이터를 뿌려주는 형태로 했다.(아래 그림) 이제 ShoesItem에서 클릭을 당하면 About에 내용을 전달한다. 물론 데이터는 App에서 뿌려주는 구조로 바꿨다. 데이터가 단방향으로 흐르게 하는 구조를 따르지 않으니 라우터 설정이나 링크 설정에 상당히 애를 먹었었다. 이처럼 바꾸니 실제로 State 변수 2개를 줄이면서, 전체적으로 코드를 줄일 수 있었다.

 

 

🤔 해결 방법

즉, 아래 2가지를 몰라서 위와 같은 고민들을 하면서 4일동안 해결하려고 생고생을 했다..

  • shoes의 State를 어떻게 상단의 데이터로 넘기지? 더보기 버튼을 안 눌렀을 땐?
    • 상단에서 데이터를 흐르게 해야 된다는 점을 몰랐다.
  • App.js -> ShoesList -> ShoesItem 흐름인데 Route는 App에서 설정되어있는데 ShoesList의 합쳐진 데이터를 About에 어떻게 전달하지? 더보기는 ShoesList에 위치. 하지만 상품들을 클릭하는 Link는 ShoesItem에 존재.
    • 부모의 State변경 함수를 props로 넘겨서 해결. 그러면 자식 컴포넌트에서 부모 State를 변경할 수 있고, 그 정보를 About에 넘김으로써 해결한다.

내가 제일 애를 먹은 부분이 더보기 버튼을 눌렀을 때, 추가 데이터를 입력하는 과정이다. 결국 데이터의 흐름 문제였고, 아래와 같이 부모에서 State변수를 변경하는 함수를 props로 setShoes={setShoes}이렇게 넘겼고, 자식에서는 props.setWShoes처럼 사용이 가능했다.

그래서 아래와 같이 더보기 버튼을 눌렀을 때, 0인지 1인지 판단한다. 이후 삼항 연산자를 통해 각각의 axios 비동기 호출을 실행시킨다. 그 과정에서 자식 컴포넌트가 부모의 State를 변경한다. console.log를 찍어보면 잘 출력이 되는 것을 확인할 수 있었다.

    const fetchData = (props) => {
        console.log('axios 시작', props);
        props.num
            ? axios // i === 1일때 여자 카테고리 더보기 버튼 클릭시
                    .get('https://minsoftk.github.io/jsontest/test' + props.num + '.json')
                    .then((result) => {
                        console.log(result);
                        let newObj = [...props.wshoes, ...result.data]; //데이터 합치기
                        setWShoesNum(Data.length + result.data.length); //원래 Data와 추가된 데이터의 길이
                        if (newObj.length >= wshoesNum) setWBtnDisable(true); //합친 데이터의 길이가 더 크다면 여자 카테고리 버튼 비활성화
                        if (props.num) props.setWShoes(newObj);
                        else props.setShoes(newObj);
                        console.log('axios 데이터 바인딩 성공');
                    })
                    .catch((e) => {
                        console.log(e);
                    })
            : axios // i === 0일때 남자 카테고리 더보기 버튼 클릭시
                    .get('https://minsoftk.github.io/jsontest/test' + props.num + '.json')
                    .then((result) => {
                        let newObj = [...props.shoes, ...result.data]; //데이터 합치기
                        setShoesNum(Data.length + result.data.length); //원래 Data와 추가된 데이터의 길이
                        if (newObj.length >= shoesNum) setBtnDisable(true); //합친 데이터의 길이가 더 크다면 남자 카테고리 버튼 비활성화
                        props.setShoes(newObj);
                        console.log('axios 데이터 바인딩 성공');
                    })
                    .catch((e) => {
                        console.log('실패', e);
                    });
    };

 

😃 결론

이제 거의 대략적인 UI 구성은 끝났고 추가할 기능들도 거의 다 개발했다. 세부적인 테스트를 해보면서 오류가 나는 부분들 수정중. 굉장히 많은 트러블 슈팅들을 기록 중인데 이 부분은 프로젝트 배포할 때 같이 넣자.

이후에 추가할 부분은 해당 상품을 클릭했을 때, 상세페이지&구매 페이지로 이동하는 기능. 장바구니 기능 등등. 또한 쓸데없이 렌더링 되는 부분이 생각보다 많다. 어떻게 줄일지 고민하기. 이를 useEffect, useMemo 훅들을 이용해서 최적화하는 방법이 있다는 것을 알았다. 이를 적용해볼 계획. 또한 Redux, Context를 통한 상태 관리법 적용예정.

혹시 잘못된 내용이나 틀린 내용이 있으면 댓글로 남겨주세요. 감사합니다 :)

728x90
반응형

'Project' 카테고리의 다른 글

[React] Astronaut-shop 삽질 개발일지2  (0) 2021.08.19
[Toy프로젝트 ] Astronaut 개발일지  (0) 2021.08.04
댓글