Skip to content

Commit e37a7cf

Browse files
author
Pangallo, Antonio
committed
connect performance improvement
1 parent 91a86a3 commit e37a7cf

File tree

9 files changed

+227
-94
lines changed

9 files changed

+227
-94
lines changed

.babelrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ let presets = [
1313
"@babel/preset-react"
1414
];
1515

16-
let plugins = [];
16+
let plugins = ["@babel/plugin-proposal-class-properties"];
1717
if (process.env.NODE_ENV === "test") {
1818
plugins.push("@babel/transform-modules-commonjs");
1919
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# <img src='https://raw.githubusercontent.com/iusehooks/redhooks/master/logo/logo.png' width="224" height='61' alt='Redhooks Logo' />
22

33
Redhooks is tiny React utility library for holding a predictable state container in your React apps.
4-
Inspired by [Redux](https://redux.js.org), it reimplements the redux paradigm of state-management by using React's new Hooks and Context API, which have recently been [officially approved](https://github.com/facebook/react/pull/14679) by the React team.
4+
Inspired by [Redux](https://redux.js.org), it reimplements the redux paradigm of state-management by using React's new Hooks and Context API, which have been [officially released](https://reactjs.org/docs/hooks-reference.html) by the React team.
55
- [Motivation](#motivation)
66
- [Basic Example](#basic-example)
77
- [Apply Middleware](#apply-middleware)

__tests__/shallowEqual.spec.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import shallowEqual from "../src/utils/shallowEqual";
2+
3+
/* Unit Tests Taken from */
4+
/* https://github.com/moroshko/shallow-equal/blob/master/src/objects.test.js */
5+
6+
const obj1 = { game: "chess", year: "1979" };
7+
const obj2 = { language: "elm" };
8+
9+
const tests = [
10+
{
11+
should: "return false when A is falsy",
12+
objA: null,
13+
objB: {},
14+
result: false
15+
},
16+
{
17+
should: "return false when B is falsy",
18+
objA: {},
19+
objB: undefined,
20+
result: false
21+
},
22+
{
23+
should: "return true when objects are ===",
24+
objA: obj1,
25+
objB: obj1,
26+
result: true
27+
},
28+
{
29+
should: "return true when both objects are empty",
30+
objA: {},
31+
objB: {},
32+
result: true
33+
},
34+
{
35+
should: "return false when objects do not have the same amount of keys",
36+
objA: { game: "chess", year: "1979", country: "Australia" },
37+
objB: { game: "chess", year: "1979" },
38+
result: false
39+
},
40+
{
41+
should: "return false when there corresponding values which are not ===",
42+
objA: { first: obj1, second: obj2 },
43+
objB: { first: obj1, second: { language: "elm" } },
44+
result: false
45+
},
46+
{
47+
should: "return true when all values are ===",
48+
objA: { first: obj1, second: obj2 },
49+
objB: { second: obj2, first: obj1 },
50+
result: true
51+
},
52+
{
53+
should: "return false when one of the input args is not a object",
54+
objA: 3,
55+
objB: {},
56+
result: false
57+
}
58+
];
59+
60+
describe("shallowEqualObjects", function() {
61+
tests.forEach(function(test) {
62+
it("should " + test.should, function() {
63+
expect(shallowEqual(test.objA, test.objB)).toBe(test.result);
64+
});
65+
});
66+
});

package-lock.json

Lines changed: 41 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@
3333
],
3434
"license": "MIT",
3535
"peerDependencies": {
36-
"react": "16.8.0-alpha.0"
36+
"react": ">=16.8.0"
3737
},
3838
"devDependencies": {
3939
"@babel/cli": "^7.2.3",
4040
"@babel/core": "^7.2.2",
41+
"@babel/plugin-proposal-class-properties": "^7.3.0",
4142
"@babel/preset-env": "^7.2.3",
4243
"@babel/preset-react": "^7.0.0",
4344
"babel-core": "^7.0.0-bridge.0",

src/Provider.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
import React, { useState, useEffect, useRef } from "react";
22
import createDispatch from "./utils/createDispatch";
3+
import { Context } from "./store";
34

4-
export default ({ store, children }) => {
5-
const { reducer, initialState, Context, middlewares, onload } = store;
5+
export default function Provider({ store, children }) {
6+
const { reducer, initialState, middlewares, onload } = store;
67

78
const storeContext = storeHooks(reducer, initialState, middlewares);
89

9-
useEffect(
10-
() => {
11-
onload && onload(storeContext);
12-
},
13-
[middlewares]
14-
);
10+
useEffect(() => {
11+
onload && onload(storeContext);
12+
}, []);
1513

1614
return <Context.Provider value={storeContext}>{children}</Context.Provider>;
17-
};
15+
}
1816

1917
function storeHooks(reducer, initialState, middlewares) {
20-
const [state, setState] = useState(initialState);
18+
const [state, setState] = useState(() => initialState);
2119

2220
const stateProvider = useRef();
2321
stateProvider.current = state; // store the state reference

src/connect.js

Lines changed: 84 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import React, { useMemo, useState, useRef } from "react";
2-
import { useStore } from "./store";
1+
import React, { PureComponent } from "react";
32
import isPlainObject from "./utils/isPlainObject";
3+
import shallowEqual from "./utils/shallowEqual";
44
import bindActionCreators from "./bindActionCreators";
5+
import { Context } from "./store";
56

67
/**
78
* `connect` is a HOC which connects a React Component to the Redhooks store.
@@ -44,70 +45,92 @@ export default (
4445
);
4546
}
4647

47-
return Comp => props => {
48-
const { state, dispatch } = useStore();
49-
50-
const propsMapped = mapStateToProps(state, props);
51-
52-
/* useRef returns a mutable ref object whose .current property is initialized
53-
* to the passed argument (initialValue). The returned object will persist for
54-
* the full lifetime of the component.
55-
*/
56-
const { current: innerState } = useRef({
57-
change: false,
58-
prevProps: propsMapped
59-
});
60-
61-
if (!isPlainObject(propsMapped)) {
62-
throw new Error(
63-
errMsg(
64-
"mapStateToProps",
48+
return Comp =>
49+
class Connect extends PureComponent {
50+
_innerState = {
51+
changeMapProp: false,
52+
prevMapPropChange: false,
53+
changeOwnProp: false,
54+
prevOwnPropsChange: false,
55+
prevMapProps: {},
56+
prevOwnProps: this.props
57+
};
58+
59+
_render = value => {
60+
const { state, dispatch } = value;
61+
const propsMapped = mapStateToProps(state, this.props);
62+
63+
// const propsMapped = {...this.props, ...propsMapped1};
64+
65+
if (!isPlainObject(propsMapped)) {
66+
throw new Error(
67+
errMsg(
68+
"mapStateToProps",
69+
propsMapped,
70+
"must return a plain object",
71+
Comp.name
72+
)
73+
);
74+
}
75+
76+
if (!this._dispatchProps) {
77+
const propsDispatch = mapDispatchToProps(dispatch, this.props);
78+
if (!isPlainObject(propsDispatch)) {
79+
throw new Error(
80+
errMsg(
81+
"mapDispatchToProps",
82+
propsDispatch,
83+
"must return a plain object",
84+
Comp.name
85+
)
86+
);
87+
}
88+
this._dispatchProps =
89+
Object.keys(propsDispatch).length > 0
90+
? propsDispatch
91+
: { dispatch };
92+
}
93+
94+
this._innerState.changeMapProp = shallowEqual(
6595
propsMapped,
66-
"must return a plain object",
67-
Comp.name
96+
this._innerState.prevMapProps
6897
)
69-
);
70-
}
71-
72-
const [dispatchProps] = useState(() => {
73-
const propsDispatch = mapDispatchToProps(dispatch, props);
74-
if (!isPlainObject(propsDispatch)) {
75-
throw new Error(
76-
errMsg(
77-
"mapDispatchToProps",
78-
propsDispatch,
79-
"must return a plain object",
80-
Comp.name
81-
)
82-
);
98+
? this._innerState.changeMapProp
99+
: !this._innerState.changeMapProp;
100+
this._innerState.changeOwnProp = shallowEqual(
101+
this.props,
102+
this._innerState.prevOwnProps
103+
)
104+
? this._innerState.changeOwnProp
105+
: !this._innerState.changeOwnProp;
106+
107+
this._innerState.prevMapProps = propsMapped;
108+
this._innerState.prevOwnProps = this.props;
109+
110+
if (
111+
this._innerState.prevMapPropChange !==
112+
this._innerState.changeMapProp ||
113+
this._innerState.prevOwnPropsChange !==
114+
this._innerState.changeOwnProp ||
115+
!this._wrapper
116+
) {
117+
this._innerState.prevMapPropChange = this._innerState.changeMapProp;
118+
this._innerState.prevOwnPropsChange = this._innerState.changeOwnProp;
119+
120+
this._wrapper = (
121+
<Comp {...this.props} {...propsMapped} {...this._dispatchProps} />
122+
);
123+
}
124+
125+
return this._wrapper;
126+
};
127+
128+
render() {
129+
return <Context.Consumer>{this._render}</Context.Consumer>;
83130
}
84-
const { length } = Object.keys(propsDispatch);
85-
return length > 0 ? propsDispatch : { dispatch };
86-
});
87-
88-
innerState.change = checkPropChange(
89-
propsMapped,
90-
innerState.prevProps,
91-
innerState.change
92-
);
93-
94-
innerState.prevProps = propsMapped;
95-
96-
const CPM = useMemo(
97-
() => <Comp {...props} {...propsMapped} {...dispatchProps} />,
98-
[innerState.change]
99-
);
100-
return CPM;
101-
};
131+
};
102132
};
103133

104-
function checkPropChange(propsMapped, prevPropsMapped, change) {
105-
const changeHappened = Object.keys(propsMapped).some(
106-
key => propsMapped[key] !== prevPropsMapped[key]
107-
);
108-
return changeHappened ? !change : change;
109-
}
110-
111134
function bindFunctionActions(mapDispatchToProps) {
112135
return dispatch => bindActionCreators({ ...mapDispatchToProps }, dispatch);
113136
}

0 commit comments

Comments
 (0)