ããã«ã¡ã¯@ gaearon-次ã®ã¹ããŒãã¡ã³ãã¯ãããã¥ã¡ã³ããèªãã§ãããšãã«ç§ã
次ã«ãreact-reduxã®connectïŒïŒé¢æ°ã䜿çšããŠãReduxã«æ¥ç¶ããã³ã³ããŒãã³ããã©ããããŸãã ããã¯ãæäžäœã®ã³ã³ããŒãã³ããŸãã¯ã«ãŒããã³ãã©ãŒã«å¯ŸããŠã®ã¿å®è¡ããŠãã ããã æè¡çã«ã¯ãã¢ããªå ã®ä»»æã®ã³ã³ããŒãã³ããReduxã¹ãã¢ã«connectïŒïŒã§ããŸãããããŒã¿ãããŒã®è¿œè·¡ãå°é£ã«ãªãããããããæ·±ãããããªãããã«ããŠãã ããã
æ·±ããããããã§ãŒã³ã¯ãç§ãFluxã䜿çšããããã«ãªã£ãReactã®åé¡ã®1ã€ã§ããã ããŒã¿ãããŒã®ãã¬ãŒã¹ã容æã«ããããã®ãã¬ãŒããªãã¯ããããããããŒãã»ããã¢ãã/ç¶æ/ãã¬ãŒã¹ããå¿ èŠãããããšã§ããããã«éèŠãªããšã«ã芪ã¯åã®ããŒã¿èŠä»¶ã«ã€ããŠç¥ãå¿ èŠããããŸãã ç§ã®çµéšããããã®ã¢ãããŒããåªããŠãããšã¯ç¢ºä¿¡ããŠããŸããããããªãã®èããèããŠã¿ããã§ãïŒsmileïŒ
ç§ã¯ãã¬ã³ãã«åŒ·ãåæããŸãã ããã¯ãRelayã®ãããªãã®ã®èåŸã«ããåºæ¬çãªèãæ¹ã®å€ãã§ãããããé«ãçµæã«ã€ãªãããŸãã
ãã¶ãããããã§ã¯äººã«ãã£ãŠå¥œã¿ãéãããšã ãèšãã¹ãã§ãããã
@ gaearon-ãããããããŒã¿ãããŒã®ãã¬ãŒã¹ãè€éã«ããæ¹æ³ã®ãã¢ã³ã¹ãã¬ãŒã·ã§ã³ãå«ããã¬ãŒããªãã«é¢ããã»ã¯ã·ã§ã³ã圹ç«ã€ã§ãããã ç§ã¯ãã®å¥œã¿ã®ç§ã®åŽã«å£²ã蟌ãããšãã§ããŸããã
ãããã æåã®ããã¥ã¡ã³ããèœã¡çããããéãããŸãŸã«ããŠãããäžåºŠç¢ºèªããŸãããã
@gaearonããã§ããïŒ :)å蚪ããããšãã«ç§ã«pingããŠãã ãã
ç§ã¯ReduxãåããŠäœ¿çšããŸãããããã¯æšå¹Žã®ç§ã«ãšã£ãŠããªã倧ããªåé¡ã§ãããããŒã¿æ§é ãå€§å¹ ã«å€æŽããã¢ããªãæ§ç¯ããŸããã ã ããç§ã¯ããã«ã€ããŠ@brentvatneã«æ¬åœã«åæããŠãããšèšããªããã°ãªããŸããã
å€ãã®ã³ã³ããŒãã³ããconnectïŒïŒãããšãã«ããŒã¿ãããŒããã¬ãŒã¹ãã以å€ã«åé¡ã¯ãããŸããïŒ ããšãã°ãããã©ãŒãã³ã¹ã®äœäžã¯ãããŸããïŒ
å€ãã®ã³ã³ããŒãã³ããconnectïŒïŒãããšãã«ããŒã¿ãããŒããã¬ãŒã¹ãã以å€ã«åé¡ã¯ãããŸããïŒ ããšãã°ãããã©ãŒãã³ã¹ã®äœäžã¯ãããŸããïŒ
ãããããŸã£ããéã§ãããããããŠã³ãããŒãæŸæ£ããããšã§ãããã©ãŒãã³ã¹ãåäžããŸãã
äžéã¬ãã«ã®ã³ã³ããŒãã³ãã®äžèŠãªåã¬ã³ããªã³ã°ãåé¿ã§ããããã§ããïŒ
ã¯ãã
è°è«ã«è¿œå ããã«ã¯ïŒç§ã¯äž»ã«ã«ãŒãã³ã³ããŒãã³ããžã®æ¥ç¶ã®äœ¿çšãå¶éããŠããŸãã è¿œå ã®ã¹ããŒãã³ã³ããŒãã³ãïŒãã©ãŒã ã¢ãŒãã«ãªã©ïŒã䜿çšã§ããããŒãžãããå Žåãåé¿çã¯èŠçŽ ãŸãã¯ããŒããæž¡ãããã ã³ã³ããŒãã³ãã«ã¬ã³ããªã³ã°ãããããšã§ããã ããã¯ããã€ã©ãŒãã¬ãŒããããå°ãããããšãæå³ããŸããããã ã³ã³ããŒãã³ãã®ãã¹ãã¯ç°¡åã§ãã ç§ã¯ãŸã ãããå®éšããŠããŸãããããã¯ç°¡åãªãã¹ã容ææ§ããããããããšãªãã¹ããŒãã³ã³ããŒãã³ããäœæããããã®æè¯ã®æ¹æ³ãããããªããšæããŸãã
倧ãŸããªäŸãæãããšïŒ
Foo.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
å±ããŠããŸãã ;ïŒ
ç°¡åã«èšããšãç§ãæ¡çšããã¢ãããŒãã¯ãä»»æã®ã³ã³ããŒãã³ãã«å²ãåœãŠãããšãã§ããã¢ãžã¥ã©ãŒãããã€ããŒãäžå¿ã«å±éããŠããŸãã ããã«ããã_çã®_ããã ãã³ã³ããŒãã³ããå¯èœã«ãªããé¢å¿ã®åé¢ãæ倧éã«è¡ãããäºææ§ã®ããä»»æã®æ°ã®ãããã€ããŒãéåžžã«ç°¡åãã€è¿ éã«äœ¿çšããã³å ±æã§ããããã«ãªããŸãã ãŸããã³ã³ããŒãã³ããæŽæ°ããããã®ããå¹ççãªæ¹æ³ãé©çšããŸãã
ã ãã...ç§ã¯actions
ã constants
ã containers
ã reducers
ãããã³stores
ãã£ã¬ã¯ããªïŒäŸå
ã§äžè¬çïŒãåãé€ããŸããïŒãããŠããããåäžã®providers
ãã£ã¬ã¯ããªã«çœ®ãæããŸããã ãããã¯ã providers
ãã£ã¬ã¯ããªã¯å¿
èŠãªããããããŸããããã®ã¢ãããŒãã§ã¯ãã¹ã¿ã³ãã¢ãã³ãââããã€ããŒãããã±ãŒãžåããŠé
åžã§ããããã§ãã ãã®ç¹å®ã®ã¢ãããŒããæ¡çšãããã°ãããã€ãã®æ¬åœã«ã¯ãŒã«ãªãã®ãçŸãããšæããŸã!! ïŒæ³šïŒãã¡ããããããã®ãã£ã¬ã¯ããªã1ã€ã«çµ±åããå¿
èŠã¯ãããŸãããã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')
);
ããŸãããã°ãããã¯ãã¹ãŠéåžžã«ç°¡åã§ãããå¿ èŠã«å¿ããŠããã詳现ã«èª¬æãã説æã®ããã«ã³ã¡ã³ããè¿œå ã§ããã°å¹žãã§ãã
ãããã®äŸã§ã¯ãããã€ãã®ãŠãŒãã£ãªãã£é¢æ°ãã€ã³ããŒããããŠããããšã«æ°ä»ãã§ãããã ãããã¯å°ããªã¢ãžã¥ãŒã«ã§ããïŒ4ã€ã®ãã¡3ã€ã¯æ°è¡ã®é·ãã§ãïŒãåºæ¬çã«ã¯æ¢åã®redux
react-redux
ã¡ãœãããš
// 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
ãã³ã¬ãŒã¿ã«ã€ããŠèšåãã䟡å€ã®ããããšã®1ã€ã¯ã react-devtools
ã䜿çšããŠãã¹ãŠãèŠããšã次ã®ãããªãã®ã衚瀺ãããããšã§ãïŒ Branches
ã¯ProvideBranches(theme,packageList,sources)
ã©ãããããŸãïŒ
ããã®ä»£ããã«ïŒå
ã®connect
ã§è¡šç€ºããã Branches
ã¯Connect(Branches)
ã©ãããããŸãïŒïŒ
ç§ã¯2æ¥åã«redux
ã«ã€ããŠåŠã³å§ããã°ãããªã®ã§ããã®ã¢ãããŒãã®çµæãšããŠã©ã®ãããªå¶éãååšããã®ãïŒããããã°ïŒæ£çŽã«ããããŸããã é ããé¢ããŠãç§ã¯æ¬åœã«äœãèããããšãã§ããããã¹ãŠãç§ã®ãŠãŒã¹ã±ãŒã¹ã§å®å
šã«æ©èœããŠããããã«èŠããŸãããããããããçµéšè±å¯ãªèª°ãããã£ã€ã ã鳎ããããšãã§ããŸãã
ãã®ç¹å®ã®ã¢ãããŒããæãã€ããã®ã¯ãä»»æã®ãããã€ããŒãå²ãåœãŠãããšãã§ãã_çã®_ããã ãã³ã³ããŒãã³ããæã€ãšããã¢ã€ãã¢ãæ¬åœã«å¥œãã ããã§ãã é¢å¿ã®åé¢ã匷å¶ããã¢ãžã¥ãŒã«ãšããŠç°¡åã«äº€æã§ãããããã€ããŒãããã€ã§ã䜿çšã§ããããã«ããŸãã
ããã«ã @ gaearonããã®ã¢ãããŒããæ°ã«å
¥ã£ãŠããã react-redux
ã®ç¯å²å
ã«ãããšæãããå Žåã¯ãè¿œå ã®PRãæåºããŠããã ããã°react-redux-providers
äœæããŠå
¬éããæçµçã«ã¯ãµã³ãã«ãããã€ããŒïŒããšãã°ã react-redux-provide-toggle
ïŒãšããã€ãã®å®éã®äŸãå
¬éããŸãã
äžéã¬ãã«ã®ã³ã³ããŒãã³ãã®äžèŠãªåã¬ã³ããªã³ã°ãåé¿ã§ããããã§ããïŒ
ã¯ã
ããã§ã®æ¹åæ§ã¯ãç¶æ
ïŒAppç¶æ
ãšUIç¶æ
ã®äž¡æ¹ãã³ã³ããŒãã³ãã«UIç¶æ
ãä¿æïŒã«Immutable.jsã䜿çšãããã¹ãŠã®ã³ã³ããŒãã³ãã«PureRenderMixinã䜿çšããããšã§ãã ããã«ããããããããŠã³ãããŒã䜿çšããããšã«ããããã©ãŒãã³ã¹ã®äœäžã解æ¶ãããŸãããïŒ
ç·šéïŒäžäœã¬ãã«ã®ã³ã³ããŒãã³ãã®1ã€ãåã¬ã³ããªã³ã°ããå¿ èŠãããå Žåãäžéã¬ãã«ã®ã³ã³ããŒãã³ãã®ããã©ãŒãã³ã¹ã®äœäžãæé€ã§ããªãããšã«æ°ã¥ããŸããããããã§ãå€ãã®ãªãŒããŒããããæé€ããå¿ èŠããããŸãã ããã«ã€ããŠäœãçµéšã¯ãããŸããïŒ
@ danmaz74ç§ãã¡ã¯ãã®ãããªã·ããªãªã§ããã©ãŒãã³ã¹ã®åé¡ã«ã¶ã€ãã£ãŠããŸãïŒreduxã§ã¯ãªããéåžžã«ãã䌌ãèªå®¶è£œã®libã䜿çšããŠããŸãïŒã ãã ããéåžžã«ãé«äŸ¡ãªãã³ã³ããŒãã³ããå«ããããªãè€éãªã¢ããªããããŸãã ãŸããã¢ããªã倧ãããªãã«ã€ããŠããããã¬ãã«ã ãã§ãªãå€ãã®å Žæã«ããŒã¿ãæ¿å ¥ããããšã§ãã³ã³ããŒãã³ãéã«æé»ã®äŸåé¢ä¿ãäœæãããã®ãé²ãã芪ãåã®ããŒã¿èŠä»¶ã«ã€ããŠããŸãç¥ããªãããã«ããããšãã§ããŸãã
@eldhæŽæ°ããŠãããŠããããšããããã¯çã«ããªã£ãŠããŸãã ç¶è¡ããéããããèŠããŠãããŸã:)
ããã«é¢ããæŽæ°ã¯ãããŸããïŒ timburã®äŸã¯ç§ã«ã¯å®å šã«çã«ããªã£ãŠããŸãããã·ã³ã°ã«ã³ãã¯ãã®ç·Žç¿ã¯ç§ã«ã¯ããããããŸããã ç§ã¯å察ã®è°è«ãããªãã¡ã人ã ã¯ããã§ç°ãªã奜ã¿ãæã£ãŠãããã«èå³ããããŸãã
åäžã®æ¥ç¶ã䜿çšãããšãç¶æ ãããšãã°10ã³ã³ããŒãã³ãã®æ·±ãéå±€ã«å€æããããã«ã巚倧ãªmapStateToPropsé¢æ°ãå¿ èŠã«ãªããŸã...ãã®èåŸã«ããèããäœã§ãããããŸãã¯äœãã誀解ããŠãããã©ããã¯å°ãæ··ä¹±ããŠããŸã...
åäžã®æ¥ç¶ã䜿çšãããšã巚倧ãªmapStateToPropsé¢æ°ãå¿ èŠã«ãªããŸã
誰ãåäžã®æ¥ç¶ãæ¯æããŠããŸããã
次ã«ãreact-reduxã®connectïŒïŒé¢æ°ã䜿çšããŠãReduxã«æ¥ç¶ããã³ã³ããŒãã³ããã©ããããŸãã ããã¯ãæäžäœã®ã³ã³ããŒãã³ããŸãã¯ã«ãŒããã³ãã©ãŒã«å¯ŸããŠã®ã¿å®è¡ããŠãã ããã æè¡çã«ã¯ãã¢ããªå ã®ä»»æã®ã³ã³ããŒãã³ããReduxã¹ãã¢ã«connectïŒïŒã§ããŸãããããŒã¿ãããŒã®è¿œè·¡ãå°é£ã«ãªãããããããæ·±ãããããªãããã«ããŠãã ããã
ãã·ã³ã°ã«ããšã¯ãäŸã§äœæãããããªå°ããªã¢ããªã®ã¿ãæããŸãã ãããããæ確ã«ããããã«ãããã¥ã¡ã³ããèªç±ã«ä¿®æ£ããŠãã ããã ç§ã¯çŸåšä»ã®ãããžã§ã¯ãã§å¿ããã®ã§ã誰ããPRãããªãéãããã®åé¡ãåããèµ·ããããšãæåŸ ããªãã§ãã ããã ããªãããããè¡ãããšãã§ããŸãã
ã説æããããšãããããŸãã
ã€ãã«react-redux-provide
ãªãªãŒã¹ã«åãæãããŸããã ãã¡ããã芧ãã ããã ãŸããæ°æ¥/æ°é±é以å
ã«ä»ã®ããã€ãã®ãã®ããªãªãŒã¹ããäºå®ã§ãã
ãšãŠãããããã§ããããããšãïŒ
ç§ã¯æ°ããreduxãããžã§ã¯ããéå§ããã°ããã§ããã¹ããŒããã³ã³ããŒãã³ãã«connectã䜿çšããŠããŸããããã¯ç§ã«ãšã£ãŠã¯ããã«çã«ããªã£ãŠããŸããããã©ãŒãã³ã¹äžã®ã¡ãªãããããå Žåã¯ãè¿œå ã®ã¡ãªããããããŸãã éã«ããã¹ãŠã®å¶åŸ¡ãã¡ã€ã³ã®ã¢ããªãŸãã¯ã«ãŒã¿ãŒã«ãŸã§ããã«ããå ŽåãSRPã¯å®å šã«å€±ãããŸã-ç©äºãå解ãå§ããåã«ãã¢ããªã¯ã©ã®ãããã®å€§ããã«ããå¿ èŠããããŸããïŒ
é¢é£ããèŠçŽ ãã³ã³ããŒãã³ããã©ã«ãã«æŽçããããšãèããŠããŸãã äž»æåçã®æšªã«ã¬ãã¥ãŒãµãŒãå ¥ããŸãã
æçµçã«ãredux / fluxã¯äºæž¬å¯èœãªç¶æ ã«ãšã£ãŠå€§ããªã¡ãªããã§ãããšæããŸãããæšæºã®mvããã®ç²Ÿç¥çãªå€å-UIã¢ããªã®éçºãã·ã³ãã«ã«ãã誰ããã¢ã¯ã»ã¹ã§ããããã«ãããã®ã¯äœã§ããæçµçã«ã¯fluxãæœè±¡åããã移åããŸã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çŽ æŽãããèšäºïŒ ãã®è³ªåã«ã€ããŠã®ããªãã®èããå ±æããŠããã ããŸãããïŒ //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ã¯ãŸã£ãããªããã³ã³ããã ãã®éå±€ãä»ããŠããããããç¶æ ã®ããªãŒ
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);
ç§ã¯ããã§æ æ°ã§ã宣èšãåçŽã«ä¿ã€ããã2ã€ã®ããŒãžã§ã³ã®mapStateToProps
ãš2ã€ã®ããŒãžã§ã³ã®mapDispatchToProps
çµã¿åãããŸããã§ããã ãããã connect
ãããä»»ããã®ã¯æªãèããã©ããçåã«æããŸãã
@timotgl ïŒDanã¯ã¢ã¯ãã£ããªã¡ã³ããã§ã¯ãªããªããŸãããçŽæ¥pingããªãã§ãã ããã
è€æ°ã®ã³ã³ããŒãã³ãã®æ¥ç¶ã«é¢ããReduxFAQãšã³ããªã§ããªãã®è³ªåã«çããŠãããšèšã£ãŠããŸããããå¥ã®ããšã«ã€ããŠè³ªåããŠããããã§ã-æå³çã«è€æ°ã®æ¥ç¶ãåäžã®ã³ã³ããŒãã³ãã«ã©ããããŠããŸããïŒ ãããŸã§èª°ããããããŠããã®ãèŠãããšããªããšã¯èšããŸããããReduxã³ãŒãã_ãããã_èŠãããšããããŸãã
å人çã«ã¯ãå¥ã®ã¢ãããŒããè©Šãããšããå§ãããŸãã ãå
±éã®å°éå
·ãHOCããã»ãšãã©ã®å Žåããããåã«æž¡ãã ãã®ãã®ãçšæããããã»ã¬ã¯ã¿ãŒã䜿çšããŠç¹å®ã®ã³ã³ããŒãã³ãã®mapState
é¢æ°ã§å
±éã®å°éå
·ãååŸããããããå¿
èŠãªç¹å®ã®å°éå
·ãšçµã¿åãããŸãã
@markeriksonç³ãèš³ãããŸãããããããç¥ããŸããã§ãããat-mentionã¯åé€ãããŸããã
ãããã£ãŠããŸã第äžã«ãããã¯æ©èœããã³ã³ããŒãã³ãã¯ãreact devããŒã«å ã®ä»ã®æ¥ç¶ãããã³ã³ããŒãã³ãã®ããã«èŠããŸããè¿œå ã®ã©ãããŒãªã©ã¯ãããŸããã
OOP /ç¶æ¿ãã©ãã€ã ãé¢äžãããããªãã£ãã®ã§ãHOCã«å察ããããšã«ããŸãããããã¯ãã³ã³ããŒãã³ãã«ããã€ãã®å°éå ·ãæäŸããã ããªã®ã§ããã以å€ã®åäœã¯å€æŽãããŸããã
mapStateToProps
é
ç·ããããšã®è¯ãç¹ã ããã¯æ©èœããŸãããå°ãªããšã2ã€ã®ãšã³ããªãã€ã³ãããããŸãã1ã€ã®ãã«ããŒé¢æ°ãåŒã³åºããŠæ¥ç¶ããæ¹ãç°¡åãªããã§ãã
ã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 2ã€ã®ãšã³ããªãã€ã³ããšã¯ã 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ãããé衚瀺ãã«ããã®ã¯éåžžã«äŸ¿å©ã§ãã ããã¯æªãããšã§ããïŒ
æãåèã«ãªãã³ã¡ã³ã
ãããããŸã£ããéã§ãããããããŠã³ãããŒãæŸæ£ããããšã§ãããã©ãŒãã³ã¹ãåäžããŸãã