Skip to content

Commit

Permalink
refactor(redux): rewrite package to use decorators (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lodin committed Jul 27, 2018
1 parent ae83286 commit 17ade2d
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 147 deletions.
56 changes: 25 additions & 31 deletions packages/redux/__tests__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import {AnyAction, Store} from "redux";
import uuid from "uuid/v4";
import {BasicConsumer, BasicProvider, defineAndMountContext} from "../../../test/utils";
import {
connect,
connectedMap,
ConnectedMap,
dispatcherMap,
DispatcherMap,
connect, connected, dispatcher,
provider,
store,
} from "../src";
Expand All @@ -31,13 +27,15 @@ describe("@corpuscule/redux", () => {
});

it("should subscribe to store", () => {
class Provider extends provider(BasicProvider) {
@provider
class Provider extends BasicProvider {
public static is: string = `x-${uuid()}`;

protected [store]: Store = reduxStore;
}

class Connected extends connect(BasicConsumer) {
@connect
class Connected extends BasicConsumer {
public static is: string = `x-${uuid()}`;
}

Expand All @@ -46,21 +44,18 @@ describe("@corpuscule/redux", () => {
});

it("should allow to declare properties bound with store", () => {
class Provider extends provider(BasicProvider) {
@provider
class Provider extends BasicProvider {
public static is: string = `x-${uuid()}`;

protected [store]: Store = reduxStore;
}

class Connected extends connect(BasicConsumer) {
@connect
class Connected extends BasicConsumer {
public static is: string = `x-${uuid()}`;

public static get [connectedMap](): ConnectedMap<typeof reduxState> {
return {
test: state => state.test,
};
}

@connected<typeof reduxState>(state => state.test)
public test?: number;
}

Expand All @@ -71,21 +66,18 @@ describe("@corpuscule/redux", () => {
});

it("should update properties when store is updated", () => {
class Provider extends provider(BasicProvider) {
@provider
class Provider extends BasicProvider {
public static is: string = `x-${uuid()}`;

protected [store]: Store = reduxStore;
}

class Connected extends connect(BasicConsumer) {
@connect
class Connected extends BasicConsumer {
public static is: string = `x-${uuid()}`;

public static get [connectedMap](): ConnectedMap<typeof reduxState> {
return {
test: state => state.test,
};
}

@connected<typeof reduxState>(state => state.test)
public test?: number;
}

Expand All @@ -99,13 +91,15 @@ describe("@corpuscule/redux", () => {
});

it("should unsubscribe from store before subscribing to a new one", () => {
class Provider extends provider(BasicProvider) {
@provider
class Provider extends BasicProvider {
public static is: string = `x-${uuid()}`;

protected [store]: Store = reduxStore;
}

class Connected extends connect(BasicConsumer) {
@connect
class Connected extends BasicConsumer {
public static is: string = `x-${uuid()}`;
}

Expand All @@ -127,23 +121,23 @@ describe("@corpuscule/redux", () => {
type: "external",
});

class Provider extends provider(BasicProvider) {
@provider
class Provider extends BasicProvider {
public static is: string = `x-${uuid()}`;

protected [store]: Store = reduxStore;
}

class Connected extends connect(BasicConsumer) {
@connect
class Connected extends BasicConsumer {
public static is: string = `x-${uuid()}`;

public static get [dispatcherMap](): DispatcherMap {
return ["external", "test"];
}

@dispatcher
public external: typeof externalActionCreator = externalActionCreator;

private str: string = "test";

@dispatcher
public test(num: number): AnyAction {
return {
num,
Expand Down
7 changes: 3 additions & 4 deletions packages/redux/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@corpuscule/redux",
"version": "0.4.1",
"version": "0.5.0",
"description": "Redux mixin for Corpuscule",
"main": "lib/index.js",
"module": "lib/index.js",
Expand All @@ -16,9 +16,8 @@
"url": "git+https://github.com/corpusculejs/corpuscule.git"
},
"dependencies": {
"@corpuscule/context": "^0.3.0",
"@corpuscule/typings": "^0.2.0",
"@corpuscule/utils": "^0.3.0"
"@corpuscule/context": "^0.4.0",
"@corpuscule/typings": "^0.3.0"
},
"peerDependencies": {
"redux": "^4.0.0"
Expand Down
19 changes: 4 additions & 15 deletions packages/redux/src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
import createContext from "@corpuscule/context";
import {CustomElementClass, UncertainCustomElementClass} from "@corpuscule/typings";
import {FieldDecorator} from "@corpuscule/typings";

export type DispatcherMap = ReadonlyArray<PropertyKey>;
export type PropertyGetter<S> = (state: S) => any;

export type ConnectedMap<S> = {
readonly [P in PropertyKey]: PropertyGetter<S>;
};
export const connected: <S>(getter: PropertyGetter<S>) => FieldDecorator;
export const dispatcher: FieldDecorator;

export interface ReduxClass<T, S> extends CustomElementClass<T> {
readonly [dispatcherMap]?: DispatcherMap;
readonly [connectedMap]?: ConnectedMap<S>;
}

export const connect:
<S, T = {}>(target: UncertainCustomElementClass<T>) => ReduxClass<T, S>;
export const connect: ClassDecorator;

export const provider: ReturnType<typeof createContext>["provider"];
export const store: unique symbol;

export const dispatcherMap: unique symbol;
export const connectedMap: unique symbol;
147 changes: 58 additions & 89 deletions packages/redux/src/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import createContext from "@corpuscule/context";
import {
context as $$context,
initDispatchers as $$initDispatchers,
registry as $$registry,
subscribe as $$subscribe,
unsubscribe as $$unsubscribe,
update as $$update,
} from "./tokens/internal";
import {dispatcherMap, connectedMap} from "./tokens/lifecycle";
import {getDescriptors} from "./utils";
import {connectedRegistry} from "./utils";

const {
consumer,
Expand All @@ -18,103 +15,75 @@ const {
} = createContext();

export {
dispatcherMap,
provider,
connectedMap,
store,
};

export const connect = (target) => {
class ReduxConnected extends consumer(target) {
static get observedAttributes() {
if (this[connectedMap]) {
this[$$registry] = Object.entries(getDescriptors(this, connectedMap));
}

if (this[dispatcherMap]) {
this[$$initDispatchers](getDescriptors(this, dispatcherMap));
}

return super.observedAttributes || [];
}

static [$$initDispatchers](dispatchers) {
for (const propertyName of dispatchers) {
const method = this.prototype[propertyName];

if (!method) {
// eslint-disable-next-line accessor-pairs
Object.defineProperty(this.prototype, propertyName, {
configurable: true,
set(value) {
Object.defineProperty(this, propertyName, {
configurable: true,
value(...args) {
this[$$context].dispatch(value(...args));
},
});
},
});
export {
connected,
dispatcher,
} from "./utils";

continue;
export const connect = (target) => {
const consumed = consumer(target);

const {
disconnectedCallback,
} = consumed;

Object.defineProperties(consumed.prototype, {
disconnectedCallback: {
configurable: true,
value() {
if (disconnectedCallback) {
disconnectedCallback.call(this);
}

Object.defineProperty(this.prototype, propertyName, {
value(...args) {
this[$$context].dispatch(method.call(this, ...args));
},
});
}
}

set [context](v) {
this[$$context] = v;

if (this[$$unsubscribe]) {
this[$$unsubscribe]();
}

this[$$subscribe]();
}

attributeChangedCallback(...args) {
if (super.attributeChangedCallback) {
super.attributeChangedCallback(...args);
}
}

disconnectedCallback() {
if (super.disconnectedCallback) {
super.disconnectedCallback();
}

if (this[$$unsubscribe]) {
this[$$unsubscribe]();
}
}

[$$subscribe]() {
this[$$update](this[$$context]);
if (this[$$unsubscribe]) {
this[$$unsubscribe]();
}
},
},
// eslint-disable-next-line sort-keys, accessor-pairs
[context]: {
set(v) {
this[$$context] = v;

if (this[$$unsubscribe]) {
this[$$unsubscribe]();
}

this[$$unsubscribe] = this[$$context].subscribe(() => {
this[$$subscribe]();
},
},
// eslint-disable-next-line sort-keys
[$$subscribe]: {
value() {
this[$$update](this[$$context]);
});
}

[$$update]({getState}) {
if (!this.constructor[$$registry]) {
return;
}
this[$$unsubscribe] = this[$$context].subscribe(() => {
this[$$update](this[$$context]);
});
},
},
[$$update]: {
value({getState}) {
const registry = connectedRegistry.get(this.constructor.prototype);

if (!registry) {
return;
}

for (const [propertyName, getter] of this.constructor[$$registry]) {
const nextValue = getter(getState());
for (const [propertyName, getter] of registry) {
const nextValue = getter(getState());

if (nextValue !== this[propertyName]) {
this[propertyName] = nextValue;
if (nextValue !== this[propertyName]) {
this[propertyName] = nextValue;
}
}
}
}
}
},
},
});

return ReduxConnected;
return consumed;
};
2 changes: 0 additions & 2 deletions packages/redux/src/tokens/internal.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
export const context = Symbol("context");
export const initDispatchers = Symbol("initDispatchers");
export const registry = Symbol("registry");
export const subscribe = Symbol("subscribe");
export const unsubscribe = Symbol("unsubscribe");
export const update = Symbol("update");
2 changes: 0 additions & 2 deletions packages/redux/src/tokens/lifecycle.js

This file was deleted.

0 comments on commit 17ade2d

Please sign in to comment.