๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Lect & Tip/node, Angular, React

์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฆฌ์•กํŠธ ๋ฉ€ํ‹ฐ ๋ ˆ์ด์•„์›ƒ connected-react-router multi layout ์ ์šฉํ•˜๊ธฐ

by ๋‚ฏ์„ ๊ณต๊ฐ„2019 2021. 2. 10.

๋ฆฌ์•กํŠธ๋ฅผ ๋ณ„๋กœ ์ข‹์•„ํ•˜์ง€ ์•Š๋Š” 1์ธ์ด๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์–ด์ฉ” ์ˆ˜ ์—†์ด ๋ฆฌ์•กํŠธ ํ”Œ์ ์„ ํ•ด์•ผ ํ•ด์„œ ํผ๋ธ”๋ฆฌ์…” ๊ฒธ ํ”„๋ŸฐํŠธ ์—”๋“œ๋กœ ์ฐธ์—ฌ ์ค‘์ด๋‹ค.

jquery๋ผ๋ฉด ๋‹จ๋ฐ•์— ๋๋‚ผ ๋ฌธ์ œ์— ๊ณ ๋ฏผํ•˜์ง€ ์•Š์•„๋„ ๋  ๋ฌธ์ œ๋“ค์ด ๋ฆฌ์•กํŠธ์—๋Š” ๋„˜์ณ๋‚œ๋‹ค.

์™œ ์“ฐ๋Š”์ง€๋Š” ์—ฌ์ „ํžˆ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ ๊นŒ๋ผ๋‹ˆ๊นŒ ๊น๋‹ค.

์ผ๋‹จ SPA ์‚ฌ์ดํŠธ์—์„œ ๋ฆฌ์•กํŠธ๊ฐ€ ํŽธํ•œ ๊ฒƒ์€ ๋Œ€์ถฉ ์ง์ž‘์€ ๋œ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ SPA์‚ฌ์ดํŠธ๋ผ๊ณ ๋Š” ํ•ด๋„, ํ•œ ์‚ฌ์ดํŠธ์— ์„œ๋กœ ๋‹ค๋ฅธ ๋ ˆ์ด์•„์›ƒ์ด ์—ฌ๋Ÿฟ ์‚ฌ์šฉํ•ด์•ผ ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.

๋‹จ์ ์œผ๋กœ GNB, LNB๊ฐ€ ์—†๋Š” IntroํŽ˜์ด์ง€, LoginํŽ˜์ด์ง€ ๊ทธ๋ฆฌ๊ณ  ์‚ฌ์šฉ์ž ํ™”๋ฉด๊ณผ ๋‹ค๋ฅธ ๊ด€๋ฆฌ์ž ํ™”๋ฉด์˜ ๋ ˆ์ด์•„์›ƒ์€ ์„œ๋กœ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ๋‹ค. ์•„๋‹ˆ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ๋Š” ์ •๋„๊ฐ€ ์•„๋‹ˆ๋ผ ๋Œ€์ฒด๋กœ ๋‹ค๋ฅด๋‹ค.

๊ทธ๋Ÿด ๋•Œ ๋ฆฌ์•กํŠธ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ ˆ์ด์•„์›ƒ ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๊ฝค ๋‹ค์–‘ํ•˜์ง€๋งŒ ์ง๊ด€์ ์ด๊ณ  ์‰ฌ์šด ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•˜๊ฒ ๋‹ค.

๋ฆฌ์•กํŠธ ๋ฉ€ํ‹ฐ ๋ ˆ์ด์•„์›ƒ react multi layout

์‚ฌ์‹ค ๊ตฌ๊ธ€๋ง ํ•ด์„œ ๋‚˜์˜ค๋Š” ์ˆ˜๋งŽ์€ react multi layout ์„ค๋ช…๋“ค์€ ์ง€๋“ค๋ผ๋ฆฌ๋งŒ ์•„๋Š” ์–˜๊ธฐ๋ฅผ ํ•˜๋Š” ๊ฑด์ง€, ์ž๊ธฐ๋“ค๋„ ๋ชจ๋ฅด๋Š” ๊ฑด์ง€ ๋„๋ฌด์ง€ ์•Œ์•„๋“ค์„ ์ˆ˜ ์—†๊ฑฐ๋‚˜, ๋˜ ์ƒˆ๋กœ์šด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•๋งŒ ์„ค๋ช…ํ•˜๊ณ  ์žˆ๋‹ค.

์—ฌ๊ธฐ์„œ ์†Œ๊ฐœํ•  multi layout์€ ๊ธฐ๋ณธ์ ์œผ๋กœ react์—์„œ ์‚ฌ์šฉํ•˜๋Š” route ๊ธฐ๋ณธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ react-router ๋งŒ์œผ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

์ œ๋ชฉ์— connected-react-router๋ผ๊ณ  ์จ ๋†“์€ ๊ฒƒ์— ๋Œ€ํ•ด์„œ๋Š” ์ซ„ ํ•„์š”๊ฐ€ ์—†๋‹ค.

์ง„ํ–‰ ์ค‘์ธ ํ”„๋กœ์ ํŠธ์—์„œ connected-react-router๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด์„œ ์˜ˆ์‹œ๋ฅผ ๊ทธ๋ ‡๊ฒŒ ๋“ค์—ˆ์„ ๋ฟ์ด๋‹ค.

connected-react-router๋Š” redux ๋•Œ๋ฌธ์— ์“ฐ๋Š” ๊ฒƒ์ด๋‹ˆ, ์–ด์ฉŒ๋ฉด ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ๋“ค์—์„œ๋„ ๊ธฐ๋ณธ์ ์œผ๋กœ ์“ฐ๋Š” ๊ฒƒ์ผ ์ˆ˜ ์žˆ๋‹ค.

์ „์ฒด ์†Œ์Šค๋ฅผ ๊ณต๊ฐœํ•˜๊ธฐ์—๋Š” ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์˜ ๋ณด์•ˆ์„œ์•ฝ ๋•Œ๋ฌธ์— ๋ถˆ๊ฐ€ํ•˜๊ณ , ๋ฉ€ํ‹ฐ ๋ ˆ์ด์–ด์˜ ํ•ต์‹ฌ๋งŒ ์–ธ๊ธ‰ํ•˜๊ฒ ๋‹ค.

import { Route, Switch } from "react-router"; // react-router v4/v5
import { ConnectedRouter } from "connected-react-router";

// Layout
import CampaignLayout from "./layouts/CampaignLayout";
import DemoGuideLayout from "./layouts/DemoGuideLayout";


// Sub Children
import CampaignPolicyLayout from "./domain/CampaignPolicy/CampaignPolicyLayout";
import CampaignPolicyFormContainer from "./domain/CampaignPolicy/CampaignPolicyFormContainer";
import CampaignPolicyInfoContainer from "./domain/CampaignPolicy/CampaignPolicyInfoContainer";
import DemoFormList from "./domain/DemoGuide/DemoFormListLayout";
import DemoGuideFormContainer from "./domain/DemoGuide/CampaignPolicyFormContainer";
import DemoGuideInfoContainer from "./domain/DemoGuide/CampaignPolicyInfoContainer";

// Login without Layout
import { default as MemberLogin } from "./domain/Member";


function App({ history, context }) {
  return (
    <ConnectedRouter history={history} context={context}>
      <Switch>
        <Route exact path="/MemberLogin" component={MemberLogin} />
        {/* ์„œ๋กœ ๋‹ค๋ฅธ ๋ ˆ์ด์•„์›ƒ์€ ํ”ผ์–ดํ•œ Route */}
        <Route path="/campaign-policy/:path?" exact>
          {/* ๋ ˆ์ด์•„์›ƒ ํ˜ธ์ถœ */}
          <CampaignLayout>
          {/* ๊ฐ™์€ ๋ ˆ์ด์•„์›ƒ์„ ๊ณต์œ ํ•˜๋Š” ์„œ๋ธŒ๋ฉ”๋‰ด๋“ค์€ ํ•˜๋‚˜์˜ Switch์—์„œ Route๋จ */}
            <Switch>
              <Route
                exact
                path="/campaign-policy"
                component={CampaignPolicyLayout}
              />
              <Route
                exact
                path="/campaign-policy/create"
                component={CampaignPolicyFormContainer}
              />
              <Route
                exact
                path="/campaign-policy/:id"
                component={CampaignPolicyInfoContainer}
              />
            </Switch>
          </CampaignLayout>
        </Route>
        <Route path="/demo-guide/:path?" exact>
          {/* ๋ ˆ์ด์•„์›ƒ ํ˜ธ์ถœ */}
          <DemoGuideLayout>
          {/* ๊ฐ™์€ ๋ ˆ์ด์•„์›ƒ์„ ๊ณต์œ ํ•˜๋Š” ์„œ๋ธŒ๋ฉ”๋‰ด๋“ค์€ ํ•˜๋‚˜์˜ Switch์—์„œ Route๋จ */}
            <Switch>
              <Route exact path="/demo-guide/" component={DemoFormList} />
              <Route
                exact
                path="/demo-guide/create"
                component={DemoGuideFormContainer}
              />
              <Route
                exact
                path="/demo-guide/:id"
                component={DemoGuideInfoContainer}
              />
            </Switch>
          </DemoGuideLayout>
        </Route>
        <ToastNotiComponent />
      </Switch>
    </ConnectedRouter>
  );
}

export default App;

๊ฝค ๊ธธ์–ด ๋ณด์ด์ง€๋งŒ, ์‚ฌ์‹ค 3๊ฐ€์ง€์˜ Route๊ฐ€ ์‚ฌ์šฉ๋˜์—ˆ๋‹ค.

์ฒซ ๋ฒˆ์งธ Switch์—์„œ 3๊ฐ€์ง€ ๋ ˆ์ด์•„์›ƒ์ด ๋ถ„๊ธฐ๋œ๋‹ค.

/MemberLogin์—์„œ ๋ถ„๊ธฐ๋œ ๋ ˆ์ด์•„์›ƒ์—๋Š” ํŠน๋ณ„ํ•œ ๋ ˆ์ด์•„์›ƒ์ด ์—†๋‹ค.

ํ•ด๋‹น ํŽ˜์ด์ง€ ์ž์ฒด๊ฐ€ ๋ ˆ์ด์•„์›ƒ ํ˜•์ƒ์„ ๊ฐ–๊ณ  ์žˆ๋‹ค.

/campaign-policy/ ๊ฒฝ๋กœ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๋ ˆ์ด์•„์›ƒ์€ <CampaignLayout></CampaignLayout> ์•ˆ์—์„œ Switch ๋˜์–ด ๋ฉ”๋‰ด๋ณ„๋กœ ๋ถ„๊ธฐ๋œ๋‹ค.

๋งŒ์•ฝ /campaign-policy/ ์™€ ๋ ˆ์ด์•„์›ƒ์„ ๊ณต์œ ํ•˜๋Š” /campaign-policy2/์„œ๋ธŒ๋ฉ”๋‰ด๊ฐ€ ์žˆ๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

import { Route, Switch } from "react-router"; // react-router v4/v5
import { ConnectedRouter } from "connected-react-router";

// Layout
import CampaignLayout from "./layouts/CampaignLayout";
import DemoGuideLayout from "./layouts/DemoGuideLayout";


// Sub Children
import CampaignPolicyLayout from "./domain/CampaignPolicy/CampaignPolicyLayout";
import CampaignPolicyFormContainer from "./domain/CampaignPolicy/CampaignPolicyFormContainer";
import CampaignPolicyInfoContainer from "./domain/CampaignPolicy/CampaignPolicyInfoContainer";
import DemoFormList from "./domain/DemoGuide/DemoFormListLayout";
import DemoGuideFormContainer from "./domain/DemoGuide/CampaignPolicyFormContainer";
import DemoGuideInfoContainer from "./domain/DemoGuide/CampaignPolicyInfoContainer";

// Login without Layout
import { default as MemberLogin } from "./domain/Member";


function App({ history, context }) {
  return (
    <ConnectedRouter history={history} context={context}>
      <Switch>
        <Route exact path="/MemberLogin" component={MemberLogin} />
        {/* ์„œ๋กœ ๋‹ค๋ฅธ ๋ ˆ์ด์•„์›ƒ์€ ํ”ผ์–ดํ•œ Route */}
        <Route path="/campaign-policy/:path?" exact>
          {/* ๋ ˆ์ด์•„์›ƒ ํ˜ธ์ถœ */}
          <CampaignLayout>
          {/* ๊ฐ™์€ ๋ ˆ์ด์•„์›ƒ์„ ๊ณต์œ ํ•˜๋Š” ์„œ๋ธŒ๋ฉ”๋‰ด๋“ค์€ ํ•˜๋‚˜์˜ Switch์—์„œ Route๋จ */}
            <Switch>
              <Route
                exact
                path="/campaign-policy"
                component={CampaignPolicyLayout}
              />
              <Route
                exact
                path="/campaign-policy/create"
                component={CampaignPolicyFormContainer}
              />
              <Route
                exact
                path="/campaign-policy/:id"
                component={CampaignPolicyInfoContainer}
              />
            </Switch>
          </CampaignLayout>
        </Route>
        <Route path="/campaign-policy2/:path?" exact>
          {/* ๋ ˆ์ด์•„์›ƒ ํ˜ธ์ถœ */}
          <CampaignLayout>
          {/* ๊ฐ™์€ ๋ ˆ์ด์•„์›ƒ์„ ๊ณต์œ ํ•˜๋Š” ์„œ๋ธŒ๋ฉ”๋‰ด๋“ค์€ ํ•˜๋‚˜์˜ Switch์—์„œ Route๋จ */}
            <Switch>
              <Route
                exact
                path="/campaign-policy2"
                component={CampaignPolicyLayout}
              />
              <Route
                exact
                path="/campaign-policy2/create"
                component={CampaignPolicyFormContainer}
              />
              <Route
                exact
                path="/campaign-policy2/:id"
                component={CampaignPolicyInfoContainer}
              />
            </Switch>
          </CampaignLayout>
        </Route>
        <Route path="/demo-guide/:path?" exact>
          {/* ๋ ˆ์ด์•„์›ƒ ํ˜ธ์ถœ */}
          <DemoGuideLayout>
          {/* ๊ฐ™์€ ๋ ˆ์ด์•„์›ƒ์„ ๊ณต์œ ํ•˜๋Š” ์„œ๋ธŒ๋ฉ”๋‰ด๋“ค์€ ํ•˜๋‚˜์˜ Switch์—์„œ Route๋จ */}
            <Switch>
              <Route exact path="/demo-guide/" component={DemoFormList} />
              <Route
                exact
                path="/demo-guide/create"
                component={DemoGuideFormContainer}
              />
              <Route
                exact
                path="/demo-guide/:id"
                component={DemoGuideInfoContainer}
              />
            </Switch>
          </DemoGuideLayout>
        </Route>
        <ToastNotiComponent />
      </Switch>
    </ConnectedRouter>
  );
}

export default App;

๊ทธ๋ฆฌ๊ณ  demo-guide ๊ฒฝ๋กœ์—์„œ๋Š” <DemoGuideLayout></DemoGuideLayout> ๋ ˆ์ด์•„์›ƒ์„ ์‚ฌ์šฉํ•œ๋‹ค.

๋ ˆ์ด์•„์›ƒ ์ธก์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‚ด์šฉ ์ฝ˜ํ…์ธ ๋ฅผ ์œ„์น˜์‹œํ‚จ๋‹ค.

import React, { useState, useEffect, Component } from "react";
import { useSelector } from "react-redux";

import classNames from "classnames/bind";
// left menu
import SideMenu from "../components/SideMenu/SideMenu";
// header
import Header from "../components/Header/Header";
import styles from "../App.css";

const cx = classNames.bind(styles);
// ์ปดํฌ๋„ŒํŠธ ์ •์˜
const CampaignLayout = ({children}) => {
  const leftSize = useSelector((state) => state.menuStore.leftSize);
  const oLeftSize = useSelector((state) => state.menuStore.oLeftSize);
  let sideMenu = null;
  // let sideMenu = null;
  function getWindowDimensions() {
    const { innerWidth: width, innerHeight: height } = window;
    return {
      width,
      height,
    };
  }
  const [windowDimensions, setWindowDimensions] = useState(
    getWindowDimensions()
  );

  useEffect(() => {
    function handleResize() {
      setWindowDimensions(getWindowDimensions());
    }

    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  // render
  return (
    <>
      <div className={cx("root")}>
        <SideMenu />
        <div
          className={cx("container0")}
          rel={windowDimensions.height}
          style={{
            transform: `translate3d(${
              leftSize === oLeftSize ? "0" : `-${oLeftSize}px`
            }, 0, 0)`,
            width: `calc(100% - ${leftSize}px)`,
            left: `${oLeftSize}px`,
            height: `100vh`,
          }}
        >
          <Header />
          <div className={cx("contents")}>{children}</div>
        </div>
      </div>
    </>
  );
};

export default CampaignLayout;

๋ณต์žกํ•ด ๋ณด์ด๊ฒ ์ง€๋งŒ ์ค‘์š”ํ•œ ํ•ต์‹ฌ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

const CampaignLayout = ({children}) => {
  // render
  return (
    <>
        {children}
    </>
  );
};

export default CampaignLayout;
๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€