Skip to content

Commit 258fd8d

Browse files
committed
fix(router): fix router state computed issue
1 parent 20a6c25 commit 258fd8d

File tree

3 files changed

+175
-2
lines changed

3 files changed

+175
-2
lines changed

packages/reactant-module/src/core/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ export * from './watch';
99
export * from './load';
1010
export * from './applyMiddleware';
1111
export * from './getRef';
12-
export { untracked } from './signal';
12+
export { untracked, Signal, signal } from './signal';

packages/reactant-router/src/router.tsx

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
/* eslint-disable consistent-return */
22
import React, { PropsWithChildren, FunctionComponent } from 'react';
3-
import { PluginModule, injectable, inject, storeKey } from 'reactant-module';
3+
import {
4+
PluginModule,
5+
injectable,
6+
inject,
7+
storeKey,
8+
signalMapKey,
9+
type Service,
10+
signal,
11+
enableAutoComputedKey,
12+
type Signal,
13+
} from 'reactant-module';
414
import type { ReducersMapObject } from 'redux';
515
import {
616
connectRouter,
@@ -91,6 +101,34 @@ class ReactantRouter extends PluginModule {
91101
`The identifier '${this.stateKey}' has a duplicate name, please reset the option 'stateKey' of 'ReactantRouter' module.`
92102
);
93103
}
104+
if ((this as Service)[enableAutoComputedKey]) {
105+
const signalMap: Record<string, Signal> =
106+
(this as Service)[signalMapKey] ?? {};
107+
signalMap[this.stateKey] = signal({});
108+
const current = signalMap[this.stateKey];
109+
if (!(this as Service)[signalMapKey]) {
110+
Object.defineProperties(this, {
111+
[signalMapKey]: {
112+
enumerable: false,
113+
configurable: false,
114+
writable: false,
115+
value: signalMap,
116+
},
117+
});
118+
}
119+
const reducer = connectRouter(this.history ?? this.defaultHistory);
120+
return Object.assign(reducers, {
121+
// TODO: fix type
122+
[this.stateKey]: (state: any, action: any) => {
123+
const result = reducer(state, action);
124+
if (result !== state) {
125+
// update signal value for auto-computed
126+
current.value = result;
127+
}
128+
return result;
129+
},
130+
});
131+
}
94132
return Object.assign(reducers, {
95133
[this.stateKey]: connectRouter(this.history ?? this.defaultHistory),
96134
});
@@ -106,6 +144,12 @@ class ReactantRouter extends PluginModule {
106144
);
107145

108146
get router(): RouterState | undefined {
147+
if ((this as Service)[enableAutoComputedKey]) {
148+
const currentRouter = (this as Service)[signalMapKey]?.[this.stateKey];
149+
if (currentRouter) {
150+
return currentRouter!.value as RouterState;
151+
}
152+
}
109153
return this.store?.getState()[this.stateKey];
110154
}
111155

packages/reactant-router/test/index.test.tsx

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {
1818
useConnector,
1919
action,
2020
state,
21+
computed,
22+
watch,
2123
} from 'reactant';
2224
import type { IRouterOptions } from '..';
2325
import {
@@ -497,3 +499,130 @@ test('`router` module with auto provider with createBrowserHistory', async () =>
497499
);
498500
expect(container.textContent).toBe('HomeDashboard0');
499501
});
502+
503+
test('`router` module with auto computed', () => {
504+
const fn = jest.fn();
505+
const computedFn = jest.fn();
506+
507+
@injectable()
508+
class Count {
509+
@state
510+
num = 0;
511+
512+
@action
513+
increase() {
514+
this.num += 1;
515+
}
516+
}
517+
518+
@injectable()
519+
class DashboardView extends ViewModule {
520+
constructor(public count: Count) {
521+
super();
522+
}
523+
524+
component() {
525+
const num = useConnector(() => this.count.num);
526+
return (
527+
<div onClick={() => this.count.increase()} id="increase">
528+
{num}
529+
</div>
530+
);
531+
}
532+
}
533+
@injectable()
534+
class HomeView extends ViewModule {
535+
text = 'app';
536+
537+
getProps(version: string) {
538+
return {
539+
version: `${this.text} v${version}`,
540+
};
541+
}
542+
543+
component({ version = '0.0.1' }) {
544+
const data = useConnector(() => this.getProps(version));
545+
return <span id="version">{data.version}</span>;
546+
}
547+
}
548+
549+
@injectable()
550+
class AppView extends ViewModule {
551+
constructor(
552+
public homeView: HomeView,
553+
public dashboardView: DashboardView,
554+
public router: Router
555+
) {
556+
super();
557+
watch(this, () => this.currentPath, fn);
558+
}
559+
560+
@computed
561+
get currentPath() {
562+
computedFn(this.router?.currentPath);
563+
return this.router?.currentPath;
564+
}
565+
566+
component() {
567+
const { ConnectedRouter } = this.router;
568+
return (
569+
<ConnectedRouter>
570+
<Switch>
571+
<Route exact path="/">
572+
<this.homeView.component version="0.1.0" />
573+
</Route>
574+
<Route path="/a">
575+
<this.dashboardView.component />
576+
</Route>
577+
<Route path="/b">
578+
<this.dashboardView.component />
579+
</Route>
580+
</Switch>
581+
</ConnectedRouter>
582+
);
583+
}
584+
}
585+
586+
const app = createApp({
587+
modules: [
588+
{
589+
provide: RouterOptions,
590+
useValue: {
591+
autoProvide: false,
592+
createHistory: () => createHashHistory(),
593+
} as IRouterOptions,
594+
},
595+
],
596+
main: AppView,
597+
render,
598+
devOptions: {
599+
reduxDevTools: true,
600+
autoComputed: true,
601+
},
602+
});
603+
act(() => {
604+
app.bootstrap(container);
605+
});
606+
expect(fn.mock.calls[0][0]).toBe('/');
607+
expect(computedFn.mock.calls[0][0]).toBe(undefined);
608+
app.instance.router.push('/a');
609+
expect(fn.mock.calls.slice(-1)[0][0]).toBe('/a');
610+
expect(computedFn.mock.calls.slice(-1)[0][0]).toBe('/a');
611+
app.instance.router.replace('/b');
612+
expect(fn.mock.calls.slice(-1)[0][0]).toBe('/b');
613+
expect(computedFn.mock.calls.slice(-1)[0][0]).toBe('/b');
614+
expect(app.instance.dashboardView.count.num).toBe(0);
615+
expect(computedFn.mock.calls.length).toBe(4);
616+
expect(app.instance.currentPath).toBe('/b');
617+
app.instance.dashboardView.count.increase();
618+
expect(app.instance.dashboardView.count.num).toBe(1);
619+
expect(computedFn.mock.calls.length).toBe(4);
620+
app.instance.router.replace('/');
621+
expect(fn.mock.calls.slice(-1)[0][0]).toBe('/');
622+
expect(computedFn.mock.calls.slice(-1)[0][0]).toBe('/');
623+
624+
expect(computedFn.mock.calls.length).toBe(5);
625+
expect(app.instance.currentPath).toBe('/');
626+
expect(app.instance.currentPath).toBe('/');
627+
expect(computedFn.mock.calls.length).toBe(5);
628+
});

0 commit comments

Comments
 (0)