티스토리 뷰
리덕스
https://velog.io/@hwang-eunji/%EB%A6%AC%EB%8D%95%EC%8A%A4-%EC%A0%95%EB%A6%AC-redux-1
[ERROR]
ERESOLVE unable to resolve dependency tree
▼ 해결 방법
https://www.korecmblog.com/ERESOLVE-unable-to-resolve-dependency-tree/
리덕스 데브 툴즈
https://bigstar-vlog.tistory.com/47
https://seongjins.tistory.com/219
리덕스 툴킷
피그마 플러그인
https://ossam5.tistory.com/304
https://yozm.wishket.com/magazine/detail/1102/
https://brunch.co.kr/@hailey-hyunjee/59
동영상 git로 변환
리액트 심화 주차 강의
A. Redux Toolkit
*npm으로 json 서버 실행하기
npx json-server --watch db.json
※ 일반 리덕스를 사용한 경우 우리가 직접 Action Value와 Action Creator를 만들었다.
* modules - counterSlice.js 전체 코드
// src/redux/modules/counterSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
number: 0,
};
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
addNumber: (state, action) => {
state.number = state.number + action.payload;
},
minusNumber: (state, action) => {
state.number = state.number - action.payload;
},
},
});
// 액션크리에이터는 컴포넌트에서 사용하기 위해 export 하고
export const { addNumber, minusNumber } = counterSlice.actions;
// reducer 는 configStore에 등록하기 위해 export default 합니다.
export default counterSlice.reducer;
위와 같이 리덕스 툴킷을 사용한 경우 Action value, Action Creator, reducer 3개가 합쳐짐
1. createSlice라는 toolkit의 api를 통해서 만들 수 있음
import { createSlice } from "@reduxjs/toolkit";
slice
When you call createApi, it automatically generates and returns an API service "slice" object structure containing Redux logic you can use to interact with the endpoints you defined. This slice object includes a reducer to manage cached data, a middleware to manage cache lifetimes and subscriptions, and selectors and thunks for each endpoint. If you imported createApi from the React-specific entry point, it also includes auto-generated React hooks for use in your components.
https://redux-toolkit.js.org/rtk-query/api/created-api/overview
2. name 이름 initialState 초기 상태 값 그리고 module의 reducer 로직이 인자로 들어가면 됨
//createSlice API 뼈대
const counterSlice = createSlice({
name: '', // 이 모듈의 이름
initialState : {}, // 이 모듈의 초기상태 값
reducers : {}, // 이 모듈의 Reducer 로직
})
createSlice라는 API는 인자로 설정 정보를 객체로 받는다.
-> 설정 정보를 객체로 받는다는 게 무슨 말일까?
redux toolkit에는 createSlice라는 action type, action 생성자 및 action 객체를 생성하는 작업을 하는 함수가 있습니다. slice의 이름을 정의하고, 그 안에 reducer 기능이 있는 객체를 작성하면 자동으로 생성됩니다. name 옵션은 각 action type의 첫번째 부분으로 사용되며 각 reducer의 기능을 담당하는 함수의 이름은 두번째 부분으로 사용됩니다.
예를들어 name을 "counter"로 설정하고, reducer의 기능 함수 이름을 "increment"설정하면 둘이 합쳐져서 { type: "counter/increment" } 이라는 action이 생성 됩니다.
name 필드 외에도 createSlice에 initialState를 전달하여 초기 state가 호출 될 수 있도록 해야합니다.
counterSlice라는 slice의 이름을 정의하고 { } 객체를 작성
3. Reducer의 logic인 동시에 Action Creater가 된다!!! Action Value도 함수의 이름을 따서 자동 생성됨
so 우리는 Reducer만 만들면 됨
// counterSlice.js의 Slice 구조
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
// 리듀서 안에서 만든 함수 자체가 리듀서의 로직이자, 액션크리에이터가 된다.
addNumber: (state, action) => {
state.number = state.number + action.payload;
},
minusNumber: (state, action) => {
state.number = state.number - action.payload;
},
},
});
addNumber와 minusNumber 함수의 이름이 Action value가 되고 이 로직을 이용해서 reducer에 로직과 Action Creator가 생성됨
4. Action Creator 는 컴포넌트에서 사용하기 위해 export 합니다.
일반 redux에서는 export를 통해 각각의 Action Creator를 내보내 줬었는데 아래와 같이 작성하면 똑같이 내보낼 수 있음.
export const { addNumber, minusNumber } = counterSlice.actions;
// reducer 는 configStore에 등록하기 위해 export default 합니다.
export default counterSlice.reducer;
So reducer에 로직이 추가될 때마다 객체 안에 함수만 추가해서 내보내주면 됨
*configStore-redux
// 일반 리덕스 combineReducers 예시 코드
import { createStore } from "redux";
import { combineReducers } from "redux";
import counter from "../modules/counter";
const rootReducer = combineReducers({
counter,
});
const store = createStore(rootReducer);
export default store;
combineReducers와 createStore 이렇게 두 가지 method를 이용하고 있음.
ㄱ. combineReducer를 통해서 module이 여러 개일 때 roofReducer로 합쳐주고
ㄴ. 그 rootReducer를 createStore 안에 인자로 넣어줘서
-> store를 생산하는 두 가지 과정을 거치고 있었음
<-> configStore - redux toolkit
1. modules 합치고 store 생성하기
// src/redux/modules/config/configStore.js
import { configureStore } from "@reduxjs/toolkit";
/**
* import 해온 것은 slice.reducer 입니다.
*/
import counter from "../modules/counterSlice";
import todos from "../modules/todosSlice";
/**
* 모듈(Slice)이 여러개인 경우
* 추가할때마다 reducer 안에 각 모듈의 slice.reducer를 추가해줘야 합니다.
*
* 아래 예시는 하나의 프로젝트 안에서 counter 기능과 todos 기능이 모두 있고,
* 이것을 각각 모듈로 구현한 다음에 아래 코드로 2개의 모듈을 스토어에 연결해준 것 입니다.
*/
const store = configureStore({
reducer: { counter: counter, todos: todos },
});
export default store;
configStore라는 method 하나로 module을 합치는 것과 store를 생성하는 것을 한 번에 하고 있음.
모듈이 추가되면 reducer : {counter: counter} 객체 안에 module을 추가로 넣어주면 됨
*todos.Slice
// src/redux/modules/todosSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
todos: [],
};
const todosSlice = createSlice({
name: "todos",
initialState,
reducers: {},
});
export const {} = todosSlice.actions;
export default todosSlice.reducer;
2. 생성한 store를 최상위 index.js 에 추가하는 과정
* Index.js
// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { Provider } from "react-redux";
import store from "./redux/config/configStore";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<App />
</Provider>
);
*App.js - 위의 <App />
// src/App.js
import React from "react";
import { useSelector } from "react-redux";
const App = () => {
// Store에 있는 todos 모듈 state 조회하기
const todos = useSelector((state) => state.todos);
// Store에 있는 counter 모듈 state 조회하기
const counter = useSelector((state) => state.counter);
return <div>App</div>;
};
export default App;
1 ~ 2 의 내용 ] store를 export default 가져와서 root의 provider에 store를 넣어주는 과정은 Redux 와 redux toolkit 동일,
그리고 그 값을 가지고 와서 사용하는 방법도 동일함
즉, useSelector를 이용해서 state안의 값을 가지고 와서 확인할 수 있다는 부분 동일함
toolkit에서도 useSelector state에서 원하는 값으로 접근해서 가져오면 됨
✔️ 결론 : Toolkit에 slice api를 이용해서 reducer, ActionValue, Action Creater를 한 번에 구현할 수 있다.
* 내장된 주요 패키지 thunk, devTools, immer 등이 있음
- 폴더 구조 예시
- /src
- index.js: 앱의 시작점
- App.js: 최상위 React 컴포넌트
- /app
- store.js: Redux 스토어 인스턴스 생성
- /features
- /counter
- Counter.js: 카운터 기능에 대한 UI를 보여주는 React 컴포넌트
- counterSlice.js: 카운터 기능에 대한 Redux 로직
- /counter
- immer
- immer
Immer 를 사용하면 우리가 상태를 업데이트 할 때, 불변성을 신경쓰지 않아도 대신 불변성을 관리해줌!
import produce from 'immer';
...
const state = {
number: 1,
dontChangeMe: 2
};
const nextState = produce(state, draft => {
draft.number += 1;
});
console.log(nextState);
// { number: 2, dontChangeMe: 2 }
*produce 함수를 사용 할 때에는 첫번째 파라미터에는 수정하고 싶은 상태, 두번째 파라미터에는 어떻게 업데이트하고 싶을지 정의하는 함수를 넣어줌.
draft.number += 1;
이렇게 불변성을 신경쓰지 않고 업데이트를 해도 알아서 불변성을 관리함
► Immer 함수형 업데이트
함수형 업데이트를 하는 경우에, Immer 를 사용하면 상황에 따라 더 편하게 코드를 작성 할 수 있음
const [todo, setTodo] = useState({
text: 'Hello',
done: false
});
const onClick = useCallback(() => {
setTodo(
produce(draft => {
draft.done = !draft.done;
})
);
}, []);
...
// ~ 결과{ text: 'Hello', done: true }
produce 함수에 첫번째 파라미터를 생략하고 바로 업데이트 함수를 넣어주게 된다면, 반환 값은 '새로운 상태가 아닌 상태'를 업데이트 해주는 함수가 됨(done만 업데이트되어 '새로운 상태가 아닌 상태'가 됨)
즉, produce 가 반환하는것이 업데이트 함수가 되기 때문에 useState 의 업데이트 함수를 사용 할 떄 위과 같이 구현 할 수 있음
# Immer simplifies handling immutable data structures
Without Immer
Without Immer, we'll have to carefully shallow copy every level of the state structure that is affected by our change:
const nextState = baseState.slice() // shallow clone the array
nextState[1] = {
// replace element 1...
...nextState[1], // with a shallow clone of element 1
done: true // ...combined with the desired update
}
// since nextState was freshly cloned, using push is safe here,
// but doing the same thing at any arbitrary time in the future would
// violate the immutability principles and introduce a bug!
nextState.push({title: "Tweet about it"})
With Immer
import produce from "immer"
const nextState = produce(baseState, draft => {
draft[1].done = true
draft.push({title: "Tweet about it"})
})
https://immerjs.github.io/immer/#immer-simplifies-handling-immutable-data-structures
- Flux
Action Creator - like a telegraph operator
액션 생성자는 타입(type)과 페이로드(payload)를 포함한 액션을 생성한다. 타입은 시스템에 정의 된 액션들(일반적으로 상수들) 중의 하나이다. 액션 생성자가 액션 메시지를 생성한 뒤에는 디스패쳐(dispatcher)로 넘겨준다.
Dispatcher - like a telephone operator
디스패쳐는 액션을 보낼 필요가 있는 모든 스토어(store)를 가지고 있고,
액션 생성자로부터 액션이 넘어오면 여러 스토어에 액션을 보낸다.
- 이 처리는 동기적으로(synchronously) 실행된다.
- 스토어들 사이에 의존성(dependency)이 있어서 하나를 다른 것보다 먼저 업데이트를 해야한다면, waitFor()를 사용해서 디스패쳐가 적절히 처리하도록 할 수 있다.
Store - like a government official
애플리케이션 내의 모든 상태와 그와 관련된 로직을 가짐. 모든 상태 변경을 결정한다.
- 스토어에 상태 변경을 요청하기 위해서는 Action Creator / Dispatcher pipeline을 통해 Action을 보내야 함
- 디스패쳐에 등록된 스토어는 모든 Action을 받는데, switch statement로 처리/무시할 Action을 정하고, 처리할 Action에 따라 '무엇을 할 지' 결정한 후 state 상태를 변경한다. 이렇게 상태 변경을 완료하면 스토어는 change event 변경 이벤트를 내보내고 그 이벤트는 controller view에 state변경했음을 알려준다.
View, Controller View - like a presenter
ㅇ View : 애플리케이션 내부에 대해서는 아는 것이 없지만, 받은 데이터를 처리해서 사람들이 이해할 수 있는 포맷(HTML)으로 어떻게 바꾸는지
ㅇ Controller View : 스토어와 뷰 사이의 중간관리자. 상태가 변경되었을 때 스토어가 그 사실을 컨트롤러 뷰에게 알려주면, 컨트롤러 뷰는 자신의 아래에 있는 모든 뷰에게 새로운 상태를 넘겨준다.
어떻게 함께 동작하는가? - 아래 링크
https://bestalign.github.io/translation/cartoon-guide-to-flux/
- Flux와 Redux
Reducer
리듀서(Reducer)는 Flux에는 없는, Redux에서만 찾을 수 있는 용어이다.
액션은 어떤 일이 일어났는지는 알려주지만 애플리케이션의 상태를 어떻게 바꾸어야 할지는 알려주지 않는데, Redux 프레임워크에서는 리듀서가 이 역할을 담당한다.
간단히 말해 Flux 애플리케이션에서 스토어 객체를 업데이트하는 콜백 함수와 하는 역할은 비슷하다.
다만, Flux 스토어의 콜백과는 달리 Array.prototype.reduce(리듀서콜백, ?초기값)에 전달된 리듀서콜백처럼 동작하기에 리듀서(reducer)라고 부른다.
Todo 애플리케이션에 다음과 같은 상태 객체가 있다고 생각해보자.
{
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: '자료 준비하기',
completed: true
},
{
text: '블로그 글쓰기',
completed: false
}
]
}
이 데이터를 업데이트하는 함수 todoApp은 다음과 같이 작성할 수 있다.
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
});
default:
return state;
}
}
위 코드의 todoApp은 인수만 전달받던 Flux의 스토어 콜백과는 달리 첫 번째 인수로 기존 상태를 전달받고, 두 번째 인수로 액션 객체를 전달받는다.
리듀서를 작성할 때는 두 가지 사항을 유의해야 한다.
- 첫 번째 인수로 전달받은 state는 수정하면 안된다.
상태를 수정할 때는 반드시 Object.assign 등을 사용하여 새로운 객체를 만들어서 반환해야 한다. - default 케이스에서 기존 state를 반환해야 한다.
전달받은 액션 객체를 이 리듀서에서 처리하지 못하는 때를 대비해 default 케이스에서는 반드시 인수로 전달받은 state를 그대로 반환해야 한다.
+ to do list 에서
Action Creator
// modules의 todos.js
Dispatcher
// Form.jsx - Create 새로운 todo 만드는 dispatch(addTodo(newTodo));
// Todo.jsx - Update 만들어진 todo의 isDone 상태에 따라 달라지는 dispatch(toggleTodo(id));
// Todo.jsx - Delete 만들어진 todo를 지우는 dispatch(deleteTodo(id));
// Details.jsx - Read 상세페이지에서 todo 정보를 읽어오는 dispatch(getTodoByID(id)
👊🏻 오늘 한 일
- [혼공자] 언어 스터디 Ch9-2 클래스 고급
- 심화 주차 팀 회의
- 와이어프레임 완료
- redux toolkit 강의
😲 오늘 느낀 점
뭔가 더 많은 일을 한 것 같은데 오늘 아침이 일주일 전처럼 기억이 잘 안 난다. 그때그때 적으면서 TIL을 쓰고 나중에 정리하려고 했는데 너무 피곤해서 메모한 내용만 옮겨놓고 잠들었다. 조금 자고 일어나도 피곤하지 않았으면 좋겠다. 이제 리액트 툴킷을 사용해서 팀 프로젝트가 시작된다. 매일매일이 숨 가쁘게 달리는 것 같다. 이것저것 다시 복습하고 하나씩 톺아볼 시간이 필요하다. 내가 좀 더 똑똑했으면 좋겠다.
👏🏻 오늘의 칭찬
팀 회의가 생각보다 재밌었다. 넣고 싶은 기능들이 많은데 시간을 아껴서 한 번 다 구현해보고 싶다.
🤔 오늘 아쉬운 점
해야 할 일, 하고 싶은 일, 계획한 일은 많은데 모두 지키지 못했다. 내 능력은 저기 아래인데 욕심은 많아서 스트레스를 받지만 그래도 최선을 다해야 한다...
⛵️ 내일 할 일
- redux toolkit 강의
- 심화 주차 강의자료 + 공식 문서, 블로그 등
'Edu_hanghae99 > TIL' 카테고리의 다른 글
[TIL] 리액트 심화 주차 팀 과제_리덕스 툴킷 (2) | 2022.12.12 |
---|---|
[TIL] 나 6시간만 더 줘요 221210 (1) | 2022.12.10 |
[TIL] 리액트 숙련 주차 회고 221208 (0) | 2022.12.08 |
[TIL] React-redux To-do List v.2 221207 (0) | 2022.12.08 |
[TIL] REDUX... 221206 (0) | 2022.12.07 |