출처는 공식 문서와 Udemy의 Modern React with Redux [2020 Update] 강의 입니다.
공식 문서 : https://ko.redux.js.org/
Udemy 강의 : https://www.udemy.com/
reducer는 redux의 기초 개념이다.
reducer에는 state(상태)값이 저장되고 action을 통해 변한다.
State v1 + action#1 >> Reducer >> State v2
이러한 reducer를 작성하는데는 몇가지 규칙이 있다.
udemy 강의에서 나온 리듀서의 기본 규칙은 다음과 같다.(영어다)
- Must return any value besides 'undefined'
- Produces 'state', or data to be used inside of your app using only previous state and the action(reducers are pure)
- Must not return reach 'out of itself' to decide what value to return
- Must not mutate its input 'state' argument
처음 리듀서가 호출 될 때
undefined + Acion #1 => Readucer => State v1
다음 리듀서가 호출 될 때
State v1 + Action #2 => Reducer => State v2
export default (state = null, action) => { if(actin.type === 'action#1'){ return action.payload return state }
공식 페이지 설명(출처 : https://ko.redux.js.org/api/combinereducers#notes)j
combineReducers로 전달되는 모든 리듀서는 아래의 규칙을 따라야 한다.
export default (state = null, action) => { // Bad return document.querySelector('input') // Bad return axios.get('/post') }
공식 페이지 설명 (출처 : https://ko.redux.js.org/basics/reducers)
Reducer가 반드시 순수해야 한다는 점만 기억해두자.
인수가 주어지면, 다음 상태를 계산해서 반환하면 된다.예기치 못한 일은 없어야 한다.
사이드 이펙트도 없어야 한다. API 호출도 안된다. 변경도 안된다. 계산만 가능하다.
강의에선 'mutate'라는 단어를 사용했는데 나는 state의 모습을 변경(변형)해서는 안 된다고 해석하였다.
그렇다면 변경(변형)된다는건 무슨 말인가?
변경(변형)은 문자열이나 숫자가 아닌 배열이나 객체에서 일어난다.
최초로 할당한 배열(객체)의 값을 변경하는 것을 'mutate'라고 생각하면 될 것 같다.
배열(colors) 최초의 모습 const colors = ['red', 'green'] //배열 변형 1) 배열의 요소 추가 colors.push('blue') // colors = ['red', 'green', 'blue'] 2) 배열의 요소 삭제 colors.pop() // colors = ['red'] 3)배열의 요소 변경 colors[0] = 'pink' // colors = ['pink', 'green']
객체(profile) 최초의 모습 cosnt profile = { name : 'jordan' } // 객체 변형 1) 객체의 요소 추가 profile.number = 23 // profile = { name: 'jordan', number: 23 } 2) 객체의 요소 삭제 delete profile.name // profile = undefinded 3) 객체의 요소 변경 profile.name = 'sam' // profile = { name: 'sam' }
공식 페이지 설명
(출처 : https://ko.redux.js.org/recipes/structuring-reducers/prerequisite-concepts#immutable-data-management)
변경은 시간 단위 디버깅과 리덕스의 connect함수를 방해하기 때문에 권장되지 않는다.
그 외 리듀서에서 유니크한 ID나 타임스탬프와 같은 것들을 생성하는 것은 코드를 예측할 수 없게 하고 디버그, 테스트를 어렵게 한다.
정리히자면 배열이나 객체와 같은 참조형 데이터의 경우,
내부의 데이터가 바뀌는 것은 새로운 객체가 만들어지는 것이 아니라 기존의 객체 내부의 값이 바뀌는 것이다.
react-redux는 성능을 위해 얇은 참조 검사를 하는데 이때 참조형 데이터는 내용은 바뀌었으나 주솟값(?)은 일치하기 때문에 변화가 된 것을 감지하지 않는다. 그래서 다시 렌더링 되지 않는다. 그래서 배열(객체)을 변화시키는게 아니라 새로운 배열(객체)을 만들고 새로운 값을 추가해야 한다.
reducer 내부의 배열과 객체를 수정할 땐 ES6에서 등장한 Spread구문(전개구문)을 비롯한 여러 메서드가 사용된다.
Spread 구문이란?
전개 구문을 사용하면 배열이나 문자열과 같이 반복 가능한 문자를 0개 이상의인수(함수로 호출할 경우) 또는 요소(배열 리터럴의 경우)로 확장하여, 0개 이상의 키-값의 쌍으로 객체로 확장시킬 수 있다.
출처 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Spread_syntax
filter() 메서드
주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열로 반환한다.
출처 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
map() 메서드
배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결괄르 모아 새로운 배열을 반환한다.
출처 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/map
lodash 라이브러리
객체 및 배열의 작업을 위해 사용되는 매우 인기있는 라이브러리(라고 한다.)
출처 : https://lodash.com/docs/
설명을 보면 무슨말인지 모르겠다. 예시를 보면 이해가 간다.
배열(colors) 최초의 모습 const colors = ['red', 'green'] 1) 배열의 요소 추가 colors.push('blue') // colors = ['red', 'green', 'blue'] //Bad(기존 배열의 요소 변경) [...colors, 'blue'] // colors = ['red', 'green'] // Good(기존 배열의 요소 변경 없음) 2) 배열의 요소 삭제 colors.pop() // colors = ['red'] // Bad colors.filter(element => element != 'green') // Good 3)배열의 요소 변경 colors[0] = 'pink' // colors = ['pink', 'green'] // Bad colors.map((el) => el === 'red' ? 'pink' : el) // Good
객체(profile) 최초의 모습 cosnt profile = { name : 'jordan', age : 30 } 1) 객체의 요소 추가 profile.number = 23 // profile = { name: 'jordan', number: 23 } // Bad { ...state, number:23 } // Good 2) 객체의 요소 삭제 delete profile.name // profile = undefinded // Bad { ...state, age: undefined } // Good _.omit(state, 'age) // Good, lodash 라이브러리를 사용하는 방법. 3) 객체의 요소 변경 profile.name = 'sam' // profile = { name: 'sam' } // Bad { ...state, name : 'Sam' } // Good
Reducer는 action의 type에 따라 그 action의 payload를 반환하거나 기존의 state를 반환한다.
const exampleReducer = (state = null, action ) => { // action의 타입을 평가한다. if (action.type === 'ACTION_NUM1') { return action.payload } return state }
이렇게 코드를 작성해도 작동되지만 reducer를 작성할 때는 주로 switch 구문을 사용한다고 한다.
switch 구문이란?
스위치 구문은 표현식의 값을 case에 따라 평가하고, 그 case와 일치하는 경우 표현식을 실행한다.(?)
(switch는 reducer에서만 사용한 특별한 구문이 아니라 자바스크립트의 구문이다.)
The switch statement evaluates an expression, matching the expression's value to a case clause, and executes statements associated with that case, as well as statements in cases that follow the matching case.
출처 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/switch
const exampleReducer = (state = null, action) => { //action의 type을 평가한다. switch (action.type) { //action의 type이 'ACTION_NUM1' 인가? case 'ACTION_NUM1': // 참(true)이면 해당 액션의 payload를 반환 return action.payload; case 'ACTION_NUM2': return action.payload; case 'ACION_NUM3': return acttion.payload; // 해당하는 action이 없는가? default: // 참(true)이면 기존의 state 값 반환 return state; } }
if와 else if 구문과 같다. 그러나 코드를 작성하기엔 더 편하다.
처음 어플리케이션을 제작할 때 그러니까 (큰 그림을 그렸지만) reducer의 정확한 목적을 정하지 않았을 때,
일단 redux를 사용하기 위해서 기초 세팅을 하고 싶지만 reducer에 무엇을 넣어야 할 지 모를 때,
등등 아무튼 이럴 때 dummy reducer를 만들어 놓고 시작하면 작업하면서 에러를 보는 일이 없다.
(redux를 사용할때 reducer가 비어있으면 에러가 뜬다.)
export default combineReducers({ dummy: () => 'hi there' })
reducer를 배우고 이해한 부분을 작성.
틀린 내용은 당연히 있으니 더 공부하자.