벌써 항해한지 꽤 오랜 시간이 지났다. 리액트 주차를 지나면서 배운 것은 Redux, Redux-Toolkit, Axios였는데, 미니 프로젝트와 클론코딩 프로젝트를 진행하면서 열심히도 써먹었었다.
근데 다만 정말 말 그대로 써먹기만 했지, 뭔가 계속 개념이 헷갈리는 것 같아서 다시 한번 정리하려고 한다.
공식 문서에 따르면 Redux는 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너
라고 한다.
리덕스를 사용하는 가장 큰 이유는 리액트에서 컴포넌트가 많아질수록 구조가 복잡해져서 상태 변경을 하기 어려워지기 때문이다. 리덕스를 사용할 경우 데이터가 Store에 저장되기 때문에 props drilling이 발생하지 않는다.
리덕스를 이해하기 위해 데이터 흐름을 한번 그려보았다.
리덕스의 상태는 읽기 전용이다.
리듀서는 순수한 함수여야 한다.
const initialState = {
counter: 0,
text: '',
list: []
};
initialState
로 정의해서 관리한다.const INCREASE = 'INCREASE';
const DECREASE = 'DECREASE';
const CHANGE_TEXT = 'CHANGE_TEXT';
const ADD_TO_LIST = 'ADD_TO_LIST';
export function increase() {
return {
type: INCREASE
};
}
export const decrease = () => ({
type: DECREASE
});
export
해야한다.function reducer(state = initialState, action) {
switch (action.type) {
case INCREASE:
return {
...state,
counter: state.counter + 1
};
case DECREASE:
return {
...state,
counter: state.counter - 1
};
case CHANGE_TEXT:
return {
...state,
text: action.text
};
case ADD_TO_LIST:
return {
...state,
list: state.list.concat(action.item)
};
default:
return state;
}
const store = createStore(reducer);
console.log(store.getState());
store.dispatch(increase());
store.dispatch(decrease());
store.dispatch(changeText('안녕하세요'));
store.dispatch(addToList({ id: 1, text: '와우' }));
그렇다면 리덕스와 리덕스 툴킷의 다른 점은 뭘까?
공식 문서에 따르면 Redux 로직을 작성하기 위해 공식적으로 Redux-Toolkit 사용을 추천한다고한다.
RTK(Redux-Toolkit)는 Redux 앱을 만들기에 필수적으로 여기는 패키지와 함수들이 이미 포함되어있기 때문에 훨씬 더 효율적이다.
RTK는 저장소 준비
, 리듀서 생산과 불변 수정 로직 작성
, 상태 "조각" 전부를 한번에 작성
등 일반적인 작업들이 단순화 됐기 때문에 효율적이다.
// 리듀서가 1개일 때
import { configureStore } from '@reduxjs/toolkit'
import rootReducer from './reducers'
const store = configureStore({ reducer: rootReducer })
// 리듀서가 여러개일 때
import { combineReducers } from 'redux'
import todos from './todos'
import counter from './counter'
export default combineReducers({
todos,
counter
})
configureStore
를 사용할 경우, 여러개의 reducer를 담아도 combineReducers를 통해 rootReducer로 묶을 수 있다.import { createAction, createReducer } from '@reduxjs/toolkit'
const increment = createAction('counter/increment')
const decrement = createAction('counter/decrement')
const incrementByAmount = createAction('counter/incrementByAmount')
const initialState = { value: 0 }
const counterReducer = createReducer(initialState, (builder) => {
builder
.addCase(increment, (state, action) => {
state.value++
})
.addCase(decrement, (state, action) => {
state.value--
})
.addCase(incrementByAmount, (state, action) => {
state.value += action.payload
})
})
import { createSlice } from '@reduxjs/toolkit'
const initialState = { value: 0 }
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment(state) {
state.value++
},
decrement(state) {
state.value--
},
incrementByAmount(state, action) {
state.value += action.payload
},
},
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
createAsyncThunk()
와 같은 비동기 함수와 함께 사용할 수 있다.createAsyncThunk()
의 첫번째 자리에는 action value, 두번째에는 함수가 들어간다.나는 주로 RTK와 thunk를 함께 조합해서 사용했고, 다른 방식은 크게 생각해보지도 않았기 때문에 굳이 전역상태 관리가 필요하지 않은 케이스에도 리덕스를 사용한것 같다.
리덕스 공식문서에도 정말 리덕스가 필요한지 생각하고 사용하라는 말이 있던데, 전역 상태관리가 정말 필요한 순간이 있고 편하게 사용할 순 있지만, 하나의 함수를 만들기 위해서 부가적으로 작성하는 코드가 너무 많은 것은 확실히 비효율적인 것 같다. 아무리 RTK를 쓴다고해도 말이지!
다음번엔 리덕스를 사용하지 않고 CRUD를 구현하도록 해야겠다.
공식 문서에 따르면 Axios는 node.js와 브라우저를 위한 Promise 기반 HTTP 클라이언트
라고 한다.
Http 메소드를 활용해서 서버와 통신할 때 사용하는 패키지라고 이해할 수 있다.
내장되어있는 기능인 fetch와는 다르게 모듈을 따로 설치해야하지만, fetch보다 호환성이 좋고 기능이 더 다양하기 때문에 더 많이 사용된다.
Axios는 기본적으로 HTTP Method와 요청 URL을 넣어서 config를 전송하면 서버에 요청을 보낼 수 있다.
하지만 매번 요청 URL을 작성할 필요 없이 baseURL을 지정할 수 있는데, 이때 만들게 되는 것은 Axios Instance다.
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
GET
이 선택된다.// 요청 인터셉터
axios.interceptors.request.use(function (config) {
return config;
}, function (error) {
return Promise.reject(error);
});
// 응답 인터셉터
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
return Promise.reject(error);
});
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 에러 핸들링
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
source.cancel('Operation canceled by the user.');
CancelToken.source
팩토리를 사용하여 취소 토큰을 만들거나, 실행자 함수를 CancelToken
생성자에 전달하여, 취소 토큰을 만들 수 있다.Fetch는 JavaScript 브라우저에 내장되어있는 라이브러리로, 서버에 네트워크 요청을 보내고 새로운 정보를 받아오기 위해 사용한다. fetch()
를 호출하면 브라우저는 네트워크 요청을 보내고 프라미스가 반환되는데, fetch 응답은 대부분 두 단계를 거쳐서 진행된다.
response
에는 프로미스를 기반으로 하는 다양한 메서드가 있기 때문에, 이 메서드들을 사용해서 다양한 형태의 응답 본문을 처리한다.Fetch는 따로 모듈을 설치할 필요가 없어서 편리하지만, axios에 비해 기능이 적어서 덜 사용하는 추세라고한다.
Fetch를 직접 사용해보진 않았지만, JSON 변환도 자동으로 안되고 인트턴스와 인터셉터도 만들 수 없으니 앞으로도 웬만해선 사용하지 않을 것 같다. 그래도 내장 라이브러리인 만큼 어떻게 동작하는지 나중에 더 찾아보면 좋을 것 같다.
오늘은 지금까지 사용은 했지만 개념적으로는 아직 친숙하지 않은 라이브러리들을 한번 살펴보았다.
정리는 매우 오래 걸렸고... 아직 당연히 전부 알지 못 하는 것 같아서 앞으로 더 공부해야겠지만, 적어도 그냥
쓰지 않고 앞으로는 최소한의 장단점은 알고 사용할 수 있을 것 같다.
코딩할때 그냥 사용하지말고 왜 이 기술, 왜 이런 방식을 사용하는지 아는 것은 중요하다고 했다.
지금까진 항해에서 배우면서 이것밖에 몰라서 사용했지만, 적어도 내가 유일하게 아는 방식들에 대해서 더 자세히 알아보도록 노력해야겠다.
출처: