Permalink
Browse files

Further refactoring to separate all-platform from cross-native-platfo…

…rm shared code
  • Loading branch information...
lmorchard committed Sep 18, 2015
1 parent d514976 commit cdbbff72cb941fac75f68c1c91279fa780997b26
View
@@ -27,49 +27,31 @@ Here are some notes to myself (and others) as I bungle my way through this.
One of the goals of this thing is to see how much code can be shared between
platforms using React for web and React Native. So, I counted some lines of
code.
On the model side, about 74% code is shared, with 26% devoted to each
platform.
On the view side, about 12% code is shared, with 88% devoted to each
platform.
Of course, these proportions will change, depending on additional app features
and my ability to spot code worth refactoring into the shared code modules.
code using `loc-metrics.sh`:
```
# Total LoC for models = 215
$ cat lib/models/**/*.js | wc -l
Total LoC
979
All models LoC
215
# ~74% common model code
$ cat lib/models/*js | wc -l
Common shared models LoC
159
# ~12% iOS specific model code
$ cat lib/models/ios/*js | wc -l
25
# ~14% web specific model code
$ cat lib/models/web/*js | wc -l
Web specific models LoC
31
# Total LoC for views = 533
$ cat lib/views/**/*.js | wc -l
533
# ~12% common view code
$ cat lib/views/*js | wc -l
67
# ~44% iOS specific view code
$ cat lib/views/ios/*js | wc -l
235
# ~43% web specific view code
$ cat lib/views/web/*js | wc -l
231
Native specific models LoC
25
All views LoC
764
Common shared views LoC
75
Web specific views LoC
233
Native shared views LoC
90
iOS specific views LoC
159
Android specific views LoC
207
```
### TODO / Questions
View
@@ -2,20 +2,22 @@ var React = require('react-native');
var { ListView, Text, TextInput, View, TouchableHighlight } = React;
var Views = require('../index');
var CommonMixins = require('../common-mixins');
var NativeMixins = require('../native-mixins');
var styles = require('./styles');
var TodoList = require('./TodoList');
var App = module.exports = React.createClass({
mixins: [Views.AppCommonMixin],
mixins: [CommonMixins.AppCommonMixin],
appStateClass: require('../../models/native/App'),
render() {
var listModeButtons = Views.LIST_MODES.map((item) => {
var listModeButtons = CommonMixins.LIST_MODES.map((item) => {
var buttonStyle = (this.state.app.mode == item.mode) ?
styles.todoListModeButtonSelected :
@@ -2,14 +2,16 @@ var React = require('react-native');
var { SwitchAndroid } = React;
var Views = require('../index');
var CommonMixins = require('../common-mixins');
var NativeMixins = require('../native-mixins');
var styles = require('./styles');
var TodoItem = module.exports = React.createClass({
styles: styles,
mixins: [
Views.TodoItemCommonMixin,
Views.TodoItemNativeCommonMixin
CommonMixins.TodoItemCommonMixin,
NativeMixins.TodoItemNativeCommonMixin
],
renderTodoCompleted() {
return (
@@ -1,14 +1,16 @@
var React = require('react-native');
var Views = require('../index');
var CommonMixins = require('../common-mixins');
var NativeMixins = require('../native-mixins');
var styles = require('./styles');
var TodoItem = require('./TodoItem');
var TodoList = module.exports = React.createClass({
styles: styles,
mixins: [
Views.TodoListCommonMixin,
Views.TodoListNativeCommonMixin
CommonMixins.TodoListCommonMixin,
NativeMixins.TodoListNativeCommonMixin
],
renderRow(item) {
return (<TodoItem key={item.cid} item={item} />);
View
@@ -0,0 +1,75 @@
// Mixins representing shared logic between web & native views.
exports.LIST_MODES = [
{mode: 'all', label: 'All (%s)', state: 'totalCount'},
{mode: 'active', label: 'Active (%s)', state: 'activeCount'},
{mode: 'completed', label: 'Completed (%s)', state: 'completedCount'}
];
exports.AppCommonMixin = {
getInitialState() {
return {
app: new (this.appStateClass)()
};
},
setListMode(mode) {
this.state.app.mode = mode;
},
componentDidMount() {
// FIXME: ()=> and context of `this` seem redundant, but we need the
// context to later remove all handlers for this component. But, if
// I just use ('change', this.forceUpdate, this), I get console warnings
this.state.app.on('change', () => this.forceUpdate(), this);
},
componentWillUnmount() {
this.state.app.off(null, null, this);
},
handleNewTodoSubmit(event) {
var val = event.nativeEvent.text.trim();
this.refs.newField.setNativeProps({text: ''});
this.state.app.todos.add({
title: val,
completed: false
});
}
};
exports.TodoListCommonMixin = {
componentWillUnmount() {
this.state.app.todos.off(null, null, this);
}
};
exports.TodoItemCommonMixin = {
getInitialState() {
return {
editing: false,
item: this.props.item
};
},
handleCompletedChange(value) {
this.state.item.completed = value;
},
handleEditStart() {
this.setState({ editing: true });
},
handleDelete() {
var item = this.state.item;
item.collection.remove(item);
},
componentDidMount() {
this.state.item.on('change', () => this.forceUpdate(), this);
},
componentWillUnmount() {
this.state.item.off(null, null, this);
},
componentWillReceiveProps(props) {
this.setState({ item: props.item });
},
componentDidUpdate(prevProps, prevState) {
if (prevState.item !== this.state.item) {
prevState.item.off(null, null, this);
this.state.item.on('change', () => this.forceUpdate(), this);
}
}
};
View
@@ -2,21 +2,23 @@ var React = require('react-native');
var { ListView, Text, TextInput, View, SegmentedControlIOS } = React;
var Views = require('../index');
var CommonMixins = require('../common-mixins');
var NativeMixins = require('../native-mixins');
var styles = require('./styles');
var TodoList = require('./TodoList');
var App = module.exports = React.createClass({
mixins: [Views.AppCommonMixin],
mixins: [CommonMixins.AppCommonMixin],
appStateClass: require('../../models/native/App'),
render() {
// Build segmented control labels with current filtered list totals
var listModeValues = Views.LIST_MODES.map((item) =>
var listModeValues = CommonMixins.LIST_MODES.map((item) =>
item.label.replace('%s', this.state.app[item.state]));
return (
@@ -2,14 +2,16 @@ var React = require('react-native');
var { SwitchIOS } = React;
var Views = require('../index');
var CommonMixins = require('../common-mixins');
var NativeMixins = require('../native-mixins');
var styles = require('./styles');
var TodoItem = module.exports = React.createClass({
styles: styles,
mixins: [
Views.TodoItemCommonMixin,
Views.TodoItemNativeCommonMixin
CommonMixins.TodoItemCommonMixin,
NativeMixins.TodoItemNativeCommonMixin
],
renderTodoCompleted() {
return (
@@ -1,14 +1,16 @@
var React = require('react-native');
var Views = require('../index');
var CommonMixins = require('../common-mixins');
var NativeMixins = require('../native-mixins');
var styles = require('./styles');
var TodoItem = require('./TodoItem');
var TodoList = module.exports = React.createClass({
styles: styles,
mixins: [
Views.TodoListCommonMixin,
Views.TodoListNativeCommonMixin
CommonMixins.TodoListCommonMixin,
NativeMixins.TodoListNativeCommonMixin
],
renderRow(item) {
return (<TodoItem key={item.cid} item={item} />);
@@ -1,48 +1,8 @@
// Mixins representing shared logic between web & iOS views.
var React = require('react-native');
var { Text, TextInput, View, ListView, SwitchAndroid,
TouchableHighlight } = React;
// Mixins representing shared logic between native views.
exports.LIST_MODES = [
{mode: 'all', label: 'All (%s)', state: 'totalCount'},
{mode: 'active', label: 'Active (%s)', state: 'activeCount'},
{mode: 'completed', label: 'Completed (%s)', state: 'completedCount'}
];
var React = require('react-native');
exports.AppCommonMixin = {
getInitialState() {
return {
app: new (this.appStateClass)()
};
},
setListMode(mode) {
this.state.app.mode = mode;
},
componentDidMount() {
// FIXME: ()=> and context of `this` seem redundant, but we need the
// context to later remove all handlers for this component. But, if
// I just use ('change', this.forceUpdate, this), I get console warnings
this.state.app.on('change', () => this.forceUpdate(), this);
},
componentWillUnmount() {
this.state.app.off(null, null, this);
},
handleNewTodoSubmit(event) {
var val = event.nativeEvent.text.trim();
this.refs.newField.setNativeProps({text: ''});
this.state.app.todos.add({
title: val,
completed: false
});
}
};
exports.TodoListCommonMixin = {
componentWillUnmount() {
this.state.app.todos.off(null, null, this);
}
};
var { Text, TextInput, View, ListView, TouchableHighlight } = React;
exports.TodoListNativeCommonMixin = {
render() {
@@ -80,40 +40,6 @@ exports.TodoListNativeCommonMixin = {
}
};
exports.TodoItemCommonMixin = {
getInitialState() {
return {
editing: false,
item: this.props.item
};
},
handleCompletedChange(value) {
this.state.item.completed = value;
},
handleEditStart() {
this.setState({ editing: true });
},
handleDelete() {
var item = this.state.item;
item.collection.remove(item);
},
componentDidMount() {
this.state.item.on('change', () => this.forceUpdate(), this);
},
componentWillUnmount() {
this.state.item.off(null, null, this);
},
componentWillReceiveProps(props) {
this.setState({ item: props.item });
},
componentDidUpdate(prevProps, prevState) {
if (prevState.item !== this.state.item) {
prevState.item.off(null, null, this);
this.state.item.on('change', () => this.forceUpdate(), this);
}
}
};
exports.TodoItemNativeCommonMixin = {
render() {
View
@@ -2,21 +2,22 @@ var React = require('react');
var _ = require('lodash');
var Views = require('..');
var CommonMixins = require('../common-mixins');
var TodoList = require('./TodoList');
var styles = require('./styles');
var KEY_ENTER = 13;
var App = module.exports = React.createClass({
mixins: [Views.AppCommonMixin],
mixins: [CommonMixins.AppCommonMixin],
appStateClass: require('../../models/web/App'),
render() {
var listModeButtons = Views.LIST_MODES.map((item) => {
var listModeButtons = CommonMixins.LIST_MODES.map((item) => {
// Produce merged set of conditional styles for button state based on
// current filter mode.
Oops, something went wrong.

0 comments on commit cdbbff7

Please sign in to comment.