본문 바로가기
경일/React

[React] router & redux

by dev_kong 2022. 5. 3.
728x90
728x90

0. 목차

1. 개요

2. router

3. redux

1. 개요

이전 node.js 프로젝트때 hash(#)를 이용해서 주소값을 변경하고
그에 따른 화면을 보여주는 작업을 했었다.


hash 뒤의 값이 바뀔 때 마다, 그걸 확인하고
그에따른 화면을 만들어주는 작업을 했었는데,
리액트에서는 react-router-dom을 통해 이를 쉽게 구현할 수 있다.


.. 진짜 쉬운지는 사실 잘 모르겠다..


그리고 useContextreducer 이용해 상태를 전역에서 관리할수 있게끔 해주는 작업을
redux 와 redux와react 를 연결해주는 라이브러리인 react-redux를 이용해서 해보려 한다.

2. router

우선 세팅을 해보자.

$ npm install react-router-dom

 

그리고 사용할 컴포넌트 파일에서 import로 컴포넌트들을 가져온다

 

// App.js

import {BrowserRouter as Router, Route, Routes, Link} from 'react-router-dom';

 

총 세가지의 컴포넌트를 가져왔다.
이름들이 비슷해서 헷갈리지만, 당연하게 다른 역할들을 맡고 있다.

  • BrowserRouter(Router)
    이름이 길어서 그런가 대부분 as를 통해 이름을 Router로 변경해서 사용한다고 한다.
    역할은 router 기능을 이용할 모든 컴포넌트들을 감싸는 최상위 컴포넌트이다.
  • Routes
    Route 컴포넌트들을 감싸는 컴포넌트 이다
  • Route
    URI가 변경되면 그 URI에 맞은 컴포넌트를 보여주는 컴포넌트이다.
    if 문과 역할이 비슷하다.
  • Link
    props(to)로 전달한 값으로 주소값을 변경해주는 컴포넌트 이다.

 

function App() {
  return (
    <>
      <Router>
        <Link to="/">Home</Link>
        <Link to="/counter">Counter</Link>
        <Link to="/comment">Comment</Link>
        <Link to="/Login">Login</Link>
        <Routes>
          <Route path="/" element={<Index />} />
          <Route path="/counter" element={<Counter />} />
          <Route path="/comment" element={<Comment />} />
          <Route path="/login" element={<Login />} />
        </Routes>
      </Router>
    </>
  );
}

 

이렇게 만들면 된다.
이제 각각 Index, Couter, Comment, Login을 만들면 된다.

컴포넌트를 만들기전에 리액트프로젝트에서 대체적으로 사용되는 디렉토리구조를 알아보자.

 

src
|- component : 공통적으로 들어갈 컴포넌트 모음
    |- header.jsx
    |- responsive.jsx
|- hook : 훅에 관련된 내용을 넣는 컴포넌트
|- pages : 사용자에게 보여줄 컴포넌트 모음
|- reducer : reducer 모음
|- Store : 전역상태를 만들어주는 디렉토리
|- comment
  |- index.js
  |- ...
  |- ...
  |- ...
  |- css

 

위와 같은 디렉토리구조가 대체적으로 사용되는 편이며,
지금 만들 4개의 컴포넌트는 모두 pages에 해당되므로
pages 디렉토리를 생성하고 만들것이다.

 

// ./pages/index/index.jsx

const Index = () => {
  return <>Index Component</>;
};

export default Index;


// ./pages/Comment/index.jsx

const Comment = () => {
  return <>Comment Component</>;
};

export default Comment;

// ./pages/Counter/index.jsx

const Counter = () => {
  return <>Counter Component</>;
};

export default Counter;

// ./pages/Login/index.jsx

const Login = () => {
  return <>Login Component</>;
};

export default Login;

 

이렇게 각각 디렉토리에 컴포넌트를 생성하고
App.js에서 해당 컴포넌트들을 불러오면 된다.

 

// App.js

import Index from './pages/index/index';
import Counter from './pages/counter';
import Comment from './pages/comment';
import Login from './pages/login';

이렇게 해주면 정상적으로 화면이 랜더되고
랜더된 메뉴들을 클릭시 URI 가 변경되고,
변경된 URI 에 따라 해당하는 컴포넌트가 랜더 되는걸 확인 할 수 있다.

 

Link 4개를 묶어서 Header 라는 컴포넌트 하나에서 관리하면 관리가 더 편리할거 같으니,
Header 컴포넌트를 만들어볼거다.


Header 컴포넌트 같은 경우는 모든 페이지에 공통적으로 사용될 컴포넌트 이므로,
component 디렉토리로 들어가는 게 좋을 것 같다.

 

// ./component/header.jsx

import { Link } from 'react-router-dom';

const Header = () => {
  return (
    <div className="Header">
      <h1>LOGO</h1>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/counter">Counter</Link>
        </li>
        <li>
          <Link to="/comment">Comment</Link>
        </li>
        <li>
          <Link to="/Login">Login</Link>
        </li>
      </ul>
    </div>
  );
};

export default Header;

 

로고 까지 넣음
CSS 는 추후에 style component 포스팅때 넣을거임

 

3. Redux

이전에는 context 를 이용하던 전역상태관리를 redux를 이용해 해보려 한다.

우선 세팅..
항상 느끼는 거지만 세팅이 제일 힘들다..

 

$ npm i redux react-redux redux-devtools-extension

 

총 세가지의 라이브러리를 설치한다.

  • redux
  • react-redux
  • redux-devtools-extensions

counter 컴포넌트에 redux를 적용하고,
전역에 counte 라는 상태를 만들고
counter 컴포넌트 내에 두개의 버튼을 만든뒤에
각각의 버튼을 눌렀을 때 상태가 변경되도록 만들어 볼거다.

 

데이터가 전달되고 전달 받는게 굉장히 어렵고 난해하다.

우선 reducer 디렉토리에
index.jsx 를 만들어서

전역에서 관리될 상태와 reducer 함수를 만들어 주자

 

// ./reducer/index.js

const initialState = {
  number: 0,
};

const rooteReducer = (state = initialState, action) => {
  const { type } = action;
  switch (type) {
    default:
      return state;
  }
};

 

우선은 기본적인 형태로만 잡아두었다.

여기서 한가지 확인할 점은 함수의 이름이다 rootReducer

원래라면 컴포넌트 마다 각각 reducer를 만들고
combineReducer를 이용해서 모든 reducer를 합치는 작업을 해준다고 한다.

 

지금은 counter 하나만 있으니까 우선은 이렇게 해보자.

 

그 다음으로 할일은 store 디렉토리에서
Store 컴포넌트를 만든 뒤,

createStore 와 Provier 이용해 하위 컴포넌트들에게 rootReducer를 전달해주는 역할을 하게끔 만들어주면 된다.

 

// ./store/useStore.jsx

import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { composeWithDevTools } from 'redux-devtools-extension';

import rootReducer from '../reducer';

const store = createStore(rootReducer, composeWithDevTools());

const Store = ({ children }) => {
  return <Provider store={store}>{children}</Provider>;
};

export default Store;

 

(composeWithDevTools()는 크롬 dev tool에서 redux를 확인하기 위해 쓰여진다)
컴포넌트를 만들어 준뒤 index.js 에서
App 컴포넌트를 감싸주면 된다..

 

root.render(
  <React.StrictMode>
    <Store>
      <App />
    </Store>
  </React.StrictMode>
);

 

이렇게 redux와 reduce 를 세팅을 해주면,
App 컴포넌트를 포함한 모든 하위 컴포넌트에서 전역상태를 사용할 수 있다.

이제 Counter 컴포넌트에
상태(number)와 상태를 변경할 수 있는 버튼 두개를 만들어보자.

 

// ./pages/counter/index.jsx

import { useSelector, useDispatch } from 'react-redux';

const Counter = () => {
  const state = useSelector((state) => state);
  const dispatch = useDispatch();

  const clickDecrease = () => {
    dispatch({ type: 'decrease' });
  };

  const clickIncrease = () => {
    dispatch({ type: 'increase' });
  };

  return (
    <>
      <h3>Counter Component</h3>
      <button onClick={clickDecrease}>-1</button>
      <p>{state.number}</p>
      <button onClick={clickIncrease}>+1</button>
    </>
  );
};

export default Counter;

useSelector 와 useDispatch 를 import 로 가져왔다.
처음 보는 애들이다.

 

우선 useSelector 는 상태를 가져오는 친구다.
콜백함수의 인자값으로 전역에 있는 상태를 가져오고 return 값으로 상태안에 또 객체가 있으면 가져올 수가 있다.
현 상황에서는 상태의 depth 가 없기 때문에 그냥 state를 return 해주었다.

 

그리고 useDispatch를 실행시켜서 dispatch 라는 변수에 담아주었는데,
dispatch에 담긴 함수는 결국 rootReducer 가 된다.

 

오늘 redux를 하면서 가장 의아했던 부분이 사실 이부분 이었다.
대체 어떻게 상태를 가져오고 어떻게 rootReducer를 가져 오는 걸까...

 

이부분에 대한 이해가 완벽하진 않지만,
나름의 추측을 해보자면,

 

우선 useStore.jsx 의 createStore가 핵심인 듯 하다.

createStore를 호출하면 리턴값이 객체로 나오는데,
이 객체 내부에는 state를 가져오는 함수와,
dispatch를 가져오는 함수를 포함하고 있다.

 

state를 가져오는 함수는
아마도 createStore의 첫번째 인자값인 rootReducer를 한번 실행시켜서

default return 값을 가져와서 useState 해주는 코드일 것이고

 

dispatch를 가져오는 함수는 rootReducer 의 리턴값을 setState 해주는 함수이지 않을까 싶다.

 

이러한 함수들을 포함하고 있는 객체 store(createStore의 리턴)는 Provider 컴포넌트의 props로 전달되고,
Provider 컴포넌트는 모든 하위 컴포넌트에게 프롭스를 전달해주는 역할을 하는 듯 싶다.

 

그리고 useSelector 와 useDispatch 함수는 Provider 컴포넌트에서 전달받은 store 프롭스를 이용해
상태를 가져오고 dispatch를 사용할 수 있게끔 해주는게 아닐까...

 

뇌피셜이지만, redux의 흐름을 이해하기에는 그렇게 나쁘지만은 않은 것 같다..

 

어느정도 이해가 됐으니 마저 코드를 작성해보자.

 

버튼의 onClick 이벤트를 통해 dispatch 함수에 action 을 인자로 집어 넣었으니

reducer에서 action.type 에 따른 코드를 작성해야 한다.

 

// ./reducer/index.js

const rootReducer = (state = initialState, action) => {
  const { type } = action;
  switch (type) {
    case 'increase':
      return {
        ...state,
        number: state.number + 1,
      };

    case 'decrease':
      return {
        ...state,
        number: state.number - 1,
      };

    default:
      return state;
  }
};

 

이렇게 해주면 클릭된 버튼에따라 숫자가 변경되는것을 확인할 수 있다.

redux 난해하고 난해하다.
사용하다보면 익숙해지지 않을까란 기대를..해본다.

전체코드는 Github에 올려두었다.

728x90
728x90

댓글