복잡한뇌구조마냥

RRR( React, Redux, Route ) 본문

이노베이션 캠프/회고록

RRR( React, Redux, Route )

지금해냥 2022. 8. 25. 11:03

단순 개념 정리

1. Redux

- 공식 홈페이지에서는 리덕스는 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너라고 정의하고있다.

- 자식  컴포넌트들 간의 다이렉트 데이터 전달은 불가능 하다.

- 자식 컴포넌트 간의 데이터를 주고 받을 때는 상태 관리를 담당하는 부모컴포넌트를 통해야 한다.

- 자식이 많이 진다면 관리가 매우 복잡하기때문에 상태 관리의 복잡성을 라이브러리로 해결하는 것이다.

 

부모 컴포넌트로부터 상속받는 자식 컴포넌트
리덕스를 통한 상태관리

- 부모 컴포넌트로부터 내려받던 데이터를 리덕스를 통해서 나눠갖게 된다.

- 전역 상태 저장소를 제공받고 *Props Drilling 문제를 해결한다.

 

*Props Drilling이란?

Props Drilling 은 props를 오로지 하위 컴포넌트로 전달하는 용도로만 쓰이는 컴포넌트들을 거치면서

React Component 트리의 한 부분에서 다른 부분으로 데이터를 전달하는 과정이다.

따라서, Props의 비효율 발생, 컴포넌트가 많아질 수록 추적이 어렵고 컴포넌트 분리의 어려움과  props수정이 번거롭다.

 

2. Route

- 서로 다른 컴포넌트의 주소에 따라 컴포넌트 배치를 변경하는 작업을 Routing이라고 한다.

- 사용자가 입력한 주소를 감지하는 역할을 한다.

- SPA라 하더라도 다수의 컴포넌트 페이지주소를 다르게 구성할 필요가 있다.

- React에서는 자체적 Router 기능이 포함되지 않아 라이브러리를 설치해야한다. (React-Router-Dom)

- 가장 많이 사용되는 라우터 컴포넌트는 BrowserRouter와 HashRouter 이다.

 

BrowserRouter          -          HashRouter

Browser Router

  • HTML5의 history API를 활용하여 UI를 업데이트 한다.
  • 동적인 페이지에 적합하다 ※ 서버에 있는 데이터들을 스크립트에 의해 가공처리 한 후 생성되어 전달되는 웹페이지
  • 새로 고침하면 경로를 찾지 못해서 에러가 난다. ※ 주소를 사용하여 페이지를 찾아갈 때에도 에러발생
  • 서버에 추가적 세팅 필요
  • 페이지의 유무를 서버에 알려줘야 하며, 서버 세팅시 검색엔진에 신경써야함
  • github pages에서 설정하기 복잡하다. (배포가 복잡함)

Hash Router

  • URL의 hash를 활용한 라우터이다.
  • 주소에 #이 붙는다.
  • 정적인 페이지에 적합하다. (미리 저장된 페이지가 그대로 보이는 웹페이지)
  • 검색엔진으로 읽지 못한다. (거의 사용하지 않는 이유) ※ #값 때문에 서버가 읽지 못하고 서버가 페이지 유무를 알지 못하기 때문
  • 새로고침해도 에러가 나지 않는다.
  • github pages에서 설정하기 간편하다. (배포가 간편함)

사용 방법 정리

1. Router

1)설치

//npm
npm install react-router-dom

//yarn
yarn add react-router-dom

2) packge.json 설치 확인 (라이브러리 설치 시 동일하게 확인)

3) <Browser Router>로 index.js 수정 작성

import {BrowserRouter} from "react-router-dom" 

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
    </Provider>
);

- App.js에서 return값을 묶어주거나 외부파일로 묶어줘도 동일 기능 동작

4) App.js 경로 설정

<구버전 방식>

import { Route, Switch } from "react-router-dom";

function App() {
  return (
    <Switch> 
    <Route path="/" exact>
      <TodoList />
    </Route>
    <Route path="/login" exact component={LoginPage}></Route>
    <Route path="/join" exact component={JoinPage}></Route>
    <Route path="/:id" exact component={DetailTodo}>
      {/* <DetailTodo/> */}
    </Route>
    <Route>
      <NotFound/>
    </Route>
    </Switch>
    
  )
}

방법 1. <Route path="경로" component = {컴포넌트 이름}/>

방법 2. <Route path="경로"> <컴포넌트/> </Route>

 

옵션1. exact - 경로 중복 제외

옵션2. /:id (다른이름 가능) 콜론(:)을 통한 경로 동적 할당

옵션3. <Route> 경로 없는 Route로 빈페이지 할당

 

<다른 문법>

import { Route, Routes } from "react-router-dom";

function App() {
  return (
    <Routes> 
        <Route path="/" exact element={<TodoList />}/>
        <Route path="/login" exact element={<LoginPage/>}/>
        <Route path="/join" exact element={<JoinPage/>}/>
        <Route path="/:id" exact element={<DetailTodo/>}>
          <Route path="1" element{<div>추가페이지</div>}/>
        </Route>
        <Route path="*" exact element={<NotFound/>}>
    </Routes>
    
  )
}

방법 1. <Route path="경로" element = {내부 작성 내용}/>

방법 2. <Route path="경로"> <컴포넌트/> </Route>

방법 3. nested routes기법 내부에 경로를 작성함으로서 경로 이어서 작성 가능 ex) localhost/id/1

 

옵션1. exact - 경로 중복 제외

옵션2. /:id (다른이름 가능) 콜론(:)을 통한 경로 동적 할당

옵션3. <Route path="*"> 나머지 경로 빈페이지 할당

 

1-2. 부가기능

1) Link

- 클릭하면 다른 주소로 이동시키는 컴포넌트

<a href="..."> .... </a>

- 리액트 라우터를 사용할 땐 일반 .... 를 사용하시면 안됩니다.

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

<Link to="/">메인화면</Link>

- 그 대신 Link 라는 컴포넌트를 사용해야 합니다.

 

이유 : a태그의 기본적인 속성은 페이지를 이동시키면서, 페이지를 아예 새로 불러오게 됩니다.

    => 리액트앱이 지니고 있는 상태 초기화 => 렌더링된 컴포넌트 제거 후 재 랜더링

   따라서, Link컴포넌트를 사용하면 HTML History API를 사용하여 주소만 바꾸고 페이지를 새로 불러오지 않습니다.

 

2) useHistory

- 리액트에서 URL주소를 변경할 때 사용하는 Hook

- URL주소를 변경시켜 URL 주소와 일치하는 Route컴포넌트를 렌더링하기 위해 사용한다.

- 리액트 특성상, URL변경 없이 내부 컴포넌트만 변경시켜 화면을 바꿔줄 수 있다.

- URL을 바꿔주면 현제 페이지의 위치를 대략적으로 알 수 있고, URL주소만으로 결과값을 불러올 수 있기 때문에 사용

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

const App = () => {
  const history = useHistory();
  
  return (
  	<>
       	<button onClick={(()=>{history.push("/")})}>메인화면</button>
        </>
  )
  }

- useHistory에는 메서드가 존재하는데 가장 많이 사용하는 메서드로는 go, back, replace가 있다.

  • go : 해당 URL을 추가한다. (Stack의 개념이기에 하나씩, 차곡차곡 쌓인다.)
  • back : 현재 URL 이전으로 변경한다.
  • replace : URL을 추가하는 것이 아니라 현재 URL을 변경한다.

 

3) useNavigate

- 훅을 실행하면 페이지 이동을 할 수 있게 해주는 함수를 반환합니다.

- 리액트 라우터 v6에서는 기존의 useHistory 대신 useNavigate를 사용해야합니다.

- useNavigate로 기존에 useHistory의 기능을 전부 대체 가능

- useHistory의 history는 객체였지만 useNavigate의 navigate는 함수다.

기존 코드

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

const App = () => {
  const history = useHistory();
  
  return (
  	<>
       	<button onClick={(()=>{history.push("/")})}>메인화면</button>
        </>
  )
  }

history.push('/');
history.goback();
history.go(-2);
history.push(`/user/${user._id}`);

v6 코드

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

const App = () => {
  let navigate = useNavigate();
  
  return (
  	<>
       	<button onClick={(()=>{navigate("/")}}>메인화면</button>
        </>
  )
  }

navigate('/');
navigate(-1);
navigate(-2);
navigate(`/user/${user._id}`);

4) Outlet

- 중첩 라우팅의 구성이 되면 Outlet을 통해서 상위의 컴포넌트를 레이아웃화 할 수 있다.

- <Outlet> 을 사용하면 {children} 을 사용하는 것과 같은 효과가 난다

import { Route, Routes, Outlet } from "react-router-dom";

function App() {
  return (
    <Routes> 
        <Route path="/:id" exact element={<Event/>}>
          <Route path="1" element{<div>생일 기념 이벤트</div>}/>
        </Route>
        <Route path="*" exact element={<NotFound/>}>
    </Routes>
    
  )
}

const Event = () =>{
  return (
    <div>
      <h4>오늘의 이벤트</h4>
      <Outlet></Outlet>
    </div>
  )
}

- 이런식으로 작성하면 상위 컴포넌트의 Outlet에 중첩 라우터의 내용을 작성할  수 있다.

2. Redux

2-1. Redux

1) 설치

//npm
npm install redux react-redux

//yarn
yarn add redux react-redux

2) 폴더 구조 생성하기

상측의 이미지와 같이 폴더 구조를 생성하세요.

  1. src 폴더 안에 redux 폴더를 생성
  2. redux 폴더 안에 config, modules 폴더를 생성
  3. config 폴더 안에 configStore.js파일을 생성합니다.

각각의 폴더와 파일은 역할이 있습니다.

  • redux : 리덕스와 관련된 코드를 모두 모아 놓을 폴더 입니다.
  • config : 리덕스 설정과 관련된 파일들을 놓을 폴더 입니다.
  • configStore : “중앙 state 관리소" 인 Store를 만드는 설정 코드들이 있는 파일 입니다.
  • modules : 우리가 만들 State들의 그룹이라고 생각하면 됩니다. 예를 들어 투두리스트를 만든다고 한다면, 투두리스트에 필요한 state들이 모두 모여있을 todos.js를 생성하게 되텐데요, 이 todos.js 파일이 곧 하나의 모듈이 됩니다.

3) <Provider>로 index.js 수정 작성 (index.js에서 App을 감싸서도 사용 가능)

import {Provider} from "react-redux"
//store 경로 지정
import store from "./redux/configStore"

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
    </Provider>
);

- App으로 Provider을 통한 store(저장소) 제공

 

4) Store 기능 저장소 세팅 redux/configStore (경로지정은 필수가 아님)

import { createStore, combineReducers } from "redux";
import 데이터명 from "받아올 경로"

const rootReducer = combineReducers({데이터명});
const store = createStore(rootReducer);

export default store;

5) module 작성 redux/modules/Todo.js (예제 경로)

// Actions
const CREATE = 'todo/CREATE';
const UPDATE = 'todo/UPDATE';
const REMOVE = 'todo/REMOVE';

// initialState
const initialState ={ 
    todo : [
        {
          id: 1,
          title: "리액트 공부하기",
          body: "리액트 기초를 공부해봅시다.",
          isDone: false,
        },
        {
          id: 2,
          title: "리액트 공부하기",
          body: "리액트 기초를 공부해봅시다.",
          isDone: true,
        },
]
}


// Reducer
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
    case "todo/CREATE":{
        const new_todo_list = [...state.todo, action.todo];
        return {todo:new_todo_list};
    }
    case "todo/UPDATE":{
        const new_todo_list = [...state.todo].map(todo =>{
            if(todo.id === action.id){
                return{
                    ...todo,
                    isDone:!todo.isDone,
                };
            }else{
                return {...todo};
            }
        });

        return {todo:new_todo_list};
    }
    case "todo/REMOVE":{
        const new_todo_list = [...state.todo].filter(todo=> {
            return todo.id !== action.id;
        });

        return {todo:new_todo_list};
    }
// do reducer stuff
default: return state;
}
}

// Action Creators
export function createTodo(todo){
    return {type: CREATE, todo: todo}
}

export function chagneTodo(id){
    return { type: UPDATE, id: id };
}

export function removeTodo(id){
    return {type: REMOVE, id: id}
}

- Action을 통해서 Action type의 변수 지정

- Action Creator 를 통한 reducer action의 type과 파라미터 지정

- initialState를 통한 초기값 설정

- reducer에 state 및 action 지정 ( switch문을 통한 Action 설정 및 함수 실행 )

 

6) 사용처에서 데이터 불러오기 및 함수 사용

import { useSelector } from "react-redux";
import Todo from "../todo/Todo";

function List() {
  const todos = useSelector((state) => state);
  console.log(todos);
  
  return (
  	<div className="list-wrapper">
        {todos.todo.todo.map((todo) => {
          if (!todo.isDone) {
            return (
              <Todo
                todo={todo}
                key={todo.id}
              />
            );
          } else {
            return null;
          }
        })}
      </div>
  )

- useSelector를 통한 Store의 데이터를 불러와 state 상태 값 사용

const todos = useSelector((state) => state.todos.todos);

- 상위 코드 처럼 작성하여 state의 상태값을 세부적으로 받아올 수 있음. store지정 값, state 지정 값 사용

import { useDispatch } from "react-redux";
import {removeTodo, chagneTodo} from "../../redux/modules/Todo";

function Todo({todo}) {
  const dispatch = useDispatch();
  
  return(
  	<div>
    	<button
          className="todo-delete-button button"
          onClick={() => dispatch(removeTodo(todo.id))}
        >
          삭제하기
        </button>
    </div>
    )

- useDispatch를 통한 Store의 데이터를 불러서 Reducer 사용 (ActionCreator도 따로 불러와서 사용)

 

2-1. Redux-Toolkit

장점

  • 초기 설정이 간편해짐 ( 리덕스 스토어 구성이 좀 더 간편화)
  • 다양한 패키지 설치를 제외 (redux devtool, immer, thunk등 라이브러리 내장)
  • 반복되는 코드가 너무 많아 코드가 복잡해지고 실수를 많이 유발했지만 이 부분 개선
  • 툴킷에서는 더이상 불변성을 신경쓰지 않아도 됩니다.

사용방법

1) 설치

// npm
npm install @reduxjs/toolkit react-redux

// yarn
yarn add @reduxjs/toolkit react-redux

2) <Provider>로 index.js 수정 작성 (index.js에서 App을 감싸서도 사용 가능)

import { Provider } from "react-redux";

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

export default App;

3) store 생성

import { configureStore, createSlice } from "@reduxjs/toolkit";
let todo = createSlice({
	// 이름 선언(예제라.. 다른거 쓰셔도 됩니다.)
	name: "todo"
	// 초기 값 설정
	initialState:[
		{id: 0, title: "리액트 공부", body: "힘내자", isDone:false}
		{id: 1, title: "다들", body: "화이팅", isDone:true}
	]
	//함수 생성
	reducers: {
		addTodo(state, action){
			//state = initialState부터 시작한 상태값
			//action 사용처에서 추가로 작성해준 데이터 ex)todo, id
			//받아온 데이터 사용하려면 action.payload 를 통해 받아오기(console에 찍어보세요)
			//만들어진 todo값을 받아온다고 가정
			state.push(action.payload);
		}
		// id값을 받아온다고 가정
		chageTodo(state, action){
			let index = state.findIndex(todo => todo.id === action.payload);
			state[index].isDone = !state[index].isDone;
		}
		// id 값을 받아 온다고 가정
		removeTodo(state, action){
			let  index = state.findIndex(todo=>  todo.id === action.payload);
			state.splice(index,1);
		}
})
// 사용하는 함수 외부 export
export  let {changeTodo, addTodo, removeTodo} = todo.actions;

//  configureStore 저장소가 가지는 reducer 값 선언
// 추가 reducer시 reducer 내부에 이어서 작성
export default configureStore({
  reducer: {
    counter: todo.reducer,
  }
});

4) 사용처에서 데이터 불러오기 및 함수 사용

import { useSelector, useDispatch } from 'react-redux/';
import { addTodo, changeTodo, RemoveTodo } from {store경로}

const Todo = () =>{
	// todo = state의 todo값 제공
	let todo = useSelector((state)=>{return state.todo});
	// 함수 사용을 위해서 작성
	let dispatch = useDispatch();

	return (
		<div>
			// todo에서 각각의 값을 map함수로 받아 사용
			{todo.map((todo)=>{
			<div>
				//데이터 불러오기
				<h1>todo.title</h1>
				<p>todo.body</p>
				<button onClick={()=>{
					// import해온 함수 사용 방법
					dispatch(chageTodo(todo.id));
				}}></button>
			</div>
			})
		</div>
	)
}

 

 

자료 참조:

https://phsun102.tistory.com/91

 

React - useHistory 사용법

1. useHistory란? useHistory는 리액트에서 URL주소를 변경할 때 사용하는 Hook이다. 예를 들어, 로그인 버튼 또는 여러 목록 중에서 하나를 선택하여 클릭했을 때, URL주소를 변경시켜 URL주소와 일치하는

phsun102.tistory.com

 

LIST