Skip to content

Commit

Permalink
make RouterStore smaller and faster
Browse files Browse the repository at this point in the history
  • Loading branch information
Cap32 committed Sep 26, 2017
1 parent b058a46 commit 188a14b
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 97 deletions.
112 changes: 41 additions & 71 deletions src/RouterStore.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

import { observable, computed } from 'mobx';
import { observable, computed, autorun, extendObservable } from 'mobx';
import { parse, stringify } from './queryString';

const stripQuery = (loc) => {
Expand All @@ -14,81 +14,51 @@ const stripQuery = (loc) => {
return loc;
};

class LocationData {
@observable pathname = '/';
@observable search = '?';
@observable hash = '';
@observable state = {};
}
class LocationStore {
constructor(routerStore, location, history) {
this.__routerStore = routerStore;
this._data = new LocationData();
const update = (loc) => {
Object.assign(this._data, loc);
};
update(location);
history.listen(update);
}
@computed get pathname() {
return this._data.pathname;
}
set pathname(pathname) {
this.__routerStore.push({ pathname });
return pathname;
}
@computed get search() {
return this._data.search;
}
set search(search) {
this.__routerStore.push({ search });
return search;
}
@computed get hash() {
return this._data.hash;
}
set hash(hash) {
this.__routerStore.push({ hash });
return hash;
}
@computed get state() {
return this._data.state;
}
set state(state) {
this.__routerStore.push({ state });
return state;
}
@computed get query() {
return parse((this._data.search || '').slice(1));
}
set query(query) {
const queryString = stringify(query);
this.search = queryString && `?${queryString}`;
return query;
}
}

export default class RouterStore {
@observable _location = { query: {} };
_prevLoc = {};

@computed get location() {
return this._location;
}
set location(loc) {
this.push(loc);
}
@observable location = {};

__initial({ location, history }) {
this._location = observable(new LocationStore(this, location, history));
class Loc {
@computed get query() {
return parse(this.search.slice(1));
}
set query(query) {
const queryString = stringify(query);
this.search = queryString && `?${queryString}`;
return query;
}

constructor(loc) {
extendObservable(this, loc);
}
}

this.location = new Loc(location);
this._prevLoc = location;

this.history = history;

history.listen((location) => {
if (typeof this.location === 'string') {
this.location = new Loc({});
}
this.location.pathname = location.pathname;
this.location.search = location.search;
this.location.hash = location.hash;
this._prevLoc = location;
});

autorun(() => {
if (typeof this.location === 'string' ||
this.location.search !== this._prevLoc.search ||
this.location.hash !== this._prevLoc.hash ||
this.location.pathname !== this._prevLoc.pathname
) {
this.push(this.location);
}
});
}

push(loc, state) {
Expand Down
80 changes: 54 additions & 26 deletions src/__tests__/RouterStore.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,98 @@ import { isObservable, autorun } from 'mobx';
import url from 'url';

describe('RouterStore', () => {
test('should routerStore work', () => {
const routerStore = new RouterStore();
let routerStore;
let history;

beforeEach(() => {
const location = {
pathname: '/',
search: '?hello=world',
hash: '',
};
const history = {
let updateFn = () => {};
routerStore = new RouterStore();
history = {
push: jest.fn((loc) => {
if (typeof loc === 'string') { loc = url.parse(loc); }
history._updateFn(loc);
loc.search = loc.search || '';
loc.hash = loc.hash || '';
loc.pathname = loc.pathname || '/';
updateFn(loc);
}),
replace: jest.fn(),
listen: jest.fn((update) => {
history._updateFn = update;
updateFn = update;
}),
};

autorun(() => {
routerStore.__initial({ history, location });
});
});

test('should push and replace be function', () => {
expect(typeof routerStore.history.push).toBe('function');
expect(typeof routerStore.history.replace).toBe('function');
expect(typeof routerStore.push).toBe('function');
expect(typeof routerStore.replace).toBe('function');
expect(isObservable(routerStore, '_location')).toBe(true);
});

test('should location observable', () => {
expect(isObservable(routerStore.location, 'pathname')).toBe(true);
expect(isObservable(routerStore.location, 'search')).toBe(true);
expect(isObservable(routerStore.location, 'hash')).toBe(true);
expect(routerStore.location.query).toEqual({ hello: 'world' });
});

test('should routerStore.location.hash work', () => {
expect(history.push.mock.calls.length).toBe(0);
routerStore.location = '/a';
expect(routerStore.location.pathname).toBe('/a');
routerStore.location.hash = '#hello';
expect(routerStore.location.hash).toBe('#hello');
expect(history.push.mock.calls.length).toBe(1);
});

routerStore.location.pathname = '/b';
expect(routerStore.location.pathname).toBe('/b');
expect(history.push.mock.calls.length).toBe(2);
test('should routerStore.location.search work', () => {
expect(history.push.mock.calls.length).toBe(0);
routerStore.location.search = '?foo=bar';
expect(routerStore.location.search).toBe('?foo=bar');
expect(history.push.mock.calls.length).toBe(1);
});

test('should routerStore.location.pathname work', () => {
expect(history.push.mock.calls.length).toBe(0);
routerStore.location.pathname = '/hello';
expect(routerStore.location.pathname).toBe('/hello');
expect(history.push.mock.calls.length).toBe(1);
});

test('should routerStore.location.query work', () => {
expect(history.push.mock.calls.length).toBe(0);
routerStore.location.query = {
...routerStore.location.query,
foo: 'bar',
};
expect(routerStore.location.query).toEqual({ foo: 'bar' });
expect(history.push.mock.calls.length).toBe(3);

routerStore.location.hash = '#hello';
expect(routerStore.location.hash).toBe('#hello');
expect(history.push.mock.calls.length).toBe(4);

routerStore.location.search = '?hello=world';
expect(routerStore.location.search).toBe('?hello=world');
expect(history.push.mock.calls.length).toBe(5);
expect(routerStore.location.query).toEqual({
foo: 'bar',
hello: 'world',
});
expect(history.push.mock.calls.length).toBe(1);
});

routerStore.location.state = { hello: 'world' };
expect(routerStore.location.state).toEqual({ hello: 'world' });
expect(history.push.mock.calls.length).toBe(6);
test('should update routerStore.location work', () => {
expect(history.push.mock.calls.length).toBe(0);
routerStore.location = '/a';
expect(routerStore.location.pathname).toBe('/a');
expect(history.push.mock.calls.length).toBe(1);
});

test('should push work', () => {
expect(history.push.mock.calls.length).toBe(0);
routerStore.push({ query: { hello: 'world' } });
expect(routerStore.location.query).toEqual({ hello: 'world' });
expect(history.push.mock.calls.length).toBe(7);
expect(history.push.mock.calls.length).toBe(1);
});

test('should replace work', () => {
expect(history.replace.mock.calls.length).toBe(0);
routerStore.replace('/');
expect(history.replace.mock.calls.length).toBe(1);
Expand Down

0 comments on commit 188a14b

Please sign in to comment.