Skip to content

Commit 07baf31

Browse files
committed
feat: improve Router
1 parent dc0eeba commit 07baf31

File tree

7 files changed

+109
-56
lines changed

7 files changed

+109
-56
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const MyComponent = mock();
7171
- [`<Provider>`](./docs/context.md#provider), [`<Consumer>`](./docs/context.md#consumer), [`withContext()`](./docs/context.md#withcontext), and `@withContext`
7272
- [`<Theme>`](./docs/theme.md#theme), [`<Themed>`](./docs/theme.md#themed), [`withTheme()`](./docs/theme.md#withtheme), and `@withTheme`
7373
- `<CssVars>`
74-
- [`<Router>`](./docs/route.md#router), [`<Route>`](./docs/route.md#route), [`withRoute()`](./docs/route.md#withroute), and `@withRoute`
74+
- [`<Router>`](./docs/route.md#router), [`<Route>`](./docs/route.md#route), [`withRoute()`](./docs/route.md#withroute), `@withRoute`, `go()`, and `<Go>`
7575
- `<Translations>`, `<Translate>`, `withTranslations`, and `@withTranslations`
7676
- [CSS resets](./docs/CSS-resets.md)
7777
- [`<CssResetEricMeyer>`](./docs/reset/CssResetEricMeyer.md) and [`<CssResetEricMeyerCondensed>`](./docs/reset/CssResetEricMeyerCondensed.md)

src/Link/index.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const isModifiedEvent = (event) =>
77
!!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
88

99
export interface ILinkProps extends React.AllHTMLAttributes<any> {
10+
bond?: boolean | string;
1011
replace?: boolean;
1112
state?: any | ((props: ILinkProps) => any);
1213
to?: string;
@@ -26,7 +27,7 @@ export class Link extends Component<ILinkProps, any> {
2627
(originalRef || noop)(el);
2728
};
2829

29-
onClick = (originalClick) => (event) => {
30+
onClick = (originalClick?) => (event) => {
3031
(originalClick || noop)(event);
3132

3233
const {replace, to, state} = this.props;
@@ -46,11 +47,23 @@ export class Link extends Component<ILinkProps, any> {
4647
};
4748

4849
render () {
49-
const {replace, state, to, a, ...rest} = this.props;
50-
let element = renderProp(this.props, {
50+
let {bond, replace, state, to, a, ...rest} = this.props;
51+
const renderPropState: any = {
5152
go,
5253
to
53-
});
54+
};
55+
56+
if (bond) {
57+
if (typeof bond !== 'string') {
58+
bond = 'bond';
59+
}
60+
61+
renderPropState[bond] = {
62+
onClick: this.onClick()
63+
};
64+
}
65+
66+
let element = renderProp(this.props, renderPropState);
5467

5568
if (!element) {
5669
return null;
@@ -61,10 +74,13 @@ export class Link extends Component<ILinkProps, any> {
6174
}
6275

6376
const props: any = {
64-
ref: this.ref(element.ref),
65-
onClick: this.onClick(element.props.originalClick)
77+
ref: this.ref(element.ref)
6678
};
6779

80+
if (!bond) {
81+
props.onClick = this.onClick(element.props.originalClick);
82+
}
83+
6884
this.target = '';
6985

7086
if (element.type === 'a') {

src/route/__story__/StoryRouteExample2.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import {h} from '../../util';
2-
import {Router, Route, go} from '..';
2+
import {Router, Route, go, Go} from '..';
33
import {Link} from '../../Link';
44

55
const StoryRouteExample2 = () => (
66
<Router>
77
<div>
88
<ul>
99
<li><a onClick={() => go('/')}>Home</a></li>
10-
<li><Link to='/about'>About</Link></li>
10+
<li><Go to='/about'>About</Go></li>
1111
<li><Link a to='/topics'><span>Topics</span></Link></li>
1212
</ul>
1313

src/route/__story__/StoryRouteTruncateRoute.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ const StoryRouteTruncateRoute = () =>
66
<div>
77
<Router route='/api/users/123.json'>
88
<Route match='/api'>
9+
/api
910
<div>
10-
API
11+
/api
1112
<Route match='/users'>
1213
<div>
13-
USERS
14+
/api/users
1415
<Route match={/.*/}>{(result) =>
15-
<pre style={{fontFamily: 'monospace'}}>{JSON.stringify(result, null, 4)}</pre>
16+
<div>
17+
/api/users/.*
18+
<pre style={{fontFamily: 'monospace'}}>{JSON.stringify(result, null, 4)}</pre>
19+
</div>
1620
}</Route>
1721
</div>
1822
</Route>
@@ -25,11 +29,20 @@ const StoryRouteTruncateRoute = () =>
2529
<pre style={{fontFamily: 'monospace'}}>{`
2630
<Router route='/api/users/123.json'>
2731
<Route match='/api'>
32+
<div>
33+
/api
2834
<Route match='/users'>
35+
<div>
36+
/api/users
2937
<Route match={/.*/}>{(result) =>
30-
<pre style={{fontFamily: 'monospace'}}>{JSON.stringify(result, null, 4)}</pre>
38+
<div>
39+
/api/users/.*
40+
<pre style={{fontFamily: 'monospace'}}>{JSON.stringify(result, null, 4)}</pre>
41+
</div>
3142
}</Route>
43+
</div>
3244
</Route>
45+
</div>
3346
</Route>
3447
</Router>
3548
`}</pre>

src/route/__tests__/index.test-server.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe('route SSR', () => {
3434
</Router>
3535
);
3636

37-
expect(html).to.equal('<div><span>1</span></div>');
37+
expect(html).to.equal('<div><span>1</span><span>3</span><span>4</span></div>');
3838
});
3939

4040
it('matches partial step', () => {

src/route/index.ts

Lines changed: 58 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import {LocationSensor} from '../LocationSensor';
33
import {Provider, Consumer} from '../context';
44
import {h, ns} from '../util';
55
import renderProp from '../util/renderProp';
6+
import {Link, ILinkProps} from '../Link';
67

78
export interface IRouteProviderProps {
89
children?: any;
910
ns?: string;
11+
fullRoute?: string;
1012
route?: string;
1113
parent?: TRouteMatchResult;
1214
}
@@ -19,18 +21,19 @@ export class Router extends Component<IRouteProviderProps, any> {
1921
};
2022

2123
renderProvider (route) {
22-
const {children} = this.props;
24+
const {children, fullRoute} = this.props;
2325
this.matches = 0;
2426

2527
const element = h(Provider, {
2628
name: ns(`route/${this.props.ns}`),
2729
value: {
30+
fullRoute: this.props.fullRoute || route,
2831
route,
2932
inc: this.inc,
3033
count: () => this.matches,
3134
parent: this.props.parent
3235
}
33-
}, Array.isArray(children) ? h('div', null, children) : children);
36+
}, renderProp(this.props));
3437

3538
return element;
3639
}
@@ -54,8 +57,37 @@ export interface TRouteMatchResult {
5457

5558
export type TRouteMatcher = (route: string) => TRouteMatchResult;
5659

60+
export function createMatcher (match: string | RegExp | TRouteMatcher, exact?: boolean): TRouteMatcher {
61+
let matcher: TRouteMatcher;
62+
63+
if (typeof match === 'function') {
64+
return match;
65+
}
66+
67+
let regex: RegExp;
68+
69+
if (typeof match === 'string') {
70+
regex = new RegExp(`^(${match}${exact ? '$' : ''})`);
71+
} else {
72+
regex = match;
73+
}
74+
75+
return (route: string) => {
76+
const matches = route.match(regex);
77+
78+
if (!matches) {
79+
return null;
80+
}
81+
82+
return {
83+
length: (matches && matches[1]) ? matches[1].length : 0,
84+
matches
85+
};
86+
};
87+
}
88+
5789
export interface IRouteMatch {
58-
children?: React.ReactElement<any> | ((params) => React.ReactElement<any>);
90+
children?: any;
5991
render?: React.ReactElement<any> | ((params) => React.ReactElement<any>);
6092
comp?: React.ComponentClass<any> | React.StatelessComponent<any>;
6193
component?: React.ComponentClass<any> | React.StatelessComponent<any>;
@@ -74,43 +106,13 @@ export class Route extends Component<IRouteMatch, any> {
74106
max: Infinity
75107
};
76108

77-
matcher (): TRouteMatcher {
78-
const {match} = this.props;
79-
let matcher: TRouteMatcher;
80-
81-
if (typeof match === 'function') {
82-
return match;
83-
}
84-
85-
let regex: RegExp;
86-
87-
if (typeof match === 'string') {
88-
regex = new RegExp(`^(${match}${this.props.exact ? '$' : ''})`);
89-
} else {
90-
regex = match;
91-
}
92-
93-
return (route: string) => {
94-
const matches = route.match(regex);
95-
96-
if (!matches) {
97-
return null;
98-
}
99-
100-
return {
101-
length: (matches && matches[1]) ? matches[1].length : 0,
102-
matches
103-
};
104-
};
105-
}
106-
107109
render () {
108110
return h(Consumer, {name: ns(`route/${this.props.ns}`)}, ({route, inc, count, parent}) => {
109-
const {children, match, preserve, min, max} = this.props;
111+
const {children, exact, match, preserve, min, max} = this.props;
110112
const matchCount = count();
111113

112114
if ((matchCount >= min) && (matchCount <= max)) {
113-
const matchResult = this.matcher()(route);
115+
const matchResult = createMatcher(match, exact)(route);
114116

115117
if (matchResult) {
116118
// Increment number of matched routes.
@@ -124,7 +126,11 @@ export class Route extends Component<IRouteMatch, any> {
124126
newRoute = newRoute.substr(length);
125127
}
126128

127-
return h(Router, {route: newRoute, parent: matchResult},
129+
return h(Router, {
130+
fullRoute: route,
131+
route: newRoute,
132+
parent: matchResult
133+
},
128134
renderProp(this.props, {
129135
match: route.substr(0, length),
130136
matches,
@@ -146,3 +152,19 @@ export const Route404 = (props) => h(Route, {
146152
});
147153

148154
export * from './go';
155+
156+
export interface IGoProps extends ILinkProps {
157+
ns?: string;
158+
}
159+
160+
export interface IGoState {
161+
162+
}
163+
164+
export class Go extends Component<IGoProps, IGoState> {
165+
render () {
166+
return h(Consumer, {name: ns(`route/${this.props.ns}`)}, ({fullRoute, route, inc, count, parent}) => {
167+
return h(Link, this.props)
168+
});
169+
}
170+
}

src/util/renderProp.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@ const renderProp = (props, ...args) => {
1717
}
1818
}
1919

20-
const {children, component, comp = component, render} = props;
20+
const {render, children = render, component, comp = component} = props;
2121

2222
return isFn(children) ?
2323
children(...args) :
24-
isFn(render) ?
25-
render(...args) :
26-
comp ?
27-
h(comp, args[0]) :
28-
(children || render || null);
24+
comp ?
25+
h(comp, args[0]) :
26+
children && (typeof children !== 'object') ?
27+
h('div', null, children) :
28+
children instanceof Array ?
29+
h('div', null, ...children) :
30+
(children || null);
2931
};
3032

3133
export default renderProp;

0 commit comments

Comments
 (0)