npm i service-x immer inversify reflect-metadata rxjs
{
"compilerOptions": {
"strict": true, // 严格模式,建议开启
"emitDecoratorMetadata": true, // 开启metadata支持
"experimentalDecorators": true, // 开启decorator支持
//...
}
}
// entry-service.ts
import {
Injectable,
Service,
Reducer,
ImmerReducer,
Effect,
EffectAction,
DefineAction
} from "service-x";
import { Entry, fetchEntries } from "./fetch-entries";
import { Observable, merge } from "rxjs";
import {
map,
distinctUntilChanged,
combineLatest,
switchMap,
debounceTime
} from "rxjs/operators";
interface State {
entries: Entry[];
keyword?: string;
}
@Injectable()
export class EntryService extends Service<State> {
defaultState: State = {
entries: []
};
// trigger loadEntries reload
@DefineAction()
reload$!: Observable<void>;
@ImmerReducer()
setKeyword(state: State, keyword: string) {
state.keyword = keyword;
}
@ImmerReducer()
setEntries(state: State, entries: Entry[]) {
state.entries = entries;
}
@Reducer()
reset(): State {
return this.defaultState;
}
@Effect()
loadEntries(
trigger$: Observable<void>,
state$: Observable<State>
): Observable<EffectAction> {
const keyword$ = state$.pipe(
map(state => state.keyword),
distinctUntilChanged(),
// debounce the input
debounceTime(500)
);
return merge(trigger$, this.reload$).pipe(
combineLatest(keyword$, (_, keyword) => keyword),
switchMap(keyword => fetchEntries(keyword)),
map(entries => this.actions().setEntries(entries))
);
}
}
import React from "react";
import ReactDOM from "react-dom";
import { useService } from "service-x";
import { EntryService } from "./entry-service";
import "./index.less";
const App = () => {
const [state, actions] = useService(EntryService, { resetOnUnmount: true });
React.useEffect(() => {
// load the entries on component mounted
actions.loadEntries();
}, [actions]);
return (
<>
<input
className="search"
value={state.keyword}
onChange={e => actions.setKeyword(e.target.value)}
placeholder="Enter keywords to search"
/>
{state.entries.length ? (
<ul className="entries">
{state.entries.map(entry => (
<li className="entry" key={entry.name}>
<span>{entry.name}</span>
<small>{entry.age}</small>
</li>
))}
</ul>
) : (
<p className="empty">Nothing found</p>
)}
<button onClick={() => actions.reload$()}>刷新</button>
</>
);
};
ReactDOM.render(<App />, document.getElementById("app"));