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",
"react-dock": "^0.1.0",
"react-hot-loader": "^1.3.0",
"redux-devtools": "^3.0.0-alpha-7",
"redux-devtools-log-monitor": "^1.0.0-alpha-7",
"redux-devtools": "^3.0.0-alpha-8",
"redux-devtools-log-monitor": "^1.0.0-alpha-8",
"webpack": "^1.9.11",
"webpack-dev-server": "^1.9.0"
}

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "redux-devtools",
"version": "3.0.0-alpha-7",
"version": "3.0.0-alpha-8",
"description": "Redux DevTools with hot reloading and time travel",
"main": "lib/index.js",
"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 instrument from './instrument';
import connectMonitor from './connectMonitor';
import React, { Children, Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import bindActionCreatorsDeep from './bindActionCreatorsDeep';
import instrument, { ActionCreators as historyActionCreators } from './instrument';
export default function createDevTools(monitor) {
const Monitor = connectMonitor(monitor);
export default function createDevTools(children) {
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 {
static contextTypes = {
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() {
return (
<Monitor {...this.props}
store={this.context.store.instrumentedStore} />
<ConnectedMonitor {...child.props}
store={this.instrumentedStore} />
);
}
};