20210702 Redux08 : redux-toolkit 사용법, createAction, createReducer, createSlice(reducers, extraReducers 작성법들), configureStore, createAsyncThunk
Redux 08
redux-tookit 사용법
createAction, createReducer 방식
- store middleware등의 환경을 건들지 안고 바로 적용할 수 있는 방식
 - 하지만, 크게 코드가 줄어 들진 않음
 
createAction()
- redux-actions에서 제공하고 있던 createAction()과 같은 방식으로 작동한다.
 - 기존의 Action creator를 세부적으로 만들 필요 없이 actionCreator를 통해서 type, payload 형태의 Actoin을 만들어줌
 
import { createAction } from "@reduxjs/toolkit";
const getUsersStart = createAction("GET_USERS_START");
const getUsersSuccess = createAction("GET_USERS_SUCCESS");
const getUsersFail = createAction("GET_USERS_FAIL");
console.log(getUsersStart, getUsersSuccess, getUsersFail);
// 단지 변수 명으로 사용시 -> function return
console.log(getUsersStart.type, getUsersSuccess.type, getUsersFail.type);
/* Type을 붙여서 사용시 -> Type return
GET_USERS_START GET_USERS_SUCCESS GET_USERS_FAIL */
console.log(getUsersStart(), getUsersSuccess(), getUsersFail());
/* 함수 호출로 사용시 -> action return (액션의 경우 어쨌거나, payload와 함깨 보내짐)
{ type: 'GET_USERS_START', payload: undefined } 
{ type: 'GET_USERS_SUCCESS', payload: undefined } 
{ type: 'GET_USERS_FAIL', payload: undefined } */
createReducer()
- 원래의 Reducer 사용방식은 기존의 state를 복사해서 새로운 state를 만들어서 기존 것을 넣고 변경된 내용을 덮어 쓰는 형태 였음
 
- createReducer를 사용하면,
    
- 복사해서 새로운 state를 만들어 return 해줄 필요 없이(구조 분해 할당으로 기존 state 할당해줄 필요 없이), mutate 하게 
push()로 state의 변경되는 요소만 변경 시킬 수 있음 (push는 어떤 것도 return 하지 않음) - 물론, 새로운 state Object를 return 시켜도 상관 없음
 - switch 구문 필요 없음
 
 - 복사해서 새로운 state를 만들어 return 해줄 필요 없이(구조 분해 할당으로 기존 state 할당해줄 필요 없이), mutate 하게 
 
createReducer(defaultState, reducerMap)- defaultState : 앱 처음 실행시, store의 state에 기본적으로 세팅된 state 구조 및 값
 - reducerMap: 
{}객체 형태로 여러 reducer를 묶어 사용함- actionCreator를 
[]로 감싸서"computed property syntax"를 사용함 (MDN :computed property syntax)- computed property syntax는 property의 이름을 밖의 변수를 참조시커나, 연산 할수 있게 하여 동적인 property 명을 가질 수 있게 함
 
 push 방식,return 방식둘다 지원함- push : 전개 연산자를 사용할 필요가 없음, 어떤 것도 return 하지 않음
 - return: 전개연산자를 사용해 주어야 함, return 형식으로 해줘야 함
 
- action에서 전달하는 데이터는 payload를 통해서 가져다 지정할 수 있음
 
 - actionCreator를 
 
- 해당 방식을 사용하면, 기존에 사용하던 redux-thunk 함수를 그대로 호환해서 사용할 수 있음
 
// actionCreator
import {createAction} from '@reduxjs/toolkit'
const getUsersStart = createAction('GET_USERS_START');
const getUsersSuccess = createAction('GET_USERS_SUCCESS');
const getUsersFail = createAction('GET_USERS_FAIL');
// Reducer (Before using createReducer of Redux-Toolkit)
const initialState = {
	loading: false,
	data: [],
	error: null,
};
const reducer = (state = initialState, action) => {
	switch (action.type) {
		case getUsersStart.type:
			return { ...state, loading: true };
		case getUsersSuccess.type:
			return { ...state, loading: false, data: action.payload };
		case getUsersFail.type:
			return { ...state, loading: false, error: action.payload };
		default:
			return state;
	}
};
export default reducer;
// ------------------- After ------------------------------------
// Reducer (After using createReducer of Redux-Toolkit)
import {createReducer} from '@reduxjs/toolkit'
const initialState = {
	loading: false,
	data: [],
	error: null,
};
const reducer = createReducer(initialState, {
  // push() 방식
	[getUsersStart]: (state) => {
		state.push({ loading: true });
	},
  // return 방식
	[getUsersSuccess]: (state, action) => ({...state, loading: false, data: action.payload });
	,
	[getUsersFail]: (state, action) => {
		state.push({ loading: false, error: action.payload });
	},
});
export default reducer;
// Thunk ActionCreator - createReducer 방식과 그대로 호환 됨
export function getUsersThunk() {
	return async (dispatch, getState) => {
		try {
			dispatch(getUsersStart());
			const res = await axios.get('https://api.github.com/users');
			dispatch(getUsersSuccess(res.data));
		} catch (error) {
			dispatch(getUsersFail(error));
		}
	};
}
Slice
configureStore()
- 기존의 createStore를 대체해서 
configureStore()사용하면, Thunk, dev tool까지 자동으로 연결해줌 - 주의 할 점은, configureStore에 넣을 인자인 rootReducer 형태를 객체로 
{reducer: rootReducer}로 만들어 주어서 넣어 주어야 함 그리고 property명은 reducer로 무조건 해줘야 함 
import rootReducer from "./modules/reducer";
import { configureStore } from "@reduxjs/toolkit";
// const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)));
const store = configureStore({
  reducer: rootReducer,
});
export default store;
- rootReducer를 reducer이름으로 사용하면 property와 이름이 같아져서 객체 단축 표현으로 
{reducer}로 사용할 수 있음 
import reducer from "./modules/reducer";
import { configureStore } from "@reduxjs/toolkit";
// const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)));
const store = configureStore({ reducer });
export default store;
- rootReducer를 연결할 때는 대신에 해당 slice의 reducer로 출력해서 연결해줘야 함
    
- slice의 경우 reducer와 action을 모두 포함하고 있기 때문에 정확하게 reducer를 연결해 줄 필요가 있음
 
 
import { combineReducers } from "redux";
import users from "./users1";
const reducer = combineReducers({
  users: users.reducer,
});
// 애초에 slice를 users.reducer로 export default하면 그냥 reducer 모양으로 받아서 넣을 수 있음
export default reducer;
createSlice()
- createSlice는 하나의 slice 객체를 인자로 받음
 - slice 객체는 
{name, initialState, reducers, extraReducers}로 구성되어 있음name: string을 넣어서 prefix로 사용initialState: defaultState가 들어감, (변수를 initialState로 지정하면, 단축으로 작성할 수 있음)reducers: slice 안에서 사용할 reducer 들을 만들수 있음, 해당 reducer들을 만들면 자동으로 slice.action에 reducers에서 만든 reducer에 대한 actionCreator 함수가 들어 있음extraReducers: slice에서 만들어진 reducers에 의한 action, reducer가 아닌 외부에서 만들어진 action을 통해 현재 slice에서 사용하는 initialState에 변경을 가하는 경우 처리받는 reducer임 (비동기 작업 함수 처리 등에 사용됨)
 
reducers 작성법 2가지
- reducer를 작성시 처리하는 방식은 2가지가 있다.
    
방법1(함수, 직접 할당 방식): 직접 값을 변경하는 방식으로 기존 값에 변경을 주는 함수를 사용하거나, 할당을 이용- js에서 사용하는 기존값에 변경을 가하는 Array 함수 등인 push 등이 있겠다.
 - 초기 자료구조가 어떻게 되어 있는지에 따라 변수에 사용할수 있는 함수는 달라지겠다.
 - 주의할점은, 집어 넣는 값과 기존의 자료구조가 어떻게 되어 있는지 조심해야한다.
            
- payload 자체로 보내기 때문에 reducer에서 값을 어떻게 받게 할 것인지 조심해야 함
 
 
방법2(복사 return 방식): 기존에 사용하던 방식으로, return을 주어 기존의 state는 복사하여 가져오고 변경된 값만 덮어 씌우는 형식으로 지정- 일단, state 전체를 바꾸는 거라서 오히려 전체구조를 그리면서 할수 있어서 어떻게 들어가는지 함수 고려를 하지 않아도 됨
 - 하지만, 
...state를 사용해서 
 
// defaultState
const initialState = {
  loading: false,
  data: [],
  error: null,
};
// slice reducers 방법01 : 있던 값을 바꾸는 형식 (state에 직접적으로 변경을 가함, 함수방식)
// 직접 변경을 가하기 때문에 기존값을 풀어써주는 전개 연산자가 필요가 없음
const users = createSlice({
  name: "usersReducer",
  initialState,
  reducers: {
    getUsersStart: (state, action) => {
      state.loading = true;
    },
    getUsersSuccess: (state, action) => {
      state.loading = false;
      state.data = action.payload;
    },
    getUsersRemove: (state, action) => {
      state.loading = false;
      state.data = [];
    },
  },
  extraReducers: {},
});
export const { getUsersRemove, getUsersSuccess, getUsersStart } = users.actions;
// slice reducers 방법02 : 기존 state를 복사하여 새로운 값을 만들어서 state에 세팅하는 방식(return 방식)
// 기존 값을 가져와 반영해야 함으로 전개연산자 필요
const users = createSlice({
  name: "usersReducer",
  initialState,
  reducers: {
    getUsersStart: (state, action) => ({ ...state, loading: true }),
    getUsersSuccess: (state, action) => ({
      ...state,
      loading: false,
      data: action.payload,
    }),
    getUsersRemove: (state, action) => ({
      ...state,
      loading: false,
      data: [],
    }),
  },
  extraReducers: {},
});
export const { getUsersRemove, getUsersSuccess, getUsersStart } = users.actions;
extraReducer 작성법 2가지
- 자신이 구현한 외부 비동기 작업 함수 사용시 사용되며, 외부에서 만들어진 action을 처리하기 때문에 외부에서 만들어진 함수를 property이름으로 사용하고, 자동으로 pending, fulfilled, rejected의 type을 가지 action을 구현해줌
 - 그래서 pending fulfilled, rejected 에 맞게 extraReducers를 작성하면됨
 
- map Object notation 방식 - return 방식 or 함수,할당 방식
 - builder callback notation 방식 - return 방식 or 함수,할당 방식
 
01 : map Object notation 방식 - return 방식 or 함수,할당 방식
// defaultState
const initialState = {
  loading: false,
  data: [],
  error: null,
};
// Slice extraReducers
// 방식1: map Object notation
// 1. return 방식 (전개 연산자 필요)
const users = createSlice({
  name: "usersReducer",
  initialState,
  reducers: {},
  extraReducers: {
    [getUsersThunk.pending]: (state, action) => ({
      ...state,
      loading: true,
    }),
    [getUsersThunk.fulfilled]: (state, action) => ({
      ...state,
      loading: false,
      data: action.payload,
    }),
    [getUsersThunk.rejected]: (state, action) => ({
      ...state,
      loading: false,
      error: action.error,
    }),
  },
});
export default users;
// 방식1: map Object notation
// 2. 함수 및 할당 방식 (전개 연산자 불필요)
const users = createSlice({
	name: 'usersReducer',
	initialState,
	reducers: {},
	extraReducers: {
		[getUsersThunk.pending]: (state, action) => {
			state.loading = true;
		},
		[getUsersThunk.fulfilled]: (state, action) => {
			state.loading = false;
			state.data = action.payload;
		},
		[getUsersThunk.rejected]: (state, action) => {
			state.loading = false;
			state.error = action.error;
		},
	},
});
export default users;
02 : builder callback notation 방식 - return 방식 or 함수,할당 방식
// defaultState
const initialState = {
  loading: false,
  data: [],
  error: null,
};
// 방식2: builder callback notation
// 1. return 방식 (전개 연산자 필요함)
const users = createSlice({
	name: 'usersReducer',
	initialState,
	reducers: {},
	extraReducers: (builder) => {
		builder
			.addCase(getUsersThunk.pending, (state, action) => ({
				...state,
				loading: true,
			}))
			.addCase(getUsersThunk.fulfilled, (state, action) => ({
				...state,
				loading: false,
				data: action.payload,
			}))
			.addCase(getUsersThunk.rejected, (state, action) => ({
				...state,
				loading: false,
				error: action.error,
			}));
	},
});
export default users;
// 방식2: builder callback notation
// 2. 함수 및 할당 방식 (전개 연산자 불필요)
const users = createSlice({
	name: 'usersReducer',
	initialState,
	reducers: {},
	extraReducers: (builder) => {
		builder
			.addCase(getUsersThunk.pending, (state, action) => {
				state.loading = true;
			})
			.addCase(getUsersThunk.fulfilled, (state, action) => {
				state.loading = false;
				state.data = action.payload;
			})
			.addCase(getUsersThunk.rejected, (state, action) => {
				state.loading = false;
				state.error = action.error;
			});
	},
});
export default users;
createAsyncThunk(): 비동기 작업 함수 작성
- slice로 구현한 state를 변경하려면, 기존의 react-thunk 사용방식으로는 안됨
 - toolkit에서 제공하는 
createAsyncThunk()를 활용해서 비동기 작업을 구현할 수 있음 - redux-toolkit 공식 문서: createAsyncThunk
 createAsyncThunk(type, payloadCreator, options)type: 해당 요청의 type명으로, prefix를 포함해서 작성해 주어야 한다. (pending, fulfilled, rejected는 알아서 상황에 맞게 붙여짐으로 고려하지 않아도 됨)payloadCreator: actionCreator로 payload와 함께 보내져 요청되는 비동기 함수 실행 부분 (인자 두개를 받음)arg: 첫번째 파라미터로 지정하면, actionCreator를 사용하면서 보낼 payload(인자)를 받아 실행하고자 하는 비동기 함수를 구성하는데 사용될 사용자 입력으로 활용할 수 있음thunkAPI: dispatch, getState, rejectWithValue, fulfillWithValue 등의 함수를 실행 할수 있는 API 묶음
- 기본적으로 해당 함수의 return 값은 fulfilled로 처리하여 
payload로 보내지고, error는 thukAPI,rejectWithValue(error)를 통해서 받아action.error로 보내짐 
// createAsyncThunk : Thunk 비동기 작업
export const getUsersThunk = createAsyncThunk(
  "users/getUsersThunk",
  async (thunkAPI) => {
    try {
      const res = await axios.get("https://api.github.com/users");
      return res.data;
    } catch (error) {
      return thunkAPI.rejectWithValue(error);
    }
  }
);
const initialState = {
  loading: false,
  data: [],
  error: null,
};
const users = createSlice({
  name: "usersReducer",
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(getUsersThunk.pending, (state, action) => {
        state.loading = true;
      })
      .addCase(getUsersThunk.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload;
      })
      .addCase(getUsersThunk.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error; // action.error인 것을 주의
      });
  },
});
export default users;