Component-centric API, horrors are encapsulated

This commit is contained in:
Dan Abramov 2015-09-28 21:12:07 +03:00
parent 34d599af77
commit 79b51a7c0f
6 changed files with 90 additions and 72 deletions

View File

@ -28,8 +28,8 @@
"node-libs-browser": "^0.5.2", "node-libs-browser": "^0.5.2",
"react-dock": "^0.1.0", "react-dock": "^0.1.0",
"react-hot-loader": "^1.3.0", "react-hot-loader": "^1.3.0",
"redux-devtools": "^3.0.0-alpha-7", "redux-devtools": "^3.0.0-alpha-8",
"redux-devtools-log-monitor": "^1.0.0-alpha-7", "redux-devtools-log-monitor": "^1.0.0-alpha-8",
"webpack": "^1.9.11", "webpack": "^1.9.11",
"webpack-dev-server": "^1.9.0" "webpack-dev-server": "^1.9.0"
} }

View File

@ -1,9 +1,10 @@
import React from 'react';
import { createDevTools } from 'redux-devtools'; import { createDevTools } from 'redux-devtools';
import createLogMonitor from 'redux-devtools-log-monitor'; import LogMonitor from 'redux-devtools-log-monitor';
import createDockMonitor from '../dock/DockMonitor'; import DockMonitor from '../dock/DockMonitor';
export default createDevTools( export default createDevTools(
createDockMonitor( <DockMonitor defaultPosition='bottom'>
createLogMonitor() <LogMonitor theme='ocean' />
) </DockMonitor>
); );

View File

@ -2,24 +2,36 @@
// TODO: extract to a separate project. // TODO: extract to a separate project.
// //
import React, { Component, PropTypes } from 'react'; import React, { cloneElement, Children, Component, PropTypes } from 'react';
import Dock from 'react-dock'; import Dock from 'react-dock';
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
const POSITIONS = ['left', 'top', 'right', 'bottom']; const POSITIONS = ['left', 'top', 'right', 'bottom'];
class DockMonitor extends Component { export default class DockMonitor extends Component {
static propTypes = { static propTypes = {
defaultPosition: PropTypes.oneOf(POSITIONS).isRequired,
defaultIsVisible: PropTypes.bool.isRequired,
toggleVisibilityShortcut: PropTypes.string.isRequired,
changePositionShortcut: PropTypes.string.isRequired,
monitorState: PropTypes.shape({ monitorState: PropTypes.shape({
position: PropTypes.oneOf(POSITIONS).isRequired, position: PropTypes.oneOf(POSITIONS).isRequired,
isVisible: PropTypes.bool.isRequired, isVisible: PropTypes.bool.isRequired,
child: PropTypes.any child: PropTypes.any
}).isRequired, }),
monitorActions: PropTypes.shape({ monitorActions: PropTypes.shape({
toggleVisibility: PropTypes.func.isRequired, toggleVisibility: PropTypes.func.isRequired,
changePosition: PropTypes.func.isRequired changePosition: PropTypes.func.isRequired
}).isRequired })
};
static defaultProps = {
defaultIsVisible: true,
defaultPosition: 'right',
toggleVisibilityShortcut: 'H',
changePositionShortcut: 'Q'
}; };
componentDidMount() { componentDidMount() {
@ -39,11 +51,11 @@ class DockMonitor extends Component {
const key = event.keyCode || event.which; const key = event.keyCode || event.which;
const char = String.fromCharCode(key); const char = String.fromCharCode(key);
switch (char) { switch (char.toUpperCase()) {
case 'H': case this.props.toggleVisibilityShortcut.toUpperCase():
this.props.monitorActions.toggleVisibility(); this.props.monitorActions.toggleVisibility();
break; break;
case 'D': case this.props.changePositionShortcut.toUpperCase():
this.props.monitorActions.changePosition(); this.props.monitorActions.changePosition();
break; break;
default: default:
@ -52,13 +64,29 @@ class DockMonitor extends Component {
} }
render() { render() {
const { children, monitorState } = this.props; const {
const { position, isVisible } = monitorState; monitorState,
monitorActions,
historyState,
historyActions,
children
} = this.props;
const {
position,
isVisible
} = monitorState;
return ( return (
<Dock position={position} <Dock position={position}
isVisible={isVisible} isVisible={isVisible}
dimMode='none'> dimMode='none'>
{children} {cloneElement(Children.only(children), {
monitorState: monitorState.child,
monitorActions: monitorActions.child,
historyState,
historyActions
})}
</Dock> </Dock>
); );
} }
@ -74,43 +102,32 @@ function changePosition() {
return { type: CHANGE_POSITION }; return { type: CHANGE_POSITION };
} }
export default function create(child, { DockMonitor.setup = function setup(props) {
defaultIsVisible = true, function position(state = props.defaultPosition, action) {
defaultPosition = 'right'
} = {}) {
function position(state = defaultPosition, action) {
return (action.type === CHANGE_POSITION) ? return (action.type === CHANGE_POSITION) ?
POSITIONS[(POSITIONS.indexOf(state) + 1) % POSITIONS.length] : POSITIONS[(POSITIONS.indexOf(state) + 1) % POSITIONS.length] :
state; state;
} }
function isVisible(state = defaultIsVisible, action) { function isVisible(state = props.defaultIsVisible, action) {
return (action.type === TOGGLE_VISIBILITY) ? return (action.type === TOGGLE_VISIBILITY) ?
!state : !state :
state; state;
} }
const ChildMonitor = child.component; const child = Children.only(props.children);
const CompositeMonitor = ({ monitorState, monitorActions, ...rest }) => ( const childSetupResult = child.type.setup(child.props);
<DockMonitor monitorState={monitorState}
monitorActions={monitorActions}>
<ChildMonitor {...rest}
monitorState={monitorState.child}
monitorActions={monitorActions.child} />
</DockMonitor>
);
return { return {
component: CompositeMonitor,
reducer: combineReducers({ reducer: combineReducers({
position, position,
isVisible, isVisible,
child: child.reducer child: childSetupResult.reducer
}), }),
actionCreators: { actionCreators: {
toggleVisibility, toggleVisibility,
changePosition, changePosition,
child: child.actionCreators child: childSetupResult.actionCreators
} }
}; };
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "redux-devtools", "name": "redux-devtools",
"version": "3.0.0-alpha-7", "version": "3.0.0-alpha-8",
"description": "Redux DevTools with hot reloading and time travel", "description": "Redux DevTools with hot reloading and time travel",
"main": "lib/index.js", "main": "lib/index.js",
"scripts": { "scripts": {

View File

@ -1,28 +0,0 @@
import { bindActionCreators } from 'redux';
import bindActionCreatorsDeep from './bindActionCreatorsDeep';
import { connect } from 'react-redux';
import { ActionCreators as historyActionCreators } from './instrument';
export default function connectMonitor({
component,
reducer = () => null,
actionCreators = {}
}) {
function mapStateToProps(state) {
return {
historyState: state.historyState,
monitorState: state.monitorState
};
}
function mapDispatchToProps(dispatch) {
return {
historyActions: bindActionCreators(historyActionCreators, dispatch),
monitorActions: bindActionCreatorsDeep(actionCreators, dispatch)
};
}
const Monitor = connect(mapStateToProps, mapDispatchToProps)(component);
Monitor.reducer = reducer;
return Monitor;
}

View File

@ -1,21 +1,49 @@
import React, { Component, PropTypes } from 'react'; import React, { Children, Component, PropTypes } from 'react';
import instrument from './instrument'; import { connect } from 'react-redux';
import connectMonitor from './connectMonitor'; import { bindActionCreators } from 'redux';
import bindActionCreatorsDeep from './bindActionCreatorsDeep';
import instrument, { ActionCreators as historyActionCreators } from './instrument';
export default function createDevTools(monitor) { export default function createDevTools(children) {
const Monitor = connectMonitor(monitor); const child = Children.only(children);
const { type: Monitor } = child;
const { reducer, actionCreators } = Monitor.setup(child.props);
function mapStateToProps(state) {
return {
historyState: state.historyState,
monitorState: state.monitorState
};
}
function mapDispatchToProps(dispatch) {
return {
historyActions: bindActionCreators(historyActionCreators, dispatch),
monitorActions: bindActionCreatorsDeep(actionCreators, dispatch)
};
}
const ConnectedMonitor = connect(
mapStateToProps,
mapDispatchToProps
)(Monitor);
return class DevTools extends Component { return class DevTools extends Component {
static contextTypes = { static contextTypes = {
store: PropTypes.object.isRequired store: PropTypes.object.isRequired
}; };
static instrument = () => instrument(Monitor.reducer); static instrument = () => instrument(reducer);
constructor(props, context) {
super(props, context);
this.instrumentedStore = context.store.instrumentedStore;
}
render() { render() {
return ( return (
<Monitor {...this.props} <ConnectedMonitor {...child.props}
store={this.context.store.instrumentedStore} /> store={this.instrumentedStore} />
); );
} }
}; };