Redux

김민경·2023년 2월 16일
0

🎈 What is Redux?

: A state management system
for cross-component or app-wide state

🎈 Why use Redux?

: don't we have useContext already?

potential disadvantages for using useContext

🎈 Redux Basics & Using Redux with React

Core Redux Concepts

there should be only one data store

npm install redux

Reducer Function

there should be no side effect code in the reducer function
(should not send a HTTP request or write/fetch something to/from a local storage)

redux-demo.js

const redux = require('redux');

// reducer function
// state can be any type(number, string ..., mostly object)
// should give state parameter a default value for the initial run
const counterReducer = (state = { counter : 0 }, action) => {
	if (action.type === 'increment') {
      return {
          counter : state.counter + 1,
      }
    }
    
    if (action.type === 'decrement') {
      return {
          counter : state.counter - 1,
      }
    }
	return state;
};

const store = redux.createStore(counterReducer);

const counterSubscriber = () => {
	const latestState = store.getState();
    console.log(latestState);
};

store.subscribe(counterSubscriber);

store.dispatch({ type : 'increment' });
store.dispatch({ type : 'decrement' });

node redux-demo.js로 실행

Using Redux with React

npm install redux react-redux

since redux can execute in any javascript environment
(which means it doesn't care or know about react)
also install the react-redux package
(a package that connects redux to react applications)

src/store/index.js

imprt { createStore } from 'redux';

const initialState = { counter : 0, showCounter : true}

const ⭐ counterReducer = (state = initialState, action) => {
	// it won't merge the existing state, it will override
	if (action.type === 'increment') {
      return {
          counter : state.counter + 1,    
          showCounter : state.showCounter
      }
      
      // state.counter ++;
      // return state;
      // should not return a modified original state
      // ❗ (should not mutate the original state)
      // should always create a new state and override it
    }
    
    if (action.type === 'increase') {
      return {
          counter : state.counter + action.amount,
          showCounter : state.showCounter
      }
    }
    
    if (action.type === 'decrement') {
      return {
          counter : state.counter - 1,
          showCounter : state.showCounter
      }
    }
    
    if (action.type === 'toggle') {
    	return {
        	showCounter : !state.showCounter,
            counter : state.counter
        }
    }
    
	return state;
}
// the dispatch type name can be overlapped
// if there are many states to manage, it can be complex
// => ✨ use redux toolkit!

const ⭐ store = createStore(⭐ counterReducer);

export default ⭐ store;

src/index.js

import { Provider } from 'react-redux';
import store from './store/index';

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

src/components/Counter.js

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

const Counter = () => {
	⭐ const dispatch = useDispatch();
    
    // useSelector
    // whenever the data in the store changes
    // it automatically updates its current state
    // (the whole component(Counter component) will be re-executed
	⭐ const counter = useSelector(state => state.counter);
    ⭐ const show = useSelector(state => state.showCounter);
    
    const incrementHanlder = () => {
    	dispatch({ type : 'increment' })
    };
    
    const increaseHandler = () => {
    	dispatch({ type : 'increase', amount : 5 })
    }
    
    const decrementHanlder = () => {
    	dispatch({ type : 'decrement' })
    };
    
    const toggleCounterHandler = () => {
    	dispatch({ type : 'toggle' })
    };
    
    
    return (
    	<main className = {classes.counter}>
        	<h1>Redux Counter</h1>
            {show && <div className = {classes.value}>{counter}</div>}
            <div>
            	<button onClick = {incrementHandler}>Increment</button>
                <button onClick = {increaseHandler}>Increase by 5</button>
                <button onClick = {decrementHandler}>Decrement</button>
            </div>
            <button onClick = {toggleCounterHandler}>Toggle Counter</button>
        </main>
    )

}

export default Counter;

using redux with class-based components

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

class Counter extends Component {

	incrementHandler() {
    	this.props.increment();
    }
    
    decrementHandler() {
    	this.props.decrement();
    }
    
    toggleCounterHandler() {}
    
	render() {
    	return(
        	<main className = {classes.counter}>
        		<h1>Redux Counter</h1>
            	<div className = {classes.value}>{this.props.counter}</div>
            	<div>
            		<button onClick = {this.incrementHandler.bind(this)}>
                    	Increment
                    </button>
                	<button onClick = {this.decrementHandler.bind(this)}>
                    	Decrement
                	</button>
            	</div>
            	<button onClick = {this.toggleCounterHandler}>Toggle Counter</button>
        	</main>
        )
    }
}

⭐ // equivalent to useSelector
const mapStateToProps = state => {
	return {
    	counter : state.counter
    }
}

⭐ // equivalent to useDispatch
const mapDispatchToProps = dispatch => {
	return {
    	increment : () => dispatch({ type : 'increment' })
        decrement : () => dispatch({ type : 'decrement' })
    }
}

export default ⭐ connect(mapStateToProps, mapDispatchToProps)(Counter);
// ❗ higher order component
// execute connect function, returns a new function
// and execute this returned, the new function as well

🎈 Redux Toolkit

npm install @reduxjs/toolkit

src/store/index.js

import { createSlice, configureStore } from '@reduxjs/toolkit';

const initialCounterState = { value : 0, showCounter : true}

const counterSlice = ⭐ createSlice ({
	name : 'counter',
    intialState : initialCounterState,
    reducers : {
    	increment(state) {
        	state.counter++;
        },
        decrement(state) {
        	state.counter--;
        },
        increase(state, action) {
        	state.counter = state.counter + action.⭐ payload;
        },
        toggleCounter (state) {
        	state.showCounter = !state.showCounter;
        }
    }
});
// ❗ in reducers, we are now allowed to modify the original state
// ❗ the toolkit package will automaticallt create a new state with a modified state
// thus translating into immutable code

const initialAuthState = {
	isAuthenticated : false
}

const authSlice = ⭐ createSlice({
	name : 'authentication',
    initialState : initialAuthState,
    reducers : {
    	login(state) {
        	state.isAuthenticated = true;
        },
        logout(state) {
        	state.isAuthenticated = false;
        }
    }
});

const store = ⭐ configureStore({
	reducer : { counter : counterSlice.reducer, auth : authSlice.reducer } ,
});

export const 🧨 counterActions = counterSlice.actions;
export const 🧨 authActions = authSlice.actions;

export default store;

src/components/Counter.js

import { 🧨 counterActions } from '../store/index';
import { useSelector, useDispatch } from 'react-redux';

const Counter = () => {
	const dispatch = useDispatch();
	const counter = useSelector(state => state.counter.value);
    const show = useSelector(state => state.counter.showCounter);
    
    const incrementHanlder = () => {
    	dispatch(🧨 counterActions.increment());
        // ⭐ when the counterActions.increment❗() executes,
        // it returns a action type object
        // ex. { type : SOME_UNIQUE_IDENTIFIER } 
    };
    
    const increaseHandler = () => {
    	dispatch(🧨 counterActions.increase(10));
        // ex. { type : SOME_UNIQUE_IDENTIFIER, payload : 10 }
        // ⭐ payload property is automatically generated
    }
    
    const decrementHanlder = () => {
    	dispatch({ 🧨 counterActions.decrement() })
    };
    
    const toggleCounterHandler = () => {
    	dispatch({ 🧨 counterActions.toggleCounter() })
    };
    
    
    return (
    	<main className = {classes.counter}>
        	<h1>Redux Counter</h1>
            {show && <div className = {classes.value}>{counter}</div>}
            <div>
            	<button onClick = {incrementHandler}>Increment</button>
                <button onClick = {increaseHandler}>Increase by 10</button>
                <button onClick = {decrementHandler}>Decrement</button>
            </div>
            <button onClick = {toggleCounterHandler}>Toggle Counter</button>
        </main>
    )

}

export default Counter;

can split the code according to different roles
(counter.js / auth.js)

counter.js

import { createSlice } from '@reduxjs/toolkit';

const initialCounterState = { value : 0, showCounter : true}

const counterSlice = createSlice ({
	name : 'counter',
    intialState : initialCounterState,
    reducers : {
    	increment(state) {
        	state.counter++;
        },
        decrement(state) {
        	state.counter--;
        },
        increase(state, action) {
        	state.counter = state.counter + action.payload;
        },
        toggleCounter (state) {
        	state.showCounter = !state.showCounter;
        }
    }
});

export const 🧨 counterActions = counterSlice.actions;

export default ⭐ counterSlice.reducer;

auth.js

import { createSlice } from '@reduxjs/toolkit';

const initialAuthState = {
	isAuthenticated : false
}

const authSlice = createSlice({
	name : 'authentication',
    initialState : initialAuthState,
    reducers : {
    	login(state) {
        	state.isAuthenticated = true;
        },
        logout(state) {
        	state.isAuthenticated = false;
        }
    }
});

export const 🧨 authActions = authSlice.actions;

export default ⭐ authSlice.reducer;

index.js

import { configureStore } from '@reduxjs/toolkit';

import counterReducer from './counter';
import authReducer from './auth';

const store = ⭐ configureStore({
	reducer : { counter : ⭐ counterReducer, auth : ⭐ authReducer } ,
});

export default store;
profile
🏛️❄💻😻🏠

0개의 댓글

관련 채용 정보