나.부.리 2
React 정리….
- 오늘은
Redux
중심
Spread Operator
정보의 업데이트가 이루어질 때
React
에서setState
를 할 때, 기존 정보가 새 정보로 업데이트 됨...
연산자를 사용해 원래 배열을 유지하면서 다른 요소를 추가할 수 있음
var colors = ['red', 'yellow', 'blue']
// var newColors = ['red', 'yellow', 'blue', 'green'] 와 같음
var newColors = [...colors, 'green']
함수 인자로 사용할 때
- 필요한 parameter 의 개수가 정해지지 않았을 때 사용
function func(...param) {
console.log(param);
}
// [ 1, 2, 3 ]
func(1, 2, 3);
Immer
React
에서 배열이나 객체를 업데이트 할 때, 직접 수정은 불가능 (기존의 배열, 객체도 함께 수정됨)- 객체의 경우 전개 연산자(…)를 이용해 값을 수정했고, 배열은 전개 연산자와 concat, filter, map 등을 이용하여 수정
- 불변성을 지켜주면서 값을 수정할 수 있게 하는 라이브러리
immer
를 사용하면 편리하지만 성능적으로는immer
를 사용하지 않은 쪽이 조금 더 빠름
// produce 로 불러옴
import produce from "immer"
const baseState = [
{
todo: "Learn typescript",
done: true
},
{
todo: "Try immer",
done: false
}
]
// baseState >> 현재 state
// draftState >> 현재 state 를 복사한 것
// nextState >> produce 가 반환하는 값
const nextState = produce(baseState, draftState => {
draftState.push({todo: "Tweet about it"})
draftState[1].done = true
})
Immer 와 함수형 업데이트
// produce 의 첫번째 parameter 의 값을 생략하고 바로 업데이트 함수를 넣어주는 경우
// 상태를 업데이트 해주는 함수가 될 수 있음
const updater = produce(draft => {
draft.done = !draft.done;
});
const nextTodo = updater(todo);
const [todo, setTodo] = useState({
text: 'Hello',
done: false
});
// setTodo 함수에 업데이트 함수를 해줌으로써
// useCallback 의 두번째 parameter 인 deps 배열에 todo 를 넣지 않아도 됨
const onClick = useCallback(() => {
setTodo(todo => ({
...todo,
done: !todo.done
}));
}, []);
// immer 를 사용한 방법
const onClick = useCallback(() => {
setTodo(
produce(draft => {
draft.done = !draft.done;
})
);
}, []);
Redux
React
생태계에서 가장 사용률이 높은 상태관리 라이브러리Component
들의 상태 관련 로직들을 다른 파일로 분리시켜서 관리할 수 있음Content API
와useReducer
Hook을 사용해서 개발하는 방식과 유사
Redux 에서 사용되는 키워드
- Action
- 상태에 변화가 필요할 때 발생시키는 것
- 저장소에 정보를 전달하기 위한 데이터 묶음
- 하나의 객체로 표현
type
필드를 필수적으로 가지고 있어야 함
{
type: "STUDY_REDUX",
data: {
id: 0,
text: "리덕스를 배워보자"
}
}
- Action Creator (액션 생성함수)
- 액션을 만드는 함수
- parameter 를 받아 액션 객체 형태로 만들어 줌
Component
에서 액션을 쉽게 발생시키기 위해 만들어 졌기 때문에export
키워드를 붙여 다른 파일에서 불러와 사용- 필수적이진 않고, 필요 시 액션을 발생시킬 때 마다 생성할 수 있음
export function codingRedux(data) {
return {
type: "CODING_REDUX",
data
};
}
// arrow function 의 형태로도 생성 가능
export const codingRedux = data => ({
type: "CODING_REDUX",
data
});
- Reducer
- 변화를 일으키는 함수
- 각각의 상태변화를 어떻게 할지 관리해주는 정보가 담겨 있음
- 두 개의 parameter(
state
,action
) 를 필요로 함 - 현재의 상태와 전달받은 액션을 참고하여 새로운 상태를 만들어서 반환
- 여러
reducer
들을 하나로 합쳐 주는CombineReducers
도 있음
function reducer(state, action) {
// 상태 업데이트 로직
return alteredState;
}
- Store
Application
당 하나의Store
를 가짐- 현재의 앱 상태,
reducer
, 내장 함수 등이 있음
// createStore
// parameter 로 reducer 들을 넣어줌
import { createStore } from "redux"
const store = createStore(firstReducer)
// getState
store.getState()
// dispatch
// store 에 있는 reducer 에게 action 을 전달해 줌
store.dispatch(firstReducer("GO_ACTION"))
- Dispatch
Store
의 내장함수 중 하나- 액션을 발생시키는 것
- 액션 형태의 값을 parameter 로 받음, dispatch(action) 의 형태
- Subscribe
Store
의 내장함수 중 하나- 함수 형태의 값을 parameter 로 받고 액션이 dispatch 될 때 마다 전달된 함수가 호출
- 직접 사용하진 않고,
connect
함수나useSelector
Hook 을 사용하여Redux
상태에 구독
Redux-React
React
에Redux
를 연결해주는 역할
- Provider
- 단순한 하나의 Component
react
로 작성된 Component 들을 Provider 안에 넣으면 하위 Component 들이 Provider 를 통해 redux store 에 접근이 가능해짐
import React from "react"
import ReactDOM from "react-dom"
import { Provider } from "react-redux"
import App from "./App"
import createStore from "redux"
// store 생성
const store = createStore( ... )
ReactDOM.render(
// Provider 안에 Component 넣기
<Provider store={store}>
<App />
</Provider>,
document.getElemenyById('root')
)
- connect
Provider
하위에 존재하는 컴포넌트가Store
에 접근할 수 있도록 연결- 반드시
mapStateToProps
,mapDispatchToProps
를 인자로 넘겨줘야Component
가this.props
로 접근할 수 있음
import { connect } from "react-redux"
..
const Todo = connect();
export default Todo(TodoApp);
// 위의 소스를 한 줄로 표현한 것
export default connect()(TodoApp);
- mapStateToProps
connect
함수의첫번째 인자
로 사용됨Store
가update
될 때마다 자동적으로 호출되기 때문에, 원하지 않는다면null
이나undefined
값을 제공해야 함- 첫번째 인자는
state
, 두번째 인자는객체
- mapDispatchToProps
connect
함수의두번째 인자
로 사용됨Store
에 접근한Component
가Store
의 상태를 바꾸기 위해dispatch
를 사용할 수 있게 함
Redux 의 3가지 규칙
- 하나의
Application
안에는 하나의Store
- 여러개의
Store
가 가능하긴 하나, 권장하지는 않음
- 여러개의
- 읽기전용 state
React
에서state
를update
할 때,setState
를 사용하거나concat
,...
으로 기존의state
는 수정하지 않고 새로운 것을 만들어서 수정- 기존 상태를 변화시키지 않기 때문에 개발자 도구를 통해 뒤로 돌리거나 앞으로 돌릴 수 있음
- 불변성을 유지해야 하는 이유는 내부적으로 데이터가 변경 되는 것을 감지하는
shallow equality
검사를 하기 때문 Immutable.js
,Immer.js
를 사용해 불변성을 유지하며 상태를 관리할 수 있음
Reducer
는순수한 함수(pure function)
여야 함Reducer
는state
와action
객체를 parameter 로 받음- 이전의
state
는 절대 건들지 않고, 변화를 일으킨 새로운 상태 객체를 만들어서 반환 - 똑같은 parameter 로 호출된
Reducer
는 언제나 똑같은 결과값을 반환해야 함 - 실행할 때 마다 다른 값을 나타내는 것들(new Date(), 랜덤 숫자 생성 …)은 Reducer 밖에서 처리해야 함 ->
Redux Middleware
를 사용
Redux Hook
Redux saga
Context API
- 프로젝트 내에서 전역적으로 사용할 수 있는 값을 관리할 수 있도록 함
함수
,외부 라이브러리 인스턴스
,DOM
등 다양한 값을 관리할 수 있음Context
생성 시Provider
라는Component
가 있는데, 이Component
의value
를 이용해Context
의 값을 정할 수 있음Provider
로 감싸진Component
라면 어디서든지Context
의 값을 사용할 수 있음
// Context 를 생성할 때는 아래의 함수를 이용하여 만듦
// parameter 로 Context 의 default 값을 설정할 수 있음
export const context = React.createContext();
<context.Provider value={value}>
...
</context.Provider>
// Context 의 값을 가져올 때는 useContext() 를 사용
const value = useContext(context);
Redux 와 Context API 의 차이
Redux
에는 미들웨어 개념이 존재Reducer
함수 사용Action
객체가Reducer
에서 처리되기 전에 원하는 작업을 수행하도록 할 수 있음Middleware
는 주로 비동기 작업을 처리할 때 사용
- 함수 및 Hook
Context API
의 경우useReducer
를 사용할 때Context
를 새로 생성하고,Provider
를 설정하고,전용 Custom Hook
을 따로 만들어서 사용Redux
의 경우 상태, 액션 생성 함수를Component의 props
로 받아올 수 있음useSelector
,useDispatch
,useStore
등의 Hook 을 사용해 상태를 조회하거나 디스패치 함Context API
는Context
가 지니고 있는 상태가 바뀌면 해당Context
의 내부Component
들이 모두 리렌더링 되지만,Redux
는 실제 상태가 바뀔 때만 리렌더링
- 상태를 관리할 때
Context API
를 사용해 글로벌 상태를 관리할 때는 기능별로Context
를 만들어서 관리 (필수적인 것은 아님)Redux
에서는 모든 글로벌 상태를 커다란 상태 객체에 넣어서 사용해야 함Redux
에서는 매번Context
를 새로 만드는 수고를 덜 수 있음
React memo
React
는 먼저Component
를렌더링(Rendering)
한 뒤, 이전 렌더링 결과와 비교하여 결과가 다를 경우DOM
을 업데이트 함Component
가React.memo()
로 래핑될 때React
는Component
를 렌더링하고 결과를메모이징(Memoizing)
하며, 다음 렌더링 시props
가 같다면 메모이징된 결과를 가져옴- 컴포넌트의
props
가 바뀌지 않았다면, 리렌더링을 방지하여Component
의 리렌더링 성능 최적화를 해줌 - 렌더링 최적화가 필요하지 않은
Component
에React.memo()
를 사용하는 것은 불필요한props
비교만 하는 것이기 때문에 꼭 필요한 경우에만 사용하는 게 좋음 - 두번째
parameter
에propsAreEqual
함수를 사용하여 특정 값들만 바뀌었는지 비교할 수 있음
// 메모이징된 MemoizedComponent 를 반환
export const MemoizedComponent = React.memo(comp);
Redux actions
CreateAction
- 액션 생성 자동화를 시켜줌
import { createAction } from "redux-actions";
// 액션 생성 함수를 만드는 일반적인 방법
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });
// CreateAction 을 활용하여 액션 생성 함수를 만드는 방법
export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);
handleActions
- 각
Action
마다update
함수를 설정 - 첫번째
parameter
에는 각Action
에 대한update
함수를 넣어주고, 두번째parameter
에는 초기 상태 값을 넣어줌
import { createAction, handleActions } from "redux-actions";
// 초기 상태 값
const initialState = {
number: 0
};
// handleActions 를 활용하여 각 action 마다 update 함수를 설정
const counter = handleActions(
{
[INCREASE]: (state, action) => ({ number: state.number + 1 }),
[DECREASE]: (state, action) => ({ number: state.number - 1 })
},
initialState
);
// switch-case 방법으로 각 action 마다 update 함수를 설정
function counter(state = initialState, action) {
switch (action.type) {
case INCREASE:
return {
number: state.number + 1
};
case DECREASE:
return {
number: state.number - 1
};
default:
return state;
}
}
export default counter;