From 7843d75b1a434998418e2d43c28800f8ffba3ff4 Mon Sep 17 00:00:00 2001 From: Nikolai Iakovlev Date: Fri, 10 Nov 2017 17:49:58 +0300 Subject: [PATCH 1/7] Added mergeProps function to connect --- README.md | 11 +++++++++-- package.json | 2 +- src/connect.js | 14 +++++++++++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1e0a4e5..0e78d87 100644 --- a/README.md +++ b/README.md @@ -102,10 +102,17 @@ function mapActionToProps(dispatch) { data: { todo } }) } - } + }; +} + +function mergeProps(stateProps, actionsProps) { + return { + ...stateProps, + ...actionsProps, + }; } -export default connect(mapStateToProps, mapActionToProps)(App); +export default connect(mapStateToProps, mapActionToProps, mergeProps)(App); ``` diff --git a/package.json b/package.json index 69093f1..1ba7870 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redux-vue", - "version": "0.7.1", + "version": "0.8.1", "description": "Vue Redux binding higher order component", "author": "Nadim Tuhin", "repository": { diff --git a/src/connect.js b/src/connect.js index d180edd..5e437cb 100644 --- a/src/connect.js +++ b/src/connect.js @@ -44,6 +44,13 @@ function getProps(component) { }; } +function defaultMergeProps(stateProps, actionsProps) { + return { + ...stateProps, + ...actionsProps, + }; +} + /** * 1. utilities are moved above because vue stores methods, states and props * in the same namespace @@ -53,11 +60,13 @@ function getProps(component) { /** * @param mapStateToProps * @param mapActionsToProps + * @param mergeProps * @returns Object */ -export default function connect(mapStateToProps, mapActionsToProps) { +export default function connect(mapStateToProps, mapActionsToProps, mergeProps) { mapStateToProps = mapStateToProps || noop; mapActionsToProps = mapActionsToProps || noop; + mergeProps = mergeProps || defaultMergeProps; return (children) => { @@ -90,8 +99,7 @@ export default function connect(mapStateToProps, mapActionsToProps) { const actionNames = Object.keys(actions); return { - ...state, - ...actions, + ...mergeProps(state, actions), vuaReduxStateNames: stateNames, vuaReduxActionNames: actionNames }; From e0a4afffffbdfa8aaa8f48b48aa5d30812a48ea6 Mon Sep 17 00:00:00 2001 From: Nikolai Iakovlev Date: Fri, 10 Nov 2017 17:53:04 +0300 Subject: [PATCH 2/7] Added lib for installing from git --- .gitignore | 2 - lib/connect.js | 128 +++++++++++++++++++++++++++++++++++++ lib/index.js | 19 ++++++ lib/normalizeProps.js | 48 ++++++++++++++ lib/normalizeProps.spec.js | 22 +++++++ lib/reduxStorePlugin.js | 21 ++++++ 6 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 lib/connect.js create mode 100644 lib/index.js create mode 100644 lib/normalizeProps.js create mode 100644 lib/normalizeProps.spec.js create mode 100644 lib/reduxStorePlugin.js diff --git a/.gitignore b/.gitignore index f9ed950..cd4a2c4 100644 --- a/.gitignore +++ b/.gitignore @@ -20,5 +20,3 @@ npm-debug.log Session.vim .netrwhist *~ - -lib \ No newline at end of file diff --git a/lib/connect.js b/lib/connect.js new file mode 100644 index 0000000..eab17ca --- /dev/null +++ b/lib/connect.js @@ -0,0 +1,128 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +exports.default = connect; + +var _normalizeProps = require('./normalizeProps'); + +var _normalizeProps2 = _interopRequireDefault(_normalizeProps); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function noop() {} + +function getStore(component) { + return component.$store; +} + +function getAttrs(component) { + return component._self.$options._parentVnode.data.attrs; +} + +function getStates(component, mapStateToProps) { + var store = getStore(component); + var attrs = getAttrs(component); + + return mapStateToProps(store.getState(), attrs) || {}; +} + +function getActions(component, mapActionsToProps) { + var store = getStore(component); + + return mapActionsToProps(store.dispatch, getAttrs.bind(null, component)) || {}; +} + +function getProps(component) { + var props = {}; + var attrs = getAttrs(component); + var stateNames = component.vuaReduxStateNames; + var actionNames = component.vuaReduxActionNames; + + for (var ii = 0; ii < stateNames.length; ii++) { + props[stateNames[ii]] = component[stateNames[ii]]; + } + + for (var _ii = 0; _ii < actionNames.length; _ii++) { + props[actionNames[_ii]] = component[actionNames[_ii]]; + } + + return _extends({}, props, attrs); +} + +function defaultMergeProps(stateProps, actionsProps) { + return _extends({}, stateProps, actionsProps); +} + +/** + * 1. utilities are moved above because vue stores methods, states and props + * in the same namespace + * 2. actions are set while created + */ + +/** + * @param mapStateToProps + * @param mapActionsToProps + * @param mergeProps + * @returns Object + */ +function connect(mapStateToProps, mapActionsToProps, mergeProps) { + mapStateToProps = mapStateToProps || noop; + mapActionsToProps = mapActionsToProps || noop; + mergeProps = mergeProps || defaultMergeProps; + + return function (children) { + + /** @namespace children.collect */ + if (children.collect) { + children.props = _extends({}, (0, _normalizeProps2.default)(children.props || {}), (0, _normalizeProps2.default)(children.collect || {})); + + var msg = 'vua-redux: collect is deprecated, use props ' + ('in ' + (children.name || 'anonymous') + ' component'); + + console.warn(msg); + } + + return { + name: 'ConnectVuaRedux-' + (children.name || 'children'), + + render: function render(h) { + var props = getProps(this); + + return h(children, { props: props }); + }, + data: function data() { + var state = getStates(this, mapStateToProps); + var actions = getActions(this, mapActionsToProps); + var stateNames = Object.keys(state); + var actionNames = Object.keys(actions); + + return _extends({}, mergeProps(state, actions), { + vuaReduxStateNames: stateNames, + vuaReduxActionNames: actionNames + }); + }, + created: function created() { + var _this = this; + + var store = getStore(this); + + this.vuaReduxUnsubscribe = store.subscribe(function () { + var state = getStates(_this, mapStateToProps); + var stateNames = Object.keys(state); + _this.vuaReduxStateNames = stateNames; + + for (var ii = 0; ii < stateNames.length; ii++) { + _this[stateNames[ii]] = state[stateNames[ii]]; + } + }); + }, + beforeDestroy: function beforeDestroy() { + this.vuaReduxUnsubscribe(); + } + }; + }; +} \ No newline at end of file diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..2c1e171 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,19 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.reduxStorePlugin = exports.connect = undefined; + +var _connect2 = require('./connect'); + +var _connect3 = _interopRequireDefault(_connect2); + +var _reduxStorePlugin2 = require('./reduxStorePlugin'); + +var _reduxStorePlugin3 = _interopRequireDefault(_reduxStorePlugin2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.connect = _connect3.default; +exports.reduxStorePlugin = _reduxStorePlugin3.default; \ No newline at end of file diff --git a/lib/normalizeProps.js b/lib/normalizeProps.js new file mode 100644 index 0000000..28c3664 --- /dev/null +++ b/lib/normalizeProps.js @@ -0,0 +1,48 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = normalizeProps; + +var _isArray = require('lodash/isArray'); + +var _isArray2 = _interopRequireDefault(_isArray); + +var _isPlainObject = require('lodash/isPlainObject'); + +var _isPlainObject2 = _interopRequireDefault(_isPlainObject); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +// https://github.com/vuejs/vue/blob/dev/src/util/options.js +function normalizeProps(props) { + var i, + val, + normalizedProps = {}; + + if ((0, _isArray2.default)(props)) { + i = props.length; + while (i--) { + val = props[i]; + if (typeof val === 'string') { + normalizedProps[val] = null; + } else if (val.name) { + normalizedProps[val.name] = val; + } + } + } else if ((0, _isPlainObject2.default)(props)) { + var keys = Object.keys(props); + i = keys.length; + while (i--) { + var key = keys[i]; + val = props[key]; + normalizedProps[key] = props[key]; + if (typeof val === 'function') { + normalizedProps[key] = { type: val }; + } + } + } + + return normalizedProps; +} \ No newline at end of file diff --git a/lib/normalizeProps.spec.js b/lib/normalizeProps.spec.js new file mode 100644 index 0000000..4e97fe5 --- /dev/null +++ b/lib/normalizeProps.spec.js @@ -0,0 +1,22 @@ +'use strict'; + +var _expect = require('expect'); + +var _expect2 = _interopRequireDefault(_expect); + +var _normalizeProps = require('./normalizeProps'); + +var _normalizeProps2 = _interopRequireDefault(_normalizeProps); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +describe('normalize props', function () { + it('should normalize array props', function () { + (0, _expect2.default)((0, _normalizeProps2.default)(['a', 'b'])).toEqual({ a: null, b: null }); + }); + + it('should normalize object props', function () { + var props = { 'a': { type: String }, 'b': null }; + (0, _expect2.default)((0, _normalizeProps2.default)(props)).toEqual(props); + }); +}); \ No newline at end of file diff --git a/lib/reduxStorePlugin.js b/lib/reduxStorePlugin.js new file mode 100644 index 0000000..53621ca --- /dev/null +++ b/lib/reduxStorePlugin.js @@ -0,0 +1,21 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = reduxStorePlugin; +function reduxStorePlugin(Vue) { + var storeId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'store'; + + Vue.mixin({ + beforeCreate: function beforeCreate() { + var options = this.$options; + // store injection + if (options[storeId]) { + this.$store = options.store; + } else if (options.parent && options.parent.$store) { + this.$store = options.parent.$store; + } + } + }); +} \ No newline at end of file From f8bc22884c2c500582e8713cba41bf43e5276265 Mon Sep 17 00:00:00 2001 From: Nikolai Iakovlev Date: Sat, 11 Nov 2017 03:40:36 +0300 Subject: [PATCH 3/7] Fixed props merging for new names --- lib/connect.js | 28 ++++++++++++---------------- src/connect.js | 28 ++++++++++++---------------- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/lib/connect.js b/lib/connect.js index eab17ca..3ca28f3 100644 --- a/lib/connect.js +++ b/lib/connect.js @@ -40,15 +40,10 @@ function getActions(component, mapActionsToProps) { function getProps(component) { var props = {}; var attrs = getAttrs(component); - var stateNames = component.vuaReduxStateNames; - var actionNames = component.vuaReduxActionNames; + var propNames = component.vuaReduxPropNames; - for (var ii = 0; ii < stateNames.length; ii++) { - props[stateNames[ii]] = component[stateNames[ii]]; - } - - for (var _ii = 0; _ii < actionNames.length; _ii++) { - props[actionNames[_ii]] = component[actionNames[_ii]]; + for (var ii = 0; ii < propNames.length; ii++) { + props[propNames[ii]] = component[propNames[ii]]; } return _extends({}, props, attrs); @@ -97,12 +92,11 @@ function connect(mapStateToProps, mapActionsToProps, mergeProps) { data: function data() { var state = getStates(this, mapStateToProps); var actions = getActions(this, mapActionsToProps); - var stateNames = Object.keys(state); - var actionNames = Object.keys(actions); + var merged = mergeProps(state, actions); + var propNames = Object.keys(merged); return _extends({}, mergeProps(state, actions), { - vuaReduxStateNames: stateNames, - vuaReduxActionNames: actionNames + vuaReduxPropNames: propNames }); }, created: function created() { @@ -112,11 +106,13 @@ function connect(mapStateToProps, mapActionsToProps, mergeProps) { this.vuaReduxUnsubscribe = store.subscribe(function () { var state = getStates(_this, mapStateToProps); - var stateNames = Object.keys(state); - _this.vuaReduxStateNames = stateNames; + var actions = getActions(_this, mapActionsToProps); + var merged = mergeProps(state, actions); + var propNames = Object.keys(merged); + _this.vuaReduxPropNames = propNames; - for (var ii = 0; ii < stateNames.length; ii++) { - _this[stateNames[ii]] = state[stateNames[ii]]; + for (var ii = 0; ii < propNames.length; ii++) { + _this[propNames[ii]] = state[propNames[ii]]; } }); }, diff --git a/src/connect.js b/src/connect.js index 5e437cb..228f443 100644 --- a/src/connect.js +++ b/src/connect.js @@ -27,15 +27,10 @@ function getActions(component, mapActionsToProps) { function getProps(component) { let props = {}; const attrs = getAttrs(component); - const stateNames = component.vuaReduxStateNames; - const actionNames = component.vuaReduxActionNames; + const propNames = component.vuaReduxPropNames; - for (let ii = 0; ii < stateNames.length; ii++) { - props[stateNames[ii]] = component[stateNames[ii]]; - } - - for (let ii = 0; ii < actionNames.length; ii++) { - props[actionNames[ii]] = component[actionNames[ii]]; + for (let ii = 0; ii < propNames.length; ii++) { + props[propNames[ii]] = component[propNames[ii]]; } return { @@ -95,13 +90,12 @@ export default function connect(mapStateToProps, mapActionsToProps, mergeProps) data() { const state = getStates(this, mapStateToProps); const actions = getActions(this, mapActionsToProps); - const stateNames = Object.keys(state); - const actionNames = Object.keys(actions); + const merged = mergeProps(state, actions); + const propNames = Object.keys(merged); return { ...mergeProps(state, actions), - vuaReduxStateNames: stateNames, - vuaReduxActionNames: actionNames + vuaReduxPropNames: propNames, }; }, @@ -110,11 +104,13 @@ export default function connect(mapStateToProps, mapActionsToProps, mergeProps) this.vuaReduxUnsubscribe = store.subscribe(() => { const state = getStates(this, mapStateToProps); - const stateNames = Object.keys(state); - this.vuaReduxStateNames = stateNames; + const actions = getActions(this, mapActionsToProps); + const merged = mergeProps(state, actions); + const propNames = Object.keys(merged); + this.vuaReduxPropNames = propNames; - for (let ii = 0; ii < stateNames.length; ii++) { - this[stateNames[ii]] = state[stateNames[ii]]; + for (let ii = 0; ii < propNames.length; ii++) { + this[propNames[ii]] = state[propNames[ii]]; } }); }, From 0ed28600dd6b89cb66f283fbd00de48561ae3815 Mon Sep 17 00:00:00 2001 From: Nikolai Iakovlev Date: Sat, 11 Nov 2017 03:58:13 +0300 Subject: [PATCH 4/7] Fixed buf and updated docs --- README.md | 16 ++++++++-------- lib/connect.js | 4 ++-- src/connect.js | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0e78d87..7ea1211 100644 --- a/README.md +++ b/README.md @@ -105,14 +105,7 @@ function mapActionToProps(dispatch) { }; } -function mergeProps(stateProps, actionsProps) { - return { - ...stateProps, - ...actionsProps, - }; -} - -export default connect(mapStateToProps, mapActionToProps, mergeProps)(App); +export default connect(mapStateToProps, mapActionToProps)(App); ``` @@ -148,3 +141,10 @@ const mapDispatchToProps = (dispatch) => ({}) export default connect(mapStateToProps, mapDispatchToProps)(Comp) ``` + +## connect([mapStateToProps], [mapDispatchToProps], [mergeProps]) +Connects a Vue component to a Redux store. +### Arguments +* [mapStateToProps(state, [ownAttrs]): stateProps] (__Function__) Subscribes component to Redux store updates. This means that any time the store is updated, mapStateToProps will be called. The results of `mapStateToProps` must be a plain object, which will be merged into the component’s props. +* [mapDispatchToProps(dispatch): dispatchProps] (__Function__) Result must be a plain object +* [mergeProps(stateProps, dispatchProps): props] (__Function__) If specified, it is passed the result of `mapStateToProps()` and `mapDispatchToProps()`. The plain object you return from it will be passed as props to the wrapped component. diff --git a/lib/connect.js b/lib/connect.js index 3ca28f3..92c5c0f 100644 --- a/lib/connect.js +++ b/lib/connect.js @@ -95,7 +95,7 @@ function connect(mapStateToProps, mapActionsToProps, mergeProps) { var merged = mergeProps(state, actions); var propNames = Object.keys(merged); - return _extends({}, mergeProps(state, actions), { + return _extends({}, merged, { vuaReduxPropNames: propNames }); }, @@ -112,7 +112,7 @@ function connect(mapStateToProps, mapActionsToProps, mergeProps) { _this.vuaReduxPropNames = propNames; for (var ii = 0; ii < propNames.length; ii++) { - _this[propNames[ii]] = state[propNames[ii]]; + _this[propNames[ii]] = merged[propNames[ii]]; } }); }, diff --git a/src/connect.js b/src/connect.js index 228f443..6ddbb3e 100644 --- a/src/connect.js +++ b/src/connect.js @@ -94,7 +94,7 @@ export default function connect(mapStateToProps, mapActionsToProps, mergeProps) const propNames = Object.keys(merged); return { - ...mergeProps(state, actions), + ...merged, vuaReduxPropNames: propNames, }; }, @@ -110,7 +110,7 @@ export default function connect(mapStateToProps, mapActionsToProps, mergeProps) this.vuaReduxPropNames = propNames; for (let ii = 0; ii < propNames.length; ii++) { - this[propNames[ii]] = state[propNames[ii]]; + this[propNames[ii]] = merged[propNames[ii]]; } }); }, From 9addb8e6eefa118c5dc5b7780180847bdff34900 Mon Sep 17 00:00:00 2001 From: Nikolai Iakovlev Date: Tue, 21 Nov 2017 12:18:13 +0300 Subject: [PATCH 5/7] Component attributes now converts to camelCase before sending to mapStateToProps --- lib/connect.js | 10 +++++++++- src/connect.js | 7 ++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/connect.js b/lib/connect.js index 92c5c0f..96e5453 100644 --- a/lib/connect.js +++ b/lib/connect.js @@ -14,6 +14,8 @@ var _normalizeProps2 = _interopRequireDefault(_normalizeProps); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + function noop() {} function getStore(component) { @@ -21,7 +23,13 @@ function getStore(component) { } function getAttrs(component) { - return component._self.$options._parentVnode.data.attrs; + var attrs = component._self.$options._parentVnode.data.attrs; + // Convert props from kebab-case to camelCase notation + return Object.keys(attrs).reduce(function (memo, key) { + return _extends({}, memo, _defineProperty({}, key.replace(/[-](.)/g, function (match, group) { + return group.toUpperCase(); + }), attrs[key])); + }, {}); } function getStates(component, mapStateToProps) { diff --git a/src/connect.js b/src/connect.js index 6ddbb3e..a190010 100644 --- a/src/connect.js +++ b/src/connect.js @@ -8,7 +8,12 @@ function getStore(component) { } function getAttrs(component) { - return component._self.$options._parentVnode.data.attrs; + const attrs = component._self.$options._parentVnode.data.attrs; + // Convert props from kebab-case to camelCase notation + return Object.keys(attrs).reduce((memo, key) => ({ + ...memo, + [key.replace(/[-](.)/g, (match, group) => group.toUpperCase())]: attrs[key], + }), {}) } function getStates(component, mapStateToProps) { From 9834c9c6d8b8fbc554b9bdf65580b3a9e7c3dc2c Mon Sep 17 00:00:00 2001 From: Nikolai Iakovlev Date: Tue, 21 Nov 2017 12:21:17 +0300 Subject: [PATCH 6/7] Fixed empty attrs bug --- lib/connect.js | 3 +++ src/connect.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/lib/connect.js b/lib/connect.js index 96e5453..8521444 100644 --- a/lib/connect.js +++ b/lib/connect.js @@ -24,6 +24,9 @@ function getStore(component) { function getAttrs(component) { var attrs = component._self.$options._parentVnode.data.attrs; + if (!attrs) { + return attrs; + } // Convert props from kebab-case to camelCase notation return Object.keys(attrs).reduce(function (memo, key) { return _extends({}, memo, _defineProperty({}, key.replace(/[-](.)/g, function (match, group) { diff --git a/src/connect.js b/src/connect.js index a190010..81c572b 100644 --- a/src/connect.js +++ b/src/connect.js @@ -9,6 +9,9 @@ function getStore(component) { function getAttrs(component) { const attrs = component._self.$options._parentVnode.data.attrs; + if (!attrs) { + return attrs + } // Convert props from kebab-case to camelCase notation return Object.keys(attrs).reduce((memo, key) => ({ ...memo, From 926bc96ea88afa8e2e8981df7adb8a9ca4d5e97c Mon Sep 17 00:00:00 2001 From: Nikolai Iakovlev Date: Fri, 24 Nov 2017 03:26:38 +0300 Subject: [PATCH 7/7] Added slots passing to connected component --- lib/connect.js | 15 ++++++++++++--- src/connect.js | 14 +++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/connect.js b/lib/connect.js index 8521444..17b048e 100644 --- a/lib/connect.js +++ b/lib/connect.js @@ -60,6 +60,14 @@ function getProps(component) { return _extends({}, props, attrs); } +function getSlots(component) { + return Object.keys(component.$slots).reduce(function (memo, name) { + return _extends({}, memo, _defineProperty({}, name, function () { + return component.$slots[name]; + })); + }, {}); +} + function defaultMergeProps(stateProps, actionsProps) { return _extends({}, stateProps, actionsProps); } @@ -96,9 +104,10 @@ function connect(mapStateToProps, mapActionsToProps, mergeProps) { name: 'ConnectVuaRedux-' + (children.name || 'children'), render: function render(h) { - var props = getProps(this); - - return h(children, { props: props }); + return h(children, { + props: getProps(this), + scopedSlots: getSlots(this) + }); }, data: function data() { var state = getStates(this, mapStateToProps); diff --git a/src/connect.js b/src/connect.js index 81c572b..108f826 100644 --- a/src/connect.js +++ b/src/connect.js @@ -47,6 +47,13 @@ function getProps(component) { }; } +function getSlots(component) { + return Object.keys(component.$slots).reduce((memo, name) => ({ + ...memo, + [name]: () => component.$slots[name], + }), {}) +} + function defaultMergeProps(stateProps, actionsProps) { return { ...stateProps, @@ -90,9 +97,10 @@ export default function connect(mapStateToProps, mapActionsToProps, mergeProps) name: `ConnectVuaRedux-${children.name || 'children'}`, render(h) { - const props = getProps(this); - - return h(children, { props }); + return h(children, { + props: getProps(this), + scopedSlots: getSlots(this), + }); }, data() {