React-Redux

Giseleยท2021๋…„ 1์›” 10์ผ
0
post-thumbnail

Redux

  • ์•ฑ์˜ ์ƒํƒœ(state)๋ฅผ ๊ด€๋ฆฌํ•ด์ฃผ๋Š” JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • facebook์˜ flux ๊ตฌ์กฐ์™€ ๋น„์Šทํ•˜๋ฉฐ ๋ฆฌ์•กํŠธ๋‚˜ ์•ต๊ทค๋Ÿฌ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•จ
  • React๋Š” React ์ปดํฌ๋„ŒํŠธ๋ณ„๋กœ ์ƒํƒœ๊ด€๋ฆฌ๋ฅผํ•œ๋‹ค
  • ๋ฆฌ์•กํŠธ๋Š” ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๊ฐ€์ง
  • ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ณต์žกํ•ด์ง€๋ฉด ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฑฐ์ณ์„œ props๋กœ ์ „๋‹ฌํ•˜๊ฒŒ ๋จ
    - ๋น„ํšจ์œจ์ , ์ž์‹ ์ปดํฌ๋„ŒํŠธ ๋•Œ๋ฌธ์— ์ž๊ธฐ ์ž์‹ ํ•œํ…Œ๋Š” ํ•„์š”ํ•˜์ง€ ์•Š์€ props๊ฐ€ ๋งŽ์•„์ง
    • ํ˜•์ œ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ถˆํ•„์š”ํ•œ ๋ Œ๋”๋ง์ด ์ผ์–ด๋‚จ
    • ์ตœ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์— ์ƒํƒœ ๊ด€๋ฆฌ ๋กœ์ง์ด ๋„ˆ๋ฌด ๋งŽ์•„ ์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด์งˆ ์ˆ˜ ์žˆ์Œ
  • React+Redux๋Š” ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ํ•˜๋Š” ์ „์šฉ์žฅ์†Œ(store)์—์„œ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ , react ์ปดํฌ๋„ŒํŠธ๋Š” ๊ทธ๊ฑธ ๋ณด์—ฌ์ฃผ๊ธฐ๋งŒ ํ•˜๋Š” ์šฉ๋„๋กœ ์“ฐ์ธ๋‹ค
  • ๋ฆฌ๋•์Šค ์–ด๋–ป๊ฒŒ ์จ์•ผ ์ž˜ ์ผ๋‹ค๊ณ  ์†Œ๋ฌธ์ด ๋‚ ๊นŒ?

๊ตฌ์„ฑ์š”์†Œ


์Šคํ† ์–ด ๊ฐ’์ด ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ๋Š” ์Šคํ† ์–ด๋ฅผ ๊ตฌ๋…ํ•œ๋‹ค

๊ตฌ์„ฑ์š”์†Œ์—ญํ• 
store์ƒํƒœ๊ฐ’ ์ €์žฅ์†Œ. ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒํƒœ ๊ฐ’์„ ๊ฐ–๊ณ  ์žˆ๊ณ , ๊ด€๋ฆฌํ•œ๋‹ค
action์ƒํƒœ์— ๋ณ€ํ™” ๋ฅผ ์ผ์œผ์ผœ์•ผํ•  ๋•Œ ์•ก์…˜์„ ์Šคํ† ์–ด์— ์ „๋‹ฌ
๊ฐ์ฒด ํ˜•ํƒœ ๋กœ ๋˜์–ด์žˆ์œผ๋ฉฐ,
์ƒํƒœ๋ฅผ ๋ณ€ํ™”์‹œํ‚ฌ ๋•Œ ์ด ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ•ด ๋ณ€ํ™”๋ฅผ ์ผ์œผํ‚ด
Action Creator์•ก์…˜ ์ƒ์„ฑ ํ•จ์ˆ˜. ์•ก์…˜ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜
dispatchaction์„ store์— ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ
reducer์•ก์…˜ ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌ๋ฐ›์•„ ์š”์ฒญ์„ ์‹๋ณ„(type)ํ•ด ์ƒํƒœ๋ฅผ ๋ฐ”๊พธ๋Š” ํ•จ์ˆ˜
(์ด์ „์ƒํƒœ, ์•ก์…˜)=>๋‹ค์Œ์ƒํƒœ
state๋ฆฌ๋•์Šค์—์„œ ์ €์žฅํ•˜๊ณ  ์žˆ๋Š” ์ƒํƒœ๊ฐ’(๋ฐ์ดํ„ฐ), ๋”•์…”๋„ˆ๋ฆฌ ํ˜•ํƒœ
  • ๋ฆฌ๋•์Šค๋Š” ๋‹จ์ผ ์Šคํ† ์–ด ๊ทœ์น™์„ ๋”ฐ๋ฅธ๋‹ค. ํ•œ ํ”„๋กœ์ ํŠธ์— ์Šคํ† ์–ด๋Š” ํ•˜๋‚˜๋งŒ
  • store์˜ state๋Š” ์˜ค์ง action์œผ๋กœ๋งŒ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ๋งˆ๊ตฌ์žก์ด๋กœ ๋ณ€ํ•˜์ง€ ์•Š๋„๋ก ๋ถˆ๋ณ€์„ฑ์„ ์œ ์ง€ํ•ด์ฃผ๊ธฐ ์œ„ํ•จ. action์„ ํ†ตํ•ด ๋ณ€๊ฒฝ์‹œ์ผœ์•ผ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋“ค์ด state๊ฐ€ ๋ณ€๊ฒฝ๋œ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Œ
  • ์–ด๋–ค ์š”์ฒญ์ด ์™€๋„ ๋ฆฌ๋“€์„œ๋Š” ๊ฐ™์€ ๋™์ž‘์„ ํ•ด์•ผํ•œ๋‹ค.
  • ๋ฆฌ๋“€์„œ๋Š” ์ˆœ์ˆ˜ํ•œ ํ•จ์ˆ˜์—ฌ์•ผ ํ•œ๋‹ค.
    - ํŒŒ๋ผ๋ฏธํ„ฐ ์™ธ์˜ ๊ฐ’์— ์˜์กดํ•˜์ง€ ์•Š๋Š”๋‹ค
    • ์ด์ „ ์ƒํƒœ๋Š” ์ˆ˜์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋ณ€ํ™”๋ฅผ ์ค€ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋ฅผ return
    • ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๊ฐ™์œผ๋ฉด, ํ•ญ์ƒ ๊ฐ™์€ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค
    • ๋ฆฌ๋“€์„œ๋Š” ์ด์ „ ์ƒํƒœ์™€ ์•ก์…˜์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š”๋‹ค

์‚ฌ์šฉ๋ฒ•

$ yarn add redux react-redux

ducks ๊ตฌ์กฐ

  • redux module(action, action creator, reduer)๋ฅผ ํ•œ ํŒŒ์ผ ์•ˆ์— ์ž‘์„ฑ
  • redux modele ์ƒ์„ฑ
// redux/modules/bucket.js
// Actions
const LOAD = 'bucket/LOAD';
const CREATE = 'bucket/CREATE';

const initailState = {
  list: ['์˜ํ™”๊ด€ ๊ฐ€๊ธฐ', '๋งค์ผ ์ฑ…์ฝ๊ธฐ', '์ˆ˜์˜ ๋ฐฐ์šฐ๊ธฐ']
};
// Action Creators
export const loadBucket = (bucket) => {
  return { type: LOAD, bucket };
};

export const loadBucket = (bucket) => {
  return { type: CREATE, bucket };
};
// Reducer
export default function reducer(state = {}, action = {}) {
  switch (action.type) {
    case 'bucket/LOAD': {
      return state;
    }

    case 'bucket/CREATE': {
      const new_bucket_list = [...state.list, action.bucket];
      return { list: new_bucket_list };
    }
    default:
      return state;
  }
}
  • store์ƒ์„ฑ
//redux/configStore.js
import { createStore, combineRedcers, combineReducers } from 'redux';
import bucket from './modules/bucket';
import { createBroswerHistory } from 'history';

export const history = createBroswerHistory();

const rootReducer = combineReducers({ bucket });

const store = createStore(rootReducer);

export default store;
  • store ์—ฐ๊ฒฐ
// index.js
...
import { Provider } from 'react-redux';
import store from './redux/configStore';
ReactDOM.render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>,
  document.getElementById('root')
);
  • ํด๋ž˜์Šคํ˜• ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉ
//App.js
import React from 'react';
import logo from './logo.svg';
// BucketList ์ปดํฌ๋„ŒํŠธ๋ฅผ import ํ•ด์˜ต๋‹ˆ๋‹ค.
// import [์ปดํฌ๋„ŒํŠธ ๋ช…] from [์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ๋Š” ํŒŒ์ผ๊ฒฝ๋กœ];
...
import { connect } from 'react-redux';
import { createBucket, loadBucket } from './redux/modules/bucket';

const mapStateToProps = (state) => {
  return { bucket_list: state.bucket.list };
};

const mapDispatchToProps = (dispatch) => {
  return {
    load: () => {
      dispatch(loadBucket());
    },
    create: (bucket) => {
      dispatch(createBucket(bucket));
    }
  };
};
// ํด๋ž˜์Šคํ˜• ์ปดํฌ๋„ŒํŠธ๋Š” ์ด๋ ‡๊ฒŒ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค!
class App extends React.Component {
  constructor(props) {
    super(props);
    // App ์ปดํฌ๋„ŒํŠธ์˜ state๋ฅผ ์ •์˜ํ•ด์ค๋‹ˆ๋‹ค.
    this.state = {
      list: ['์˜ํ™”๊ด€ ๊ฐ€๊ธฐ', '๋งค์ผ ์ฑ…์ฝ๊ธฐ', '์ˆ˜์˜ ๋ฐฐ์šฐ๊ธฐ']
    };
  }

  // ๋žœ๋” ํ•จ์ˆ˜ ์•ˆ์— ๋ฆฌ์•กํŠธ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ๋„ฃ์–ด์ค๋‹ˆ๋‹ค!
  render() {
    return (
      <div className="App">
        <Container>         
      	...
        </Container>
      </div>
    );
  }
}
...
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(App));
  • ์•ก์…˜์ƒ์„ฑํ•จ์ˆ˜ ์‚ฌ์šฉํ•˜๊ธฐ
// ์ปดํฌ๋„ŒํŠธ onclick์ด๋ฒคํŠธ
addBucketList = () => {
    const new_item = this.text.current.value;
    this.props.create(new_item);
  };
  • ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉ
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { deleteBucket } from './redux/modules/bucket';

const Detail = (props) => {
  const bucket_list = useSelector((state) => state.bucket.list);
  const dispatch = useDispatch();
  const bucketIdx = parseInt(props.match.params.index);
  const onDeleteBucket = () => {
    dispatch(deleteBucket(bucketIdx));
    props.history.push('/');
  };
  return (
    <div>
      <h1>{bucket_list[bucketIdx]}</h1>
      <button onClick={onDeleteBucket}>์‚ญ์ œํ•˜๊ธฐ</button>
    </div>
  );
};

export default Detail;

redux-toolkit

$ yarn add @reduxjs/toolkit
// configStore.js
import { createStore, applyMiddleware, combineReducers } from 'redux';
import schedule from './modules/schedule';
//import { createBrowserHistory } from 'history';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';

//export const history = createBrowserHistory();

const middlewares = [thunk];
const enhancer = applyMiddleware(...middlewares);

const rootReducer = combineReducers({ schedule });

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

export default store;
// moduels/schedule.js
import { createReducer, createAction } from '@reduxjs/toolkit';

export const initialState = {
  fullSchedule: []
};

export const fetchFullSchedule = createAction('FETCH_FULL_SCHEDULE');

const schedule = createReducer(initialState, {
  [fetchFullSchedule]: (state, { payload }) => {
    state.fullSchedule.push(payload);
  }
});

export default schedule;

next-redux-wrapper

// store/configureStore.js
import {createWrapper} from 'next-redux-wrapper'
import { applyMiddleware, compose, createStore } from 'redux';
import reducer from '../reducers'
import {composeWithDevTools} from 'redux-devtools-extension'

const configureStore = ()=>{
	// middle ware์ ์šฉ์‹œ
    const middlewares = [];

    const enhancer = process.env.NODE_ENV === 'production'
    ? compose(applyMiddleware(...middlewares))
    : composeWithDevTools(
      applyMiddleware(...middlewares),
    );
	
  //store ์ƒ์„ฑ
    const store = createStore(reducer, enhancer)
    return store
}

const wrapper = createWrapper(configureStore,{
    debug:process.env.NODE_ENV === 'development',
})

export default wrapper ;

//app.js
import Proptypes from 'prop-types';
import Head from 'next/head'
import wrapper from '../store/configureStore';

const App = ({ Component })=>{
    return(
    <>
        <Head>
            <title>๋ฆฌ์•กํŠธ์•ฑ</title>
        </Head>
        <Component />
    </>
    )
};

App.Proptypes = {
    Component:Proptypes.elementType.isRequired
}

export default wrapper.withRedux(App);

API

Hooks

useDispatch()

  • React.useContext ์œผ๋กœ Provider ์—์„œ ์ •์˜ํ•œ contextValue ๋ฅผ ๊ฐ€์ ธ์™€ store.dispatch ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
export const CounterComponent = ({ value }) => {
  const dispatch = useDispatch() //

  return (
    <div>
      <span>{value}</span>
      <button onClick={() => dispatch({ type: 'increment-counter' })}>
        Increment counter
      </button>
    </div>
  )
}

useSelector()

  • store์— ์žˆ๋Š” state ์‚ฌ์šฉ
  • mapStateToProps, connect์™€ ์œ ์‚ฌํ•œ ๊ธฐ๋Šฅ
  • action์ด dispatch ๋ ๋•Œ๋งˆ๋‹ค, selector์— ์ ‘๊ทผ๋˜์–ด ๊ฐ’์„ ๋ฐ˜ํ™˜
// loginForm.js - ๋กœ๊ทธ์ธ 
import { useDispatch,useSelector } from 'react-redux';

...
const onSubmitForm = useCallback(()=>{        
        dispatch(loginAction({id,password}))
    },[id,password])
...

const {isLoggedIn} = useSelector((state)=> state.user)

๐Ÿ“‘reference

profile
ํ•œ์•ฝ์€ ๊ฑฐ๋“ค๋ฟ

0๊ฐœ์˜ ๋Œ“๊ธ€