Skip to content

Instantly share code, notes, and snippets.

@berlysia
Last active March 29, 2020 19:32
Show Gist options
  • Save berlysia/f25b87f5a03bdc449d9d4aa417999ae3 to your computer and use it in GitHub Desktop.
Save berlysia/f25b87f5a03bdc449d9d4aa417999ae3 to your computer and use it in GitHub Desktop.
Resourceとキャッシュを一緒に考えたい
import React, { Suspense, useState, useCallback, ChangeEvent } from "react";
import ReactDOM from "react-dom";
import { createService } from "./Service";
interface User {
id: number;
name: string;
}
let count = 0;
function fetchUser(id: User["id"]): Promise<User> {
return new Promise((resolve) =>
setTimeout(() => resolve({ id, name: `user ${id} ${++count}` }), 3000)
);
}
const UserService = createService(fetchUser);
const UserComponent: React.FC<{ id: number }> = ({ id }) => {
const user = UserService.useResource(id).read();
return (
<pre>
<code>{JSON.stringify(user, null, 2)}</code>
</pre>
);
};
const Wrapper: React.FC<{ defaultId: number }> = ({ defaultId }) => {
const [id, setId] = useState(defaultId);
const [tmpId, setTmpId] = useState(id);
const handleChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) =>
setTmpId(event.target.valueAsNumber),
[setTmpId]
);
const applyChange = useCallback(() => setId(tmpId), [setId, tmpId]);
const buster = UserService.useCacheBuster(id);
return (
<div>
<input type="number" onChange={handleChange} value={tmpId}></input>
<button type="button" onClick={applyChange}>
apply
</button>
<button type="button" onClick={buster}>
cache busting for {id}
</button>
<Suspense fallback={<div>loading...</div>}>
<UserComponent id={id} />
</Suspense>
</div>
);
};
const Root = () => {
return (
<UserService.VersionProvider>
<div>
<Wrapper defaultId={0} />
<Wrapper defaultId={100} />
</div>
</UserService.VersionProvider>
);
};
const rootEl = document.createElement("div");
rootEl.id = "root";
document.body.appendChild(rootEl);
// @ts-ignore
ReactDOM.createRoot(document.getElementById("root")!).render(<Root />);
// https://gist.github.com/fsubal/b1ab80acb82763c28a3d92ed8f2010af
type Status = "pending" | "success" | "error";
export default class Resource<T> {
static from<T>(promise: Promise<T>): Resource<T> {
return new this(promise);
}
private status: Status;
private result: T | undefined;
private suspender: Promise<void>;
private constructor(promise: Promise<T>) {
this.status = "pending";
this.suspender = promise.then(
(r) => {
this.status = "success";
this.result = r;
},
(e) => {
this.status = "error";
this.result = e;
}
);
}
read(): T {
switch (this.status) {
case "pending": {
throw this.suspender;
}
case "error": {
throw this.result;
}
case "success": {
return this.result!;
}
default: {
throw new RangeError(
`invariant status: ${JSON.stringify(this.status)}`
);
}
}
}
}
import React, {
createContext,
useContext,
useReducer,
useCallback,
createElement,
} from "react";
import Resource from "./Resource";
interface Service<T, K> {
VersionProvider: React.FC;
useCacheBuster(id: K): () => void;
useResource(id: K): Resource<T>;
}
export function createService<T, K>(
fetcher: (key: K) => Promise<T>
): Service<T, K> {
const VersionContext = createContext({
version: 0,
updateVersion: () => {},
});
const resourceMap = new Map<K, Resource<T>>();
function clearCache(key: K): void {
resourceMap.delete(key);
}
return {
VersionProvider: ((props) => {
const [version, updateVersion] = useReducer((v) => v + 1, 0);
const value = { version, updateVersion };
return createElement(VersionContext.Provider, {
value,
...props,
});
}) as React.FC,
useCacheBuster(key: K): () => void {
const { updateVersion } = useContext(VersionContext);
return useCallback((): void => {
clearCache(key);
updateVersion();
}, [key, updateVersion]);
},
useResource(key: K): Resource<T> {
// re-render on version update
const _ignore = useContext(VersionContext);
if (resourceMap.has(key)) {
return resourceMap.get(key)!;
}
const resource = Resource.from(fetcher(key));
resourceMap.set(key, resource);
return resource;
},
};
}
@berlysia
Copy link
Author

真面目に条件付きリクエストする方が得なのではないか

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment