Skip to content

Commit

Permalink
Merge pull request #9 from jimengio/hooks-support
Browse files Browse the repository at this point in the history
simplify implementation with hooks; support useContext
  • Loading branch information
chenyong authored Mar 11, 2019
2 parents fdeefc3 + fae86f1 commit cd95f56
Show file tree
Hide file tree
Showing 6 changed files with 832 additions and 887 deletions.
38 changes: 18 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@jimengio/rex",
"version": "0.0.7",
"version": "0.1.0-a1",
"description": "Data wrapper",
"main": "./lib/rex.js",
"types": "./lib/rex.d.ts",
Expand All @@ -20,40 +20,38 @@
"license": "ISC",
"devDependencies": {
"@types/history": "^4.7.2",
"@types/lodash": "^4.14.117",
"@types/react": "^16.4.18",
"@types/react-dom": "^16.0.9",
"@types/shallowequal": "^0.2.3",
"@types/lodash": "^4.14.122",
"@types/react": "^16.8.7",
"@types/react-dom": "^16.8.2",
"@types/shallowequal": "^1.1.1",
"awesome-typescript-loader": "^5.2.1",
"css-loader": "^1.0.0",
"emotion": "^9.2.12",
"file-loader": "^2.0.0",
"css-loader": "^2.1.1",
"emotion": "^10.0.7",
"file-loader": "^3.0.1",
"font-awesome": "^4.7.0",
"history": "^4.6.1",
"html-webpack-plugin": "^3.2.0",
"http-server": "^0.11.0",
"lodash": "^4.17.11",
"prettier": "^1.14.3",
"randomcolor": "^0.5.3",
"react": "^16.6.0",
"react-dom": "^16.6.0",
"prettier": "^1.16.4",
"randomcolor": "^0.5.4",
"react": "^16.8.4",
"react-dom": "^16.8.4",
"ruled-router": "^0.2.2",
"style-loader": "^0.23.1",
"terser-webpack-plugin": "^1.1.0",
"typescript": "^3.1.3",
"typescript-styled-plugin": "^0.12.0",
"uglifyjs-webpack-plugin": "^2.0.1",
"typescript": "^3.1.3333",
"typescript-styled-plugin": "^0.14.0",
"url-loader": "^1.1.2",
"webpack": "^4.23.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.0",
"webpack": "^4.29.6",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.2.1",
"webpack-hud": "^0.1.2"
},
"peerDependencies": {
"react": "^16.5.2"
},
"dependencies": {
"immer": "*",
"immer": "^2.1.1",
"shallowequal": "*"
}
}
132 changes: 59 additions & 73 deletions rex/rex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,15 @@ import * as React from "react";
import produce from "immer";
import * as shallowequal from "shallowequal";

function devPoint(...args) {
function devPoint(...args: any[]) {
// console.log(...args)
}
let RexContext = React.createContext(null);

interface IRexProviderProps {
value: any;
}
// Rex Store Implementation

export class RexProvider extends React.Component<IRexProviderProps, any> {
render() {
return <RexContext.Provider value={this.props.value}>{this.props.children}</RexContext.Provider>;
}
}
interface IRexStore<T> {
getState: () => T;
subscribe: (f: (store: T) => void) => void;
unsubscribe: (f: (store: T) => void) => void;
subscribe: (f: (store: T) => void) => { unsubscribe: () => void };
update: (f: (store: T) => void) => void;
}

Expand All @@ -36,11 +27,14 @@ export function createStore<T>(initalState: T) {
// bypass warning of "setState on unmounted component" with unshift
draft.listeners.unshift(f);
});
},
unsubscribe: (f) => {
store = produce(store, (draft) => {
draft.listeners = draft.listeners.filter((x) => x != f);
});

return {
unsubscribe: () => {
store = produce(store, (draft) => {
draft.listeners = draft.listeners.filter((x) => x != f);
});
},
};
},
update: (f) => {
let newStore = produce(store.currentState as any, f);
Expand All @@ -54,80 +48,72 @@ export function createStore<T>(initalState: T) {
} as IRexStore<T>;
}

interface IRexDataLayerProps {
store: IRexStore<any>;
parentProps: any;
Child: any;
selector: (s: any, ownProps: any) => any;
}
interface IRexDataLayerState {
props: any;
// Context

let RexContext = React.createContext(null);

// Context Provider

interface IRexProviderProps {
value: IRexStore<any>;
}

class RexDataLayer extends React.Component<IRexDataLayerProps, IRexDataLayerState> {
constructor(props) {
super(props);
export let RexProvider: React.SFC<IRexProviderProps> = (props) => {
let [storeValue, setStoreValue] = React.useState(props.value.getState);

this.state = {
props: this.computeProps(),
React.useEffect(() => {
let result = props.value.subscribe(() => {
setStoreValue(props.value.getState());
});
return () => {
result.unsubscribe();
};
}
}, []);

computeProps() {
return produce(this.props.selector(this.props.store.getState(), this.props.parentProps), (x) => {});
}
return <RexContext.Provider value={storeValue}>{props.children}</RexContext.Provider>;
};

immerState(f: (s: any) => void, cb?) {
this.setState(produce<IRexDataLayerState>(f), cb);
}
// Context Consumer and Child rendering

interface IRexDataLayerProps {
parentProps: any;
computedProps: any;
Child: any;
}

class RexDataLayer extends React.Component<IRexDataLayerProps, any> {
render() {
devPoint("render Rex wrapper");
let Child = this.props.Child;
return <Child {...this.state.props} {...this.props.parentProps} />;
return <Child {...this.props.computedProps} {...this.props.parentProps} />;
}

shouldComponentUpdate(nextProps: IRexDataLayerProps, nextState: IRexDataLayerState) {
if (!shallowequal(nextProps.parentProps, this.props.parentProps)) {
return true;
}
if (!shallowequal(nextState.props, this.state.props)) {
return true;
}
// only usage of this component is to prevent unnecessary changes
shouldComponentUpdate(nextProps: IRexDataLayerProps, nextState: any) {
if (!shallowequal(nextProps.parentProps, this.props.parentProps)) return true;
if (!shallowequal(nextProps.computedProps, this.props.computedProps)) return true;
return false;
}

componentDidMount() {
this.props.store.subscribe(this.onStoreChange);
}

componentWillUnmount() {
this.props.store.unsubscribe(this.onStoreChange);
}

onStoreChange = (newStore) => {
this.immerState((state) => {
state.props = this.computeProps();
});
};
}

export function connectRex<T>(selector: (s: T, ownProps?: any) => any): any {
return (Target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
return class RexContainer extends React.Component {
render() {
devPoint("render interal");
return (
<RexContext.Consumer>
{(value: any) => {
let store = value as IRexStore<T>;
devPoint("consumer called");

return <RexDataLayer store={store} parentProps={this.props} Child={Target} selector={selector} />;
}}
</RexContext.Consumer>
);
}
let RexContainer: React.SFC<any> = (props) => {
devPoint("render interal");

let storeValue: T = React.useContext(RexContext);
devPoint("consumer called");
return <RexDataLayer parentProps={props} Child={Target} computedProps={selector(storeValue, props)} />;
};
return RexContainer;
};
}

// Hooks APIs

export function useRexContext<T>(selector: (s: T) => any): any {
// [Caution on performance] components use useContext will be called on every change
let contextValue: T = React.useContext(RexContext);

return selector(contextValue);
}
20 changes: 14 additions & 6 deletions src/pages/home.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from "react";
import React, { useEffect } from "react";
import { css } from "emotion";
import { doIncData, doIncBranch, doIncHome } from "controllers/data";
import Inside from "./inside";
import BranchData from "./branch-data";
import randomcolor from "randomcolor";
import { randomBg } from "util/color";
import HooksChild from "./hooks-child";

interface IProps {
data: number;
Expand All @@ -15,13 +16,16 @@ export default class Home extends React.PureComponent<IProps, any> {
console.log("rendering home");

return (
<div style={randomBg()}>
Home Page, data:
{this.props.data}
<div>ok</div>
<a onClick={doIncHome}>Add home</a>
<div>
<div className={styleHome} style={randomBg()}>
Home Page, data:
{this.props.data}
<div>ok</div>
<a onClick={doIncHome}>Add home</a>
</div>
<Inside passed={"home data"} />
<BranchData passed={"home data"} />
<HooksChild passed={"data from home"} />
</div>
);
}
Expand All @@ -37,3 +41,7 @@ const styleSpace = css`
display: inline-block;
width: 50px;
`;

const styleHome = css`
padding: 8px;
`;
39 changes: 39 additions & 0 deletions src/pages/hooks-child.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { SFC } from "react";
import { css, cx } from "emotion";
import randomcolor from "randomcolor";

import { connectRex, useRexContext } from "rex";
import { IGlobalStore } from "models/global";
import { doIncData } from "controllers/data";
import { randomBg } from "util/color";

interface IProps {
passed: string;
}

let HooksChild: SFC<IProps> = (props) => {
let contextData = useRexContext((store: IGlobalStore) => {
return {
data: store.data,
homeData: store.homeData,
};
});

return (
<div className={styleContainer} style={randomBg()}>
<div>this is page inside</div>
<div>passed value: {props.passed}</div>

<a onClick={doIncData}>Add data</a>
<pre>{JSON.stringify(contextData, null, 2)}</pre>
</div>
);
};

const styleContainer = css`
border: 1px solid #ddd;
margin: 20px;
padding: 20px;
`;

export default HooksChild;
2 changes: 1 addition & 1 deletion src/pages/inside.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface IState {}
return { data: store.data };
})
export default class Inside extends React.PureComponent<IProps, IState> {
constructor(props) {
constructor(props: IProps) {
super(props);

this.state = {};
Expand Down
Loading

0 comments on commit cd95f56

Please sign in to comment.