Redux는 페이스북에서 만든 자바스크립트 라이브러리이다. 앞서 말한 Flux의 아이디어를 발전시키되 복잡성을 줄였다. React만 사용해도 되지만 대규모 애플리케이션의 경우 상태 관리하기가 힘들어진다. 그래서 Redux를 사용해 상태 관리를 하는 것이다.
Redux의 개념
Redux의 기본 개념은 Flux의 기본 개념과 동일하다. 언급하지 않은 게 있는데, 바로 구독(Subscribe)과 리듀서(Reducer)라는 개념이다.
구독(Subscribe)
구독은 원하는 스토어를 설정해서 그 스토어에서 값을 받아온다고 생각하면 된다. 개념이 유튜브의 구독과 거의 똑같다. 원하는 채널(스토어)을 구독하고 그 채널의 동영상(데이터)를 본다는 것에 비유하면 이해하기가 쉽다. Redux의 내장함수에는 구독 함수가 존재하지만, react-redux를 사용하면 connect 함수가 대신 해준다. 따라서 직접 구독 함수를 사용할 일은 거의 없다.
리듀서(Reducer)
액션은 무언가가 일어난다는 사실만을 기술한다. 애플리케이션의 상태가 어떻게 바뀌는지 정해주는 것은 리듀서가 할 일이다.
Redux로 카운터 만들기
결과 미리보기
플러스, 마이너스는 표시된 수를 더하고 뺀다. 랜덤 버튼을 누르면 랜덤한 숫자가 표시된다. 정말 심플한 카운터다.
시작하기
React 프로젝트를 생성하고 redux, react-redux를 전부 설치한다. redux는 리덕스 모듈이고, react-redux는 리액트에서 리덕스를 사용하기 위한 보조 패키지이다.
1 2 3 4 5 6 7 8
# React 프로젝트 생성하기 $ create-react-app redux-counter
# 폴더 이동 $ cd redux-counter
# redux, react-redux 전부 설치하기 $ yarn add redux react-redux
const counterApp = combineReducers({ counter // 다른 리듀서를 만들면 이 아래에 적어준다 });
exportdefault counterApp;
switch-case 문으로 각 액션마다 어떻게 상태가 바뀌는지를 정해준다. 이것도 마찬가지로 export를 해주어야 한다. export한 리듀서는 스토어를 만들 때 사용한다.
스토어를 만들고 연동하기
Redux 라이브러리 안에 있는 createStore 함수를 사용해서 스토어를 만들어준다. 스토어는 아까 export한 리듀서로 만들 수 있다. 그리고 react-redux 라이브러리안에 있는 Provider 함수를 사용해서 createStore 함수로 만든 스토어를 연동해주면 된다.
// Provider 사용해서 기존의 App 감싸주기 ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById("root") ); serviceWorker.unregister();
connect 함수를 사용해 스토어 연동하기
마지막으로 컴포넌트에 리덕스 스토어 안에 있는 값과 액션 생성 함수를 연결해주면 된다. 이건 react-redux의 내장함수 connect 함수를 사용하면 된다. connect 함수로 연동한 스토어에서 받아오는 값과 액션 생성 함수는 this.props로 접근이 가능하다.
스토어를 연동하기 전에 mapStateToProps와 mapDispatchProps의 개념을 알아야 한다. mapStateToProps는 스토어 안에 있는 값을 props로 전달해주고, mapDispatchProps는 액션 생성 함수를 props로 전달해준다.
지금 만든 스토어에는 값이 number, 액션 생성 함수는 increment, decrement, random_number가 전부이다. number는 mapStateToProps를 사용해 props로 전달하고, increment, decrement, random_number는 mapDispatchProps를 사용해 props로 전달한다.
이미지를 보고 규칙을 찾아보면, 주어진 수가 n일 때 n - 1, n - 2를 해서 원하는 값들을 찾을 수가 있다.
점화식은 이렇다. dp[i] = dp[i - 2] + dp[i - 1]
Code (JAVA)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
import java.io.*;
publicclassMain{
publicstaticvoidmain(String args[])throws IOException{ BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(br.readLine()); long dp[] = newlong[n + 1];
dp[0] = 0; dp[1] = 1;
for(int i = 2; i <= n; i++) { dp[i] = dp[i - 2] + dp[i - 1]; }
publicstaticvoidmain(String args[])throws IOException{ int[][] dp = newint[101][10]; BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(br.readLine()); int MOD = 1000000000; long sum = 0;
publicstaticvoidmain(String args[])throws IOException{ BufferedReader bf = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(bf.readLine()); long memo[];
memo = newlong[n + 1];
memo[0] = 0; memo[1] = 1;
for(int i = 2; i <= n; i++) { memo[i] = memo[i - 1] + memo[i - 2]; }
자바스크립트의 this는 현재 실행 문맥이다. 여기서 현재 실행 문맥이라는 건 무엇을 뜻할까? 바로 호출자를 뜻한다. 호출자는 크게 세 가지로 나뉘어진다. 첫 번째는 객체의 메소드, 두 번째는 함수, 그리고 마지막으로 생성자 함수이다. 만약 this가 나온다면 이 세 가지 경우 중 하나이니 누가 호출했는지 보고 this가 어떤 의미를 가지는지를 따져보면 된다. 이를 함수 호출 패턴이라고 하며, 자바스크립트의 this 는 함수 호출 패턴에 따라 this 가 어떤 객체의 this 가 될 지 정해진다. 이를 this 바인딩이라고 한다. 바인딩(biding)의 사전적 의미는 묶이다인데, 누가 호출했는지에 따라 this가 호출자에게 묶인다는 의미로 이해하면 그나마 이해하기 쉬워진다.
1. this 바인딩의 호출 패턴
객체의 메소드를 호출할 때의 this 바인딩
함수를 호출할 때의 this 바인딩
생성자 함수를 호출할 때의 this 바인딩
1.1 객체의 메소드 호출할 때 this 바인딩
자신을 호출한 객체에 바인딩이 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13
var obj = { sayName: function() { console.log(this.name); } };
var kim = obj; kim.name = "kim"; kim.sayName(); // "kim"
var lee = obj; lee.name = "lee"; lee.sayName(); // "lee"
하지만 함수 호출에서의 this 바인딩은 내부 함수를 호출할 때도 그대로 적용이 된다. 아래 예제를 보면, 결과가 2,3,4 로 나와야 할 것 같지만 그렇게 나오지 않는다. 왜냐면 자바스크립트가 내부 함수 호출 패턴을 정해놓지 않았기 때문에, 내부 함수도 함수 취급을 받아 이러한 결과가 나온 것이다. 따라서 함수 호출 패턴 패턴 규칙에 따라 내부 함수의 this 는 전역 객체(window)에 바인딩된다.
publicclassMain{ publicstaticvoidmain(String args[])throws IOException{ BufferedReader bf = new BufferedReader(new InputStreamReader(System.in)); int input = Integer.parseInt(bf.readLine()); int number = input; int cnt = 0; do { int left = number / 10; int right = number % 10; int hap = left + right; number = (right * 10) + (hap % 10); cnt++; } while(input != number); System.out.println(cnt); } }
위 테이블을 보면, 1은 만들 필요가 없으니까 0, 2와 3은 2로 3으로 나누어 떨어지고, 몫이 1이 나오니까 횟수가 1이다. 그렇다면 4부터는 어떻게 구할까?
| 인덱스 | 횟수 | | 4 | 3, 1 또는 2, 1 |
4를 1로 만드는 최솟값을 구해야 하는 경우, 4를 1로 뺐을 때 3이 되고, 3의 값은 1이므로 총 두 번이 된다. 하지만 4를 2로 나누는 경우 몫이 2가 되는데, 2의 값은 1이므로 3으로 나누었을 때와 마찬가지로 총 두 번이 된다. 이러나저러나 4를 1로 만드는 최솟값은 2가 되는 것이다.
| 인덱스 | 횟수 | | 5 | 4, 3, 1 또는 4, 2, 1 |
다시 5를 예로 들자면, 5는 3이나 2로 나누어 떨어지지 않으므로 무조건 1을 빼고 시작해야 한다. 5-1=4인데, 4의 횟수는 아까 구한 2이다. 따라서 5의 최솟값은 4의 연산 횟수(2) + 빼기 연산(1) = 3이 된다.
이렇게 10까지 반복하면 아래와 같은 테이블이 만들어지게 된다.
인덱스
횟수
1
0
2
1
3
1
4
3, 1 또는 2, 1
5
4, 3, 1
6
3, 1 또는 2, 1
7
6, 3, 1
8
4, 3, 1
9
3, 1
10
9, 3, 1
각 인덱스의 최솟값을 담은 배열을 A라고 했을 때, 점화식은 다음과 같다.
1을 뺐을 때: A[n - 1] + 1
n % 2 == 0일 때: A[n / 2 == 0] + 1
n % 3 == 0일 때: A[n / 3 == 0] + 1
횟수를 계산해야 하기 때문에 1을 더한다. 각 케이스의 값을 비교해 최소값을 찾아내서 A[n] 배열에 넣어주면 된다.
} return dp[n]; } publicstaticvoidmain(String args[])throws IOException{ BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int N = Integer.parseInt(br.readLine()); dp = newint[N + 1]; System.out.println(dp(N));
} }
Code (Javascript)
역시 백준에서 자바스크립트 입력은… 노우…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
var fs = require('fs'); var input = fs.readFileSync('/dev/stdin').toString().split("\n"); var X = parseInt(input[0]);
// 1부터 3까지의 값을 미리 넣어준다. // 편의를 위해 인덱스는 1부터 시작한다 let dp = [0, 0, 1, 1];
for (let i = 4; i <= X; i++) { // -1을 뺄 때, 2로 나눌 때, 3으로 나눌 때의 값을 비교해 최소값을 알아냄 // -1을 했을 때 dp[i] = dp[i - 1] + 1; // 3으로 나누어 떨어지는 수일 때 if (i % 3 === 0) dp[i] = Math.min(dp[i], dp[i / 3] + 1); // 2로 나누어 떨어지는 수일 때 if (i % 2 === 0) dp[i] = Math.min(dp[i], dp[i / 2] + 1); }