Redux
"Redux is a predicatale state container for JavaScript apps"
React+Redux
Redux state container 가 component 외부에 존재하고 state의 변화를 감지한다
전체 application의 state는 하나의 객체 tree 형태로 하나의 store에 저장된다.
state를 변경하려면, action 객체에 type을 통해 변경한다. 직접 state에 접근해 변경하지 않는다.
action 에 의해 state를 변경시키려면, pure reducer를(pure functions) 사용해야 한다.
Reducer - (previousState,action) => newState
출처 : https://www.youtube.com/watch?v=2lxCaLJ2Rbk&list=PLC3y8-rFHvwheJHvseC3I0HuYI2f46oAK&index=5
const BUY_CAKE = "BUY_CAKE"
//먼저 action 의 type을 나타내 줄 string constants 를 지정한다.
//오타 실수를 방지하기 위해 사전적으로 지정해주는 것이며 꼭 지정할 필요는 없다.
{
type: BUY_CAKE,
info : "First redux action"
}
//action을 define 한다.action은 객체여야 하고 type을 받는다.
//꼭 타입만 있어야 하는건 아니고, 다른 string을 받는 property를 추가할 수 있다. 예: info
function buyCake () {
return {
type:BUY_CAKE;
info:"First redux action"
}
}
//action creator 함수를 작성한다(action을 return 하는 함수)
//(previousState, action) => newState
const initialState = {
numOfCakes : 10 // 오픈 당시의 케잌의 수를 initial state값으로 지정해 준다.
}
const reducer = (state = initialState,action) => {
switch(action.type) {
case BUY_CAKE : return {
...state,
// 지금은 하나 밖에 없지만 보통 여러 값을 가지므로,
//그중에서 numOfCakes 만 변경해 주어야 하므로 spread operator를 사용해준다.
//먼저 state object를 copy하고 그중에 numOfCakes 만 변경해줘 라는 의미이다.
numOfCakes : state.numOfCakes -1
//BUY_CAKE action이 일어나면 기존 cake 수에서 -1 해준다.
}
default: return state
}
}
import { createStore } from 'redux'
//상단에 서 import 해준다
const store = createStore(reducer);
//reducer함수를 argument로 받는 store를 지정해 준다.
getState()
함수를 통해 state에 접근할 수 있다.const store = createStore(reducer);
//store를 변수에 담고 다른 action을 취하지 않고 바로
//console.log로 getState를 하면 초기 값을 확인 할 수 있다.
console.log("Initial state", store.getState())
const unsubscribe = store.subscribe(() => console.log("Updated state", store.getState()))
dispatch(action)
함수를 통해 state 가 update될 수 있게 하고 unsubscribe()를 호출해서 update된 state를 확인한다. store.dispatch(buyCake())
store.dispatch(buyCake())
store.dispatch(buyCake())
store.dispatch(buyCake())
store.dispatch(buyCake())
store.dispatch(buyCake())
unsubscribe()
const BUY_CAKE = 'BUY_CAKE';
const BUY_ICECREAM = 'BUY_ICECREAM';
//define action
function buyCake() {
return {
type: BUY_CAKE,
};
}
function buyIceCream() {
return {
type: BUY_ICECREAM,
};
}
const initialCakeState = {
numOfCakes: 10,
};
const initialIceCreamState = {
numOfIceCream: 20,
};
const cakeReducer = (state = initialCakeState, action) => {
switch (action.type) {
case BUY_CAKE:
return {
...state,
numOfCakes: state.numOfCakes - 1,
};
default:
return state;
}
};
const iceCreamReducer = (state = initialIceCreamState, action) => {
switch (action.type) {
case BUY_ICECREAM:
return {
...state,
numOfIceCream: state.numOfIceCream - 1,
};
default:
return state;
}
};
-comebineReducers
함수를 호출해서 합치는 두 reducer를 relevant 한 key 값과 함께 묶어준다.
const rootReducer = redux.combineReducers({ cake: cakeReducer, iceCream: iceCreamReducer });
const store = createStore(rootReducer);
예시로서 redux-logger을 설치한다 npm install redux-logger
Middleware를 적용하는데 필요한 변수를 import 해준다.
const logger = reduxLogger.createLogger();
const applyMiddleware = redux.applyMiddleware;
store의 createStore 함수에 두 개의 인자를 주고, applyMiddleware에는 logger를 argument로 받는 함수를 두번째 인자로 넣어준다.
const store = createStore(rootReducer, applyMiddleware(logger));
그러고 terminal에 node로 실행해보면 아래와 같이 과정 logging이 되고 있는 것을 알 수 있다.
비동기적으로 작동하는 action(ex. API fetch) 관리하는 방법
const initialState = {
loading: false,
users: [],
error: '',
};
const FETCH_USERS_REQUEST = 'FETCH_USERS_REQUEST';
const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS';
const FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE';
const fetchRequest = () => {
return {
type: FETCH_USERS_REQUEST,
};
};
const fetchSuccess = (users) => {
return {
type: FETCH_USERS_SUCCESS,
payload: users,
};
};
const fetchFailure = (error) => {
return {
type: FETCH_USERS_FAILURE,
payload: error,
};
};
axios,
redux-thunk - async action creators
const fetchReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_USERS_REQUEST:
return {
...state,
loading: true,
};
case FETCH_USERS_SUCCESS:
return {
...state,
loading: false,
users: action.payload,
error: '',
};
case FETCH_USERS_FAILURE:
return {
...state,
loading: false,
users: [],
error: action.payload,
};
}
};
const fetchUsers = () => {
return function (dispatch) {
dispatch(fetchRequest());
axios
.get('https://jsonplaceholder.typicode.com/users')
.then((response) => {
const users = response.data.map((user) => user.id);
dispatch(fetchSuccess(users));
})
.catch((error) => {
dispatch(fetchFailure(error.message));
});
};
};
const store = createStore(fetchReducer, applyMiddleware(thunkMiddleware));
store.subscribe(() => {
console.log(store.getState());
});
store.dispatch(fetchUsers());
CRA 에 redux사용에 필요한 library를 설치하고 시작한다!
npx create-react-app redux-react-demo
npm install redux react-redux
별도의 js 파일로 분리해서 import 해서 사용할수 있도록
export const BUY_CAKE = "BUY_CAKE"
import { BUY_CAKE } from './cakeType';
export const buyCake = () => {
return {
type: BUY_CAKE,
}
}
cakeReducer.js 파일을 만들고 initialstate를 담을 변수와 reducer 함수를 작성한다.
import { BUY_CAKE } from "./cakeType"
const initialState = {
numOfCakes : 10
}
const cakeReducer = (state = initialState, action ) => {
switch(action.type) {
case BUY_CAKE : return {
...state,
numOfCakes = state.numOfCakes-1
}
default: return state
}
}
export default cakeReducer
ES6 방식으로 createStore를 import 해주고 createStore method를 만들어 reducer함수를 파라미터로 받게 작성한다.
import { createStore } from 'redux';
import cakeReduer from './cake/cakeReducer';
const store = createStore(cakeReduer)
export default store
<Provider ><Provider />
로 감싸 준다.import {Provider} from "react-redux"
import './App.css';
import CakeContainer from './components/CakeContainer';
function App() {
return (
<Provider>
<div className='App'>
<CakeContainer />
</div>
</Provider>
import store from "./redux/store"
<Provider store ={store}>
store와 react를 connect 해준다.
import React from 'react';
import { connect } from 'react-redux';
//react function과 redux를 connect해준다
import { buyCake } from '../redux';
function CakeContainer(props) {
return (
<div>
<h2>Number of Cakes - {props.numOfCakes}</h2>
<button onClick={props.buyCake}>Buy Cake</button>
//connect 했기 때문에 props로 받아오는 것이 가능!
</div>
);
}
const mapStateToProps = (state) => {
return {
numOfCakes : state.numOfCakes
};
};
//redux state를 parameter로 받는다
const mapDispatchToProps = (dispatch) => {
return {
buyCake: () => dispatch(buyCake())
};
};
//redux dispatch를 parameter로 받는다
export default connect(mapStateToProps, mapDispatchToProps)(CakeContainer);
//connect(state관련 props,dispatch관련 props)(해당 컴포넌트)로 표현하면 된다.
const mapStateToProps
와 비슷한 역할 을 한다.
나머지 셋팅은 위와 동일하고 components 파일 경로에 HooksCakeContainer를 만들어 준다.
상단에 useSelector를 사용하기 위해 import 해준다.
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { buyCake } from '../redux';
import { addCake } from '../redux';
function HooksCakeContainer() {
const numOfCakes = useSelector((state) => state.numOfCakes);
const dispatch = useDispatch();
return (
<div>
<h1>num of cake -{numOfCakes}</h1>
<button onClick={() => dispatch(buyCake())}>buy cake</button>
<button onClick={() => dispatch(addCake())}>add cake</button>
</div>
);
}
export default HooksCakeContainer;