์๋ ํ์ธ์ @gaearon - ๋ฌธ์๋ฅผ ์ฝ๋ ๋์ ๋ค์ ์ง์ ์ด ๋๋ฅผ
๊ทธ๋ฐ ๋ค์ react-redux์ connect() ํจ์๋ฅผ ์ฌ์ฉํ์ฌ Redux์ ์ฐ๊ฒฐํ๋ ค๋ ๊ตฌ์ฑ ์์๋ฅผ ๋ํํฉ๋๋ค. ์ต์์ ๊ตฌ์ฑ ์์ ๋๋ ๊ฒฝ๋ก ์ฒ๋ฆฌ๊ธฐ์ ๋ํด์๋ง ์ด ์์ ์ ์ํํ์ญ์์ค. ๊ธฐ์ ์ ์ผ๋ก ์ฑ์ ๋ชจ๋ ๊ตฌ์ฑ ์์๋ฅผ Redux ์คํ ์ด์ ์ฐ๊ฒฐํ ์ ์์ง๋ง ๋๋ฌด ๊น๊ฒ ์ฐ๊ฒฐํ๋ฉด ๋ฐ์ดํฐ ํ๋ฆ์ ์ถ์ ํ๊ธฐ ๋ ์ด๋ ค์์ง๋ฏ๋ก ํผํ์ธ์.
Deep prop chains๋ ๋ด๊ฐ Flux๋ฅผ ์ฌ์ฉํ๊ฒ ๋ง๋ React์ ๋ฌธ์ ์ค ํ๋์์ต๋๋ค. ๋ฐ์ดํฐ ํ๋ฆ์ ๋ ์ฝ๊ฒ ์ถ์ ํ ์ ์๋๋ก ํ๊ธฐ ์ํ ์ ์ถฉ์์ prop ํ๋ฆ์ ์ค์ /์ ์ง/์ถ์ ํด์ผ ํ๋ฉฐ ๋ ์ค์ํ ๊ฒ์ ๋ถ๋ชจ๊ฐ ์๋ ์ ๋ฐ์ดํฐ ์๊ตฌ ์ฌํญ์ ๋ํด ์์์ผ ํ๋ค๋ ๊ฒ์ ๋๋ค. ๋ด ๊ฒฝํ์ ์ด ์ ๊ทผ ๋ฐฉ์์ด ๋ ๋ซ๋ค๋ ํ์ ์ ์์ง๋ง ์ด๋ป๊ฒ ์๊ฐํ์๋์ง ๊ถ๊ธํฉ๋๋ค.์ค๋ง์ผ:
๋๋ Brent์ ๊ฐ๋ ฅํ๊ฒ ๋์ํฉ๋๋ค. ์ด๊ฒ์ Relay์ ๊ฐ์ ๊ฒ์ ์ด๋ฉด์ ์๋ ๊ทผ๋ณธ์ ์ธ ์๊ฐ์ ๋๋ถ๋ถ์ด๋ฉฐ ๋ ๋์ ์์ง๋ ฅ์ผ๋ก ์ด์ด์ง๋๋ค.
์๋ง๋ ์ฐ๋ฆฌ๋ "์ฌ๋๋ค์ ์ฌ๊ธฐ์ ๋ค๋ฅธ ์ ํธ๋๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค"๋ผ๊ณ ๋งํด์ผ ํ ๊ฒ์ ๋๋ค.
@gaearon - ํธ๋ ์ด์ฑ ๋ฐ์ดํฐ ํ๋ฆ์ ๋ณต์กํ๊ฒ ๋ง๋๋ ๋ฐฉ๋ฒ์ ๋ํ ๋ฐ๋ชจ๋ฅผ ํฌํจํ๋
๋ฉ์๋. ์ด์ด๋๊ณ ์ด๊ธฐ ๋ฌธ์๊ฐ ์ ๋ฆฌ๋ ํ ๋ค์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
@gaearon ์ ๋ดค์ต๋๋ค! :) ๋ค์ ๋ฐฉ๋ฌธํ๊ณ ์ถ์ ๋ ์ ์๊ฒ Ping
์ ๋ Redux๋ฅผ ์ฒ์ ์ ํ์ง๋ง ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ์๋นํ ๋ง์ด ๋ณ๊ฒฝํ ์ฑ์ ๊ตฌ์ถํ๋ฉด์ ์ง๋ 1๋ ๋์ ์ด๊ฒ์ด ์ ์๊ฒ ๊ฝค ํฐ ๊ณจ์นซ๊ฑฐ๋ฆฌ์์ต๋๋ค. ๊ทธ๋์ ๋๋ ์ด๊ฒ ์ ๋ํด
๋ง์ ๊ตฌ์ฑ ์์๋ฅผ ์ฐ๊ฒฐ()ํ ๋ ๋ฐ์ดํฐ ํ๋ฆ์ ์ถ์ ํ๋ ๊ฒ ์ธ์ ๋ค๋ฅธ ๋ฌธ์ ๊ฐ ์์ต๋๊น? ์๋ฅผ ๋ค์ด ์ฑ๋ฅ ์ ํ๊ฐ ์์ต๋๊น?
๋ง์ ๊ตฌ์ฑ ์์๋ฅผ ์ฐ๊ฒฐ()ํ ๋ ๋ฐ์ดํฐ ํ๋ฆ์ ์ถ์ ํ๋ ๊ฒ ์ธ์ ๋ค๋ฅธ ๋ฌธ์ ๊ฐ ์์ต๋๊น? ์๋ฅผ ๋ค์ด ์ฑ๋ฅ ์ ํ๊ฐ ์์ต๋๊น?
์๋์, ์ ๋ฐ๋์ ๋๋ค. ํํฅ์ ํ๋ฆ์ ํฌ๊ธฐํ๋ฉด ๋ ๋์ ์ฑ๋ฅ์ ์ป์ ์ ์์ต๋๋ค.
์ค๊ฐ ์์ค ๊ตฌ์ฑ ์์์ ๋ถํ์ํ ๋ค์ ๋ ๋๋ง์ ํผํ ์ ์๊ธฐ ๋๋ฌธ์?
์.
ํ ๋ก ์ ์ถ๊ฐํ๋ ค๋ฉด: ์ ๋ ์ฃผ๋ก ์ฐ๊ฒฐ ๊ตฌ์ฑ ์์์ ๋ํ ์ฌ์ฉ์ ์ ํํ๊ณ ์์ต๋๋ค. ์ถ๊ฐ ์ค๋งํธ ๊ตฌ์ฑ ์์(์: ์์ ๋ชจ๋ฌ)๋ฅผ ์ฌ์ฉํ ์ ์๋ ํ์ด์ง๊ฐ ์๋ ๊ฒฝ์ฐ ๋ด ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์์ ๋๋ ๋ ธ๋๋ฅผ ์ ๋ฌํ๊ณ ๋ฉ์ฒญํ ๊ตฌ์ฑ ์์๊ฐ ์ด๋ฅผ ๋ ๋๋งํ๋๋ก ํ๋ ๊ฒ์ด์์ต๋๋ค. ๊ทธ๊ฒ์ ๋น์ ์ด ์ข ๋ ์์ฉ๊ตฌ๋ฅผ ๊ฐ์ง๊ณ ์๋ค๋ ๊ฒ์ ์๋ฏธํ์ง๋ง, ๋ฉ์ฒญํ ์ปดํฌ๋ํธ๋ฅผ ํ ์คํธํ๋ ๊ฒ์ ์ฌ์ ํ โโ์ฝ์ต๋๋ค. ๋๋ ์ฌ์ ํ ์ด๊ฒ์ ์คํํ๊ณ ์์ง๋ง ์ด๊ฒ์ด ์ฌ์ด ํ ์คํธ ๊ฐ๋ฅ์ฑ์ ํฌ๊ธฐํ์ง ์๊ณ ์ค๋งํธ ๊ตฌ์ฑ ์์๋ฅผ ๊ตฌ์ฑํ๋ ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์ผ ์ ์๋ค๊ณ ์๊ฐํฉ๋๋ค.
๋๋ต์ ์ธ ์๋ฅผ ๋ค์๋ฉด:
ํธ.jsx
export class Foo extends Component {
render () {
return (
<div className='foo'>
{/* foo stuff going on in here */}
{this.props.Bar}
</div>
)
}
}
FooContainer.jsx
@connect(getState, getActions)
export class FooContainer extends Component {
render () {
return (
<Foo
Bar={<BarContainer/>}
{...this.props}
/>
)
}
}
@brentvatne ๋ฌด์ธ๊ฐ๋ฅผ ์์ฑํ๊ณ ์ถ๋ค๋ฉด ํ์ฌ ๋ฌธ์ ๊ตฌ์กฐ์์ ์ด๋์ ๋ง์ถ์ง ํ์ ํ๊ณ ์์ ๋กญ๊ฒ ์งํํ์ธ์! :-)
2015/10/19 ์
๋ฐ์ดํธ: ์ด ๋๊ธ์ ๊ฐ์ ํ์ฌ react-redux-provide
๋ก ์ถ์ํ์ต๋๋ค. ์๋ https://github.com/rackt/redux/issues/419#issuecomment -149325401์ ์ฐธ์กฐํ์ธ์.
#475์์ ๊ณ์ํด์ ๋ด๊ฐ ์๊ฐํด๋ธ ๊ฒ์ ๋ํด ์ค๋ช
ํ๊ฒ ์ต๋๋ค. ๋น๋ก ์ด ๋
ผ์๊ฐ ํ์ฌ react-redux
์ํ ๊ฒ์
๋๋ค. ;)
๊ฐ๋จํ ๋งํด์, ๋ด๊ฐ ์ทจํ ์ ๊ทผ ๋ฐฉ์์ ๋ชจ๋ ๊ตฌ์ฑ ์์์ ํ ๋นํ ์ ์๋ ๋ชจ๋์ ๊ณต๊ธ์๋ฅผ ์ค์ฌ์ผ๋ก ์ด๋ฃจ์ด์ง๋๋ค. ์ด๊ฒ์ _truly_ "๋ฉ์ฒญํ" ๊ตฌ์ฑ ์์๋ฅผ ํ์ฉํ๊ณ , ๋ฌธ์ ์ ์ต๋ ๋ถ๋ฆฌ๋ฅผ ์ํํ๋ฉฐ, ๊ตํ ๊ฐ๋ฅํ ๊ณต๊ธ์๋ฅผ ๋งค์ฐ ์ฝ๊ณ ๋น ๋ฅด๊ฒ ์ฌ์ฉํ๊ณ ๊ณต์ ํ๋ ๊ฒ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค. ๋ํ ๊ตฌ์ฑ ์์๋ฅผ ์ ๋ฐ์ดํธํ๋ ๋ณด๋ค ํจ์จ์ ์ธ ๋ฐฉ๋ฒ์ ์ํํฉ๋๋ค.
๊ทธ๋์... actions
, constants
, containers
, reducers
๋ฐ stores
๋๋ ํ ๋ฆฌ๋ฅผ ์ ๊ฑฐํ์ต๋๋ค(์์ ๋ด์์ ๊ณตํต ) ๋จ์ผ providers
๋๋ ํ ๋ฆฌ๋ก ๋์ฒดํ์ต๋๋ค. ๋๋ providers
๋๋ ํ ๋ฆฌ๊ฐ ํ์ํ์ง ์์ ์๋ ์์ต๋๋ค. ์ด ์ ๊ทผ ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด ๋
๋ฆฝ ์คํํ ๊ณต๊ธ์๋ฅผ ํจํค์งํ๊ณ ๋ฐฐํฌํ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค. ์ด ํน์ ์ ๊ทผ ๋ฐฉ์์ด ์ฑํ๋๋ฉด ์ ๋ง ๋ฉ์ง ๊ฒ๋ค์ด ๋์ฌ ๊ฒ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค!! (์ฐธ๊ณ : ๋ฌผ๋ก ํด๋น ๋๋ ํ ๋ฆฌ๋ฅผ ํ๋๋ก ํตํฉํ ํ์๋ ์์ง๋ง 1) ํจ์ฌ ๋ ์ฝ๊ธฐ/์ดํดํ๊ธฐ ์ฝ๊ฒ ๋ง๋ค๊ณ 2) ์์ฉ๊ตฌ๋ฅผ ์ค์ด๊ณ 3) ๊ฐ๋ณ ๊ณต๊ธ์๊ฐ ์ถฉ๋ถํ ์์์ ์๋ฏธ๊ฐ ์๋ค๊ณ ์๊ฐํฉ๋๋ค. )
// components/Branch.js
import React, { Component } from 'react';
import provide from '../utilities/provide.js';
import { branchName, tree, toggle, open, theme } from '../common/propTypes.js';
import Limbs from './Limbs.js';
<strong i="22">@provide</strong> // maybe require specifying expected props? e.g., @provide('theme')
export default class Branch extends Component {
static propTypes = { branchName, tree, toggle, open, theme };
onClick(event) {
const { branchName, toggle } = this.props; // toggle is from a provider
event.stopPropagation();
toggle(branchName);
}
render() {
const props = this.props;
const { branchName, tree, open, theme } = props; // latter 3 from providers
const classes = theme.sheet.classes || {};
const imgSrc = open ? 'folder-open.png' : 'folder-closed.png';
return (
<div
onClick={::this.onClick}
className={classes.branch}
>
<h4 className={classes.branchName}>
<img
className={classes.branchIcon}
src={theme.imagesDir+imgSrc}
/>
<span>{branchName}</span>
</h4>
<Limbs
tree={tree}
open={open}
/>
</div>
);
}
}
// providers/toggle.js
import createProvider from '../utilities/createProvider.js';
export const TOGGLE = 'TOGGLE';
export const actions = {
toggle(fullPath) {
return { type: TOGGLE, fullPath };
}
};
export const reducers = {
open(state = {}, action) {
switch (action.type) {
case TOGGLE:
const { fullPath } = action;
return { ...state, [fullPath]: !state[fullPath] };
default:
return state;
}
}
};
function merge (stateProps, dispatchProps, parentProps) {
return Object.assign({}, parentProps, {
open: !!stateProps.open[parentProps.fullPath]
});
}
export const provider = createProvider(actions, reducers, merge);
export default provider;
์ธ๊ธํ๋ฏ์ด ์์ด๋์ด๋ ์์์ ๊ณต๊ธ์๋ฅผ "๋ฉ์ฒญํ" ๊ตฌ์ฑ ์์์ ์ฝ๊ฒ ํ ๋นํ ์ ์๋ค๋ ๊ฒ์ ๋๋ค. ๋ฐ๋ผ์ ์ฑ์ ๋ง์ดํธํ ๋ ๋ค์๊ณผ ๊ฐ์ด ํ ์ ์์ต๋๋ค.
import React from 'react';
import { Provider } from 'react-redux';
import assignProviders from './utilities/assignProviders.js';
import createStoreFromProviders from './utilities/createStoreFromProviders.js';
import dark from './themes/dark.js';
import github from './sources/github.js';
import * as providers from './providers/index.js';
import * as components from './components/index.js';
const { packageList, sources, toggle, theme } = providers;
const { Branches, Branch, Limbs, Limb } = components;
const initialState = {
packageList: [
'github:gaearon/react-redux<strong i="10">@master</strong>',
'github:loggur/branches<strong i="11">@master</strong>',
'github:rackt/redux<strong i="12">@master</strong>'
],
sources: {
github: github({
token: 'abc123',
auth: 'oauth'
})
},
open: {
'github': true,
'github:gaearon': true,
'github:gaearon/react-redux<strong i="13">@master</strong>': true,
'github:rackt': true,
'github:rackt/redux<strong i="14">@master</strong>': true
},
theme: dark
};
const store = createStoreFromProviders(providers, initialState);
assignProviders({ theme }, components);
assignProviders({ packageList }, { Branches });
assignProviders({ sources }, { Branches, Branch });
assignProviders({ toggle }, { Branch, Limb });
React.render(
<Provider store={store}>
{() => <Branches/>}
</Provider>,
document.getElementById('root')
);
์ด ๋ชจ๋ ๊ฒ์ด ๋งค์ฐ ๊ฐ๋จํ๊ธฐ๋ฅผ ๋ฐ๋๋๋ค. ๊ทธ๋ฌ๋ ํ์ํ ๊ฒฝ์ฐ ๋ ์์ธํ ์ค๋ช ํ๊ณ ์ค๋ช ์ ์ถ๊ฐํ ์ ์์ด ๊ธฐ์ฉ๋๋ค.
์ด ์์ ์์ ๊ฐ์ ธ์ค๋ ์ ํธ๋ฆฌํฐ ํจ์๊ฐ ๋ช ๊ฐ ์์์ ์ ์ ์์ต๋๋ค. ์ด๋ค์ ๊ธฐ๋ณธ์ ์ผ๋ก ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ์ฌ์ฉ ์ฌ๋ก๋ฅผ ์ํด ์ค๊ณ๋ ๊ธฐ์กด redux
๋ฐ react-redux
๋ฉ์๋์ ์กฐํฉ์ธ ์์ ๋ชจ๋(4๊ฐ ์ค 3๊ฐ๋ ๋ช ์ค ๊ธธ์ด์)์
๋๋ค. ๋ฌผ๋ก ์ด๋ฌํ ๊ธฐ๋ฅ์ ๊ตญํ๋์ง ์์ต๋๋ค. ๊ทธ๊ฒ๋ค์ ๋จ์ง ์ผ์ ๋ ์ฝ๊ฒ ํ๊ธฐ ์ํด ์กด์ฌํฉ๋๋ค.
// utilities/createProvider.js
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
/**
* Creates an object to be used as a provider from a set of actions and
* reducers.
*
* <strong i="10">@param</strong> {Object} actions
* <strong i="11">@param</strong> {Object} reducers
* <strong i="12">@param</strong> {Function} merge Optional
* <strong i="13">@return</strong> {Object}
* <strong i="14">@api</strong> public
*/
export default function createProvider (actions, reducers, merge) {
return {
mapState(state) {
const props = {};
for (let key in reducers) {
props[key] = state[key];
}
return props;
},
mapDispatch(dispatch) {
return bindActionCreators(actions, dispatch);
},
merge
};
}
// utilities/createStoreFromProviders.js
import { createStore, combineReducers } from 'redux';
import { createStore, applyMiddleware, combineReducers } from 'redux';
/**
* Creates a store from a set of providers.
*
* <strong i="17">@param</strong> {Object} providers
* <strong i="18">@param</strong> {Object} initialState Optional
* <strong i="19">@return</strong> {Object}
* <strong i="20">@api</strong> public
*/
export default function createStoreFromProviders (providers, initialState) {
const reducers = {};
const middleware = [];
let create = createStore;
for (let key in providers) {
let provider = providers[key];
Object.assign(reducers, provider.reducers);
if (provider.middleware) {
if (Array.isArray(provider.middleware)) {
for (let mid of provider.middleware) {
if (middleware.indexOf(mid) < 0) {
middleware.push(mid);
}
}
} else if (middleware.indexOf(provider.middleware) < 0) {
middleware.push(provider.middleware);
}
}
}
if (middleware.length) {
create = applyMiddleware.apply(null, middleware)(createStore);
}
return create(combineReducers(reducers), initialState);
}
// utilities/assignProviders.js
/**
* Assigns each provider to each component. Expects each component to be
* decorated with `@provide` such that it has an `addProvider` static method.
*
* <strong i="5">@param</strong> {Object} providers
* <strong i="6">@param</strong> {Object} components
* <strong i="7">@api</strong> public
*/
export default function assignProviders (providers, components) {
for (let providerName in providers) {
let provider = providers[providerName];
if (provider.default) {
provider = provider.default;
} else if (provider.provider) {
provider = provider.provider;
}
for (let componentName in components) {
let addProvider = components[componentName].addProvider;
if (typeof addProvider === 'function') {
addProvider(providerName, provider);
}
}
}
}
๋ง์ง๋ง์ผ๋ก provide
๋ฐ์ฝ๋ ์ดํฐ๊ฐ ์์ต๋๋ค. ์์ผ๋ก ํ ๋นํ ์ ์๋๋ก ์ค๊ณ๋ connect
์ ์์ ๋ ๋ฒ์ ์
๋๋ค.
// utilities/provide.js
import React, { Component, PropTypes } from 'react';
import createStoreShape from 'react-redux/lib/utils/createStoreShape';
import shallowEqual from 'react-redux/lib/utils/shallowEqual';
import isPlainObject from 'react-redux/lib/utils/isPlainObject';
import wrapActionCreators from 'react-redux/lib/utils/wrapActionCreators';
import invariant from 'invariant';
const storeShape = createStoreShape(PropTypes);
const defaultMapState = () => ({});
const defaultMapDispatch = dispatch => ({ dispatch });
const defaultMerge = (stateProps, dispatchProps, parentProps) => ({
...parentProps,
...stateProps,
...dispatchProps
});
// Helps track hot reloading.
let nextVersion = 0;
export default function provide (WrappedComponent) {
const version = nextVersion++;
const providers = [];
let shouldSubscribe = false;
function getDisplayName () {
return ''
+'Provide'
+(WrappedComponent.displayName || WrappedComponent.name || 'Component')
+'('+providers.map(provider => provider.name).join(',')+')';
}
function addProvider (name, { mapState, mapDispatch, merge }) {
if (Boolean(mapState)) {
shouldSubscribe = true;
}
providers.push({
name,
mapState: mapState || defaultMapState,
mapDispatch: isPlainObject(mapDispatch)
? wrapActionCreators(mapDispatch)
: mapDispatch || defaultMapDispatch,
merge: merge || defaultMerge
});
Provide.displayName = getDisplayName();
}
function computeStateProps (store) {
const state = store.getState();
const stateProps = {};
for (let provider of providers) {
let providerStateProps = provider.mapState(state);
invariant(
isPlainObject(providerStateProps),
'`mapState` must return an object. Instead received %s.',
providerStateProps
);
Object.assign(stateProps, providerStateProps);
}
return stateProps;
}
function computeDispatchProps (store) {
const { dispatch } = store;
const dispatchProps = {};
for (let provider of providers) {
let providerDispatchProps = provider.mapDispatch(dispatch);
invariant(
isPlainObject(providerDispatchProps),
'`mapDispatch` must return an object. Instead received %s.',
providerDispatchProps
);
Object.assign(dispatchProps, providerDispatchProps);
}
return dispatchProps;
}
function computeNextState (stateProps, dispatchProps, parentProps) {
const mergedProps = {};
for (let provider of providers) {
let providerMergedProps = provider.merge(
stateProps, dispatchProps, parentProps
);
invariant(
isPlainObject(providerMergedProps),
'`merge` must return an object. Instead received %s.',
providerMergedProps
);
Object.assign(mergedProps, providerMergedProps);
}
return mergedProps;
}
const Provide = class extends Component {
static displayName = getDisplayName();
static contextTypes = { store: storeShape };
static propTypes = { store: storeShape };
static WrappedComponent = WrappedComponent;
static addProvider = addProvider;
shouldComponentUpdate(nextProps, nextState) {
return !shallowEqual(this.state.props, nextState.props);
}
constructor(props, context) {
super(props, context);
this.version = version;
this.store = props.store || context.store;
invariant(this.store,
`Could not find "store" in either the context or ` +
`props of "${this.constructor.displayName}". ` +
`Either wrap the root component in a <Provider>, ` +
`or explicitly pass "store" as a prop to "${this.constructor.displayName}".`
);
this.stateProps = computeStateProps(this.store);
this.dispatchProps = computeDispatchProps(this.store);
this.state = { props: this.computeNextState() };
}
recomputeStateProps() {
const nextStateProps = computeStateProps(this.store);
if (shallowEqual(nextStateProps, this.stateProps)) {
return false;
}
this.stateProps = nextStateProps;
return true;
}
recomputeDispatchProps() {
const nextDispatchProps = computeDispatchProps(this.store);
if (shallowEqual(nextDispatchProps, this.dispatchProps)) {
return false;
}
this.dispatchProps = nextDispatchProps;
return true;
}
computeNextState(props = this.props) {
return computeNextState(
this.stateProps,
this.dispatchProps,
props
);
}
recomputeState(props = this.props) {
const nextState = this.computeNextState(props);
if (!shallowEqual(nextState, this.state.props)) {
this.setState({ props: nextState });
}
}
isSubscribed() {
return typeof this.unsubscribe === 'function';
}
trySubscribe() {
if (shouldSubscribe && !this.unsubscribe) {
this.unsubscribe = this.store.subscribe(::this.handleChange);
this.handleChange();
}
}
tryUnsubscribe() {
if (this.unsubscribe) {
this.unsubscribe();
this.unsubscribe = null;
}
}
componentDidMount() {
this.trySubscribe();
}
componentWillReceiveProps(nextProps) {
if (!shallowEqual(nextProps, this.props)) {
this.recomputeState(nextProps);
}
}
componentWillUnmount() {
this.tryUnsubscribe();
}
handleChange() {
if (this.recomputeStateProps()) {
this.recomputeState();
}
}
getWrappedInstance() {
return this.refs.wrappedInstance;
}
render() {
return (
<WrappedComponent ref='wrappedInstance' {...this.state.props} />
);
}
}
if ((
// Node-like CommonJS environments (Browserify, Webpack)
typeof process !== 'undefined' &&
typeof process.env !== 'undefined' &&
process.env.NODE_ENV !== 'production'
) ||
// React Native
typeof __DEV__ !== 'undefined' &&
__DEV__ //eslint-disable-line no-undef
) {
Provide.prototype.componentWillUpdate = function componentWillUpdate () {
if (this.version === version) {
return;
}
// We are hot reloading!
this.version = version;
// Update the state and bindings.
this.trySubscribe();
this.recomputeStateProps();
this.recomputeDispatchProps();
this.recomputeState();
};
}
return Provide;
}
provide
๋ฐ์ฝ๋ ์ดํฐ์ ๋ํด ์ธ๊ธํ ๊ฐ์น๊ฐ ์๋ ํ ๊ฐ์ง๋ react-devtools
์ฌ์ฉํ์ฌ ๋ชจ๋ ๊ฒ์ ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ ๊ฒ์ ๋ณผ ์ ์๋ค๋ ๊ฒ์
๋๋ค( Branches
๋ ProvideBranches(theme,packageList,sources)
):
์ด ๋์ ( Branches
๊ฐ Connect(Branches)
๋ํ๋๋ ์๋ connect
์์ ๋ณผ ์ ์์):
์ดํ ์ ์ redux
์ ๋ํด ๋ฐฐ์ฐ๊ธฐ ์์ํ๊ธฐ ๋๋ฌธ์ ์์งํ ์ด ์ ๊ทผ ๋ฐฉ์์ ๊ฒฐ๊ณผ๋ก ์ด๋ค ์ ํ(์๋ ๊ฒฝ์ฐ)์ด ์๋์ง ์ ํ ๋ชจ๋ฆ
๋๋ค. ๋ด ๋จธ๋ฆฌ ๊ผญ๋๊ธฐ์์, ๋๋ ์ ๋ง๋ก ์๋ฌด ๊ฒ๋ ์๊ฐํ ์ ์์ผ๋ฉฐ ๋ชจ๋ ๊ฒ์ด ๋ด ์ฌ์ฉ ์ฌ๋ก์ ์๋ฒฝํ๊ฒ ์๋ํ๋ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง๋ง ์๋ง๋ ๋ ๊ฒฝํ์ด ๋ง์ ์ฌ๋์ด ์๋ฆด ์ ์์ต๋๋ค.
์ด ํน์ ์ ๊ทผ ๋ฐฉ์์ ์๊ฐํด ๋ธ ์ด์ ๋ ํ ๋น๋ ๊ณต๊ธ์๋ฅผ ๊ฐ์ง ์ ์๋ _์ ๋ง_ "๋ฉ์ฒญํ" ๊ตฌ์ฑ ์์๊ฐ ์๋ค๋ ์์ด๋์ด๊ฐ ๋ง์์ ๋ค๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด๋ ๊ด์ฌ์ฌ์ ์ง์ ํ ๋ถ๋ฆฌ๋ฅผ ์ํํ๊ณ ๋ชจ๋๋ก ์ฝ๊ฒ ๊ตํํ ์ ์๋ ์ ๊ณต์๋ฅผ ์ํ๋ ์๋งํผ ํ์ฉํฉ๋๋ค.
๋ํ @gaearon ์ด ์ด ์ ๊ทผ ๋ฐฉ์์ ์ข์ํ๊ณ react-redux
๋ฒ์์ ํด๋นํ๋ค๊ณ ์๊ฐํ๋ฉด ์ถ๊ฐ๋ PR์ ์ ์ถํ๊ฒ ๋์ด ๊ธฐ์ฉ๋๋ค. ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ react-redux-providers
๋ฅผ ๋ง๋ค๊ณ ๊ฒ์ํ ๋ค์ ๊ฒฐ๊ตญ์๋ ์ค์ ์ฌ๋ก์ ํจ๊ป ์์ ๊ณต๊ธ์(์: react-redux-provide-toggle
)๋ฅผ ๊ฒ์ํ ๊ฒ์
๋๋ค.
์ค๊ฐ ์์ค ๊ตฌ์ฑ ์์์ ๋ถํ์ํ ๋ค์ ๋ ๋๋ง์ ํผํ ์ ์๊ธฐ ๋๋ฌธ์?
์
์ง๊ธ ์ทจํ ๋ฐฉํฅ์ ์ํ์ Immutable.js๋ฅผ ์ฌ์ฉํ๊ณ (์ฑ ์ํ์ UI ์ํ ๋ชจ๋, ์ผ๋ถ UI ์ํ๋ ๊ตฌ์ฑ ์์์ ์ ์ง) ๋ชจ๋ ๊ตฌ์ฑ ์์์ PureRenderMixin์ ์ฌ์ฉํ๋ค๋ ๊ฒ์
๋๋ค. ์ด๊ฒ์ ํํฅ์ ํ๋ฆ์ ์ฌ์ฉํ ๋์ ์ฑ๋ฅ ์ ํ๋ฅผ ์ ๊ฑฐํ์ง ์์ต๋๊น?
ํธ์ง: ๋ฎ์ ์์ค์ ๊ตฌ์ฑ ์์ ์ค ํ๋๊ฐ ์ฌ์ ํ ๋ค์ ๋ ๋๋งํด์ผ ํ๋ ๊ฒฝ์ฐ ์ค๊ฐ ์์ค ๊ตฌ์ฑ ์์์ ๋ํ ์ฑ๋ฅ ์ ํ๋ฅผ ์ ๊ฑฐํ์ง ๋ชปํ์ง๋ง ์ฌ์ ํ ๋ง์ ์ค๋ฒํค๋๋ฅผ ์ ๊ฑฐํด์ผ ํ๋ค๋ ๊ฒ์ ๊นจ๋ฌ์์ต๋๋ค. ์ด๋ค ๊ฒฝํ์ด ์์ต๋๊น?
@danmaz74 ์ฐ๋ฆฌ๋ ์ด์ ๊ฐ์ ์๋๋ฆฌ์ค์์ ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ
@eldh ์ ๋ฐ์ดํธ์ ๊ฐ์ฌ๋๋ฆฝ๋๋ค. ์๋ฏธ๊ฐ ์์ต๋๋ค. ์งํํ๋ ๋์ ์ฐธ๊ณ ํ๊ฒ ์ต๋๋ค. :)
์ด์ ๋ํ ์ ๋ฐ์ดํธ๊ฐ ์์ต๋๊น? timbur์ ์๋ ๋์๊ฒ ์๋ฒฝํ๊ฒ ์ดํด๋์ง๋ง ๋จ์ผ ์ฐ๊ฒฐ ์ฐ์ต์ ์ ์ดํดํ์ง ๋ชปํฉ๋๋ค. ๋๋ ๋ฐ๋ ๋ ผ์ฆ, ์ฆ "์ฌ๋๋ค์ ์ฌ๊ธฐ์์ ์ ํธํ๋ ์ฐจ์ด๊ฐ ์์ต๋๋ค"์ ๊ด์ฌ์ด ์์ต๋๋ค.
๋จ์ผ ์ฐ๊ฒฐ์ ์ฌ์ฉํ๋ฉด ์ํ๋ฅผ 10๊ฐ์ ๊ตฌ์ฑ ์์ ๊น์ ๊ณ์ธต ๊ตฌ์กฐ๋ก ์์ ํ ๋ณํํ๊ธฐ ์ํด ๊ฑฐ๋ํ mapStateToProps ํจ์๊ฐ ํ์ํฉ๋๋ค... ๊ทธ ์ด๋ฉด์ ์๊ฐ์ด ๋ฌด์์ธ์ง ๋๋ ๋ด๊ฐ ๋ญ๊ฐ๋ฅผ ์คํดํ๊ณ ์๋์ง ์ฝ๊ฐ ํผ๋์ค๋ฝ์ต๋๋ค...
๋จ์ผ ์ฐ๊ฒฐ์ ์ฌ์ฉํ๋ฉด ๊ฑฐ๋ํ mapStateToProps ํจ์๊ฐ ํ์ํฉ๋๋ค.
์๋ฌด๋ ๋จ์ผ ์ฐ๊ฒฐ์ ์นํธํ์ง ์์ต๋๋ค.
๊ทธ๋ฐ ๋ค์ react-redux์ connect() ํจ์๋ฅผ ์ฌ์ฉํ์ฌ Redux์ ์ฐ๊ฒฐํ๋ ค๋ ๊ตฌ์ฑ ์์๋ฅผ ๋ํํฉ๋๋ค. ์ต์์ ๊ตฌ์ฑ ์์ ๋๋ ๊ฒฝ๋ก ์ฒ๋ฆฌ๊ธฐ ์ ๋ํด์๋ง ์ด ์์ ์ ์ํํ์ญ์์ค. ๊ธฐ์ ์ ์ผ๋ก ์ฑ์ ๋ชจ๋ ๊ตฌ์ฑ ์์๋ฅผ Redux ์คํ ์ด์ ์ฐ๊ฒฐํ ์ ์์ง๋ง ๋๋ฌด ๊น๊ฒ ์ฐ๊ฒฐ ํ๋ฉด ๋ฐ์ดํฐ ํ๋ฆ์ ์ถ์ ํ๊ธฐ๊ฐ ๋ ์ด๋ ค์์ง๋ฏ๋ก
"๋จ์ผ"์ ์์ ์์ ๋ง๋ ๊ฒ๊ณผ ๊ฐ์ ์์ ์ฑ๋ง ๋ํ๋ ๋๋ค. ์ด๊ฒ์ ๋ ๋ช ํํ ํ๊ธฐ ์ํด ์์ ๋กญ๊ฒ ๋ฌธ์๋ฅผ ์์ ํ์ญ์์ค. ๋๋ ์ง๊ธ ๋ค๋ฅธ ํ๋ก์ ํธ๋ก ๋ฐ์๋ฏ๋ก ๋๊ตฐ๊ฐ๊ฐ PR์ ํ์ง ์๋ ํ ์ด ๋ฌธ์ ๊ฐ ์ด๋ค ์์ง์๋ ๊ธฐ๋ํ์ง ๋ง์ญ์์ค. ๋น์ ๋ ํ ์ ์์ต๋๋ค.
๋ช ํํ๊ฒ ํด ์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค.
๋ง์นจ๋ด react-redux-provide
์ถ์์ ์ด๋ฅด๋ ์ต๋๋ค. ์ฌ๊ธฐ์์ ํ์ธ
์์ฃผ ์ข์ ๋ณด์ ๋๋ค. ๊ฐ์ฌํฉ๋๋ค!
์ ๋ ์๋ก์ด redux ํ๋ก์ ํธ๋ฅผ ๋ง ์์ํ๊ณ '์ค๋งํธ' ๊ตฌ์ฑ ์์์ ์ฐ๊ฒฐ์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ์ด๊ฒ์ ๋์๊ฒ ํจ์ฌ ๋ ์ดํด๊ฐ ๋๋ฉฐ ์ฑ๋ฅ์์ ์ด์ ์ด ์๋ค๋ฉด ์ถ๊ฐ ์น๋ฆฌ๊ฐ ์์ต๋๋ค. ๋ฐ๋๋ก, ๋ชจ๋ ์ ์ด๋ฅผ ๊ธฐ๋ณธ ์ฑ์ด๋ ๋ผ์ฐํฐ๊น์ง ๋ฒ๋ธ๋งํ๋ฉด SRP๊ฐ ์์ ํ ์์ค๋ฉ๋๋ค. ๋ฌธ์ ๋ฅผ ๋ถํดํ๊ธฐ ์์ํ๊ธฐ ์ ์ ์ฑ์ด ์ผ๋ง๋ ์ปค์ผ ํ ๊น์?
๊ด๋ จ ์์๋ฅผ ๊ตฌ์ฑ ์์ ํด๋๋ก ๊ตฌ์ฑํ๋ ๋ฐฉ๋ฒ๋ ์๊ฐํ๊ณ ์์ต๋๋ค. ์ฃผ์ฑ๋ถ ์์ ๊ฐ์๊ธฐ๋ฅผ ๋์ญ์์ค.
๊ถ๊ทน์ ์ผ๋ก ๋๋ redux/flux๊ฐ ์์ธก ๊ฐ๋ฅํ ์ํ์ ๋ํ ํฐ ์ด์ ์ด๋ผ๊ณ ๋ฏฟ์ต๋๋ค. ๊ทธ๋ฌ๋ ํ์ค mv์์ ๊ทธ๋ฌํ ์ ์ ์ ๋ณํ-UI ์ฑ ๊ฐ๋ฐ์ ๊ฐ๋จํ๊ณ ๋๊ตฌ๋ ์ก์ธ์คํ ์ ์๊ฒ ๋ง๋ ๊ฒ์ด ๋ฌด์์ด๋ ๊ฐ์, ๊ฒฐ๊ตญ์๋ ๊ทธ ํ๋ญ์ค๊ฐ ์ถ์ํ๋๊ณ ์ฐ๋ฆฌ๋ ์ด๋ํ ๊ฒ์ ๋๋ค. mv*์ ๋ ๋น์ทํ๊ฒ ๋ณด์ด๋ ๊ฒ์ผ๋ก ๋์๊ฐ๋๋ค.
์ด๊ฒ์ #1285์์ ์์ ๋๊ณ ์์ต๋๋ค.
์
๋ฐ์ดํธ๋ ๋ฌธ์์์ ๋ ์ด์ ์ปจํ
์ด๋ ๊ตฌ์ฑ ์์๋ฅผ ๋ง๋๋ ๊ฒ์ ๊ถ์ฅํ์ง ์์ต๋๋ค.
http://redux.js.org/docs/basics/UsageWithReact.html
์๋ ํ์ธ์, ์ฌ๊ธฐ์ ๋์์ด ๋ ๋งํ ๋ช ๊ฐ์ง ํญ๋ชฉ์ ๋ํด ์ผ์ต๋๋ค. :)
https://medium.com/@timbur/react -automatic-redux-providers-and-replicators-c4e35a39f1
https://github.com/reactjs/redux/issues/419#issuecomment -183769392๋ #1353์ ๋์์ด ๋ ์ ์๋ค๊ณ ์๊ฐํฉ๋๋ค.
@timbur ๋ฉ์ง ๊ธฐ์ฌ! ์ด ์ง๋ฌธ์ ๋ํ ์๊ฐ์ ๊ณต์ ํด ์ฃผ์๊ฒ ์ต๋๊น? https://github.com/reactjs/react-redux/issues/278
์ด๊ฒ์ด ๋์ ๋๊ฒ ๋ถ๋ช ํ์ง ํ์คํ์ง ์์ง๋ง ๋ช ํ์ฑ์ ์ํด ์ฌ๊ธฐ์ ์ถ๊ฐํ ๊ฐ์น๊ฐ ์๋ค๊ณ ์๊ฐํฉ๋๋ค. ์ธ๊ธ๋ ๋ง์ ๋ด์ฉ์ด ์ด ์ค๋ ๋์ ์ค๋ redux๋ฅผ ์ฒ์ ์ ํ๋ ์ฌ๋์๊ฒ๋ ๋ค์ ์ถ์์ ์ผ ์ ์๋ค๊ณ ์๊ฐํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
๋ด๊ฐ ์ฒ์ redux๋ฅผ ์ฌ์ฉํ๊ธฐ ์์ํ์ ๋ ๋ด ์ฑ์ด ๋ง์ ๋ถ๋ฆฌ๋ฅผ ํ์๋ก ํ์ง ์์ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ๊ธฐ ๋๋ฌธ์ (๊ทธ๋ฅ ํ๋ฒํ ์ค๋๋ ๋ฉ์ฒญํ) "๊ตฌ์ฑ ์์" ์์ "์ปจํ ์ด๋"(์ฐ๊ฒฐ๋ ๊ตฌ์ฑ ์์)๋ฅผ ์๋ชป ์ ์ผ๋ก ํ์ํ์ต๋๋ค. ๋ด๊ฐ ์ผ๋ง๋ ํ๋ ธ์ด. ์ด๋ฌํ ๊ตฌ์ฑ ์์ ์ค ๋ง์ ๋ถ๋ถ์ ์ฌ์ฌ์ฉํด์ผ ํ๋ค๋ ๊ฒ์ ๊นจ๋ฌ์์ ๋ ์๋นํ ์์ ๋ฆฌํฉํ ๋ง์ ์ํํด์ผ ํ๊ณ ๋ง์ ์ค๋งํธ ํญ๋ชฉ์ ํธ๋ฆฌ ๋งจ ์๋ก ์ฎ๊ฒผ์ง๋ง ๊ณง ๋ค๋ฃจ๊ธฐ ์ด๋ ค์์ก์ต๋๋ค. ์ปจํ ์คํธ๋ฅผ ์ ๊ณตํ๋ ์๋จ์ "์ ๊ณต์"(ํ์ํ ๋๋ก)๋ฟ ์๋๋ผ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ ์ฑ(ํ์ง ์์์ผ ํจ)๋ ์ ๊ณตํฉ๋๋ค.
๋ด๊ฐ ์ ๊ทผํ๋ ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์ ๋งจ ์์ ๊ณต๊ธ์๊ฐ ์๋ ๋ฒ์ด๋ฆฌ ๊ตฌ์ฑ ์์๋ฅผ ๊ตฌ์ฑํ๋ ์ปจํ ์ด๋ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ๊ฐ๋ ๊ฒ์ ๋๋ค. ์ปจํ ์ด๋๋ ๋ค๋ฅธ ์ปจํ ์ด๋ ๋ด๋ถ์๋ง ์์ด์ผ ํฉ๋๋ค . ์ฑ์ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ํ์ํ๋ ์ปจํ ์ด๋์ ๊ณ์ธต ๊ตฌ์กฐ์ฌ์ผ ํฉ๋๋ค.
์ข ์ข ๋ชฉ๋ก์ ์ฌ์ฉํ์ฌ ์ด ์์ ์ ์ํํ๋ ์ข์ ๋ฐฉ๋ฒ์ ์ค๋งํธ ๊ตฌ์ฑ ์์๋ฅผ ํตํด ID๋ฅผ ์ ๋ฌํ๋ ๊ฒ์ ๋๋ค. ID๊ฐ ํ์ํ๋ค๋ ๊ฒ์ ๋ฌด์ธ๊ฐ๊ฐ ์ฑ์ ๋๋ฉ์ธ์ ์ํ๋ค๋ ํ์์ ๋๋ค. ๋ฐ๋ผ์ ๊ฐ๋ฅํ ๊ฒฝ์ฐ ํ ์ปจํ ์ด๋์ ์๋ ID ๋ชฉ๋ก์ ๊ฐ์ ธ์์ ID๋ฅผ ์ฌ์ฉํ์ฌ ์ํ๋ ์ ๋ณด๋ฅผ ์ป์ ์ ์๋ ๋ค๋ฅธ ์ปจํ ์ด๋๋ก ์ ๋ฌํฉ๋๋ค. ๊ฐ ์ปจํ ์ด๋ ๋ด์์ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฉํ์ฌ ID ์์ด๋ ํด๋น ์ ๋ณด๋ฅผ ๋ ๋๋งํฉ๋๋ค.
์๋์์ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฉํ์ฌ ํ์ํ๋ ์ปจํ ์ด๋ ๊ณ์ธต์ ํตํด ์ฑ์ ์ฐ๊ฒฐ๋ ๋ถ๋ถ์ ์ ๋ฌํ๋ ๋ฐฉ๋ฒ์ (๋ณต์กํ) ์์ ๋ฅผ ์กฐ๋กฑํ์ต๋๋ค.
// Provider component that renders some containers and some components and provides the store
class TodoAppProvider {
constructor() {
// setup store etc.
}
render() {
return (
<Provider store={this.store}> {/* Provider from 'react-redux' */}
<AppLayoutComponent title="My Todos" footer={<TodoFooter />}>
<TodoListsContainer />
</AppLayoutComponent>
</Provider>
);
}
);
// AppLayoutComponent
// Lots of nice css, other dumb components etc. no containers!
export default const AppLayoutComponent = ({ title, children, footer }) => (
<header>
{title}
</header>
<main>
{children /* This variable can be a container or components but it's not hardcoded! */}
</main>
<footer>
{footer}
</footer>
);
// TodoFooter
// Another dumb component
export default const TodoFooter = () => (
<footer>
© {Date.now() /* we are copyrighted to the millisecond */}
</footer>
);
// TodoListsContainer
// Smart component that renders all the lists
class TodoListsContainer extends React.Component {
render() {
return () {
<div>
{todoLists.map(id => (
{/* this container renders another container */ }
<TodoListContainer key={id} todoListId={id} />
))}
</div>
}
}
}
const mapStateToProps = state => ({
todoLists: getTodoLists(state),
});
export default connect(mapStateToProps)(TodoListsContainer);
// TodoListContainer
// Gets the props and visibleTodo IDs for the list
class TodoListContainer {
render() {
const { id, title, visibleTodos } = this.props;
return (
<div>
{/* Render a component but passes any connected data in as props / children */}
<TodoListPanelComponent title={title}>
{visibleTodos.map(id => (
<TodoContainer todoId={id} />
))}
</TodoListPanelComponent>
</div>
);
}
}
const mapStateToProps = (state, { todoListId }) => ({
...getTodoList(state, todoListId), // A todo object (assume we need all the attributes)
visibleTodos: getVisibleTodos(state, todoListId), // returns ids
});
export default connect(mapStateToProps)(TodoListContainer);
// TodoListPanelComponent
// render the panel to sit the todos in
// children should be todos
// No containers!
export default const TodoListPanelComponent = ({ title, children }) => (
<div>
<h3>{title}</h3>
<div>
{children}
</div>
</div>
);
// TodoContainer
// This just wraps the TodoComponent and passed the props
// No separate class or JSX required!
const mapStateToProps = (state, { todoId }) => ({
...getTodo(state, todoId),
});
const mapDispatchToProps = (dispatch, { todoListId }) => ({
handleFilter: () => dispatch(hideTodo(id)), // Pass ALL smart stuff in
});
export default connect(mapStateToProps, mapDispatchToProps)(TodoComponent); // Passing in the component to connect
// TodoComponent
// Render the component nicely; again, as all of its connected stuff passed in
// The FilterLinkContainer is an example of a smell that will come back to bite you!
export default const TodoComponent = ({ content, isComplete, handleFilter }) => (
<div>
<div>
{content}
</div>
<div>
{isComplete ? 'โ' : 'โ'}
</div>
<div>
{/* Don't do this, can't re-use TodoComponent outside the app context! */}
<FilterLinkContainer />
{/* Instead do this (or similar), component can be reused! */}
<Link onClick={handleFilter}>
'Filter'
</Link>
</div>
</div>
);
๋ฐ๋ผ์ ์ฌ๊ธฐ์ ์ปจํ
์ด๋ ๊ณ์ธต ๊ตฌ์กฐ๋ TodoAppProvider
> TodoListsContainer
> TodoListContainer
> TodoContainer
์
๋๋ค. ๊ทธ๊ฒ๋ค์ ๊ฐ๊ฐ ์๋ก์ ์ํด ๋ ๋๋ง๋๊ณ ๊ตฌ์ฑ ์์ ๋ด๋ถ์์ ๋ ๋๋ง๋์ง ์์ผ๋ฉฐ ์์ ๋ณด๊ธฐ ์ฝ๋๋ฅผ ํฌํจํ์ง ์์ต๋๋ค(React ๋ํ ์ด์ ๋ก ๊ฐ๋ div
์ ์ธ).
๊ถ๊ทน์ ์ผ๋ก ๋ด๊ฐ ์๊ฐํ๋ ๋ฐฉ์์ ๋ง์น ์ฒ์์ UI๊ฐ ๊ด์ฌ์ ๊ฐ๋ ์ ์ฉํ ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋งคํํ๋ ์ฐ๊ฒฐ๋ ๊ตฌ์ฑ ์์์ ํธ๋ฆฌ๋ฅผ ๋ง๋๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค. ๊ทธ๋ฌ๋ UI๋ ์ ํ ์์ผ๋ฉฐ ์ปจํ ์ด๋์ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ํตํด ๋งคํ๋ ์ํ ํธ๋ฆฌ ๋ง ์์ต๋๋ค. ๊ทธ ํ, ์ํ๋ ๋ฐฉ์์ผ๋ก(์ฆ, DOM์์) ์ค์ ๋ก ๋ฐ์ดํฐ๋ฅผ ํ์ํ๊ธฐ ์ํด ํด๋น ์ปจํ ์ด๋ ๋ด๋ถ์ ํ๋ฆฌ์ ํ ์ด์ ๊ตฌ์ฑ ์์๋ฅผ ์ดํด๋ณด๊ณ ๋ฟ๋ฆฝ๋๋ค. ๋ถ๋ช ํ ์ด 2๋จ๊ณ ๋ฐฉ์์ผ๋ก ์ฑ์ ์์ฑํ๋ ๊ฒ์ ์ ์ฉํ์ง ์์ง๋ง ์ด์ ๊ฐ์ ๋ชจ๋ธ์ ๊ฐ๋ ํํ๋ ๊ฒ์ด ์ ์ฉํ๋ค๋ ๊ฒ์ ์์์ต๋๋ค.
connect()
๋ ๋ฒ ์ด์ ์ฌ์ฉํ๋ฉด ์ฑ๋ฅ ์ ํ(๋๋ ๋ค๋ฅธ ์ํคํ
์ฒ์์ ์ด์ )๊ฐ ์์ต๋๊น? ๋ค์๊ณผ ๊ฐ์ ๋ฐฉ์์ผ๋ก ์ฐ๊ฒฐ ์ง์
์ ์ ์ถ์ํํ์ฌ ๊ตฌ์ฑ ์์์ ์์ฃผ ์ฌ์ฉ๋๋ ์ํ์ ์ ๊ณตํ๋ ค๊ณ ํฉ๋๋ค.
// connectCommonProps.js (mergeProps not included for the sake of simplicity)
const _mapStateToProps = (state) => ({ [often used slices of state] });
const _mapDispatchToProps = (dispatch) => ({ [often used actions] });
const connectCommonProps = (mapStateToProps, mapDispatchToProps, component) => {
// First connect
const connectedComponent = connect(mapStateToProps, mapDispatchToProps)(component);
// Second connect
return connect(_mapStateToProps, _mapDispatchToProps)(connectedComponent);
};
export default connectMapAndFieldProps;
// Some component that needs the often used props
...
export default connectCommonProps(..., ..., Component);
๋๋ ์ฌ๊ธฐ์์ ๊ฒ์ผ๋ฅด๊ณ ์ ์ธ์ ๋จ์ํ๊ฒ ์ ์งํ๊ธฐ ๋๋ฌธ์ mapStateToProps
์ ๋ ๊ฐ์ง ๋ฒ์ ๊ณผ mapDispatchToProps
์ ๋ ๊ฐ์ง ๋ฒ์ ์ ๊ฒฐํฉํ์ง ์์์ต๋๋ค. ํ์ง๋ง connect
๊ฐ ๋๋ฅผ ์ํด ์ผํ๊ฒ ํ๋ ๊ฒ์ด ๋์ ์๊ฐ์ธ์ง ๊ถ๊ธํฉ๋๋ค.
@timotgl : Dan์ ๋ ์ด์ ํ์ฑ ์ ์ง ๊ด๋ฆฌ์๊ฐ ์๋๋๋ค. Dan์๊ฒ ์ง์ ping์ ๋ณด๋ด์ง ๋ง์ญ์์ค.
์ฌ๋ฌ ๊ตฌ์ฑ ์์ ์ฐ๊ฒฐ ์
๊ฐ์ธ์ ์ผ๋ก ๋ค๋ฅธ ์ ๊ทผ ๋ฐฉ์์ ์๋ํ๋ ๊ฒ์ด ์ข์ต๋๋ค. "๊ณตํต props" HOC ๋๋ ๋๋ถ๋ถ ์์์๊ฒ ์ ๋ฌํ๋ ๋ฌด์ธ๊ฐ๊ฐ ์๊ฑฐ๋ ์ ํ๊ธฐ๋ฅผ ์ฌ์ฉํ์ฌ ํน์ ๊ตฌ์ฑ ์์์ mapState
ํจ์์์ ๊ณตํต props๋ฅผ ๊ฒ์ํ๊ณ ํ์ํ ํน์ props์ ๊ฒฐํฉํฉ๋๋ค.
@markerikson ์ฃ์กํฉ๋๋ค ๋ชฐ๋์ต๋๋ค ๋ฉ์ ์ด ์ญ์ ๋์์ต๋๋ค.
๋ฐ๋ผ์ ์ฐ์ ์๋ํ๊ณ ๊ตฌ์ฑ ์์๋ ๋ฐ์ ๊ฐ๋ฐ ๋๊ตฌ์ ๋ค๋ฅธ ์ฐ๊ฒฐ๋ ๊ตฌ์ฑ ์์์ฒ๋ผ ๋ํ๋๋ฉฐ ์ถ๊ฐ ๋ํผ ๋๋ ์ด์ ์ ์ฌํ ๊ฒ์ด ์์ต๋๋ค.
๋๋ OOP/์์ ํจ๋ฌ๋ค์์ ํฌํจํ๊ณ ์ถ์ง ์์๊ธฐ ๋๋ฌธ์ HOC์ ๋ฐ๋ํ๊ธฐ๋ก ๊ฒฐ์ ํ์ต๋๋ค. ๊ตฌ์ฑ ์์์ ๋ ๋ง์ ์ํ์ ์ ๊ณตํ๋ ๊ฒ์ผ ๋ฟ์ด๋ฏ๋ก ๋์์ ๊ทธ๋๋ก ์ ์ง๋ฉ๋๋ค.
mapStateToProps
์์ ๋ฐฐ์ ํ๋ ๊ฒ์ ๋ํ ์ข์ ์ ์
๋๋ค. ๊ทธ๊ฒ์ ์๋ํ์ง๋ง ์ ์ด๋ 2๊ฐ์ ์ง์
์ ์ด ์์ต๋๋ค. ์ฐ๊ฒฐํ๊ธฐ ์ํด ํ๋์ ๋์ฐ๋ฏธ ํจ์๋ฅผ ํธ์ถํ๋ ๊ฒ์ด ๋ ๊ฐ๋จํด ๋ณด์
๋๋ค.
"2๊ฐ์ ์ง์ ์ "์ด ๋ฌด์์ ์๋ฏธํ๋์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.
๋ด๊ฐ ๊ทธ๋ฆฌ๋ ๊ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
import {selectCommonProps} from "app/commonSelectors";
import {selectA, selectB} from "./specificSelectors";
const mapState = (state) => {
const propA = selectA(state);
const propB = selectB(state);
const commonProps = selectCommonProps(state);
return {a, b, ...commonProps};
}
@markerikson ๋ ๊ฐ์ ์ง์
์ ์ผ๋ก mapDispatchToProps
๋ํด ๋์ผํ ์์
์ ์ํํด์ผ ํ๊ณ ์ ์ฌ์ ์ผ๋ก mergeProps
๋ํด์๋ ๋ง์ฐฌ๊ฐ์ง์
๋๋ค.
mergeProps
๋ ๊ฑฐ์ ์ฌ์ฉํ์ง ์์์ผ ํฉ๋๋ค. ์ด๋ ์ตํ์ ์๋จ์ผ๋ก ํ์ถ๊ตฌ๋ก ์ฌ์ฉํ๋ ๊ฒ์ด๋ฏ๋ก ์ฌ์ฉ์ ๊ถ์ฅํ์ง ์์ต๋๋ค. ๋ํ ์ผ๋ฐ์ ์ผ๋ก ์ค์ mapDispatch
ํจ์๋ฅผ ์ค์ ๋ก ์์ฑํ์ง ์๊ณ ๋์ "๊ฐ์ฒด ์๊ธฐ"๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
import {addTodo, toggleTodo} from "./todoActions";
class TodoList extends Component {}
const actions = {addTodo, toggleTodo};
export default connect(mapState, actions)(TodoList);
๋ชจ๋ "๊ณตํต" ์์ ์์ฑ์๋ฅผ ๋ค์ ๋ด๋ณด๋ด๊ณ ๋ค์๊ณผ ๊ฐ์ ์์ ์ ์ํํ๋ ์ผ๋ถ index.js ํ์ผ์ ์ฝ๊ฒ ๊ฐ์ง ์ ์์ต๋๋ค.
import * as commonActions from "app/common/commonActions";
import {specificAction1, specificAction2} from "./actions";
const actionCreators = {specificAction1, specificAction2, ...commonActions};
export default connect(null, actionCreators)(MyComponent);
mergeProps
๋ ๊ฑฐ์ ์ฌ์ฉํ์ง ์์์ผ ํฉ๋๋ค. ์ด๋ ์ตํ์ ์๋จ์ผ๋ก ํ์ถ๊ตฌ๋ก ์ฌ์ฉํ๋ ๊ฒ์ด๋ฏ๋ก ์ฌ์ฉ์ ๊ถ์ฅํ์ง ์์ต๋๋ค.
@markerikson๋ ์๋ ํ์ธ์, ๋๊ตฐ๊ฐ mergeProps ์ฌ์ฉ์ ํผํด์ผ ํ๋ ์ด์ ๊ฐ ๊ถ๊ธํฉ๋๋ค. mapDispatchToProps์ ๋ด ์์ ์ ํ์ํ ์ ์์ง๋ง ๊ตฌ์ฑ ์์๊ฐ ์๋ mapStateToProps์์ ์ํ์ "์จ๊ธฐ๊ธฐ"ํ๋ ๊ฒ์ด ๋งค์ฐ ํธ๋ฆฌํฉ๋๋ค. ๋์ ์ผ์ ๋๊น?
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
์๋์, ์ ๋ฐ๋์ ๋๋ค. ํํฅ์ ํ๋ฆ์ ํฌ๊ธฐํ๋ฉด ๋ ๋์ ์ฑ๋ฅ์ ์ป์ ์ ์์ต๋๋ค.