쉽고 낙관적인 업데이트, 보다 간결한 조회
35395 단어 sveltesveltequery
긍정적인 업데이트
전통적으로 웹 응용 프로그램은 서버에 요청을 보내고 결과를 기다린 다음에 UI를 적절하게 업데이트함으로써 돌연변이를 실현한다.
이것은 좋지만 클라이언트 렌더링의 흥행은 좋은 기교를 허용하여 우리의 응용 프로그램이 더욱 호응성을 가지도록 한다.낙관적인 업데이트 기술은 성공을 예측하고 예상 상태를 반영하기 위해 인터페이스를 즉시 업데이트한다. 심지어 서버 응답이 확인되기 전에.
이것은 사용자 인터페이스를 간결하게 하지만, 오류를 처리할 때 각별히 조심해야 한다.전통적인 서버 응답을 기다리는 방법에서, 우리는 단지 오류를 보였을 뿐, 다른 할 일은 없었다.여기서, 우리는 오류를 표시할 뿐만 아니라, 다시 정확하게 상태를 반영하기 위해 낙관적인 업데이트를 취소해야 한다.
기본 실현
svelte query 문서는 낙관적인 업데이트를 어떻게 실현하는지 설명합니다.이 코드는 the example에서 얻은 것이다.
import { useQueryClient, useMutation } from '@sveltestack/svelte-query';
const queryClient = useQueryClient();
useMutation(updateTodoFn, {
// When mutate is called:
onMutate: async (newTodo) => {
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
await queryClient.cancelQueries('todos');
// Snapshot the previous value
const previousTodos = queryClient.getQueryData('todos');
// Optimistically update to the new value
queryClient.setQueryData('todos', (old) => [...old, newTodo]);
// Return a context object with the snapshotted value
return { previousTodos };
},
// If the mutation fails, use the context returned from onMutate to roll back
onError: (err, newTodo, context) => {
queryClient.setQueryData('todos', context.previousTodos);
},
// Always refetch after error or success:
onSettled: () => {
queryClient.invalidateQueries('todos');
},
});
이것은 매우 효과가 있지만, 모든 돌연변이에 쓰기에는 좀 불편할 수 있다.우리는 여러 개의 돌연변이에 대해 낙관적인 업데이트를 하기 쉽도록 그것을 하나의 함수로 추상화할 수 있다.function mutationOptions({ key }) {
let queryClient = useQueryClient();
return {
onMutate: async (newData) => {
await queryClient.cancelQueries(key);
let existingData = client.getQueryData(key);
client.setQueryData((existing) => ({
...existing,
[newData.id]: {
...newData,
isUpdating: true,
}
});
return { previousData };
},
onSettled: (data, error, variables, context) => {
if(error) {
queryClient.setQueryData(key, context.previousData);
// In real code, also notify the user somehow.
} else {
// It succeeded, so just unset isUpdating.
client.setQueryData((existing) => ({
...existing,
[data.id]: data
});
}
}
};
}
여기에서, 우리는 또한 함수를 업데이트하여, 한 항목에 isUpdating
로고를 설정하여, 사용자 인터페이스가 업데이트가 끝날 때까지 그것을 전문적으로 표시할 수 있도록 했다.여러 질의 업데이트
때때로, 한 단락의 데이터가 여러 개의 조회에 존재할 수 있다.프로젝트 집합에 대한 조회와 모든 항목에 대한 조회가 있을 때, svelte 조회 문서는 example of this를 제공합니다.이런 상황에서 낙관적인 업데이트는 두 위치에서 프로젝트를 업데이트해야 한다.
이렇게 처리하는 장점은 우리가 두 지역의 실시간 데이터 업데이트를 자동으로 처리할 수 있기 때문에 데이터가 동기화되지 않을 위험이 없다는 것이다.
모든 변이가 여러 개의 조회를 업데이트해야 하는 것은 아니기 때문에, 우리는 업데이트 함수 목록을 가져오고, 흔히 볼 수 있는 함수를 처리하기 위해
mutationOptions
변경할 수 있습니다.다음은 이 개념을 보여주는 간단한 REPL 예입니다.
View this post on the website 대화식 예제를 제공합니다.
다음은 코드의 상세한 연습이다.
우선, 우리는 필요한 모든 물건을 수입하기만 하면 된다.이 파일을 추출할 항목을 가져오려면
ky-universal
패키지를 사용하십시오. 그래서 오류 형식을 가져왔습니다.import {
QueryClient,
QueryKey,
UseMutationOptions,
useQueryClient,
} from '@sveltestack/svelte-query';
import { HTTPError } from 'ky-universal';
우리는 이 시스템이 사용하는 모든 대상에 id
키가 있고 모든 집합이 대상이며 그 중에서 키는 대상id
이라고 가정한다.다른 시스템은 이를 위해 수조를 사용할 수 있기 때문에 이 경우 코드를 약간 변경해야 한다.
export interface HasId {
id: string | number;
}
우리의 추상은 낙관적인 업데이트 함수를 하나 이상 사용할 수 있습니다.하나하나 DataRestorer
를 되돌려줍니다. 이것은 오류가 발생했을 때 원시 데이터를 복원하는 함수입니다.export type DataRestorer = () => void;
이 함수는 단일 대상을 업데이트하는 데 사용됩니다. 예를 들어 QueryKey
는 ['todos', 5]
일 수 있습니다.조회한 전체 데이터는 단지 대상일 뿐이기 때문에, 이곳에는 해야 할 일이 그리 많지 않다.export async function optimisticUpdateSingleton<T extends HasId>(
client: QueryClient,
key: QueryKey,
data: T,
isUpdating: boolean
): Promise<DataRestorer> {
await client.cancelQueries(key);
let original = client.getQueryData(key);
client.setQueryData(key, { ...data, isUpdating });
return () => client.setQueryData(key, original);
}
이것은 우리가 이전에 본 예이다. 우리는 하나의 대상 집합 중의 단일 대상을 업데이트하고 있다.export async function optimisticUpdateCollectionMember<T extends HasId>(
client: QueryClient,
key: QueryKey,
data: T,
isUpdating: boolean
): Promise<DataRestorer> {
await client.cancelQueries(key);
let overall = client.getQueryData(key) || {};
let originalItem = overall[data.id];
client.setQueryData(key, {
...overall,
[data.id]: {
...data,
isUpdating,
},
});
여기의restorer 함수는 원본 항목을 데이터 수집에 되돌려 주거나, 이 항목이 돌변하기 전에 존재하지 않으면 키를 완전히 삭제합니다. return () => client.setQueryData(key, (overall) => {
if(originalItem) {
return {
...overall,
[data.id]: originalItem,
};
} else {
// The item was absent in the original data, so just delete it.
let { [data.id]: _, ...rest } = overall;
return rest;
}
});
}
마지막으로 돌연변이가 없는 함수에 사용할 수 있습니다.이 낙관적으로 집합에서 대상을 삭제하고 복구 기능을 되돌려줍니다.export async function optimisticDeleteCollectionMember<T extends HasId>(
client: QueryClient,
key: QueryKey,
id: string,
_isDeleting: boolean
): Promise<DataRestorer> {
await client.cancelQueries(key);
let overall = client.getQueryData(key);
let { [id]: originalItem, ...rest } = overall ?? {};
client.setQueryData(key, rest);
// The restore function puts the item back.
return () => client.setQueryData((overall) => ({
...overall,
[id]: originalItem,
}));
}
이러한 각 기능은 새로운 버전mutationOptions
과 함께 사용할 수 있습니다.여기서, 우리는 그것이 필요로 하는 매개 변수를 묘사한다.svelte-query
4가지 공통 유형을 사용합니다.DATA
는 변이를 호출할 때fetch 함수에서 되돌아오는 데이터입니다.VARIABLES
는 돌연변이에 보내는 데이터다.ERROR
는 함수를 가져오는 오류 형식입니다.여기에서 우리는 단지 그것을 하드코딩으로 HTTPError
했을 뿐이다.CONTEXT
는 onMutate
갈고리에서 되돌아온 데이터로 다른 돌연변이 갈고리에 사용된다.export interface MutationOptions<
DATA extends HasId,
VARIABLES = DATA,
CONTEXT extends { previousData?: DataRestorer[] } = {
previousData?: DataRestorer[];
}
> {
/** Invalidate and refetch these query keys after the mutation is done. */
invalidate?: (item: VARIABLES) => QueryKey[];
/** A function that uses one or more of the optimisitic update functions above. */
optimisticUpdates?: (
client: QueryClient,
item: VARIABLES,
isUpdating: boolean
) => Promise<DataRestorer[]>;
우리는 여전히 돌연변이가 자신의 갈고리를 가지기를 희망하기 때문에 여기에는 onMutate
와 onSettled
옵션이 있다.만약 제공된다면 내부의 연결mutationOptions
도 이것이라고 할 수 있다. onMutate?: UseMutationOptions<
DATA,
HTTPError,
VARIABLES,
Omit<CONTEXT, 'previousData'>
>['onMutate'];
onSettled?: UseMutationOptions<DATA, HTTPError, VARIABLES, CONTEXT>['onSettled'];
}
마지막은 우리의 기능이다.추가 Typescript 구문을 제외하고는 위에서 설명한 것과 크게 다르지 않은 것처럼 보입니다.export function mutationOptions<
DATA extends HasId,
CONTEXT extends { previousData?: DataRestorer[] } = {
previousData?: DataRestorer[];
}
>(
options: MutationOptions<DATA, DATA, CONTEXT>
): Partial<UseMutationOptions<DATA, HTTPError, DATA, CONTEXT>> {
let queryClient = useQueryClient();
return {
async onMutate(data: DATA) {
let previousData: DataRestorer[] | undefined;
낙관적인 업데이트를 수행합니다.업데이트가 진행 중임을 나타내는 함수에 true
를 전달합니다.위의 함수는 대상의 isUpdating
로고를 설정하는 데 사용됩니다. if (options.optimisticUpdates) {
previousData = await options.optimisticUpdates(queryClient, data, true);
}
옵션(제공된 경우)에서 onMutate
함수를 호출하고 상하문과 previousData
을 되돌려줍니다. let c = options.onMutate ? await options.onMutate(data) : {};
return { ...c || {}, previousData };
},
async onSettled(data, error, variables, context) {
오류가 발생하면 저희 previousData
를 훑어보고 모든 설정을 원래의 값으로 되돌려 주십시오. if(error && context?.previousData) {
// Undo the optimistic update
for (let restorer of context.previousData) {
restorer();
}
유효하면 낙관적인 업데이트 함수를 다시 호출하지만, 이번에는 새로운 데이터를 사용하고 오류 값 isUpdating
을 사용합니다.이 가설은 돌연변이fetch 함수로 업데이트된 데이터의 복사본을 되돌려줍니다.만약 없다면, 당신은 variables
로 대체하거나, 당신에게 적합한 어떤 일을 할 수 있습니다. } else if(options.optimisticUpdates) {
await options.optimisticUpdates(queryClient, data, false);
}
만약 다른 관련 키가 효력을 상실할 것이 있다면, 우리는 여기서 실행할 것입니다.일반적으로 업데이트 중인 컬렉션을 무효화하는 것은 원치 않습니다. 한 번에 여러 항목을 업데이트할 때 이상한 UI 경쟁 조건이 발생할 수 있기 때문입니다. if(options.invalidate) {
for (let key of options.invalidate(variables)) {
queryClient.invalidateQueries(key);
}
}
return options.onSettled?.(data, error, variables, context);
},
};
}
이 모든 것이 있으면 모든 관련 검색을 낙관적으로 업데이트하는 변종을 만드는 것이 비교적 간단하다.export function updateTodoMutation() {
return useMutation(
(todo: Todo) =>
ky.put(`api/todos/${todo.id}`, { json: todo }).json<Todo>(),
mutationOptions({
optimisticUpdates:
(queryClient: QueryClient, todo: Todo, isUpdating: boolean) =>
Promise.all([
optimisticUpdateCollectionMember(queryClient, 'todos',
todo, isUpdating),
optimisticUpdateSingleton(queryClient, ['todos', todo.id],
todo, isUpdating),
]),
})
);
}
여기에 optimisticUpdates
함수는 optimisticUpdateSingleton
와 optimisticUpdateCollectionMember
만 호출하고 필요한 매개 변수를 가지고 있습니다.일부 샘플도 있지만, 이렇게 하면 특수한 상황에 쉽게 사용자 정의 행동을 추가할 수 있다.항목을 삭제하는 것은 비슷한 효과가 있습니다.우리는 거의 같은 방식으로 돌연변이를 했지만
optimisticDeleteCollectionMember
로 대체했다.삭제 기능의 다른 버전은 이 항목에
isDeleting
로고를 추가한 다음 서버에서 삭제를 확인한 후에만 삭제할 수 있습니다.프로그램이 빠르게 응답하는 것처럼 보이지만 삭제에 실패할 때 과도한 움직임을 방지할 수 있습니다.두 가지 방법은 모두 효과적이다.이것은 주로 서버가 특정 응용 프로그램의 삭제를 거부할 가능성에 달려 있다.
따라서, 우리는 낙관적인 업데이트를 검색에 구축할 수 있는 재사용 가능한 시스템이 생겼다.이런 시스템이 있으면 모든 돌연변이에 알림이나 오류 보고 같은 추가 기능을 쉽게 추가할 수 있다.
Reference
이 문제에 관하여(쉽고 낙관적인 업데이트, 보다 간결한 조회), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/dimfeld/easy-optimistic-updates-with-svelte-query-m99텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)