算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
urql【设计与创意】urql is a small, extensible GraphQL client for React, Preact, Vue, Svelte, Solid and plain JavaScript that sends queries, mutations and subscriptions and caches the results. Use when a user asks to add a GraphQL client to a frontend, fetch data with useQuery or useMutation, set up normalized caching with Graphcache, add auth, retry or subscription ex…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
urql is a GraphQL client built from a small core (@urql/core) and a pipeline of "exchanges" — middleware that each operation passes through, such as caching, auth, retries and the HTTP fetch itself. The default document cache stores whole query results and is enough for content-driven apps; @urql/exchange-graphcache adds a normalized cache when several queries must stay in sync after a mutation. The urql package holds the React bindings; @urql/preact, @urql/vue, @urql/svelte and @urql/solid wrap the same core.
npm install urql graphql # React bindings + core (graphql is an optional peer dependency)
npm install @urql/exchange-graphcache # Optional normalized cache
npm install @urql/exchange-auth @urql/exchange-retry graphql-ws # Optional: auth, retries, subscriptions
import { Client, Provider, cacheExchange, fetchExchange, gql, useQuery, useMutation } from "urql";
const client = new Client({
url: "https://api.northwind.dev/graphql",
exchanges: [cacheExchange, fetchExchange], // required; order matters
fetchOptions: () => ({
headers: { Authorization: `Bearer ${getToken()}` },
}),
});
function App() {
return <Provider value={client}><Dashboard /></Provider>;
}
type Post = { id: string; title: string; createdAt: string; author: { id: string; name: string } };
// Type arguments give typed `data` and `variables` in the hooks
const POSTS_QUERY = gql<{ posts: Post[] }, { limit: number }>`
query Posts($limit: Int!) {
posts(limit: $limit) { id title author { id name } createdAt }
}
`;
function PostList() {
const [result, reexecute] = useQuery({
query: POSTS_QUERY,
variables: { limit: 10 },
});
const { data, fetching, error } = result;
if (fetching) return <Spinner />;
if (error) return <ErrorBanner message={error.message} />;
return (
<div>
{data?.posts.map(p => <PostCard key={p.id} post={p} />)}
<button onClick={() => reexecute({ requestPolicy: "network-only" })}>Refresh</button>
</div>
);
}
Since @urql/core 6 (urql 5), queries are sent as HTTP GET when the query string plus variables is under 2048 characters. If the server only accepts POST, add preferGetMethod: false to the Client options. Mutations always use POST.
const CREATE_POST = gql`
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) { id title createdAt author { id name } }
}
`;
function CreatePostForm() {
const [result, createPost] = useMutation(CREATE_POST);
const handleSubmit = (input: { title: string }) => {
// The promise never rejects — check result.error instead of try/catch
createPost({ input }).then(result => {
if (result.error) console.error(result.error);
});
};
return <PostForm onSubmit={handleSubmit} loading={result.fetching} />;
}
import { cacheExchange } from "@urql/exchange-graphcache";
// Replaces the default cacheExchange in the exchanges array
const cache = cacheExchange({
// Types are keyed by `id` or `_id` automatically; configure others here
keys: { PostStats: () => null }, // null = embedded, not a separate entity
resolvers: {
Query: {
// Serve Query.post(id) from a Post already cached by the list query
post: (_, args) => ({ __typename: "Post", id: args.id }),
},
},
updates: {
Mutation: {
// New entities are not added to lists automatically
createPost(result, _args, cache) {
cache.updateQuery({ query: POSTS_QUERY, variables: { limit: 10 } }, (data) => {
if (data) data.posts.unshift(result.createPost as Post);
return data;
});
},
deletePost(_result, args, cache) {
cache.invalidate({ __typename: "Post", id: args.id as string });
},
},
},
});
import { Client, cacheExchange, fetchExchange, subscriptionExchange } from "urql";
import { authExchange } from "@urql/exchange-auth";
import { retryExchange } from "@urql/exchange-retry";
import { createClient as createWSClient } from "graphql-ws";
const wsClient = createWSClient({ url: "wss://api.northwind.dev/graphql" });
const client = new Client({
url: "https://api.northwind.dev/graphql",
exchanges: [
cacheExchange, // synchronous exchanges first
authExchange(async (utils) => {
let token = localStorage.getItem("token");
return {
addAuthToOperation: (operation) =>
token ? utils.appendHeaders(operation, { Authorization: `Bearer ${token}` }) : operation,
didAuthError: (error) => error.graphQLErrors.some(e => e.extensions?.code === "UNAUTHENTICATED"),
refreshAuth: async () => {
token = await refreshSession(); // runs once; failed operations are then retried
},
};
}),
retryExchange({ maxNumberAttempts: 2, retryIf: (error) => !!error.networkError }),
fetchExchange,
subscriptionExchange({
forwardSubscription(request) {
const input = { ...request, query: request.query || "" };
return { subscribe: (sink) => ({ unsubscribe: wsClient.subscribe(input, sink) }) };
},
}),
],
});
In components, useSubscription({ query }, (previous = [], event) => [event.newMessages, ...previous]) accumulates events with a reducer.
User request: "Our React dashboard needs to read posts from our GraphQL API. Set up a client and show the ten latest."
The agent installs urql graphql, creates the Client and Provider and writes PostList as above. Checking the network tab (or server log) shows one request and none on re-render:
GET /graphql?query=query+Posts(...)&operationName=Posts&variables={"limit":10}
The second render of PostList with the same variables is answered from the document cache (cache-first). After CreatePost runs, the document cache drops every cached query containing a Post and PostList refetches on its own.
User request: "After creating a post the list reloads from the server and flickers. Make the new post appear instantly."
The agent swaps the default cacheExchange for the Graphcache configuration above (exchanges: [cache, fetchExchange]) and adds the createPost updater. The server now sees only the mutation:
GET Posts → 2 posts
POST CreatePost → { id: "p3", title: "Postmortem: checkout latency" }
(no further request) → list shows 3 posts, new one first
Query.post(id: "p1") is also answered without a request, because the resolver points it at the Post already normalized from the list.
__typename after mutations; sufficient for most apps[] carries no __typename. Pass context: useMemo(() => ({ additionalTypenames: ["Post"] }), []) to useQuery, or use Graphcacheid/_id or a keys entry, otherwise Graphcache warns and embeds it in its parentfetchExchange after them; the retry exchange only works between the cache and the fetch exchangeGET; set preferGetMethod: false for servers, CDNs or CSRF middleware that expect POSTcache-first (default), network-only for refresh, cache-and-network for stale-while-revalidate (watch result.stale), cache-only to never hit the networkcontext object reference stable with useMemo, and use pause: true to hold a query back until its variables existfetchOptions as a function or the auth exchange); never hardcode them in the client configssrExchange for server-side rendering and rehydration, or @urql/next for the Next.js App RoutersubscriptionExchange with a graphql-ws client; for servers that stream subscriptions over HTTP, set fetchSubscriptions: true instead