์ฌ๋ณด์ธ์. ์นด์ดํฐ ์์ ๋ฅผ ๋ ๋ฆฝ ์นด์ดํฐ์ ๋์ ๋ชฉ๋ก์ผ๋ก ํ์ฅํ๋ ์ข์ ๋ฐฉ๋ฒ์ ๋ฌด์์ ๋๊น?
๋์ ์ด๋ ์นด์ดํฐ ๋ชฉ๋ก ๋์ ์๋ UI์ ๋ชฉ๋ก์ ์ ์นด์ดํฐ๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ๋ง์ง๋ง ์นด์ดํฐ๋ฅผ ์ ๊ฑฐํ๊ธฐ ์ํ +
๋ฐ -
๋ฒํผ์ด ์์์ ์๋ฏธํฉ๋๋ค.
์ด์์ ์ผ๋ก๋ ์นด์ดํฐ ๊ฐ์๊ธฐ์ ๊ตฌ์ฑ ์์๊ฐ ๊ทธ๋๋ก ์ ์ง๋ฉ๋๋ค. ๋ชจ๋ ์ข ๋ฅ์ ์ํฐํฐ๋ฅผ ์์งํ๊ธฐ ์ํด ์ผ๋ฐํ๋ ๋ชฉ๋ก ์ ์ฅ์+๊ตฌ์ฑ ์์๋ฅผ ์ด๋ป๊ฒ ๋ง๋ค ์ ์์ต๋๊น? todomvc-example ์์ ์นด์ดํฐ์ ํ ์ผ ํญ๋ชฉ์ ๋ชจ๋ ๊ฐ์ ธ์ค๊ธฐ ์ํด ๋ชฉ๋ก ์ ์ฅ์+๊ตฌ์ฑ ์์๋ฅผ ๋์ฑ ์ผ๋ฐํํ ์ ์์ต๋๊น?
์์ ์ ์ด์ ๊ฐ์ ๊ฒ์ด ์์ผ๋ฉด ์ข์ ๊ฒ์ ๋๋ค.
๋๋ ์ด๊ฒ์ด ์ข์ ์๋ผ๋ ๋ฐ ๋์ํฉ๋๋ค.
Redux๋ ์ฌ๊ธฐ์์ Elm Architecture ์ ์ ์ฌํ๋ฏ๋ก ์์ ๋กญ๊ฒ ์์ ์์ ์๊ฐ์ ์ป์ ์ ์์ต๋๋ค.
๋ชจ๋ ์ข ๋ฅ์ ์ํฐํฐ๋ฅผ ์์งํ๊ธฐ ์ํด ์ผ๋ฐํ๋ ๋ชฉ๋ก ์ ์ฅ์+๊ตฌ์ฑ ์์๋ฅผ ์ด๋ป๊ฒ ๋ง๋ค ์ ์์ต๋๊น? todomvc-example์์ ์นด์ดํฐ์ ํ ์ผ ํญ๋ชฉ์ ๋ชจ๋ ๊ฐ์ ธ์ค๊ธฐ ์ํด ๋ชฉ๋ก ์ ์ฅ์+๊ตฌ์ฑ ์์๋ฅผ ๋์ฑ ์ผ๋ฐํํ ์ ์์ต๋๊น?
์, ํ์คํ ๊ฐ๋ฅํฉ๋๋ค.
๊ณ ์ฐจ ๊ฐ์๊ธฐ์ ๊ณ ์ฐจ ๊ตฌ์ฑ ์์๋ฅผ ๋ง๋ค๊ณ ์ถ์ต๋๋ค.
๊ณ ์ฐจ ๊ฐ์๊ธฐ์ ๊ฒฝ์ฐ ์ฌ๊ธฐ์์ ์ ๊ทผ ๋ฐฉ์์ ์ฐธ์กฐํ์ญ์์ค.
function list(reducer, actionTypes) {
return function (state = [], action) {
switch (action.type) {
case actionTypes.add:
return [...state, reducer(undefined, action)];
case actionTypes.remove:
return [...state.slice(0, action.index), ...state.slice(action.index + 1)];
default:
const { index, ...rest } = action;
if (typeof index !== 'undefined') {
return state.map(item => reducer(item, rest));
}
return state;
}
}
}
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return counter + 1;
case 'DECREMENT':
return counter - 1;
}
}
const listOfCounters = list(counter, {
add: 'ADD_COUNTER',
remove: 'REMOVE_COUNTER'
});
const store = createStore(listOfCounters);
store.dispatch({
type: 'ADD_COUNTER'
});
store.dispatch({
type: 'ADD_COUNTER'
});
store.dispatch({
type: 'INCREMENT',
index: 0
});
store.dispatch({
type: 'INCREMENT',
index: 1
});
store.dispatch({
type: 'REMOVE_COUNTER',
index: 0
});
(์คํํ์ง๋ ์์์ง๋ง ์ต์ํ์ ๋ณ๊ฒฝ์ผ๋ก ์๋ํด์ผ ํฉ๋๋ค.)
๊ฐ์ฌํฉ๋๋ค. ์ด ์ ๊ทผ ๋ฐฉ์์ด ์๋ํ๋๋ก ๋ ธ๋ ฅํ๊ฒ ์ต๋๋ค.
๋๋ ์ฌ์ ํ combineReducers
๋ฅผ ํตํด ๋ชฉ๋ก ๊ธฐ๋ฅ์ ์ฌ์ฌ์ฉํ์ฌ ์นด์ดํฐ ๋ชฉ๋ก๊ณผ ํ ์ผ ํญ๋ชฉ ๋ชฉ๋ก์ ๋ ๋ค ๊ฐ์ง ์ ์๋์ง ๊ถ๊ธํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ๊ฒ์ด ์๋ฏธ๊ฐ ์๋ค๋ฉด. ๊ทธ๋ฌ๋ ๋๋ ํ์คํ ๊ทธ๊ฒ์ ์๋ ํ ๊ฒ์
๋๋ค.
๋๋ ์ฌ์ ํ ์นด์ดํฐ ๋ชฉ๋ก๊ณผ ํ ์ผ ํญ๋ชฉ ๋ชฉ๋ก์ ๋ชจ๋ ๊ฐ๊ธฐ ์ํด CombineReducers๋ฅผ ํตํด ๋ชฉ๋ก ๊ธฐ๋ฅ์ ์ฌ์ฌ์ฉํ ์ ์๋์ง ๊ถ๊ธํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ๊ฒ์ด ์๋ฏธ๊ฐ ์๋ค๋ฉด. ๊ทธ๋ฌ๋ ๋๋ ํ์คํ ๊ทธ๊ฒ์ ์๋ ํ ๊ฒ์ ๋๋ค.
์, ์์ ํ:
const reducer = combineReducers({
counterList: list(counter, {
add: 'ADD_COUNTER',
remove: 'REMOVE_COUNTER'
}),
todoList: list(counter, {
add: 'ADD_TODO',
remove: 'REMOVE_TODO'
}),
});
@gaearon ๋ชฉ๋ก ์์
์ index
๋ฅผ ์ด๋ป๊ฒ ์ป์ด์ผ ํฉ๋๊น?
๊ทํ์ ์ง์์ ๋ฐ๋ผ ๊ณ ์ฐจ ๊ฐ์๊ธฐ๋ฅผ ๋ง๋ค ์ ์์์ง๋ง ๊ณ ์ฐจ ๊ตฌ์ฑ ์์๋ก ์ธํด ์ด๋ ค์์ ๊ฒช๊ณ ์์ต๋๋ค. ํ์ฌ ์ฐ๋ฆฌ์ ๊ตฌ์ฑ ์์๋ Counter ์ด์ธ์ ๋ค๋ฅธ ๊ตฌ์ฑ ์์์ ํจ๊ป ์ฌ์ฉํ๊ธฐ์ ์ถฉ๋ถํ ์ผ๋ฐ์ ์ด์ง ์์ต๋๋ค. ์ฐ๋ฆฌ์ ๋ฌธ์ ๋ ์ผ๋ฐ์ ์ธ ๋ฐฉ์์ผ๋ก ์์ ์ ์ธ๋ฑ์ค๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
์ฌ๊ธฐ์์ ์๋ฃจ์ ์ ๋ณผ ์ ์์ต๋๋ค. https://github.com/Zeikko/redux/commit/6a222885c8c93950dbdd0d4cf3532cd99a32206c
๋ฌธ์ ๊ฐ ๋๋ ๋ถ๋ถ์ ๊ฐ์กฐํ๊ธฐ ์ํด ์ปค๋ฐ์ ๋ช ๊ฐ์ง ์ฃผ์์ ์ถ๊ฐํ์ต๋๋ค.
์นด์ดํฐ ๋ชฉ๋ก ๋ชฉ๋ก์ ์ํํ ์ ์๋ ์ผ๋ฐ ๋ชฉ๋ก ๋ฆฌ๋์ + ๊ตฌ์ฑ ์์๊ฐ ์์ผ๋ฉด ์ข์ ๊ฒ์ ๋๋ค.
ํ์ฌ ์ฐ๋ฆฌ์ ๊ตฌ์ฑ ์์๋ Counter ์ด์ธ์ ๋ค๋ฅธ ๊ตฌ์ฑ ์์์ ํจ๊ป ์ฌ์ฉํ๊ธฐ์ ์ถฉ๋ถํ ์ผ๋ฐ์ ์ด์ง ์์ต๋๋ค. ์ฐ๋ฆฌ์ ๋ฌธ์ ๋ ์ผ๋ฐ์ ์ธ ๋ฐฉ์์ผ๋ก ์์ ์ ์ธ๋ฑ์ค๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
"์ผ๋ฐ์ ์ธ ๋ฐฉ์์ผ๋ก ์ธ๋ฑ์ค ์ถ๊ฐ"๊ฐ ๋ฌด์์ ์๋ฏธํ๋์ง ์ค๋ช
ํ ์ ์์ต๋๊น? ์์
์์ index
ํค์ ๋ํด ๋ค๋ฅธ ์ด๋ฆ์ ์ฌ์ฉํ๊ณ ์ถ์ผ์ญ๋๊น?
์ด์ ๋ฌด์จ ๋ง์ธ์ง ์ ๊ฒ ๊ฐ์์.
์ง๊ธ์ ๋๊ธ์ ๋ง์ด ๋ชป์จ์ ์ฃ์กํฉ๋๋ค. ๋๋ ๋ด์ผ ๋์์ฌ ๊ฒ์ด๋ค.
์ด์ ๋ฌธ์ ๋ฅผ ์ดํดํฉ๋๋ค. ๊ทธ๊ฒ์ ๋ํด ์ฐพ๊ณ ์์ต๋๋ค.
Redux๊ฐ Elm ์ํคํ
์ฒ์์ ๋ฒ์ด๋๋ ๋ฐฉ์์ ๋ด์ฌ๋ ๋ช ๊ฐ์ง ์ ํ ์ฌํญ์ ๋น ๋ฅด๊ฒ ๋ถ๋ช์ณค์ต๋๋ค.
๋ด๊ฐ ์ ์ ๊ทธ๋ค์ ์ดํดํ์ง ๋ชปํ๋ค๋ ์ ๊ฐ์
๋๋ค!
dispatch
์ํ์ ์๋ฝํด์ผ ํฉ๋๋ค. ์ฆ, ์ก์
์์ฑ์๋ฅผ ์ํ์ผ๋ก ๋ง๋๋ ๊ฒ์ ํผํ๊ณ ๊ตฌ์ฑ๋ ๊ตฌ์ฑ ์์์์ dispatch()
๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ connect()
bindActionCreators(Component, actionCreators) => Component
๋ฅผ ๋์
ํด์ผ ํฉ๋๋ค. ๊ทธ๋ฌ๋ ์ค์ ๋ก ์์ ์ ์ฐ๊ฒฐํ์ง ์๊ณ ๋์ action-creator-as-props-wanting-component๋ฅผ this.props.dispatch
-wanting-component๋ก ๋์ฒดํฉ๋๋ค.function (dispatch, getState) { ... }
$์ด ๋ชฉ๋ก์ ์ํด { action: function (dispatch, getState) { ... } }
๋ก ๋ฐ๋๊ธฐ ๋๋ฌธ์ ๋ ์ด์ incrementAsync()
๋ฅผ ๋ฐ์กํ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ bam! ์ฝํฌ ๋ฏธ๋ค์จ์ด๋ ๋ ์ด์ ๊ทธ๊ฒ์ ์ธ์ํ์ง ๋ชปํฉ๋๋ค.์ด๊ฒ์ ๋ํ ์ด์ํ์ง ์์ ์๋ฃจ์
์ด ์์ ์ ์์ง๋ง ์์ง ๋ณด์ง ๋ชปํ์ต๋๋ค.
์ง๊ธ์ ์ด ์ปค๋ฐ ์ ์์๋ก ๋ณด์ญ์์ค(์์ ์ค๋ช
๋ ์ ํ ์ฌํญ ํฌํจ).
์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
import React, { Component, PropTypes } from 'react';
import { increment, incrementIfOdd, incrementAsync, decrement } from '../actions/counter';
class Counter extends Component {
render() {
const { dispatch, counter } = this.props;
return (
<p>
Clicked: {counter} times
{' '}
<button onClick={() => dispatch(increment())}>+</button>
{' '}
<button onClick={() => dispatch(decrement())}>-</button>
{' '}
<button onClick={() => dispatch(incrementIfOdd())}>Increment if odd</button>
{' '}
<button onClick={() => dispatch(incrementAsync())}>Increment async</button>
</p>
);
}
}
Counter.propTypes = {
dispatch: PropTypes.func.isRequired,
counter: PropTypes.number.isRequired
};
export default Counter;
import React, { Component, PropTypes } from 'react';
import { addToList, removeFromList, performInList } from '../actions/list';
export default function list(mapItemStateToProps) {
return function (Item) {
return class List extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
items: PropTypes.array.isRequired
};
render() {
const { dispatch, items } = this.props;
return (
<div>
<button onClick={() =>
dispatch(addToList())
}>Add counter</button>
<br />
{items.length > 0 &&
<button onClick={() =>
dispatch(removeFromList(items.length - 1))
}>Remove counter</button>
}
<br />
{this.props.items.map((item, index) =>
<Item {...mapItemStateToProps(item)}
key={index}
dispatch={action =>
dispatch(performInList(index, action))
} />
)}
</div>
)
}
}
};
}
export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';
export function increment() {
return {
type: INCREMENT_COUNTER
};
}
export function decrement() {
return {
type: DECREMENT_COUNTER
};
}
export function incrementIfOdd() {
return (dispatch, getState) => {
const { counter } = getState();
if (counter % 2 === 0) {
return;
}
dispatch(increment());
};
}
export function incrementAsync(delay = 1000) {
return dispatch => {
setTimeout(() => {
dispatch(increment());
}, delay);
};
}
export const ADD_TO_LIST = 'ADD_TO_LIST';
export const REMOVE_FROM_LIST = 'REMOVE_FROM_LIST';
export const PERFORM_IN_LIST = 'PERFORM_IN_LIST';
export function addToList() {
return {
type: ADD_TO_LIST
};
}
export function removeFromList(index) {
return {
type: REMOVE_FROM_LIST,
index
};
}
export function performInList(index, action) {
return {
type: PERFORM_IN_LIST,
index,
action
};
}
import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../actions/counter';
export default function counter(state = 0, action) {
switch (action.type) {
case INCREMENT_COUNTER:
return state + 1;
case DECREMENT_COUNTER:
return state - 1;
default:
return state;
}
}
import { ADD_TO_LIST, REMOVE_FROM_LIST, PERFORM_IN_LIST } from '../actions/list';
export default function list(reducer) {
return function (state = [], action) {
const {
index,
action: innerAction
} = action;
switch (action.type) {
case ADD_TO_LIST:
return [
...state,
reducer(undefined, action)
];
case REMOVE_FROM_LIST:
return [
...state.slice(0, index),
...state.slice(index + 1)
];
case PERFORM_IN_LIST:
return [
...state.slice(0, index),
reducer(state[index], innerAction),
...state.slice(index + 1)
];
default:
return state;
}
}
}
import { combineReducers } from 'redux';
import counter from './counter';
import list from './list'
const counterList = list(counter);
const rootReducer = combineReducers({
counterList
});
export default rootReducer;
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import list from '../components/list';
const CounterList = list(function mapItemStateToProps(itemState) {
return {
counter: itemState
};
})(Counter);
export default connect(function mapStateToProps(state) {
return {
items: state.counterList
};
})(CounterList);
cc @acdlite โ ํ์ฌ ๋ฏธ๋ค์จ์ด + React Redux ๋์์ธ์ด ๋ค์ ๋ฌด๋์ง ์์
๋๋ค.
์ด๊ฒ์ wontfix๋ก ์ ์ธํ ์ ์์ง๋ง ์ฐ๋ฆฌ๊ฐ ์ด๊ฒ์ ํผํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์๋์ง ์ดํด๋ณด๊ณ ์ถ์ ์๋ ์์ต๋๋ค.
์ผ ๊ฒ์ด๋ค
https://github.com/erikras/multireducer/ ๋์๋ง?
์ ๋ React์ฉ ์๋น์ค(IoC) ์ปจํ ์ด๋ ์ฌ์ฉ์ ์คํํด ์์ผ๋ฉฐ ์ด์ ์ด ํ ์คํธ ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ๋ง๋ค์์ต๋๋ค. https://github.com/magnusjt/react-ioc
CounterList๊ฐ ์์ง ๋ชปํ๋ ์ํ์์ ์์ ์์ฑ์๋ฅผ Counter์ ์ ๋ฌํ ์ ์๊ธฐ ๋๋ฌธ์ ์ ์ฌ์ ์ผ๋ก ๋ฌธ์ ์ ์ผ๋ถ๋ฅผ ํด๊ฒฐํ ์ ์๋ค๊ณ ์๊ฐํฉ๋๋ค. ์ด๊ฒ์ ์ก์ ์์ฑ์๊ฐ props๊ฐ ์๋ Counter์ ์์ฑ์์ ๋ค์ด๊ฐ๊ธฐ ๋๋ฌธ์ ๊ฐ๋ฅํฉ๋๋ค.
์๋ก ๋ง๋๋ ๋ชจ๋ Counter ๊ตฌ์ฑ ์์์ ๋ํด ๋ค๋ฅธ ์์ ์์ฑ์๋ฅผ ์ ๋ฌํ ์ ์์ต๋๋ค(์๋ง๋ ์ธ๋ฑ์ค ๊ฐ์ ์์ ์์ฑ์์ ๋ฐ์ธ๋ฉํ์ฌ). ๋ฌผ๋ก ๋ฐ์ดํฐ๋ฅผ ์นด์ดํฐ๋ก ๊ฐ์ ธ์ค๋ ๋ฐ ์ฌ์ ํ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ๊ทธ๊ฒ์ด ์๋น์ค ์ปจํ ์ด๋๋ก ํด๊ฒฐํ ์ ์๋ ๊ฒ์ธ์ง๋ ์์ง ํ์คํ์ง ์์ต๋๋ค.
@gaearon , ๋น์ ์ ์๋ ๋์๊ฒ ๋ฑ ๋ง๋ ๊ฒ ๊ฐ์ต๋๋ค. ์ก์ ์์ฑ์๋ฅผ ์ ๋ฌํ๊ณ ์๋๋ก ํ๊ฒฌํด์ผ ํฉ๋๋ค. ์ด๋ฐ ์์ผ๋ก ๊ณ ์ฐจ์ ๊ธฐ๋ฅ์ผ๋ก ์ก์ ์ ์ ๋จํ ์ ์์ต๋๋ค.
๋๋ ๋น์ ์ ๋ ๋ฒ์งธ ์์ ์ด ํ์ํ์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค. ์๋ก์ด ๋ฉ์์ง ํ์์ผ๋ก ์ธํด ๋ฏธ๋ค์จ์ด๋ฅผ ๋์น ์ ์์ง๋ง performInList
์ ๋ ํฐ ๋ฌธ์ ๋ ์ถ์ํ๋ฅผ ํ๋์ ๋ชฉ๋ก์ผ๋ก ์ ํํ๋ค๋ ๊ฒ์
๋๋ค. @pe3 ์ ์นด์ดํฐ ๋ชฉ๋ก ๋ชฉ๋ก์ ์ธ๊ธํ์ต๋๋ค. ์ด์ ๊ฐ์ ์์์ ์ถ์ํ๋ฅผ ์ํด์๋ ์ด๋ป๊ฒ๋ ์ก์
์ ์ค์ฒฉํด์ผ ํ ํ์๊ฐ ์๋ค๊ณ ์๊ฐํฉ๋๋ค.
์ด ํฐ์ผ์์ ์ ํ์ ์ค์ฒฉํ๋ ๋ฐฉ๋ฒ์ ์๊ฐํด ๋์ต๋๋ค.
https://github.com/rackt/redux/issues/897
๊ทธ๋ฌ๋ ๋ ๊ทผ๋ณธ์ ์ผ๋ก, ๋น์ ์ ํ๋์ ์์ ํ ์ค์ฒฉํ๊ณ ์ถ์ ๊ฒ์ ๋๋ค....
์ข์, ๋ฐฉ๊ธ ํด๋ดค์ด.
๋๋ ๋ฌผ๊ฑด์ ๊ฝค ๋จ์ํํ์ต๋๋ค. ์ด ๋ฉ์ง ์ผ์ ํ๊ธฐ ์ ์ ์นด์ดํฐ ์ฑ์ด ์์ต๋๋ค.
๋ค์์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๋๋ "๋ฆฌํํธ"๊ฐ ์ ์ ํ ์ฉ์ด์ธ์ง ํ์คํ์ง ์์ต๋๋ค. ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์์ ๊ทธ๊ฒ์ด ์๋ฏธํ๋ ๋ฐ๊ฐ ์๋ค๋ ๊ฒ์ ์๊ณ ์์ง๋ง ๋์๊ฒ๋ ๊ด์ฐฎ๋ค๊ณ ๋๊ผ์ต๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ์์ ์ ํด์ ํ๋ฉด ๋ค๋ฅธ ์์ ์์ ์์ ์ ์ค์ฒฉํ๊ฒ ๋ฉ๋๋ค.
const liftActionCreator = liftingAction => actionCreator => action => Object.assign({}, liftingAction, { nextAction: actionCreator(action) })
๊ทธ๋ฆฌ๊ณ ๋ดํฌ๋ ๋์์ ๊ฐ์๊ธฐ๋ฅผ ๋ค์ด ์ฌ๋ ค ์ ๊ฑฐ๋ฉ๋๋ค. ๋ฆฌํํ ๊ฐ์๊ธฐ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํ์ ๊ฐ์๊ธฐ(์ ์ ํ ๋์์ผ๋ก ๋ถ๋ถ์ ์ผ๋ก ์ ์ฉ๋จ)๋ฅผ ์ผ๋ถ ํ์ ์ํ์ ์ ์ฉํฉ๋๋ค.
const liftReducer = liftingReducer => reducer => (state, action) => liftingReducer(state, action)((subState) => reducer(subState, action.nextAction))
๋ฐ๋ผ์ ๊ตฌ์ฑ ์์ ๋ชฉ๋ก ๊ฐ์๊ธฐ์ ๊ฒฝ์ฐ ํ์ ์์ ์ด ์ ์ฉ๋๋ ์ธ๋ฑ์ค์ ๊ตฌ์ฑ ์์๋ฅผ ์ง์ ํ๋ ์์ ์ด ์์ต๋๋ค.
// list actions
const LIST_INDEX = 'LIST_INDEX'
function actOnIndex(i) {
return {
type: LIST_INDEX,
index: i
}
}
๊ทธ๋ฆฌ๊ณ ํ์ ๊ฐ์๊ธฐ๋ฅผ ์ ์ ํ ํ์ ์ํ์ ์ ์ฉํ๋ "๊ณ ์ฐจ"(๋ฐ๋ก ๋๋์ด ๋๋ ๋ ๋ค๋ฅธ ๋ฉ์ง ์ฉ์ด, ํํ ;) ๊ฐ์๊ธฐ๊ฐ ์์ต๋๋ค.
const list = (state=[], action) => (reduce) => {
switch (action.type) {
case LIST_INDEX:
let nextState = state.slice(0)
nextState[action.index] = reduce(nextState[action.index])
return nextState
default:
return state;
}
}
๊ทธ๋ฆฌ๊ณ ๋จ์ ๊ฒ์ ์นด์ดํธ ๊ฐ์๊ธฐ๋ฅผ ๋ชฉ๋ก ๊ฐ์๊ธฐ๋ก "๋ค์ด ์ฌ๋ฆฌ๋" ๊ฒ์ ๋๋ค.
const reducer = combineReducers({
counts: liftReducer(list)(count)
});
์ด์ ์นด์ดํฐ ๋ชฉ๋ก์ ๋ํด ์นด์ดํฐ๋ก ์ ๋ฌํ ๋ ์์ ์ ํด์ ํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
class App extends Component {
render() {
const counters = [0,1,2,3,4].map((i) => {
return (
<Counter count={this.props.state.counts[i]}
increment={liftActionCreator(actOnIndex(i))(increment)}
decrement={liftActionCreator(actOnIndex(i))(decrement)}
dispatch={this.props.dispatch}
key={i}/>
)
})
return (
<div>
{counters}
</div>
);
}
}
๋๋ ์ด๊ฒ์ด ์ ์ ํ ์ฉ์ด๋ก ๋ ๊ณต์ํ ๋ ์ ์๋ค๊ณ ์๊ฐํฉ๋๋ค. ์ฌ๊ธฐ ๊ณ ์ฐจ ๋ฆฌ๋์์๋ ๋ ์ฆ๋ฅผ ์ธ ์ ์์ ๊ฒ ๊ฐ์๋ฐ, ์ ๋๋ก ์จ๋ณธ ์ ์ด ์๊ตฐ์, ํํ.
๊ทธ๋ฆฌ๊ณ ์ ๋ ์ง๋๋ฒ ๋๊ธ์์ ์ ๊ฐ ํ ๋ง์ ๋๋๋ฆฝ๋๋ค -- @gaearon ์ด ์ณ์ต๋๋ค. ์ด์ ๊ฐ์ด ์์ ์ ์ค์ฒฉํ๋ฉด ๋ฏธ๋ค์จ์ด๋ฅผ ๋์น๊ฒ ๋๊ณ ์์ ์์ฑ์๋ฅผ ์กฐ์ํ ์ ์๋๋ก ๋์คํจ์น๋ฅผ โโ๋๊น์ง ์ ๋ฌํด์ผ ํฉ๋๋ค. ์ด๋ฅผ ์ง์ํ๊ธฐ ์ํด Redux๋ ๋ฏธ๋ค์จ์ด๋ฅผ ํตํด ๋ชจ๋ ํ์ ์์ ์ ์ ์ฉํด์ผ ํฉ๋๋ค. ๋ํ, ๋ ๋ค๋ฅธ ๋ฌธ์ ๋ ๋ชฉ๋ก ๋ด์ ์ํ๋ฅผ ์ด๊ธฐํํ๋ ๊ฒ์ ๋๋ค...
๋น์ ์ด ์ค๋ช ํ๋ ๊ฒ์ Elm Architecture๋ก ์๋ ค์ ธ ์์ต๋๋ค. ์ฌ๊ธฐ๋ฅผ ์ฐธ์กฐํ์ญ์์ค: https://github.com/gaearon/react-elmish-example
์น๊ตฌ, ๋น์ ์ ํญ์ ํ๋ฐ ์์ ์์ต๋๋ค! ๋ฉ์ง ๊ฒ๋ค์ ๋ํ ๋งํฌ๋ก ์ ์๊ฒ ์ค์ํ์ธ์ :+1:
๋ํ๋ ๊ตฌ์ฑ ์์์์ ๋ฏธ๋ค์จ์ด๊ฐ ํ์ํ ์์ ์ ์ ๋ฌํ ์ ์์ต๋๋ค. ์ด๊ฒ์ ๋ถ๋๋ฌ์ด ์ผ์ ๋๋ค! ๋ชฉ๋ก์ผ๋ก ์นด์ดํฐ๋ฅผ ๋ํํ๋ฉด function (dispatch, getState) { ... } ๋ฐฉ๊ธ ๋์คํจ์นํ ๊ฒ์ด { action: function (dispatch, getState) { ... } }๋ก ๋ฐ๋์๊ธฐ ๋๋ฌธ์ ๋ ์ด์ incrementAsync()๋ฅผ ๋์คํจ์นํ ์ ์์ต๋๋ค. ๋ชฉ๋ก๊ณผ bam! ์ฝํฌ ๋ฏธ๋ค์จ์ด๋ ๋ ์ด์ ๊ทธ๊ฒ์ ์ธ์ํ์ง ๋ชปํฉ๋๋ค.
@gaearon ์ด ์๋ฃจ์ ์ ์ด๋ป์ต๋๊น? ๋ค์๊ณผ ๊ฐ์ด ์์ ๊ฐ์๊ธฐ๋ฅผ ํธ์ถํ๋ ์ผ๋ฐ ๊ฐ์๊ธฐ ๋์
case PERFORM_IN_LIST:
return [
...state.slice(0, index),
reducer(state[index], innerAction),
...state.slice(index + 1)
];
์์ ์ dispatch
์ฒ๋ผ ์๋ํ๋ ๋ช ๊ฐ์ง ํน๋ณํ ๋ฉ์๋ dispatchTo(reducer, state, action, callback)
์ ๊ณต
export default function list(reducer, dispatchTo) {
return function (state = [], action) {
...
case PERFORM_IN_LIST:
dispatchTo(reducer, state[index], innerAction, newState =>
[
...state.slice(0, index),
newState,
...state.slice(index + 1)
]);
default:
return state;
}
}
}
์ด๊ฒ์ด Redux์์ ๊ฐ๋ฅํ์ง ๋ชจ๋ฅด๊ฒ ์ต๋๋ค. ์์ด๋์ด๋ ์ผ๋ถ ๋ฏธ๋ค์จ์ด๋ฅผ ๋ฃจํธ ์ ์ฅ์๋ก ์ฌ์ฉํ์ฌ ๊ตฌ์ฑ๋ ์ํ ํธ๋ฆฌ์ ํด๋น ๋ถ๋ถ์ ๋ํ ํ์ ์ ์ฅ์ ๋ฅผ ๋ฐํํ๋ ์ผ๋ถ ๋ด๋ถ store.derive(reducer, state)
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ dispatchTo
๋ฅผ ๊ตฌํํ๋ ๊ฒ์
๋๋ค. ์๋ฅผ ๋ค์ด
function dispatchTo(reducer, state, action, callback) {
const childStore = store.derive(reducer, state)
childStore.subscribe(() => setRootState( callback(getState() ))
childStore.dispatch(action)
}
์ด๊ฒ์ ๋ด๊ฐ Redux์ ๋ด๋ถ๋ฅผ ์์ง ๋ชปํ๋ค๊ณ ๋งํ๊ธฐ ๋๋ฌธ์ ๋จ์ง ์์ด๋์ด์ผ ๋ฟ์ ๋๋ค. ๊ทธ๋์ ์๋ง๋ ์ ๊ฐ ๋ญ๊ฐ๋ฅผ ๋์ณค์ ๊ฒ์ ๋๋ค.
ํธ์งํ๋ค
_์๋ง๋ ์ด๊ฒ์ ๊ฐ์๊ธฐ ๋ฉ์๋๊ฐ ๋๊ธฐ์์ด์ด์ผ ํ๊ธฐ ๋๋ฌธ์ ์ด์ํ ๊ฒ์
๋๋ค. ๋ฉ์๋๋ฅผ ๋น๋๊ธฐ์์ผ๋ก ๋ง๋๋ ๊ฒ์ ๋ชจ๋ ์ฐ๊ฒฐ๋ ๋น๋๊ธฐ์์ด์ด์ผ ํจ์ ์๋ฏธํฉ๋๋ค.
์๋ง๋ ๊ฐ์ฅ ์ข์ ํด๊ฒฐ์ฑ
์ store.derive(reducer)
๋ฉ์๋๋ฅผ ์ง์ ๋
ธ์ถํ๊ณ ์ผ์ข
์ Store ๊ตฌ์ฑ์ ์ฌ์ฉํ์ฌ ์ผ๋ฐ ๊ฐ์๊ธฐ๋ฅผ ๋น๋ํ๋ ๊ฒ์
๋๋ค.
๊ทธ๊ฒ์ ๋๋ฌด ๋ง์ ํฉ๋ณ์ฆ์ด๋ฉฐ IMO์ ๊ฐ์น๊ฐ ์์ต๋๋ค. ์ด๋ฐ ์์ผ๋ก ํ๊ณ ์ถ๋ค๋ฉด ๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํ์ง ๋ง์ญ์์ค(๋๋ applyMiddleware
์ ๋์ฒด ๊ตฌํ์ ์ฌ์ฉํ์ญ์์ค). ๊ทธ๋ฌ๋ฉด ๋ชจ๋ ์ค๋น๊ฐ ์๋ฃ๋ฉ๋๋ค.
๋ํ, ์ด์ ๋ํด ์กฐ์น๋ฅผ ์ทจํ ๊ณํ์ด ์๊ธฐ ๋๋ฌธ์ ๋ซ์ต๋๋ค.
ํ ๋ก ์ ์ํด @gaearon :
์ด ์ปค๋ฐ์ ์ฒจ๋ถํ ์ฝ๋ ์์ : https://github.com/rackt/redux/commit/a83002aed8e36f901ebb5f139dd14ce9c2e4cab4
2๊ฐ์ ์นด์ดํฐ ๋ชฉ๋ก(๋๋ ๋ณ๋์ '๋ชจ๋ธ')์ด ์๋ ๊ฒฝ์ฐ addToList
๋ฅผ ๋ฐ์กํ๋ฉด ๋ ๋ชฉ๋ก์ ํญ๋ชฉ์ด ์ถ๊ฐ๋ฉ๋๋ค. ์๋ํ๋ฉด ์์
์ ํ์ด ๋์ผํ๊ธฐ ๋๋ฌธ์
๋๋ค.
// reducers/index.js
import { combineReducers } from 'redux';
import counter from './counter';
import list from './list'
const counterList = list(counter);
const counterList2 = list(counter);
const rootReducer = combineReducers({
counterList,
counterList2
});
export default rootReducer;
์ฌ๊ธฐ์ reducers/list
๊ฐ ์ด๋ป๊ฒ ๋์์ด ๋๋์? ์์
์ ํ์ด๋ ๋ค๋ฅธ ๊ฒ์ ์ ๋์ฌ๋ฅผ ๋ถ์ผ ํ์๊ฐ ์์ต๋๊น?
2๊ฐ์ ์นด์ดํฐ ๋ชฉ๋ก(๋๋ ๋ณ๋์ '๋ชจ๋ธ')์ด ์๋ ๊ฒฝ์ฐ addToList๋ฅผ ์ ๋ฌํ๋ฉด ์์ ์ ํ์ด ๋์ผํ๊ธฐ ๋๋ฌธ์ ๋ ๋ชฉ๋ก์ ํญ๋ชฉ์ด ์ถ๊ฐ๋ฉ๋๋ค.
a83002aed8e36f901ebb5f139dd14ce9c2e4cab4๋ฅผ ์์ธํ ์ดํด๋ณด์ญ์์ค. ๊ทธ๊ฒ์ ํ๋์ ์ค์ฒฉํฉ๋๋ค. ์ปจํ
์ด๋ ๊ตฌ์ฑ ์์์์ ์ ๋ฌ๋ dispatch
๋ ์ก์
์ performInList
์ก์
์ผ๋ก ๋ํํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ๋ด๋ถ ์์
์ด ๊ฐ์๊ธฐ์์ ๊ฒ์๋ฉ๋๋ค. ์ด๊ฒ์ด Elm Architecture๊ฐ ์๋ํ๋ ๋ฐฉ์์
๋๋ค.
@gaearon ์ด์ฉ๋ฉด ๋ด๊ฐ ๋ญ๊ฐ๋ฅผ ๋์น๊ณ ์์์ง๋ ๋ชจ๋ฅด์ง๋ง ์์์ ์ธ๊ธํ ์ถ๊ฐ counterList2
๋ฆฌ๋์์ ํจ๊ป ์ด UI๋ ์ฌ์ ํ ๊ฐ ์์
์ ๋ํ ๋ ๋ชฉ๋ก์ ๋ชจ๋ ์
๋ฐ์ดํธํฉ๋๋ค(๊ตฌ์ฑ ๋ฐฉ์์ ๋ฐ๋ผ ์์๋์ง๋ง ์๋ฃจ์
์ ๋ฌด์์ธ๊ฐ์?):
// reducers/index.js
import { combineReducers } from 'redux';
import counter from './counter';
import list from './list'
const counterList = list(counter);
const counterList2 = list(counter);
const rootReducer = combineReducers({
counterList,
counterList2
});
export default rootReducer;
// containers/App.js
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import counter from '../components/Counter';
import list from '../components/list';
let CounterList = list(function mapItemStateToProps(itemState) {
return {
counter: itemState
};
})(counter);
CounterList = connect(function mapStateToProps(state) {
return {
items: state.counterList
};
})(CounterList);
let CounterList2 = list(function mapItemStateToProps(itemState) {
return {
counter: itemState
};
})(counter);
CounterList2 = connect(function mapStateToProps(state) {
return {
items: state.counterList2
};
})(CounterList2);
export default class App extends React.Component {
render() {
return (
<div>
<CounterList />
<CounterList2 />
</div>
)
}
}
@elado ์นด์ดํฐ ๋ชฉ๋ก๊ณผ ๋์ผํ ๋ฐฉ์์ผ๋ก ๋ ๋ชฉ๋ก์ ๋ํด ์์ ์ด ์ถฉ๋ํ์ง ์๋๋ก ๋ชฉ๋ก์ผ๋ก ๋ค์ ๋ํํด์ผ ํฉ๋๋ค.
@ccorcos
๋ชฉ๋ก์ผ๋ก ๋ค์ ํฌ์ฅํ๋ ค๋ฉด
์ ํํ ๋ฌด์์ ํฌ์ฅ?
@ccorcos
์ฌ๊ธฐ์ ์์ ๋ฅผ ์
๋ก๋ํ์ต๋๋ค: http://elado.s3-website-us-west-1.amazonaws.com/redux-counter/
์์ค ๋งต์ด ์์ต๋๋ค.
์ฌ์ ํ ๋น์ ์ด ์๋ฏธํ๋ ๋ฐ๋ฅผ ์์ ํ ํ์ ํ์ง ๋ชปํฉ๋๋ค. ๋ด๊ฐ ๋งํ๋ฏ์ด - ํ์ฌ ๋์์ ์์๋๋ ๋์์ ๋๋ค. ๋์ ์ด๋ฆ์ด ๋์ผํ๊ณ ๋์ ์์ฑ์์ ์ด๋ฅผ ์ํํ ๋ชฉ๋ก์ด ์๊ธฐ ๋๋ฌธ์ ๊ฒฐ๊ตญ ๋ ๋ชฉ๋ก์ ๋ชจ๋ ์ํฅ์ ๋ฏธ์น๋ ๋ชจ๋ ๊ฐ์๊ธฐ์์ ๋์์ ์คํํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
๊ทธ๋์ ๋๋ ์ค์ Redux์ ๊ธฐ๋ฅ์ ์ ๋ชจ๋ฅด์ง๋ง ๋๋ฆ ๋๋ฌด์ ๋ํด์๋ ์์ฃผ ์ ์๊ณ ์์ต๋๋ค.
์ด ์ ์์์๋ ์ต์์ ์์ค์์ ์์ ์์ฑ์๋ฅผ ๋ฐ์ธ๋ฉํ์ง ์์ต๋๋ค. ๋์ ๋์คํจ์น ํจ์๋ฅผ ํ์ ๊ตฌ์ฑ ์์๋ก ์ ๋ฌํ๊ณ ํด๋น ํ์ ๊ตฌ์ฑ ์์๋ ์์ ์ ๋์คํจ์น ํจ์๋ก ์ ๋ฌํ ์ ์์ต๋๋ค.
์ถ์ํ๊ฐ ์ ์๋ํ๋๋ก ํ์ฌ ์์ ์ถฉ๋์ด ์๋๋ก ํ๋ ค๋ฉด "listOf" ๊ตฌ์ฑ ์์๊ฐ ๋์คํจ์น๋ฅผ โโ์์์๊ฒ ์ ๋ฌํ ๋ ์ค์ ๋ก ๋ชฉ๋ก ๊ตฌ์ฑ ์์๊ฐ ์ดํดํ ์ ์๋ ํ์์ผ๋ก ์์ ์ ๋ํํ๋ ํจ์๋ฅผ ์ ๋ฌํฉ๋๋ค.
children.map((child, i) => {
childDispatch = (action) => dispatch({action, index: i})
// ...
์ด์ listOf(counter)
๋๋ listOf(listOf(counter))
๋ฅผ ์์ฑํ ์ ์์ผ๋ฉฐ pairOf
๋ผ๋ ๊ตฌ์ฑ ์์๋ฅผ ์์ฑํ๋ ค๋ฉด ์ก์
์ ์ ๋ฌํ ๋ ๋ฉํํด์ผ ํฉ๋๋ค. ํ์ฌ App
๊ตฌ์ฑ ์์๋ ๋์คํจ์น ํจ์๋ฅผ ๋ํํ์ง ์๊ณ ๋๋ํ ๋ ๋๋งํ๋ฏ๋ก ์์
์ถฉ๋์ด ๋ฐ์ํฉ๋๋ค.
@ccorcos
๊ฐ์ฌ ํด์. ๋ฐ๋ผ์ ๊ฒฐ๋ก ์ ์ผ๋ก "๋ง๋ฒ"์ด ๋ฐ์ํ์ง ์์ต๋๋ค. ์์
์๋ ์์
์ ์ํํ ์ธ์คํด์ค๋ฅผ ๊ฐ์๊ธฐ์ ์๋ ค์ฃผ๋ ๋ฐ ํ์ํ ๋ชจ๋ ์ ๋ณด๊ฐ ํ์ํฉ๋๋ค. listOf(listOf(counter))
์ธ ๊ฒฝ์ฐ ์์
์๋ 2D ๋ฐฐ์ด์ ๋ ์ธ๋ฑ์ค๊ฐ ๋ชจ๋ ํ์ํฉ๋๋ค. listOf(listOf(counter))
๋ ์๋ํ ์ ์์ง๋ง ๋จ์ผ ํ๋ซ ๋ชฉ๋ก์ ๋ชจ๋ ์นด์ดํฐ๊ฐ ์์
์์ ์ ๋ฌ๋๋ ์ ์ผํ ID๋ก ์ธ๋ฑ์ฑ๋๋๋ก ํ๋ ๊ฒ์ด ๋ ์ ์ฐํด ๋ณด์
๋๋ค.
์ ์ฐํ๊ณ ๋ณต์กํ Redux ์ฑ์ ๋น๋ํ๋ ์ ์ผํ ๋ฐฉ๋ฒ์ ์์คํ ์ ๋ชจ๋ ์ํฐํฐ๊ฐ ์คํ ์ด์ ID๋ก ์ธ๋ฑ์ฑ๋๋๋ก ํ๋ ๊ฒ ๊ฐ์ต๋๋ค. ๋๋๋ก ์์ ์์ ์ ๊ณต๋๋ ๋ค๋ฅธ ๊ฐ๋จํ ์ ๊ทผ ๋ฐฉ์์ ํ๊ณ์ ๋น ๋ฅด๊ฒ ๋๋ฌํฉ๋๋ค. ์ด ์ธ๋ฑ์ค๋ ๊ฑฐ์ ๊ด๊ณํ DB์ ๋ฏธ๋ฌ์ ๋๋ค.
์์ ์๋ 2d ๋ฐฐ์ด์ ๋ ์ธ๋ฑ์ค๊ฐ ๋ชจ๋ ํ์ํฉ๋๋ค.
๋น์ ์ ์๊ฐ๊ณผ ํ๋์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
{type: 'increment', index:[0 5]}
ํ์ง๋ง ์ค์ ๋ก๋ ๋ค์๊ณผ ๊ฐ์์ผ ํฉ๋๋ค.
{type:'child', index: 0, action: {type: 'child', index: 5, action: {type: 'increment'}}}
๊ทธ๋ ๊ฒ ํ๋ฉด listOf(listOf(listOf(...listOf(counter)...)))
์์ํ ํ ์ ์์ต๋๋ค!
์ด๊ฒ์ ๋ชจ๋ ๋๋ฆ ๋๋ฌด์์ ์ต๋๋ค. btw. Elm ์ํคํ ์ฒ ํํ ๋ฆฌ์ผ ์ ํ์ธํ์ธ์
๋๋ ํํฐ์ ์กฐ๊ธ ๋๋ฆฌ์ง ๋ง ์ด๊ฒ์ด "๋ฏธ๋ค์จ์ด์ ํจ๊ป ์๋ํ์ง ์๋"๊ณณ์ด ๋ณด์ด์ง ์์ต๋๊น? @ccorcos ๋ก ๋ฌดํ ์ค์ฒฉ์ ์ ๊ณตํ๋ ๊ฒฝ์ฐ ์ค์ฒฉ์ ์ฒ๋ฆฌํ๊ธฐ ์ํด ๋ฏธ๋ค์จ์ด๋ฅผ ์ฐ๊ฒฐํ ์ ์์ต๋๊น? ์๋๋ฉด ์ค์ฒฉ๋ ์์
์ด ์ด์ํจ์ ์๋ฏธํ๋ redux-thunk
์ ๋ํด์๋ง ์ด์ผ๊ธฐํ๊ณ ์์ต๋๊น?
๋ฏธ๋ค์จ์ด๋ ๋์์ ์๋ ๊ทธ๋๋ก ํด์ํ ์ง ์๋๋ฉด ์ค์ฒฉ ๋์์ ์ฐพ์์ผ ํ๋์ง ์ด๋ป๊ฒ ์ ์ ์์ต๋๊น?
์ํ.
@gaearon
์๋ .
๋๋ ๋ฌธ์ ์ ๋ํ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ดํดํ๋ค๊ณ ๊ธ์ ์ ์ด์ง ์์ผ๋ฉฐ ๊ฐ๋จํ ๋ต๋ณ์ ์ ๋ง ๊ฐ์ฌ๋๋ฆฝ๋๋ค.
๋์ผํ ํ์ด์ง์ ์ฌ๋ฌ ๊ตฌ์ฑ ์์ ๋ณต์ฌ๋ณธ์ด ์๋ ๊ฒฝ์ฐ _๋ฏธ๋ค์จ์ด(๋ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ๋๋ถ๋ถ์ Redux ์์ฝ์์คํ )๋ฅผ ์ฌ์ฉํ์ง ์์ต๋๊น? ๋๋ ๋ํ ๋๊ตฐ๊ฐ๊ฐ ๋ค์ค ๊ฐ์๊ธฐ ์ ์์ ์๋ตํ๋์ง ์ฌ๋ถ๋ฅผ ๋ณด์ง ๋ชปํ์ต๋๋ค.
๋ชจ๋ ์ค๋ช ์ด ๋์์ด ๋ ๊ฒ์ ๋๋ค.
์๋์, ์ด๊ฒ์ ํด์๋๊ฐ ์๋๋๋ค. ์ค๋ ๋๋ ํ์ด์ง์์ ๊ตฌ์ฑ ์์์ ์ฌ๋ฌ ID๋ฅผ ๊ฐ๋ ๊ฒ์ด ์๋๋๋ค. ์ด๋ฅผ ๊ตฌํํ๋ ค๋ฉด ์์ ์์ ID๋ฅผ ์ ๋ฌํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค. ์ฐ๋ ๋๋ _๊ทธ๊ฒ์ ํ๊ธฐ ์ํ ์ผ๋ฐ ํจ์๋ฅผ ์์ฑํ๋ ๊ฒ_์ ๊ดํ ๊ฒ์ด์์ต๋๋ค. ๋ถํํ๋ ๋ฏธ๋ค์จ์ด์ ๊ฐ๋ ๊ณผ ์ถฉ๋ํฉ๋๋ค.
๊ฐ์ฌํฉ๋๋ค.
์ฌ๋ฌ๋ถ, ์๋
ํ์ธ์,
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ ์๋ ์์ง๋ง react
deku
๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ์ ํธํฉ๋๋ค ^^
๋๊ตฐ๊ฐ ์ด ์คํ, ํนํ taskMiddleware
์์ด๋์ด์ ๋ํ ํต์ฐฐ๋ ฅ์ ์ ๊ณตํ๋ฉด ๊ธฐ์ ๊ฒ์
๋๋ค. @gaearon ์๊ฐ๋์ค์๊ฐ์๋์? :ํ:
๊ฐ์ฌ ํด์!
์ด๋ฌํ ์๋ฃจ์
์ค ์ด๋ ๊ฒ๋ ์ด๊ธฐ ์ํ๋ฅผ ์ฒ๋ฆฌํ๋ ๊ฒ์ ๋ชฉํ๋ก ํ์ง ์๋๋ค๋ ์ ์ ๋ช
ํํ ํ๊ณ ์ถ์ต๋๋ค.
์ด๊ฒ์ ์ด๋ป๊ฒ๋ @gaearon ์ ๋ฌ์ฑํ ์ ์์ต๋๊น? ์์ ๋ชฉ๋ก ๊ฐ์๊ธฐ๊ฐ ์นด์ดํฐ์ ์ด๊ธฐ ๋ชฉ๋ก์ ๊ฐ๋๋ก ํ์ฉํ์๊ฒ ์ต๋๊น?
๊ทธ๊ฒ ์ ๋ฌธ์ ๊ฐ ๋ ๊น์? undefined
์ํ์ ์์ ๊ฐ์๊ธฐ๋ฅผ ํธ์ถํ๊ณ ํด๋น ๊ฐ์ ์ฌ์ฉํ ์ ์๋ค๊ณ ์์ํฉ๋๋ค.
์์ ์ด ์ฒซ ๋ฒ์งธ ๋์คํจ์น๋ก ์ด๊ธฐํ๋ ๋ (๋ด ๋ด๋ถ ๋ ผ๋ฆฌ์ ๋ํด) ํด๋น ๋ชฉ๋ก์ ๋ํ ์ด๊ธฐ ์ํ๊ฐ ํ์ํ๊ณ ์ฌ์ ์ ์๋ ์นด์ดํฐ ๋ชฉ๋ก(๋ชฉ๋ก ํญ๋ชฉ ์ ํ)์ด์ด์ผ ํฉ๋๋ค.
์ด ๊ฐ์?
export default function list(reducer) {
return function (state = [
// e.g. 2 counters with default values
reducer(undefined, {}),
reducer(undefined, {}),
], action) {
const {
index,
action: innerAction
} = action;
// ...
}
}
์ํ๋ค๋ฉด ์ด๊ฒ์ list
์ ๋ํ ์ธ์๋ก ๋ง๋ค ์๋ ์์ต๋๋ค.
์ด๊ฒ์ ํ์ด์ง์ ๋์ผํ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ๋ฌ ๊ฐ ๊ฐ์ง ์ ์๋๋ก ์ ๋ง ๋ณต์กํด ๋ณด์ ๋๋ค.
๋๋ ์ฌ์ ํ Redux์ ๋ํด ๋จธ๋ฆฌ๋ฅผ ์ธ๋งค๊ณ ์์ง๋ง ๊ถ์ฅ๋๋ Redux ์ฌ์ฉ ํจํด์ด ์๋๋๋ผ๋ ์ฌ๋ฌ ์ ์ฅ์๋ฅผ ๋ง๋๋ ๊ฒ์ด ํจ์ฌ ๊ฐ๋จํด ๋ณด์ ๋๋ค.
@deevus : ์ด๋ฌํ ํ ๋ก ์ค ์ผ๋ถ๊ฐ ๋น์ ์ ๋๋ผ๊ฒ ํ์ง ๋ง์ญ์์ค. Redux ์ปค๋ฎค๋ํฐ์๋ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ ์งํฅํ๋ ๋ง์ ์ฌ๋๋ค์ด ์์ต๋๋ค. ์ด ์ค๋ ๋์ ๋ค๋ฅธ ์ ์ฌํ ๊ฐ๋ ์์ ๋ ผ์๋ ๊ฐ๋ ์ค ์ผ๋ถ๋ ๊ฐ์น๊ฐ ์์ง๋ง "์๋ฒฝํ" ๊ฒ์ ์ถ๊ตฌํ๋ ๊ฒฝํฅ์ด ์์ต๋๋ค. , ๋จ์ํ "์ข์"์ด ์๋๋ผ.
์ผ๋ฐ์ ์ผ๋ก ํ ํ์ด์ง์ ์ฌ๋ฌ ๊ตฌ์ฑ ์์ ์ธ์คํด์ค๊ฐ ์์ ์ ์์ต๋๋ค. ์ด ํ ๋ก ์ ๋ชฉํ๋ ์ค์ฒฉ๋ ๊ตฌ์ฑ ์์์ ์์ ๊ตฌ์ฑ์ ๋๋ค. ์ด๋ ํฅ๋ฏธ๋กญ์ง๋ง ๋๋ถ๋ถ์ ์ฑ์์ ์ํํด์ผ ํ๋ ๊ฒ์ ์๋๋๋ค.
๊ทธ ์ธ์ ํน์ ๋ฌธ์ ๊ฐ ์๋ ๊ฒฝ์ฐ ์ผ๋ฐ์ ์ผ๋ก ์คํ ์ค๋ฒํ๋ก๊ฐ ์ง๋ฌธํ๊ธฐ์ ์ข์ ๊ณณ์ ๋๋ค. ๋ํ Discord์ Reactiflux ์ปค๋ฎค๋ํฐ์๋ React ๋ฐ ๊ด๋ จ ๊ธฐ์ ์ ๋ํ ํ ๋ก ์ ์ฉ ์ฑํ ์ฑ๋์ด ์์ผ๋ฉฐ, ํญ์ ๋ํํ๊ณ ๋์์ ์ฃผ๊ณ ์ ํ๋ ์ฌ๋๋ค์ด ์์ต๋๋ค.
@markerikson ๊ฐ์ฌํฉ๋๋ค. Discord์์ Reactiflux์ ๋์์ ๋ฐ์ผ๋ ค๊ณ ํฉ๋๋ค.
์ด์ ๋ํ ํ์ ์กฐ์น:
ํ๋ก์ ํธ์์ ๋ฆฌ๋์๋ฅผ ์ฌ์ฌ์ฉํ๋ ํ์ฅ ๊ฐ๋ฅํ ๋ฐฉ๋ฒ์ ์ฐพ์์ต๋๋ค. ๋ฆฌ๋์ ๋ด์์ ๋ฆฌ๋์๋ฅผ ์ฌ์ฌ์ฉํ ์๋ ์์ต๋๋ค.
๋ค์์คํ์ด์ค ํจ๋ฌ๋ค์
๋ง์ "๋ถ๋ถ" ๋ฆฌ๋์ ์ค ํ๋์ ์์ฉํ๋ ์ก์
์ด โโ๋์ผํ ์ก์
type
(์ https://github.com/reactjs/redux/issues/822#issuecomment-172958967)
๊ทธ๋ฌ๋ ๋ค์์คํ์ด์ค๋ฅผ ๋ณด์ ํ์ง ์๋ ์์ ์ ์ฌ์ ํ โโ๋ค๋ฅธ ๋ฆฌ๋์ ๋ด์ ๋ชจ๋ ๋ถ๋ถ ๋ฆฌ๋์๋ก ์ ํ๋ฉ๋๋ค.
๋ถ๋ถ ๊ฐ์๊ธฐ๊ฐ A
A(undefined, {}) === Sa
์ด๊ณ ์ด๊ธฐ ์ํ๊ฐ B(undefined, {}) === { a1: Sa, a2: Sa }
์ด๊ณ ํค๊ฐ a1
์ธ ๊ฐ์๊ธฐ B
๊ฐ ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค. a1
๋ฐ a2
๋ A
์ ์ธ์คํด์ค์
๋๋ค.
['a1']
๋ค์์คํ์ด์ค๊ฐ ์๋ ์์
(* ๋ค์์คํ์ด์ค๋ ํญ์ ๋ถ๋ถ ๋ฆฌ๋์์ ๋ํ ์ํ ํค์ ์ ์ฌํ ๋ฌธ์์ด ๋ฐฐ์ด๋ก ์ ๋ ฌ๋จ) B
์ ์บ์คํ
๋๋ฉด ๋ค์ ๊ฒฐ๊ณผ๊ฐ ์์ฑ๋ฉ๋๋ค.
const action = {
type: UNIQUE_ID,
namespace: ['a1']
};
B(undefined, action) == { a1: A(undefined, action*), a2: Sa }
๊ทธ๋ฆฌ๊ณ ๋ค์์คํ์ด์ค๊ฐ ์๋ ์ก์ ์ ๋ฐ๋ ์
const action = {
type: UNIQUE_ID
};
B(undefined, action) == { a1: A(undefined, action), a2: A(undefined, action) }
์ฃผ์ ์ฌํญ
A
๊ฐ ์ฃผ์ด์ง ์์
์ ์ฒ๋ฆฌํ์ง ์์ผ๋ฉด(๋ฆฌ๋์๋ฅผ ์์ง) ๋์ผํ ์ํ๋ฅผ ๋ฐํํด์ผ ํฉ๋๋ค. ์ด๋ p
์์
์ ๋ํด A
์ ๋ํด ์ฒ๋ฆฌํ ์ ์์์ ์๋ฏธํฉ๋๋ค. B(undefined, p)
{ a1: Sa, a2: Sa }
ํ๋ฉฐ ์ด๋ ์ฐ์ฐํ B
์ ์ด๊ธฐ ์ํ์ ๋์ผํฉ๋๋ค.action*
์ผ๋ก ํ์)์ ๋ฒ์๋ฅผ ์ขํ๋ ๋ฐ ์ฌ์ฉ๋๋ ๋ค์์คํ์ด์ค๋ฅผ ์ ๊ฑฐํด์ผ ํฉ๋๋ค. ๋ฐ๋ผ์ B
์ ์ ๋ฌ๋ ์์
์ด { type: UNIQUE_ID, namespace: ['a1'] }
์ด๋ฉด A
์ ์ ๋ฌ๋ ์์
์ { type: UNIQUE_ID, namespace: [] }
์
๋๋ค.์ด๊ฒ์ ๋ฌ์ฑํ๊ธฐ ์ํด ๋๋ ๋น์ ์ ๋ฆฌ๋์์์ ๋ค์์คํ์ด์ค๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ ๋ช ๊ฐ์ง ์์ฌ์ฝ๋๋ฅผ ์๊ฐํด ๋์ต๋๋ค. ์ด๊ฒ์ด ์๋ํ๋ ค๋ฉด ๋ฆฌ๋์๊ฐ ์ก์ ์ ์ฒ๋ฆฌํ ์ ์๋์ง์ ๋ฆฌ๋์์ ์กด์ฌํ๋ ๋ถ๋ถ ๋ฆฌ๋์์ ์์ ๋ฏธ๋ฆฌ ์์์ผ ํฉ๋๋ค.
(state = initialState, { ...action, namespace = [] }) => {
var partialAction = { ...action, namespace: namespace.slice(1) };
var newState;
if (reducerCanHandleAction(reducer, action) and namespaceExistsInState(namespace, state)) {
// apply the action to the matching partial reducer
newState = {
...state,
[namespace]: partialReducers[namespace](state[namespace], partialAction)
};
} else if (reducerCantHandleAction(reducer, action) {
// apply the action to all partial reducers
newState = Object.assign(
{},
state,
...Object.keys(partialReducers).map(
namespace => partialReducers[namespace](state[namespace], action)
)
);
} else {
// can't handle the action
return state;
}
return reducer(newState, action);
}
๋ฆฌ๋์๊ฐ ์์ ์ ๋ฏธ๋ฆฌ ์ฒ๋ฆฌํ ์ ์๋์ง ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉ์์๊ฒ ๋ฌ๋ ค ์์ต๋๋ค. ์ ๋ ์์ ์ ํ์ด ํค์ด๊ณ ํธ๋ค๋ฌ ํจ์๊ฐ ๊ฐ์ธ ๊ฐ์ฒด ๋งต์ ์ฌ์ฉํฉ๋๋ค.
๊ฒ์์ ์กฐ๊ธ ๋ฆ์์ง๋ง ๋์์ด ๋ ์ ์๋ ๋ฒ์ฉ ๊ฐ์๊ธฐ๋ฅผ ์์ฑํ์ต๋๋ค.
https://gist.github.com/crisu83/42ecffccad9d04c74605fbc75c9dc9d1
๋๋ mutilreducer ๊ฐ ํ๋ฅญํ ๊ตฌํ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.
@jeffhtli multireducer๋ ์ ์๋์ง ์์ ์์ ๊ฐ์๊ธฐ๋ฅผ ํ์ฉํ์ง ์๊ธฐ ๋๋ฌธ์ ์ข์ ์๋ฃจ์
์ด ์๋๋๋ค. ๋์ ์ ์ ๊ฐ์๊ธฐ ๋ชฉ๋ก์ ์์ฑํ๋๋ก ๋ฏธ๋ฆฌ ์์ฒญํฉ๋๋ค.
๋์ ๊ฐ ๊ตฌ์ฑ ์์ ์ธ์คํด์ค์ ๋ํ UUID์ ๊ฐ UUID์ ๋ํ ๊ณ ์ ์ํ๋ฅผ ์ฌ์ฉํ์ฌ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์์ต๋๋ค.
https://github.com/eloytoro/react-redux-uuid
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
์, ํ์คํ ๊ฐ๋ฅํฉ๋๋ค.
๊ณ ์ฐจ ๊ฐ์๊ธฐ์ ๊ณ ์ฐจ ๊ตฌ์ฑ ์์๋ฅผ ๋ง๋ค๊ณ ์ถ์ต๋๋ค.
๊ณ ์ฐจ ๊ฐ์๊ธฐ์ ๊ฒฝ์ฐ ์ฌ๊ธฐ์์ ์ ๊ทผ ๋ฐฉ์์ ์ฐธ์กฐํ์ญ์์ค.
(์คํํ์ง๋ ์์์ง๋ง ์ต์ํ์ ๋ณ๊ฒฝ์ผ๋ก ์๋ํด์ผ ํฉ๋๋ค.)