Redux

Redux사용시 컴포넌트들을 props 없이 state 공유가능
useReducer와 비슷한 구조를 지님
redux에서는 객체를 action객체라고 말함
action객체를 reducer로 보내는 행위 = dispatch

Redux 설치방법

npm

npm i redux react-redux

yarn

yarn add redux react-redux

디자인 패턴?

파일구성을 어떻게 분리하고 관리를 할건지

Container Presenter 패턴

Container Presenter 패턴은 리액트 디자인 패턴 중 하나로, 기능과 UI를 컴포넌트 상으로 분리한 것
데이터를 처리하고, 받아오는 부분(기능)은 Container 컴포넌트에서 담당
데이터를 보여주는 부분(UI)은 Presenter 컴포넌트에서 담당

기존 패턴(정통적패턴)

  • 폴더구성
  • counter.js만들기
  1. constants -> counter.js
    어떤 action이 필요한지 만듦
  2. export const INC_COUNT = "INC_COUNT" export const DEC_COUNT = "DEC_COUNT"
  3. actions -> counter.js
    action 객체를 만듦
import { INC_COUNT, DEC_COUNT } from '../constants/counter'

export function incCount(diff) {
  return {
    type: INC_COUNT,
    payload: {diff}
  }
}

export function decCount(diff) {
  return {
    type: DEC_COUNT,
    payload: {diff}
  }
}
  1. reducers -> counter.js
    어떤 action들을 case로 넣을지 갖고오기
import { INC_COUNT, DEC_COUNT } from "../constants/counter";

//초기값설정
const initialState = {number: 0}

//state: 현재상태, action: 괄호안에 넣은 그 값
export default function counter(state= initialState, action) {
  switch (action.type) {
    case INC_COUNT:
      return {number: state.number + action.payload.diff} 
    case  DEC_COUNT:
      return {number: state.number - action.payload.diff}
    default:
      return state  
  }
}
  1. store -> counter.js(index.js로 파일 이름이 지어줘도됨)
    store는 한 앱에 하나만 있는게 규칙
    이제 App.js에서 Provider형태로 쓸 수 있게됨
import { legacy_createStore as createStore } from "redux";
import counter from '../reducers/counter'

const store = createStore(counter)

export default store
  1. App.js
import { Provider } from "react-redux";
import store from "./redux/store/counter";

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

export default App;
  1. 폴더 생성
  2. components -> Counter.jsx
//데이터를 보여주는 부분(UI)

import React from 'react'

function Counter(props) {

  const { number, onDecrease, onIncrease } = props

  return (
    <div>
      <p>{number}</p>
      <button onClick={onIncrease}>+2</button>
      <button onClick={onDecrease}>-2</button>
    </div>
  )
}

export default Counter
  1. container -> CounterContainers.jsx
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import Counter from '../components/Counter'
import { incCount, decCount } from '../redux/actions/counter'

function CounterContainers() {
  const dispatch = useDispatch()
  const number = useSelector((state) => state.number)

  const onIncrease = () => {
    dispatch(incCount(2))
  }

  const onDecrease = () => {
    dispatch(decCount(2))
  }

  return (
    <Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease} />
  )
}

export default CounterContainers
  1. App.js
import CounterContainers from "./containers/CounterContainers";

    <Provider store={store}>
      <CounterContainers />
    </Provider>

ducks 패턴

분리된 파일을 합쳐서 하나의 파일에다가 모든 코드를 작성하는 것

ducks 규칙

  1. 반드시 리듀서 함수를 default export 해야 한다.
  2. 반드시 액션 생성 함수를 export 해야 한다.
  3. 반드시 접두사를 붙인 형태로 액션타입을 정의해야 한다.
  4. (선택) 액션 타입은 UPPER_SNAKE_CASE 형태로 이름을 짓고 export 할 수 있다.

ducks패턴 사용방법

  1. 파일구성
  2. modules -> counter.js
    해당 파일에 constants, actions, reducers내용 넣기

// constatns폴더. 액션타입
const INC_COUNT = "INC_COUNT"
const DEC_COUNT = "DEC_COUNT"

// actions폴더. 액션 생성 함수
export function incCount(diff) {
  return {
    type: INC_COUNT,
    payload: {diff}
  }
}

export function decCount(diff) {
  return {
    type: DEC_COUNT,
    payload: {diff}
  }
}

// reducers 폴더
//초기값
const initialState = {number: 0}
// 리듀서
export default function counter(state= initialState, action) {
  switch (action.type) {
    case INC_COUNT:
      return {number: state.number + action.payload.diff} 
    case  DEC_COUNT:
      return {number: state.number - action.payload.diff}
    default:
      return state  
  }
}

Redux Looger

redux로 실행되는 로직에 대해 콘솔창에 기록을 남겨주는 역할담당 => 리덕스 미들웨어
(store, action객체 다룰때의 과정을 남겨줌)

설치

yarn

  yarn add redux-logger --dev

npm

  npm i redux-logger --dev
  import { applyMiddleware } from "redux";  
  import logger from 'redux-logger'

  const store = createStore(rootReducer, applyMiddleware(logger))

chrome Redux DevTools 확장프로그램 사용방법

Redux DevTools 확장프로그램 추가

  import { applyMiddleware, compose, legacy_createStore as createStore } from "redux";

  const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
  const store = createStore(rootReducer, composeEnhancers(applyMiddleware(logger)));

검사창에서 실시간 로깅 확인가능

  1. modules -> index.js
//store내용 넣기
import { applyMiddleware, legacy_createStore as createStore } from "redux";
import logger from 'redux-logger'
import counter from './counter'

const store = createStore(counter)

export default store
  • 만약 여러개의 리듀서가 있을경우
import { legacy_createStore as createStore } from "redux";
import counter from './counter'
import { combineReducers } from "redux";

//combine을 해줬기때문에
//state.counter -> counter 리듀서가 관리하는 상태에 접근
const rootReducer = combineReducers({
  counter,
})

const store = createStore(rootReducer, applyMiddleware(logger))

export default store
  1. App.js
import { Provider } from "react-redux";
import CounterContainers from "./containers/CounterContainers";
import store from "./modules";

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

export default App;
  1. containers -> CounterContainers
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import Counter from '../components/Counter'
import { incCount, decCount } from '../modules/counter'

function CounterContainers() {
  const dispatch = useDispatch()
  const number = useSelector((state) => state.number)

  const onIncrease = () => {
    dispatch(incCount(2))
  }

  const onDecrease = () => {
    dispatch(decCount(2))
  }

  return (
    <Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease} />
  )
}

export default CounterContainers

1, 2, 3번까지 같은방식으로 생성 후 pages폴더 생성해서 구분하는방법
pages폴더는 components안에 구분해 놓은파일들이 들어가는 폴더

Redux toolkit 설치방법

  1. 버전확인
    "react": "^18.2.0", "react-dom": "^18.2.0" -> 두 버전이 18버전이상인지 확인

  2. npm

npm i @reduxjs/toolkit react-redux

yarn

yarn add @reduxjs/toolkit react-redux
  1. store.js파일생성
import { configureStore } from '@reduxjs/toolkit'

export default configureStore({
  reducer: { }
}) 
  1. index.js로 이동
import { Provider } from 'react-redux';
import store from '경로'

<Provider store={store}></Provider> //Provider로 감싸기

Redux store에 state 보관하는 방법


1.
import { createSlice } from '@reduxjs/toolkit'

2.
//useState와 비슷한용도. state하나를 slice라 부름
const 작명 = createSlice({
  name: 'state이름',
  initialState: '값'
})


3.
export default configureStore({
  reducer: {
    작명 : 작명.reducer
   }
}) 

Redux store에 state 꺼내는 방법

useSelector

store에 저장되있는 상태를 가져오기 위한 Hook

1. `state` 꺼낼 컴포넌트로 이동
import { useSelector } from 'react-redux';

2.
const selector = useSelector((state)=>{ return state })
// { return state }state는 모든state를 말함 
// 원하는 state만 출력가능 
// ex) {return state.작명한이름}
console.log(selector) //store에 등록한 정보가 넘어오는걸 확인가능

Redux store에 state 변경하는 방법

useDispatch

useDispatchdispatch 함수를 실행할 수 있도록 해주는 Hook

1. store.js로 이동
  `state` 변경 원하는 `state`에 아래와 같은 형식으로 작성해주기
  reducers: {
    함수명(state) {
      return 변경시켜주세요
    }
  }
2. export 해주기
  export const { 함수명 } = state함수명.actions
3. 
  import { useDispatch } from 'react-redux';
  import { 함수명 } from './../store';

  const dispatch = useDispatch()

  dispatch(state변경함수())
  <button onClick={ () =>{ dispatch( 함수명() ) }}>변경하기</button>

state가 object/array일 경우 변경하는 방법

state가 object/array일 경우 return 없이 직접 수정 가능
(그래서 값이 하나여도 object/array형태로 만들어서 사용하기도함)

  initialState: { name: 'Yu', age: 20 },
  reducers: {
    함수명(state) {
      state.name = 'Sunny'
    },

    함수명(state, action) {
      state.age += action.payload
    }
  }
  // 파라미터에 값을 넣어줘서 변경가능
  <button onClick={ () =>{ dispatch( 함수명(10) ) }}>변경하기</button>

'프론트엔드 > React' 카테고리의 다른 글

useNavigate, NavLink, Lazy Loading  (0) 2022.08.09
React router dom v6 (리액트 라우터 버전6)  (0) 2022.08.09

useNavigate 사용방법

navigatestate를 넘길 수 있음
넘긴 stateuseLocation으로 받으면 된다.
navigatereplace:true를 같이 사용해주면 이력을 없애줌

navigatelink의 차이점
navigate는 함수 안에서 쓸 수 있음

1.
 import { useNavigate } from 'react-router-dom'
2. 
  const navigate = useNavigate()
3. 
  <button onClick={()=>navigate("경로", {state: {data: '데이터넘김'} })}>경로로 가기</button>
  <button onClick={()=>navigate(-1, {replace:true})}>뒤로 가기</button>

NavLink 사용방법

1. 
  import { NavLink } from 'react-router-dom'
2. 
  <NavLink 
    style={ ({isActive}) => ({color: isActive ? "lime" : "salmon"}) }>
  </NavLink>

NavLink는 기본적으로 isActive라는 props를 가지고 있음

Lazy Loading

ReactSPA이기 때문에 처음 앱을 불로오는 시간이 오래걸림
이러한 단점을 보안하기위해 파일 자체를 분할하는게 중요
가장 기본적인방법은 dynamic import

Reactlazysuspense로 이를 구현함

Lazy Loading사용방법

1.
  import { lazy, Suspense } from 'react';
2.
  const lazy원하는곳 = lazy( () => import("lazy할경로"))
3.
  //아직 컴포넌트가 불러와지기전에 유저(사용자)에게 fallback에 있는 정보를 보여줌
  <Route path="/" element={<Suspense fallback={<div>Loading...</div>}><lazy원하는곳 /></Suspense>} />

'프론트엔드 > React' 카테고리의 다른 글

Redux  (0) 2022.08.16
React router dom v6 (리액트 라우터 버전6)  (0) 2022.08.09

React router dom v6 사용방법

  1. yarn start react-router-dom@6 설치

  2. index.js로 이동

    import { BrowserRouter } from 'react-router-dom'
    <BrowserRouter>
     <App /> 을 감싼다
    </BrowserRouter>
  3. 폴더를 만든다
    ex) components, routes, pages ......
    components ->
    Posts ->index.jsx
    Users ->index.jsx

  4. App.js로 이동

    import { Route, Routes, Link } from 'react-router-dom';
    <div className="App">
    <Link to="경로">경로이동</Link> //경로 클릭시 해당 경로로 이동
    <Routes>
     <Route path="경로" element={<연결된컴포넌트 />} />
     <Route path="*" element={<p>Not Found</p>} />
    </Routes>
    </div> 

    Routes의 역할: 주소변경 감지해 일치하는 Route를 보여줌
    *의 역할: 작성해둔 path와 일치하지 않는 모든 경우에 보여줄 element

nested router 사용방법

  1. Route안에 Route작성

  2. path에 변수이름 작성 ex)Id, pathId

    <div className="App">
    <Routes>
     <Route path="경로" element={<연결된컴포넌트 />}>
       <Route index element={<연결된컴포넌트 />} />
       {/* `path="경로"`로 들어간 후 기본적으로 보여줄 요소 */}
       <Route path:"변수이름" element={<연결된컴포넌트 />} />
     </Route>
    </Routes>
    </div> 
  3. path="경로" 해당파일로 이동
    Outlet은 하위파일이 보일 위치를 설정해줌

    import { Outlet } from 'react-router-dom'
function 경로() {
  return (
    <div>
      경로
      <Outlet />
    </div> 
  )
}
  1. Outlet안에 들어갈 컴포넌트로 이동
    import { useParams } from 'react-router-dom'
function 이름() {

  const params = useParams()

  return (
    <div>
      {/* `path="변수이름"`을 Id로 가정했을때 */}
      {params.Id}
    </div> 
  )
}

주소창에 입력한 주소가 {params.Id}부분에 표시됨

데이터를 받았을경우 예제

postData.js 내부구조
{
"userId": 1,
"id": 1,
"title": ''
"body": ''
}

import React from 'react'
import { Link } from 'react-router-dom'
import { postData } from '../../../constants/postData'
// export로 넘겼을 경우 {}해주기
// export default 넘겼을 경우 중괄호를 안해줘도됨

function PostIndex() {
  return (
    <div>{postData.map((post)=>{
      return(
      <Link to={`/posts/${post.id}`}><p>{post.title}</p></Link>
      )
    })}</div>
  )
}

export default PostIndex
import React from 'react'
import { useParams } from 'react-router-dom'
import { postData } from '../../../constants/postData'

function PostDetail() {

  const params = useParams()
  const post = postData.find((post)=>{return post.id === parseInt(params.postId)}) 
  //params.postId = String, post.id = Number이기때문에 결과가 표시안됨
  //해결방법: params.postId에 parseInt()해주기

  return (
    <>
    <p>{post.title}</p>
    <p>{post.body}</p>
    </>

  )
}

export default PostDetail

'프론트엔드 > React' 카테고리의 다른 글

Redux  (0) 2022.08.16
useNavigate, NavLink, Lazy Loading  (0) 2022.08.09

+ Recent posts