{"id":285,"date":"2026-01-25T01:42:08","date_gmt":"2026-01-24T17:42:08","guid":{"rendered":"https:\/\/skwp.zeabur.app\/2026\/01\/25\/%e3%80%90tanstack-query-zustand-%e6%95%99%e5%ad%b8%e3%80%9103-tanstack-query-%e9%80%b2%e9%9a%8e%ef%bc%9ausemutation-%e8%88%87%e6%a8%82%e8%a7%80%e6%9b%b4%e6%96%b0\/"},"modified":"2026-01-25T01:42:08","modified_gmt":"2026-01-24T17:42:08","slug":"%e3%80%90tanstack-query-zustand-%e6%95%99%e5%ad%b8%e3%80%9103-tanstack-query-%e9%80%b2%e9%9a%8e%ef%bc%9ausemutation-%e8%88%87%e6%a8%82%e8%a7%80%e6%9b%b4%e6%96%b0","status":"publish","type":"post","link":"https:\/\/blog.itsmygo.uk\/wordpress\/2026\/01\/25\/%e3%80%90tanstack-query-zustand-%e6%95%99%e5%ad%b8%e3%80%9103-tanstack-query-%e9%80%b2%e9%9a%8e%ef%bc%9ausemutation-%e8%88%87%e6%a8%82%e8%a7%80%e6%9b%b4%e6%96%b0\/","title":{"rendered":"\u3010TanStack Query + Zustand \u6559\u5b78\u3011#03 TanStack Query \u9032\u968e\uff1auseMutation \u8207\u6a02\u89c0\u66f4\u65b0"},"content":{"rendered":"\n<div id=\"quiz-basic\">\n<style>\n#quiz-basic .question {\n        background: white;\n        border-radius: 8px;\n        padding: 20px;\n        margin: 20px 0;\n        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n      }#quiz-basic .question h3 {\n        margin-top: 0;\n        color: #333;\n      }#quiz-basic .options {\n        list-style: none;\n        padding: 0;\n      }#quiz-basic .options li {\n        padding: 12px 15px;\n        margin: 8px 0;\n        border: 2px solid #ddd;\n        border-radius: 6px;\n        cursor: pointer;\n        transition: all 0.2s;\n      }#quiz-basic .options li:hover:not(.disabled) {\n        border-color: #4caf50;\n        background: #f0fff0;\n      }#quiz-basic .options li.correct {\n        border-color: #4caf50;\n        background: #e8f5e9;\n      }#quiz-basic .options li.wrong {\n        border-color: #f44336;\n        background: #ffebee;\n      }#quiz-basic .options li.disabled {\n        cursor: default;\n        opacity: 0.7;\n      }#quiz-basic .options li.show-answer {\n        border-color: #4caf50;\n        background: #e8f5e9;\n      }#quiz-basic .feedback {\n        margin-top: 15px;\n        padding: 12px;\n        border-radius: 6px;\n        display: none;\n      }#quiz-basic .feedback.correct {\n        background: #e8f5e9;\n        color: #2e7d32;\n        display: block;\n      }#quiz-basic .feedback.wrong {\n        background: #ffebee;\n        color: #c62828;\n        display: block;\n      }#quiz-basic .score {\n        text-align: center;\n        font-size: 1.5em;\n        padding: 20px;\n        background: white;\n        border-radius: 8px;\n        margin-top: 20px;\n      }#quiz-basic .code-block {\n        background: #1e1e1e;\n        color: #d4d4d4;\n        padding: 14px;\n        border-radius: 6px;\n        font-family: \"Monaco\", \"Consolas\", \"Courier New\", monospace;\n        font-size: 0.9em;\n        overflow-x: auto;\n        margin: 12px 0;\n        white-space: pre-wrap;\n      }\n<\/style>\n<h1>\u6e2c\u9a57\uff1aTanStack Query \u9032\u968e &#8211; useMutation \u8207\u6a02\u89c0\u66f4\u65b0<\/h1>\n    <p>\u5171 5 \u984c\uff0c\u9ede\u9078\u7b54\u6848\u5f8c\u6703\u7acb\u5373\u986f\u793a\u7d50\u679c<\/p>\n\n    <div class=\"question\" id=\"q1\">\n      <h3>1. \u5728 TanStack Query \u4e2d\uff0cQuery \u548c Mutation \u5206\u5225\u5c0d\u61c9\u4ec0\u9ebc\u985e\u578b\u7684\u64cd\u4f5c\uff1f<\/h3>\n      <ul class=\"options\">\n        <li onclick=\"checkAnswer(1, 'A', 'B')\">A. Query \u7528\u65bc\u8b8a\u66f4\u8cc7\u6599\uff0cMutation \u7528\u65bc\u8b80\u53d6\u8cc7\u6599<\/li>\n        <li onclick=\"checkAnswer(1, 'B', 'B')\">B. Query \u7528\u65bc\u8b80\u53d6\u8cc7\u6599\uff08GET\uff09\uff0cMutation \u7528\u65bc\u8b8a\u66f4\u8cc7\u6599\uff08POST\/PUT\/DELETE\uff09<\/li>\n        <li onclick=\"checkAnswer(1, 'C', 'B')\">C. Query \u548c Mutation \u90fd\u53ef\u4ee5\u7528\u65bc\u8b80\u53d6\u548c\u8b8a\u66f4\u8cc7\u6599<\/li>\n        <li onclick=\"checkAnswer(1, 'D', 'B')\">D. Query \u7528\u65bc\u524d\u7aef\u5feb\u53d6\uff0cMutation \u7528\u65bc\u5f8c\u7aef\u5feb\u53d6<\/li>\n      <\/ul>\n      <div class=\"feedback\" id=\"fb1\"><\/div>\n    <\/div>\n\n    <div class=\"question\" id=\"q2\">\n      <h3>2. <code>useMutation<\/code> \u7684\u56de\u8abf\u51fd\u6578\u57f7\u884c\u9806\u5e8f\u662f\u4ec0\u9ebc\uff1f<\/h3>\n      <ul class=\"options\">\n        <li onclick=\"checkAnswer(2, 'A', 'C')\">A. mutationFn \u2192 onMutate \u2192 onSuccess\/onError \u2192 onSettled<\/li>\n        <li onclick=\"checkAnswer(2, 'B', 'C')\">B. onSuccess \u2192 mutationFn \u2192 onMutate \u2192 onSettled<\/li>\n        <li onclick=\"checkAnswer(2, 'C', 'C')\">C. onMutate \u2192 mutationFn \u2192 onSuccess\/onError \u2192 onSettled<\/li>\n        <li onclick=\"checkAnswer(2, 'D', 'C')\">D. onSettled \u2192 onMutate \u2192 mutationFn \u2192 onSuccess\/onError<\/li>\n      <\/ul>\n      <div class=\"feedback\" id=\"fb2\"><\/div>\n    <\/div>\n\n    <div class=\"question\" id=\"q3\">\n      <h3>3. \u7576 mutation \u64cd\u4f5c\u6210\u529f\u5f8c\uff0c\u8981\u8b93\u756b\u9762\u4e0a\u7684\u8cc7\u6599\u4fdd\u6301\u6700\u65b0\uff0c\u6700\u7c21\u55ae\u7684\u65b9\u5f0f\u662f\u4f7f\u7528\u54ea\u500b\u65b9\u6cd5\uff1f<\/h3>\n      <ul class=\"options\">\n        <li onclick=\"checkAnswer(3, 'A', 'A')\">A. <code>queryClient.invalidateQueries()<\/code><\/li>\n        <li onclick=\"checkAnswer(3, 'B', 'A')\">B. <code>queryClient.refetchQueries()<\/code><\/li>\n        <li onclick=\"checkAnswer(3, 'C', 'A')\">C. <code>queryClient.setQueryData()<\/code><\/li>\n        <li onclick=\"checkAnswer(3, 'D', 'A')\">D. <code>queryClient.removeQueries()<\/code><\/li>\n      <\/ul>\n      <div class=\"feedback\" id=\"fb3\"><\/div>\n    <\/div>\n\n    <div class=\"question\" id=\"q4\">\n      <h3>4. \u6a02\u89c0\u66f4\u65b0\uff08Optimistic Updates\uff09\u7684\u4e3b\u8981\u7279\u9ede\u662f\u4ec0\u9ebc\uff1f<\/h3>\n      <ul class=\"options\">\n        <li onclick=\"checkAnswer(4, 'A', 'B')\">A. \u7b49\u5f85 API \u56de\u61c9\u5f8c\u624d\u66f4\u65b0 UI<\/li>\n        <li onclick=\"checkAnswer(4, 'B', 'B')\">B. \u5148\u66f4\u65b0 UI\uff0c\u518d\u7b49\u5f85 API \u78ba\u8a8d\uff0c\u5982\u679c\u5931\u6557\u5247\u56de\u5fa9\u539f\u72c0\u614b<\/li>\n        <li onclick=\"checkAnswer(4, 'C', 'B')\">C. \u53ea\u5728\u672c\u5730\u66f4\u65b0\u8cc7\u6599\uff0c\u4e0d\u547c\u53eb API<\/li>\n        <li onclick=\"checkAnswer(4, 'D', 'B')\">D. \u540c\u6642\u767c\u9001\u591a\u500b API \u8acb\u6c42\u4ee5\u63d0\u9ad8\u901f\u5ea6<\/li>\n      <\/ul>\n      <div class=\"feedback\" id=\"fb4\"><\/div>\n    <\/div>\n\n    <div class=\"question\" id=\"q5\">\n      <h3>5. \u5728\u5be6\u4f5c\u6a02\u89c0\u66f4\u65b0\u6642\uff0c<code>onMutate<\/code> \u56de\u8abf\u51fd\u6578\u9700\u8981\u56de\u50b3\u4ec0\u9ebc\uff1f<\/h3>\n      <div class=\"code-block\">onMutate: async (todoId) => {\n  await queryClient.cancelQueries({ queryKey: [&#8216;todos&#8217;] });\n  const previousTodos = queryClient.getQueryData([&#8216;todos&#8217;]);\n  queryClient.setQueryData([&#8216;todos&#8217;], (old) =>\n    old.filter(todo => todo.id !== todoId)\n  );\n  return { previousTodos };  \/\/ \u9019\u88e1\u56de\u50b3\u4ec0\u9ebc\uff1f\n}<\/div>\n      <ul class=\"options\">\n        <li onclick=\"checkAnswer(5, 'A', 'D')\">A. \u65b0\u7684 todo \u8cc7\u6599<\/li>\n        <li onclick=\"checkAnswer(5, 'B', 'D')\">B. API \u8acb\u6c42\u7684\u7d50\u679c<\/li>\n        <li onclick=\"checkAnswer(5, 'C', 'D')\">C. \u932f\u8aa4\u8a0a\u606f<\/li>\n        <li onclick=\"checkAnswer(5, 'D', 'D')\">D. \u820a\u8cc7\u6599\uff08previousTodos\uff09\uff0c\u4f9b onError \u5931\u6557\u6642\u56de\u5fa9\u4f7f\u7528<\/li>\n      <\/ul>\n      <div class=\"feedback\" id=\"fb5\"><\/div>\n    <\/div>\n\n    <div class=\"score\" id=\"score\" style=\"display:none;\"><\/div>\n<script>\n\n      let answered = 0;\n      let correct = 0;\n      const total = 5;\n      const explanations = {\n        1: \"\u5728 TanStack Query \u4e2d\uff0cQuery \u5c0d\u61c9\u8b80\u53d6\u8cc7\u6599\u7684 GET \u8acb\u6c42\uff0c\u800c Mutation \u5c0d\u61c9\u8b8a\u66f4\u8cc7\u6599\u7684 POST\u3001PUT\u3001DELETE \u8acb\u6c42\u3002\u9019\u662f\u5169\u7a2e\u4e0d\u540c\u7528\u9014\u7684 Hook\u3002\",\n        2: \"\u56de\u8abf\u51fd\u6578\u7684\u57f7\u884c\u9806\u5e8f\u662f\uff1aonMutate\uff08mutation \u57f7\u884c\u524d\uff09\u2192 mutationFn\uff08\u5be6\u969b\u57f7\u884c API \u8acb\u6c42\uff09\u2192 onSuccess \u6216 onError\uff08\u6839\u64da\u7d50\u679c\uff09\u2192 onSettled\uff08\u4e0d\u8ad6\u6210\u529f\u6216\u5931\u6557\u90fd\u6703\u57f7\u884c\uff09\u3002\",\n        3: \"invalidateQueries \u662f\u6700\u7c21\u55ae\u7684\u65b9\u5f0f\uff0c\u5b83\u6703\u5c07\u6307\u5b9a\u7684\u5feb\u53d6\u6a19\u8a18\u70ba\u904e\u671f\uff0c\u8b93 useQuery \u81ea\u52d5\u91cd\u65b0 fetch \u8cc7\u6599\u3002\u96d6\u7136 setQueryData \u4e5f\u80fd\u66f4\u65b0\u8cc7\u6599\uff0c\u4f46\u9700\u8981\u624b\u52d5\u8655\u7406\u8cc7\u6599\u8f49\u63db\u3002\",\n        4: \"\u6a02\u89c0\u66f4\u65b0\u7684\u6838\u5fc3\u6982\u5ff5\u662f\u300c\u5148\u66f4\u65b0 UI\uff0c\u518d\u7b49 API \u78ba\u8a8d\u300d\u3002\u4f7f\u7528\u8005\u9ede\u64ca\u5f8c\u7acb\u5373\u770b\u5230\u8b8a\u5316\uff0c\u4e0d\u9700\u8981\u7b49\u5f85\u3002\u5982\u679c API \u5931\u6557\uff0c\u5247\u628a UI \u56de\u5fa9\u5230\u539f\u672c\u7684\u72c0\u614b\u3002\u9019\u80fd\u63d0\u4f9b\u66f4\u6d41\u66a2\u7684\u4f7f\u7528\u8005\u9ad4\u9a57\u3002\",\n        5: \"onMutate \u9700\u8981\u56de\u50b3\u820a\u8cc7\u6599\uff08\u901a\u5e38\u5305\u88dd\u6210\u7269\u4ef6\u5982 { previousTodos }\uff09\uff0c\u9019\u500b\u56de\u50b3\u503c\u6703\u4f5c\u70ba context \u53c3\u6578\u50b3\u7d66 onError\uff0c\u8b93\u6211\u5011\u5728 API \u5931\u6557\u6642\u80fd\u5920\u5c07\u8cc7\u6599\u56de\u5fa9\u5230\u6a02\u89c0\u66f4\u65b0\u524d\u7684\u72c0\u614b\u3002\"\n      };\n\n      function checkAnswer(qNum, selected, correctAns) {\n        const question = document.getElementById(\"q\" + qNum);\n        if (question.classList.contains(\"answered\")) return;\n\n        question.classList.add(\"answered\");\n        answered++;\n\n        const options = question.querySelectorAll(\".options li\");\n        options.forEach((opt) => {\n          opt.classList.add(\"disabled\");\n          if (opt.textContent.startsWith(correctAns + \".\")) {\n            opt.classList.add(\"show-answer\");\n          }\n        });\n\n        const feedback = document.getElementById(\"fb\" + qNum);\n        if (selected === correctAns) {\n          correct++;\n          feedback.className = \"feedback correct\";\n          feedback.innerHTML = \"&#10003; \u6b63\u78ba\uff01\" + explanations[qNum];\n        } else {\n          feedback.className = \"feedback wrong\";\n          feedback.innerHTML =\n            \"&#10007; \u932f\u8aa4\u3002\u6b63\u78ba\u7b54\u6848\u662f \" + correctAns + \"\u3002\" + explanations[qNum];\n          options.forEach((opt) => {\n            if (opt.textContent.startsWith(selected + \".\")) {\n              opt.classList.add(\"wrong\");\n            }\n          });\n        }\n\n        if (answered === total) {\n          const scoreDiv = document.getElementById(\"score\");\n          scoreDiv.style.display = \"block\";\n          scoreDiv.innerHTML =\n            \"\u6e2c\u9a57\u5b8c\u6210\uff01\u5f97\u5206\uff1a\" +\n            correct +\n            \" \/ \" +\n            total +\n            (correct === total\n              ? \" \u5168\u5c0d\uff01\"\n              : correct >= Math.ceil(total * 0.6)\n                ? \" \u4e0d\u932f\uff01\"\n                : \" \u518d\u8907\u7fd2\u4e00\u4e0b\u5427\");\n        }\n      }\n    \n<\/script>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">\u524d\u8a00<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u5728\u4e0a\u4e00\u7bc7\u6587\u7ae0\u4e2d\uff0c\u6211\u5011\u5b78\u6703\u4e86\u4f7f\u7528 <code>useQuery<\/code> \u4f86\u53d6\u5f97\u8cc7\u6599\u3002\u4f46\u5be6\u969b\u7684\u61c9\u7528\u7a0b\u5f0f\u4e0d\u53ea\u9700\u8981\u300c\u8b80\u53d6\u300d\u8cc7\u6599\uff0c\u9084\u9700\u8981\u300c\u65b0\u589e\u300d\u3001\u300c\u66f4\u65b0\u300d\u3001\u300c\u522a\u9664\u300d\u8cc7\u6599\u3002\u9019\u6642\u5019\u5c31\u9700\u8981\u4f7f\u7528 <code>useMutation<\/code>\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u672c\u7bc7\u5c07\u5e36\u4f60\u5b78\u6703\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u4f7f\u7528 <code>useMutation<\/code> \u8655\u7406\u8cc7\u6599\u8b8a\u66f4\u64cd\u4f5c<\/li>\n<li>\u5728\u64cd\u4f5c\u6210\u529f\u5f8c\u81ea\u52d5\u91cd\u65b0\u53d6\u5f97\u8cc7\u6599<\/li>\n<li>\u5be6\u4f5c\u6a02\u89c0\u66f4\u65b0\uff0c\u8b93\u4f7f\u7528\u8005\u9ad4\u9a57\u66f4\u6d41\u66a2<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">useMutation \u57fa\u672c\u7528\u6cd5<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u4ec0\u9ebc\u662f Mutation\uff1f<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u5728 TanStack Query \u7684\u4e16\u754c\u88e1\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Query<\/strong> = \u8b80\u53d6\u8cc7\u6599\uff08GET \u8acb\u6c42\uff09<\/li>\n<li><strong>Mutation<\/strong> = \u8b8a\u66f4\u8cc7\u6599\uff08POST\u3001PUT\u3001DELETE \u8acb\u6c42\uff09<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\u6700\u5c0f\u7bc4\u4f8b\uff1a\u65b0\u589e Todo<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { useMutation, useQueryClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@tanstack\/react-query'<\/span>;\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">AddTodoForm<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> queryClient = useQueryClient();\n\n  <span class=\"hljs-keyword\">const<\/span> mutation = useMutation({\n    <span class=\"hljs-attr\">mutationFn<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">newTodo: { title: string }<\/span>) =&gt;<\/span> {\n      <span class=\"hljs-keyword\">return<\/span> fetch(<span class=\"hljs-string\">'\/api\/todos'<\/span>, {\n        <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">'POST'<\/span>,\n        <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify(newTodo),\n      }).then(<span class=\"hljs-function\"><span class=\"hljs-params\">res<\/span> =&gt;<\/span> res.json());\n    },\n    <span class=\"hljs-attr\">onSuccess<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n      <span class=\"hljs-comment\">\/\/ \u65b0\u589e\u6210\u529f\u5f8c\uff0c\u91cd\u65b0\u53d6\u5f97 todos \u5217\u8868<\/span>\n      queryClient.invalidateQueries({ <span class=\"hljs-attr\">queryKey<\/span>: &#91;<span class=\"hljs-string\">'todos'<\/span>] });\n    },\n  });\n\n  <span class=\"hljs-keyword\">const<\/span> handleSubmit = <span class=\"hljs-function\">(<span class=\"hljs-params\">e: React.FormEvent<\/span>) =&gt;<\/span> {\n    e.preventDefault();\n    mutation.mutate({ <span class=\"hljs-attr\">title<\/span>: <span class=\"hljs-string\">'\u8cb7\u725b\u5976'<\/span> });\n  };\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{handleSubmit}<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"submit\"<\/span> <span class=\"hljs-attr\">disabled<\/span>=<span class=\"hljs-string\">{mutation.isPending}<\/span>&gt;<\/span>\n        {mutation.isPending ? '\u65b0\u589e\u4e2d...' : '\u65b0\u589e Todo'}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u8b80\u61c2 useMutation \u7684\u56de\u50b3\u503c<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> mutation = useMutation({ <span class=\"hljs-attr\">mutationFn<\/span>: ... });\n\n<span class=\"hljs-comment\">\/\/ \u5e38\u7528\u5c6c\u6027<\/span>\nmutation.mutate(variables);  <span class=\"hljs-comment\">\/\/ \u57f7\u884c mutation<\/span>\nmutation.isPending;          <span class=\"hljs-comment\">\/\/ \u662f\u5426\u6b63\u5728\u57f7\u884c<\/span>\nmutation.isSuccess;          <span class=\"hljs-comment\">\/\/ \u662f\u5426\u6210\u529f<\/span>\nmutation.isError;            <span class=\"hljs-comment\">\/\/ \u662f\u5426\u5931\u6557<\/span>\nmutation.error;              <span class=\"hljs-comment\">\/\/ \u932f\u8aa4\u7269\u4ef6<\/span>\nmutation.data;               <span class=\"hljs-comment\">\/\/ \u6210\u529f\u56de\u50b3\u7684\u8cc7\u6599<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">useMutation \u7684\u56de\u8abf\u51fd\u6578<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>useMutation<\/code> \u63d0\u4f9b\u56db\u500b\u91cd\u8981\u7684\u56de\u8abf\u51fd\u6578\uff1a<\/p>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> mutation = useMutation({\n  <span class=\"hljs-attr\">mutationFn<\/span>: <span class=\"hljs-keyword\">async<\/span> (newTodo) =&gt; {\n    <span class=\"hljs-keyword\">const<\/span> res = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">'\/api\/todos'<\/span>, {\n      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">'POST'<\/span>,\n      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify(newTodo),\n    });\n    <span class=\"hljs-keyword\">return<\/span> res.json();\n  },\n\n  <span class=\"hljs-comment\">\/\/ mutation \u57f7\u884c\u524d<\/span>\n  <span class=\"hljs-attr\">onMutate<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">variables<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'\u6e96\u5099\u65b0\u589e:'<\/span>, variables);\n  },\n\n  <span class=\"hljs-comment\">\/\/ mutation \u6210\u529f\u6642<\/span>\n  <span class=\"hljs-attr\">onSuccess<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">data, variables<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'\u65b0\u589e\u6210\u529f:'<\/span>, data);\n  },\n\n  <span class=\"hljs-comment\">\/\/ mutation \u5931\u6557\u6642<\/span>\n  <span class=\"hljs-attr\">onError<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">error, variables<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'\u65b0\u589e\u5931\u6557:'<\/span>, error);\n  },\n\n  <span class=\"hljs-comment\">\/\/ mutation \u7d50\u675f\u6642\uff08\u4e0d\u8ad6\u6210\u529f\u6216\u5931\u6557\uff09<\/span>\n  <span class=\"hljs-attr\">onSettled<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">data, error, variables<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'\u64cd\u4f5c\u7d50\u675f'<\/span>);\n  },\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u56de\u8abf\u57f7\u884c\u9806\u5e8f<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\"><span><code class=\"hljs\">onMutate \u2192 mutationFn \u2192 onSuccess\/onError \u2192 onSettled\n<\/code><\/span><\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">\u4f7f\u7528 invalidateQueries \u540c\u6b65\u8cc7\u6599<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u7576\u6211\u5011\u65b0\u589e\u3001\u66f4\u65b0\u6216\u522a\u9664\u8cc7\u6599\u5f8c\uff0c\u9700\u8981\u8b93\u756b\u9762\u4e0a\u986f\u793a\u7684\u8cc7\u6599\u4fdd\u6301\u6700\u65b0\u3002\u6700\u7c21\u55ae\u7684\u65b9\u5f0f\u662f\u4f7f\u7528 <code>invalidateQueries<\/code>\uff1a<\/p>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { useQueryClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@tanstack\/react-query'<\/span>;\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">TodoActions<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> queryClient = useQueryClient();\n\n  <span class=\"hljs-keyword\">const<\/span> deleteMutation = useMutation({\n    <span class=\"hljs-attr\">mutationFn<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">todoId: number<\/span>) =&gt;<\/span>\n      fetch(<span class=\"hljs-string\">`\/api\/todos\/<span class=\"hljs-subst\">${todoId}<\/span>`<\/span>, { <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">'DELETE'<\/span> }),\n    <span class=\"hljs-attr\">onSuccess<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n      <span class=\"hljs-comment\">\/\/ \u522a\u9664\u6210\u529f\u5f8c\uff0c\u8b93 todos \u7684\u5feb\u53d6\u5931\u6548<\/span>\n      <span class=\"hljs-comment\">\/\/ TanStack Query \u6703\u81ea\u52d5\u91cd\u65b0 fetch<\/span>\n      queryClient.invalidateQueries({ <span class=\"hljs-attr\">queryKey<\/span>: &#91;<span class=\"hljs-string\">'todos'<\/span>] });\n    },\n  });\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> deleteMutation.mutate(1)}&gt;\n      \u522a\u9664 Todo #1\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">invalidateQueries \u7684\u904b\u4f5c\u539f\u7406<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\"><span><code class=\"hljs\">\u4f7f\u7528\u8005\u9ede\u64ca\u522a\u9664 \u2192 mutation \u57f7\u884c \u2192 API \u56de\u61c9\u6210\u529f\n\u2192 invalidateQueries \u6a19\u8a18\u5feb\u53d6\u70ba\u904e\u671f \u2192 useQuery \u81ea\u52d5\u91cd\u65b0 fetch \u2192 UI \u66f4\u65b0\n<\/code><\/span><\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">\u9019\u7a2e\u65b9\u5f0f\u7c21\u55ae\u53ef\u9760\uff0c\u4f46\u6709\u4e00\u500b\u5c0f\u7f3a\u9ede\uff1a\u4f7f\u7528\u8005\u9700\u8981\u7b49\u5f85 API \u56de\u61c9\u5f8c\u624d\u80fd\u770b\u5230\u8b8a\u5316\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u6a02\u89c0\u66f4\u65b0\uff08Optimistic Updates\uff09<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u4ec0\u9ebc\u662f\u6a02\u89c0\u66f4\u65b0\uff1f<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u6a02\u89c0\u66f4\u65b0\u662f\u4e00\u7a2e\u300c\u5148\u66f4\u65b0 UI\uff0c\u518d\u7b49 API \u56de\u61c9\u300d\u7684\u7b56\u7565\uff1a<\/p>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\"><span><code class=\"hljs\">\u50b3\u7d71\u505a\u6cd5\uff1a\u9ede\u64ca \u2192 \u7b49\u5f85 API \u2192 \u66f4\u65b0 UI\uff08\u4f7f\u7528\u8005\u7b49\u5f85\uff09\n\u6a02\u89c0\u66f4\u65b0\uff1a\u9ede\u64ca \u2192 \u7acb\u5373\u66f4\u65b0 UI \u2192 \u7b49\u5f85 API \u78ba\u8a8d\uff08\u4f7f\u7528\u8005\u4e0d\u7b49\u5f85\uff09\n<\/code><\/span><\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">\u5982\u679c API \u5931\u6557\uff0c\u5c31\u628a UI \u56de\u5fa9\u5230\u539f\u672c\u7684\u72c0\u614b\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u5be6\u4f5c\u6a02\u89c0\u66f4\u65b0\uff1a\u522a\u9664 Todo<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">TodoList<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> queryClient = useQueryClient();\n\n  <span class=\"hljs-keyword\">const<\/span> deleteMutation = useMutation({\n    <span class=\"hljs-attr\">mutationFn<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">todoId: number<\/span>) =&gt;<\/span>\n      fetch(<span class=\"hljs-string\">`\/api\/todos\/<span class=\"hljs-subst\">${todoId}<\/span>`<\/span>, { <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">'DELETE'<\/span> }),\n\n    <span class=\"hljs-comment\">\/\/ \u6b65\u9a5f 1\uff1a\u5728 mutation \u57f7\u884c\u524d\uff0c\u5148\u6a02\u89c0\u66f4\u65b0 UI<\/span>\n    <span class=\"hljs-attr\">onMutate<\/span>: <span class=\"hljs-keyword\">async<\/span> (todoId) =&gt; {\n      <span class=\"hljs-comment\">\/\/ \u53d6\u6d88\u6b63\u5728\u9032\u884c\u7684 queries\uff0c\u907f\u514d\u8986\u84cb\u6211\u5011\u7684\u6a02\u89c0\u66f4\u65b0<\/span>\n      <span class=\"hljs-keyword\">await<\/span> queryClient.cancelQueries({ <span class=\"hljs-attr\">queryKey<\/span>: &#91;<span class=\"hljs-string\">'todos'<\/span>] });\n\n      <span class=\"hljs-comment\">\/\/ \u4fdd\u5b58\u76ee\u524d\u7684\u8cc7\u6599\uff0c\u4ee5\u4fbf\u5931\u6557\u6642\u56de\u5fa9<\/span>\n      <span class=\"hljs-keyword\">const<\/span> previousTodos = queryClient.getQueryData(&#91;<span class=\"hljs-string\">'todos'<\/span>]);\n\n      <span class=\"hljs-comment\">\/\/ \u6a02\u89c0\u66f4\u65b0\uff1a\u7acb\u5373\u5f9e\u5feb\u53d6\u4e2d\u79fb\u9664\u88ab\u522a\u9664\u7684 todo<\/span>\n      queryClient.setQueryData(&#91;<span class=\"hljs-string\">'todos'<\/span>], (old: Todo&#91;]) =&gt;\n        old.filter(<span class=\"hljs-function\"><span class=\"hljs-params\">todo<\/span> =&gt;<\/span> todo.id !== todoId)\n      );\n\n      <span class=\"hljs-comment\">\/\/ \u56de\u50b3\u820a\u8cc7\u6599\uff0c\u4f9b onError \u4f7f\u7528<\/span>\n      <span class=\"hljs-keyword\">return<\/span> { previousTodos };\n    },\n\n    <span class=\"hljs-comment\">\/\/ \u6b65\u9a5f 2\uff1a\u5982\u679c mutation \u5931\u6557\uff0c\u56de\u5fa9\u5230\u539f\u672c\u7684\u72c0\u614b<\/span>\n    <span class=\"hljs-attr\">onError<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">err, todoId, context<\/span>) =&gt;<\/span> {\n      queryClient.setQueryData(&#91;<span class=\"hljs-string\">'todos'<\/span>], context?.previousTodos);\n    },\n\n    <span class=\"hljs-comment\">\/\/ \u6b65\u9a5f 3\uff1a\u4e0d\u8ad6\u6210\u529f\u6216\u5931\u6557\uff0c\u90fd\u91cd\u65b0\u53d6\u5f97\u6700\u65b0\u8cc7\u6599\u78ba\u4fdd\u540c\u6b65<\/span>\n    <span class=\"hljs-attr\">onSettled<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n      queryClient.invalidateQueries({ <span class=\"hljs-attr\">queryKey<\/span>: &#91;<span class=\"hljs-string\">'todos'<\/span>] });\n    },\n  });\n\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u6a02\u89c0\u66f4\u65b0\u6d41\u7a0b\u5716\u89e3<\/h3>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\"><span><code class=\"hljs\">\u4f7f\u7528\u8005\u9ede\u64ca\u522a\u9664\n    \u2502\n    \u25bc\nonMutate \u57f7\u884c\n    \u251c\u2500\u2500 \u53d6\u6d88\u9032\u884c\u4e2d\u7684 queries\n    \u251c\u2500\u2500 \u4fdd\u5b58\u820a\u8cc7\u6599 (previousTodos)\n    \u2514\u2500\u2500 \u7acb\u5373\u66f4\u65b0 UI\uff08\u79fb\u9664\u8a72 todo\uff09\n    \u2502\n    \u25bc\nmutationFn \u57f7\u884c\uff08\u547c\u53eb API\uff09\n    \u2502\n    \u251c\u2500\u2500 \u6210\u529f \u2192 onSuccess \u2192 onSettled \u2192 invalidateQueries\n    \u2502\n    \u2514\u2500\u2500 \u5931\u6557 \u2192 onError\uff08\u56de\u5fa9 previousTodos\uff09\u2192 onSettled\n<\/code><\/span><\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">\u5b8c\u6574\u7bc4\u4f8b\uff1aTodo App CRUD<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u8b93\u6211\u5011\u628a\u5b78\u5230\u7684\u77e5\u8b58\u6574\u5408\u6210\u4e00\u500b\u5b8c\u6574\u7684\u7bc4\u4f8b\uff1a<\/p>\n\n\n<div class=\"cbc-code-wrapper\"><div class=\"cbc-code-bar\"><button class=\"cbc-copy-button\">Copy<\/button><\/div><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { useQuery, useMutation, useQueryClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@tanstack\/react-query'<\/span>;\n\ninterface Todo {\n  <span class=\"hljs-attr\">id<\/span>: number;\n  title: string;\n  completed: boolean;\n}\n\n<span class=\"hljs-comment\">\/\/ API \u51fd\u6578<\/span>\n<span class=\"hljs-keyword\">const<\/span> api = {\n  <span class=\"hljs-attr\">getTodos<\/span>: (): <span class=\"hljs-built_in\">Promise<\/span>&lt;Todo&#91;]&gt; =&gt;\n    fetch(<span class=\"hljs-string\">'\/api\/todos'<\/span>).then(<span class=\"hljs-function\"><span class=\"hljs-params\">res<\/span> =&gt;<\/span> res.json()),\n\n  <span class=\"hljs-attr\">addTodo<\/span>: (title: string): <span class=\"hljs-built_in\">Promise<\/span>&lt;Todo&gt; =&gt;\n    fetch(<span class=\"hljs-string\">'\/api\/todos'<\/span>, {\n      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">'POST'<\/span>,\n      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({ title, <span class=\"hljs-attr\">completed<\/span>: <span class=\"hljs-literal\">false<\/span> }),\n    }).then(<span class=\"hljs-function\"><span class=\"hljs-params\">res<\/span> =&gt;<\/span> res.json()),\n\n  <span class=\"hljs-attr\">updateTodo<\/span>: (todo: Todo): <span class=\"hljs-built_in\">Promise<\/span>&lt;Todo&gt; =&gt;\n    fetch(<span class=\"hljs-string\">`\/api\/todos\/<span class=\"hljs-subst\">${todo.id}<\/span>`<\/span>, {\n      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">'PUT'<\/span>,\n      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify(todo),\n    }).then(<span class=\"hljs-function\"><span class=\"hljs-params\">res<\/span> =&gt;<\/span> res.json()),\n\n  <span class=\"hljs-attr\">deleteTodo<\/span>: (id: number): <span class=\"hljs-built_in\">Promise<\/span>&lt;<span class=\"hljs-keyword\">void<\/span>&gt; =&gt;\n    fetch(<span class=\"hljs-string\">`\/api\/todos\/<span class=\"hljs-subst\">${id}<\/span>`<\/span>, { <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">'DELETE'<\/span> }).then(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {}),\n};\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">TodoApp<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> queryClient = useQueryClient();\n\n  <span class=\"hljs-comment\">\/\/ \u8b80\u53d6 todos<\/span>\n  <span class=\"hljs-keyword\">const<\/span> { <span class=\"hljs-attr\">data<\/span>: todos = &#91;], isLoading } = useQuery({\n    <span class=\"hljs-attr\">queryKey<\/span>: &#91;<span class=\"hljs-string\">'todos'<\/span>],\n    <span class=\"hljs-attr\">queryFn<\/span>: api.getTodos,\n  });\n\n  <span class=\"hljs-comment\">\/\/ \u65b0\u589e todo<\/span>\n  <span class=\"hljs-keyword\">const<\/span> addMutation = useMutation({\n    <span class=\"hljs-attr\">mutationFn<\/span>: api.addTodo,\n    <span class=\"hljs-attr\">onSuccess<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n      queryClient.invalidateQueries({ <span class=\"hljs-attr\">queryKey<\/span>: &#91;<span class=\"hljs-string\">'todos'<\/span>] });\n    },\n  });\n\n  <span class=\"hljs-comment\">\/\/ \u66f4\u65b0 todo\uff08\u4f7f\u7528\u6a02\u89c0\u66f4\u65b0\uff09<\/span>\n  <span class=\"hljs-keyword\">const<\/span> updateMutation = useMutation({\n    <span class=\"hljs-attr\">mutationFn<\/span>: api.updateTodo,\n    <span class=\"hljs-attr\">onMutate<\/span>: <span class=\"hljs-keyword\">async<\/span> (updatedTodo) =&gt; {\n      <span class=\"hljs-keyword\">await<\/span> queryClient.cancelQueries({ <span class=\"hljs-attr\">queryKey<\/span>: &#91;<span class=\"hljs-string\">'todos'<\/span>] });\n      <span class=\"hljs-keyword\">const<\/span> previousTodos = queryClient.getQueryData&lt;Todo&#91;]&gt;(&#91;<span class=\"hljs-string\">'todos'<\/span>]);\n\n      queryClient.setQueryData&lt;Todo&#91;]&gt;<span class=\"hljs-function\">(<span class=\"hljs-params\">&#91;<span class=\"hljs-string\">'todos'<\/span>], (old<\/span>) =&gt;<\/span>\n        old?.map(<span class=\"hljs-function\"><span class=\"hljs-params\">todo<\/span> =&gt;<\/span>\n          todo.id === updatedTodo.id ? updatedTodo : todo\n        )\n      );\n\n      <span class=\"hljs-keyword\">return<\/span> { previousTodos };\n    },\n    <span class=\"hljs-attr\">onError<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">err, variables, context<\/span>) =&gt;<\/span> {\n      queryClient.setQueryData(&#91;<span class=\"hljs-string\">'todos'<\/span>], context?.previousTodos);\n    },\n    <span class=\"hljs-attr\">onSettled<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n      queryClient.invalidateQueries({ <span class=\"hljs-attr\">queryKey<\/span>: &#91;<span class=\"hljs-string\">'todos'<\/span>] });\n    },\n  });\n\n  <span class=\"hljs-comment\">\/\/ \u522a\u9664 todo\uff08\u4f7f\u7528\u6a02\u89c0\u66f4\u65b0\uff09<\/span>\n  <span class=\"hljs-keyword\">const<\/span> deleteMutation = useMutation({\n    <span class=\"hljs-attr\">mutationFn<\/span>: api.deleteTodo,\n    <span class=\"hljs-attr\">onMutate<\/span>: <span class=\"hljs-keyword\">async<\/span> (todoId) =&gt; {\n      <span class=\"hljs-keyword\">await<\/span> queryClient.cancelQueries({ <span class=\"hljs-attr\">queryKey<\/span>: &#91;<span class=\"hljs-string\">'todos'<\/span>] });\n      <span class=\"hljs-keyword\">const<\/span> previousTodos = queryClient.getQueryData&lt;Todo&#91;]&gt;(&#91;<span class=\"hljs-string\">'todos'<\/span>]);\n\n      queryClient.setQueryData&lt;Todo&#91;]&gt;<span class=\"hljs-function\">(<span class=\"hljs-params\">&#91;<span class=\"hljs-string\">'todos'<\/span>], (old<\/span>) =&gt;<\/span>\n        old?.filter(<span class=\"hljs-function\"><span class=\"hljs-params\">todo<\/span> =&gt;<\/span> todo.id !== todoId)\n      );\n\n      <span class=\"hljs-keyword\">return<\/span> { previousTodos };\n    },\n    <span class=\"hljs-attr\">onError<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">err, variables, context<\/span>) =&gt;<\/span> {\n      queryClient.setQueryData(&#91;<span class=\"hljs-string\">'todos'<\/span>], context?.previousTodos);\n    },\n    <span class=\"hljs-attr\">onSettled<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n      queryClient.invalidateQueries({ <span class=\"hljs-attr\">queryKey<\/span>: &#91;<span class=\"hljs-string\">'todos'<\/span>] });\n    },\n  });\n\n  <span class=\"hljs-keyword\">if<\/span> (isLoading) <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\u8f09\u5165\u4e2d...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>;\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> addMutation.mutate('\u65b0\u7684\u5f85\u8fa6\u4e8b\u9805')}&gt;\n        \u65b0\u589e Todo\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul<\/span>&gt;<\/span>\n        {todos.map(todo =&gt; (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{todo.id}<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n              <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"checkbox\"<\/span>\n              <span class=\"hljs-attr\">checked<\/span>=<span class=\"hljs-string\">{todo.completed}<\/span>\n              <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span>\n                updateMutation.mutate({\n                  ...todo,\n                  completed: !todo.completed,\n                })\n              }\n            \/&gt;\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span>&gt;<\/span>{todo.title}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> deleteMutation.mutate(todo.id)}&gt;\n              \u522a\u9664\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n        ))}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ul<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">\u4f55\u6642\u8a72\u7528\u6a02\u89c0\u66f4\u65b0\uff1f<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>\u60c5\u5883<\/th>\n<th>\u5efa\u8b70<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>\u64cd\u4f5c\u6210\u529f\u7387\u9ad8\uff08\u5982\u52fe\u9078 checkbox\uff09<\/td>\n<td>\u4f7f\u7528\u6a02\u89c0\u66f4\u65b0<\/td>\n<\/tr>\n<tr>\n<td>\u64cd\u4f5c\u53ef\u80fd\u5931\u6557\uff08\u5982\u9700\u8981\u9a57\u8b49\uff09<\/td>\n<td>\u4e0d\u4f7f\u7528\u6a02\u89c0\u66f4\u65b0<\/td>\n<\/tr>\n<tr>\n<td>\u56de\u5fa9\u64cd\u4f5c\u6210\u672c\u9ad8<\/td>\n<td>\u4e0d\u4f7f\u7528\u6a02\u89c0\u66f4\u65b0<\/td>\n<\/tr>\n<tr>\n<td>\u4f7f\u7528\u8005\u9ad4\u9a57\u512a\u5148<\/td>\n<td>\u4f7f\u7528\u6a02\u89c0\u66f4\u65b0<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">\u672c\u7bc7\u91cd\u9ede\u6574\u7406<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>useMutation<\/strong> \u7528\u65bc\u8655\u7406\u8cc7\u6599\u8b8a\u66f4\u64cd\u4f5c\uff08POST\/PUT\/DELETE\uff09<\/li>\n<li><strong>\u56db\u500b\u56de\u8abf\u51fd\u6578<\/strong>\uff1a<code>onMutate<\/code> \u2192 <code>onSuccess<\/code>\/<code>onError<\/code> \u2192 <code>onSettled<\/code><\/li>\n<li><strong>invalidateQueries<\/strong> \u8b93\u5feb\u53d6\u5931\u6548\uff0c\u89f8\u767c\u91cd\u65b0\u53d6\u5f97\u8cc7\u6599<\/li>\n<li><strong>\u6a02\u89c0\u66f4\u65b0<\/strong>\u7684\u4e09\u6b65\u9a5f\uff1a\n<ul>\n<li><code>onMutate<\/code>\uff1a\u4fdd\u5b58\u820a\u8cc7\u6599\u3001\u7acb\u5373\u66f4\u65b0 UI<\/li>\n<li><code>onError<\/code>\uff1a\u5931\u6557\u6642\u56de\u5fa9\u820a\u8cc7\u6599<\/li>\n<li><code>onSettled<\/code>\uff1a\u91cd\u65b0\u53d6\u5f97\u6700\u65b0\u8cc7\u6599<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">\u4e0b\u4e00\u7bc7\u9810\u544a<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u5728\u4e0b\u4e00\u7bc7\u6587\u7ae0\u4e2d\uff0c\u6211\u5011\u5c07\u5b78\u7fd2\u5982\u4f55\u4f7f\u7528 Zustand \u7ba1\u7406\u672c\u5730\u72c0\u614b\uff0c\u4ee5\u53ca\u5982\u4f55\u5c07 TanStack Query \u8207 Zustand \u6574\u5408\uff0c\u5efa\u7acb\u5b8c\u6574\u7684\u72c0\u614b\u7ba1\u7406\u67b6\u69cb\u3002<\/p>\n\n\n\n<div id=\"quiz-advanced\">\n<style>\n#quiz-advanced .badge {\n        display: inline-block;\n        padding: 4px 10px;\n        border-radius: 12px;\n        font-size: 0.75em;\n        font-weight: bold;\n        margin-left: 8px;\n      }#quiz-advanced .badge.scenario {\n        background: #ebf8ff;\n        color: #2b6cb0;\n      }#quiz-advanced .badge.debug {\n        background: #fef3c7;\n        color: #92400e;\n      }#quiz-advanced .question-adv {\n        background: white;\n        border-radius: 10px;\n        padding: 24px;\n        margin: 20px 0;\n        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n        border-left: 4px solid #ed8936;\n      }#quiz-advanced .question-adv h3 {\n        margin-top: 0;\n        color: #2d3748;\n      }#quiz-advanced .code-block {\n        background: #1e1e1e;\n        color: #d4d4d4;\n        padding: 14px;\n        border-radius: 6px;\n        font-family: \"Menlo\", \"Monaco\", \"Courier New\", monospace;\n        font-size: 0.9em;\n        overflow-x: auto;\n        margin: 12px 0;\n        white-space: pre-wrap;\n      }#quiz-advanced .options-adv {\n        list-style: none;\n        padding: 0;\n      }#quiz-advanced .options-adv li {\n        padding: 14px 16px;\n        margin: 10px 0;\n        border: 2px solid #e2e8f0;\n        border-radius: 8px;\n        cursor: pointer;\n        transition: all 0.2s;\n      }#quiz-advanced .options-adv li:hover:not(.disabled) {\n        border-color: #ed8936;\n        background: #fffaf0;\n      }#quiz-advanced .options-adv li.disabled {\n        cursor: default;\n        opacity: 0.7;\n      }#quiz-advanced .options-adv li.show-answer {\n        border-color: #48bb78;\n        background: #f0fff4;\n      }#quiz-advanced .options-adv li.wrong {\n        border-color: #fc8181;\n        background: #fff5f5;\n      }#quiz-advanced .feedback-adv {\n        margin-top: 16px;\n        padding: 14px;\n        border-radius: 8px;\n        display: none;\n        line-height: 1.6;\n      }#quiz-advanced .feedback-adv.correct {\n        background: #f0fff4;\n        color: #276749;\n        display: block;\n      }#quiz-advanced .feedback-adv.wrong {\n        background: #fff5f5;\n        color: #c53030;\n        display: block;\n      }#quiz-advanced .feedback-adv \n      .score-adv {\n        text-align: center;\n        font-size: 1.4em;\n        padding: 24px;\n        background: white;\n        border-radius: 10px;\n        margin-top: 24px;\n        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n      }#quiz-advanced .intro {\n        background: #ebf8ff;\n        padding: 16px;\n        border-radius: 8px;\n        margin-bottom: 20px;\n        color: #2c5282;\n      }\n<\/style>\n<h1>\u9032\u968e\u6e2c\u9a57\uff1aTanStack Query \u9032\u968e &#8211; useMutation \u8207\u6a02\u89c0\u66f4\u65b0<\/h1>\n    <div class=\"intro\">\n      <strong>\u6e2c\u9a57\u76ee\u6a19\uff1a<\/strong>\u9a57\u8b49\u4f60\u662f\u5426\u80fd\u5728\u5be6\u969b\u60c5\u5883\u4e2d\u61c9\u7528\u6240\u5b78\u3002<br \/>\n      \u5171 5 \u984c\uff0c\u5305\u542b\u60c5\u5883\u984c\u8207\u932f\u8aa4\u8a3a\u65b7\u984c\u3002\n    <\/div>\n\n    <!-- \u60c5\u5883\u984c 1 -->\n    <div class=\"question-adv\" id=\"qAdv1\">\n      <h3>1. \u4f60\u6b63\u5728\u958b\u767c\u4e00\u500b Todo App\uff0c\u9700\u8981\u5728\u4f7f\u7528\u8005\u9ede\u64ca\u300c\u65b0\u589e\u300d\u6309\u9215\u5f8c\u5c07\u65b0\u7684 Todo \u9001\u5230\u5f8c\u7aef\uff0c\u4e26\u5728\u6210\u529f\u5f8c\u66f4\u65b0\u5217\u8868\u3002 <span class=\"badge scenario\">\u60c5\u5883\u984c<\/span><\/h3>\n      <p>\u4f60\u61c9\u8a72\u5982\u4f55\u8a2d\u8a08\u9019\u500b\u529f\u80fd\uff1f<\/p>\n      <ul class=\"options-adv\">\n        <li onclick=\"checkAnswerAdv(1, 'A', 'B')\">A. \u4f7f\u7528 <code>useQuery<\/code> \u642d\u914d POST \u65b9\u6cd5\u767c\u9001\u8acb\u6c42<\/li>\n        <li onclick=\"checkAnswerAdv(1, 'B', 'B')\">B. \u4f7f\u7528 <code>useMutation<\/code> \u767c\u9001\u8acb\u6c42\uff0c\u5728 <code>onSuccess<\/code> \u4e2d\u547c\u53eb <code>invalidateQueries<\/code><\/li>\n        <li onclick=\"checkAnswerAdv(1, 'C', 'B')\">C. \u76f4\u63a5\u4f7f\u7528 <code>fetch<\/code> \u767c\u9001\u8acb\u6c42\uff0c\u7136\u5f8c\u624b\u52d5\u547c\u53eb <code>window.location.reload()<\/code><\/li>\n        <li onclick=\"checkAnswerAdv(1, 'D', 'B')\">D. \u4f7f\u7528 <code>useMutation<\/code> \u4f46\u4e0d\u9700\u8981\u4efb\u4f55\u56de\u8abf\u51fd\u6578<\/li>\n      <\/ul>\n      <div class=\"feedback-adv\" id=\"fbAdv1\"><\/div>\n    <\/div>\n\n    <!-- \u60c5\u5883\u984c 2 -->\n    <div class=\"question-adv\" id=\"qAdv2\">\n      <h3>2. \u4f60\u7684 Todo App \u6709\u4e00\u500b\u300c\u52fe\u9078\u5b8c\u6210\u300d\u7684\u529f\u80fd\uff0c\u4f7f\u7528\u8005\u52fe\u9078\u5f8c\u9700\u8981\u7acb\u5373\u770b\u5230\u8b8a\u5316\uff0c\u4e0d\u60f3\u7b49\u5f85 API \u56de\u61c9\u3002\u5982\u679c API \u5931\u6557\uff0c\u8981\u80fd\u56de\u5fa9\u539f\u72c0\u3002 <span class=\"badge scenario\">\u60c5\u5883\u984c<\/span><\/h3>\n      <p>\u9019\u7a2e\u60c5\u6cc1\u6700\u9069\u5408\u4f7f\u7528\u4ec0\u9ebc\u6280\u8853\uff1f<\/p>\n      <ul class=\"options-adv\">\n        <li onclick=\"checkAnswerAdv(2, 'A', 'C')\">A. \u4f7f\u7528 <code>invalidateQueries<\/code> \u4e26\u8a2d\u5b9a <code>staleTime: 0<\/code><\/li>\n        <li onclick=\"checkAnswerAdv(2, 'B', 'C')\">B. \u5728 <code>onSuccess<\/code> \u4e2d\u7acb\u5373\u66f4\u65b0\u672c\u5730\u72c0\u614b<\/li>\n        <li onclick=\"checkAnswerAdv(2, 'C', 'C')\">C. \u4f7f\u7528\u6a02\u89c0\u66f4\u65b0\uff1a\u5728 <code>onMutate<\/code> \u5148\u66f4\u65b0 UI\uff0c<code>onError<\/code> \u56de\u5fa9\u820a\u8cc7\u6599<\/li>\n        <li onclick=\"checkAnswerAdv(2, 'D', 'C')\">D. \u4f7f\u7528 <code>setTimeout<\/code> \u5ef6\u9072 UI \u66f4\u65b0\u4ee5\u7b49\u5f85 API<\/li>\n      <\/ul>\n      <div class=\"feedback-adv\" id=\"fbAdv2\"><\/div>\n    <\/div>\n\n    <!-- \u60c5\u5883\u984c 3 -->\n    <div class=\"question-adv\" id=\"qAdv3\">\n      <h3>3. \u4f60\u6b63\u5728\u5be6\u4f5c\u6a02\u89c0\u66f4\u65b0\u7684\u522a\u9664\u529f\u80fd\u3002\u5728 <code>onMutate<\/code> \u4e2d\uff0c\u4f60\u9700\u8981\u5148\u505a\u4ec0\u9ebc\u64cd\u4f5c\u4f86\u907f\u514d\u5feb\u53d6\u88ab\u8986\u84cb\uff1f <span class=\"badge scenario\">\u60c5\u5883\u984c<\/span><\/h3>\n      <div class=\"code-block\">onMutate: async (todoId) => {\n  \/\/ \u9019\u88e1\u61c9\u8a72\u5148\u505a\u4ec0\u9ebc\uff1f\n  const previousTodos = queryClient.getQueryData([&#8216;todos&#8217;]);\n  queryClient.setQueryData([&#8216;todos&#8217;], (old) =>\n    old.filter(todo => todo.id !== todoId)\n  );\n  return { previousTodos };\n}<\/div>\n      <ul class=\"options-adv\">\n        <li onclick=\"checkAnswerAdv(3, 'A', 'A')\">A. \u547c\u53eb <code>queryClient.cancelQueries({ queryKey: ['todos'] })<\/code><\/li>\n        <li onclick=\"checkAnswerAdv(3, 'B', 'A')\">B. \u547c\u53eb <code>queryClient.resetQueries({ queryKey: ['todos'] })<\/code><\/li>\n        <li onclick=\"checkAnswerAdv(3, 'C', 'A')\">C. \u547c\u53eb <code>queryClient.removeQueries({ queryKey: ['todos'] })<\/code><\/li>\n        <li onclick=\"checkAnswerAdv(3, 'D', 'A')\">D. \u4e0d\u9700\u8981\u4efb\u4f55\u984d\u5916\u64cd\u4f5c\uff0c\u76f4\u63a5\u66f4\u65b0\u5373\u53ef<\/li>\n      <\/ul>\n      <div class=\"feedback-adv\" id=\"fbAdv3\"><\/div>\n    <\/div>\n\n    <!-- \u932f\u8aa4\u8a3a\u65b7\u984c 1 -->\n    <div class=\"question-adv\" id=\"qAdv4\">\n      <h3>4. \u5c0f\u660e\u5be6\u4f5c\u4e86\u6a02\u89c0\u66f4\u65b0\uff0c\u4f46\u767c\u73fe API \u5931\u6557\u6642\u8cc7\u6599\u6c92\u6709\u56de\u5fa9\u3002\u8acb\u770b\u4ee5\u4e0b\u7a0b\u5f0f\u78bc\uff0c\u554f\u984c\u51fa\u5728\u54ea\u88e1\uff1f <span class=\"badge debug\">\u932f\u8aa4\u8a3a\u65b7<\/span><\/h3>\n      <div class=\"code-block\">const deleteMutation = useMutation({\n  mutationFn: (todoId) => fetch(`\/api\/todos\/${todoId}`, { method: &#8216;DELETE&#8217; }),\n  onMutate: async (todoId) => {\n    await queryClient.cancelQueries({ queryKey: [&#8216;todos&#8217;] });\n    const previousTodos = queryClient.getQueryData([&#8216;todos&#8217;]);\n    queryClient.setQueryData([&#8216;todos&#8217;], (old) =>\n      old.filter(todo => todo.id !== todoId)\n    );\n    \/\/ \u5fd8\u8a18 return { previousTodos }\n  },\n  onError: (err, todoId, context) => {\n    queryClient.setQueryData([&#8216;todos&#8217;], context?.previousTodos);\n  },\n});<\/div>\n      <ul class=\"options-adv\">\n        <li onclick=\"checkAnswerAdv(4, 'A', 'B')\">A. <code>cancelQueries<\/code> \u4f7f\u7528\u932f\u8aa4\uff0c\u61c9\u8a72\u7528 <code>invalidateQueries<\/code><\/li>\n        <li onclick=\"checkAnswerAdv(4, 'B', 'B')\">B. <code>onMutate<\/code> \u6c92\u6709 return <code>{ previousTodos }<\/code>\uff0c\u5c0e\u81f4 <code>onError<\/code> \u7684 context \u662f undefined<\/li>\n        <li onclick=\"checkAnswerAdv(4, 'C', 'B')\">C. <code>onError<\/code> \u7684\u53c3\u6578\u9806\u5e8f\u932f\u8aa4<\/li>\n        <li onclick=\"checkAnswerAdv(4, 'D', 'B')\">D. <code>setQueryData<\/code> \u7684 filter \u908f\u8f2f\u5beb\u53cd\u4e86<\/li>\n      <\/ul>\n      <div class=\"feedback-adv\" id=\"fbAdv4\"><\/div>\n    <\/div>\n\n    <!-- \u932f\u8aa4\u8a3a\u65b7\u984c 2 -->\n    <div class=\"question-adv\" id=\"qAdv5\">\n      <h3>5. \u5c0f\u83ef\u7684\u65b0\u589e Todo \u529f\u80fd\u53ef\u4ee5\u6210\u529f\u547c\u53eb API\uff0c\u4f46\u756b\u9762\u4e0a\u7684\u5217\u8868\u6c92\u6709\u66f4\u65b0\u3002\u8acb\u770b\u4ee5\u4e0b\u7a0b\u5f0f\u78bc\uff0c\u554f\u984c\u6700\u53ef\u80fd\u662f\u4ec0\u9ebc\uff1f <span class=\"badge debug\">\u932f\u8aa4\u8a3a\u65b7<\/span><\/h3>\n      <div class=\"code-block\">const addMutation = useMutation({\n  mutationFn: (newTodo) => {\n    return fetch(&#8216;\/api\/todos&#8217;, {\n      method: &#8216;POST&#8217;,\n      body: JSON.stringify(newTodo),\n    }).then(res => res.json());\n  },\n  \/\/ \u6c92\u6709\u4efb\u4f55\u56de\u8abf\u51fd\u6578\n});\n\n\/\/ \u4f7f\u7528\naddMutation.mutate({ title: &#8216;\u8cb7\u725b\u5976&#8217; });<\/div>\n      <ul class=\"options-adv\">\n        <li onclick=\"checkAnswerAdv(5, 'A', 'C')\">A. <code>mutationFn<\/code> \u7684 fetch \u8a2d\u5b9a\u6709\u8aa4<\/li>\n        <li onclick=\"checkAnswerAdv(5, 'B', 'C')\">B. <code>mutate<\/code> \u61c9\u8a72\u4f7f\u7528 <code>mutateAsync<\/code><\/li>\n        <li onclick=\"checkAnswerAdv(5, 'C', 'C')\">C. \u7f3a\u5c11 <code>onSuccess<\/code> \u56de\u8abf\u4f86\u547c\u53eb <code>invalidateQueries<\/code> \u4f7f\u5feb\u53d6\u5931\u6548<\/li>\n        <li onclick=\"checkAnswerAdv(5, 'D', 'C')\">D. \u61c9\u8a72\u628a <code>useMutation<\/code> \u63db\u6210 <code>useQuery<\/code><\/li>\n      <\/ul>\n      <div class=\"feedback-adv\" id=\"fbAdv5\"><\/div>\n    <\/div>\n\n    <div class=\"score-adv\" id=\"scoreAdv\" style=\"display:none;\"><\/div>\n<script>\n\n      let answeredAdv = 0;\n      let correctAdv = 0;\n      const totalAdv = 5;\n      const explanationsAdv = {\n        1: \"<code>useMutation<\/code> \u662f\u5c08\u9580\u7528\u4f86\u8655\u7406\u8cc7\u6599\u8b8a\u66f4\u64cd\u4f5c\u7684 Hook\u3002\u5728 <code>onSuccess<\/code> \u4e2d\u547c\u53eb <code>invalidateQueries<\/code> \u53ef\u4ee5\u8b93 TanStack Query \u81ea\u52d5\u91cd\u65b0 fetch \u8cc7\u6599\uff0c\u78ba\u4fdd\u756b\u9762\u986f\u793a\u6700\u65b0\u5167\u5bb9\u3002\u4f7f\u7528 <code>useQuery<\/code> \u642d\u914d POST \u662f\u932f\u8aa4\u7528\u6cd5\uff0c\u800c\u624b\u52d5 reload \u9801\u9762\u6703\u5c0e\u81f4\u4e0d\u597d\u7684\u4f7f\u7528\u8005\u9ad4\u9a57\u3002\",\n        2: \"\u9019\u6b63\u662f\u6a02\u89c0\u66f4\u65b0\u7684\u5178\u578b\u4f7f\u7528\u5834\u666f\u3002\u5728 <code>onMutate<\/code> \u4e2d\u5148\u4fdd\u5b58\u820a\u8cc7\u6599\u4e26\u7acb\u5373\u66f4\u65b0 UI\uff0c\u8b93\u4f7f\u7528\u8005\u4e0d\u9700\u7b49\u5f85\u3002\u5982\u679c API \u5931\u6557\uff0c\u5728 <code>onError<\/code> \u4e2d\u5c07\u8cc7\u6599\u56de\u5fa9\u5230\u539f\u72c0\u614b\u3002\u52fe\u9078\u64cd\u4f5c\u6210\u529f\u7387\u901a\u5e38\u5f88\u9ad8\uff0c\u5f88\u9069\u5408\u4f7f\u7528\u6a02\u89c0\u66f4\u65b0\u3002\",\n        3: \"\u5728\u6a02\u89c0\u66f4\u65b0\u7684 <code>onMutate<\/code> \u4e2d\uff0c\u61c9\u8a72\u5148\u547c\u53eb <code>cancelQueries<\/code> \u53d6\u6d88\u6b63\u5728\u9032\u884c\u7684 queries\u3002\u9019\u662f\u70ba\u4e86\u907f\u514d\u9032\u884c\u4e2d\u7684 fetch \u56de\u4f86\u5f8c\u8986\u84cb\u6211\u5011\u525b\u525b\u505a\u7684\u6a02\u89c0\u66f4\u65b0\uff0c\u9020\u6210 UI \u9583\u720d\u6216\u8cc7\u6599\u4e0d\u4e00\u81f4\u7684\u554f\u984c\u3002\",\n        4: \"\u554f\u984c\u51fa\u5728 <code>onMutate<\/code> \u6c92\u6709 return <code>{ previousTodos }<\/code>\u3002\u9019\u500b\u56de\u50b3\u503c\u6703\u4f5c\u70ba context \u53c3\u6578\u50b3\u7d66 <code>onError<\/code>\uff0c\u5982\u679c\u6c92\u6709\u56de\u50b3\uff0ccontext \u5c31\u6703\u662f undefined\uff0c\u5c0e\u81f4 <code>context?.previousTodos<\/code> \u4e5f\u662f undefined\uff0c\u7121\u6cd5\u56de\u5fa9\u8cc7\u6599\u3002\",\n        5: \"Mutation \u6210\u529f\u5f8c\uff0c\u5982\u679c\u6c92\u6709\u5728 <code>onSuccess<\/code> \u4e2d\u547c\u53eb <code>invalidateQueries<\/code>\uff0cTanStack Query \u4e0d\u6703\u77e5\u9053\u9700\u8981\u91cd\u65b0\u53d6\u5f97\u8cc7\u6599\u3002\u5feb\u53d6\u4e2d\u7684\u8cc7\u6599\u4ecd\u662f\u820a\u7684\uff0c\u6240\u4ee5\u756b\u9762\u4e0d\u6703\u66f4\u65b0\u3002\u6b63\u78ba\u505a\u6cd5\u662f\u52a0\u4e0a <code>onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['todos'] }) }<\/code>\u3002\"\n      };\n\n      function checkAnswerAdv(qNum, selected, correctAns) {\n        const question = document.getElementById(\"qAdv\" + qNum);\n        if (question.classList.contains(\"answered\")) return;\n\n        question.classList.add(\"answered\");\n        answeredAdv++;\n\n        const options = question.querySelectorAll(\".options-adv li\");\n        options.forEach((opt) => {\n          opt.classList.add(\"disabled\");\n          if (opt.textContent.startsWith(correctAns + \".\")) {\n            opt.classList.add(\"show-answer\");\n          }\n        });\n\n        const feedback = document.getElementById(\"fbAdv\" + qNum);\n        if (selected === correctAns) {\n          correctAdv++;\n          feedback.className = \"feedback-adv correct\";\n          feedback.innerHTML = \"&#10003; \u6b63\u78ba\uff01\" + explanationsAdv[qNum];\n        } else {\n          feedback.className = \"feedback-adv wrong\";\n          feedback.innerHTML =\n            \"&#10007; \u932f\u8aa4\u3002\u6b63\u78ba\u7b54\u6848\u662f \" + correctAns + \"\u3002\" + explanationsAdv[qNum];\n          options.forEach((opt) => {\n            if (opt.textContent.startsWith(selected + \".\")) {\n              opt.classList.add(\"wrong\");\n            }\n          });\n        }\n\n        if (answeredAdv === totalAdv) {\n          const scoreDiv = document.getElementById(\"scoreAdv\");\n          scoreDiv.style.display = \"block\";\n          let msg = \"\";\n          if (correctAdv === totalAdv) msg = \"\u5b8c\u7f8e\uff01\u4f60\u5df2\u7d93\u6e96\u5099\u597d\u5be6\u6230\u4e86\uff01\";\n          else if (correctAdv >= Math.ceil(totalAdv * 0.8))\n            msg = \"\u5f88\u68d2\uff01\u518d\u8907\u7fd2\u4e00\u4e0b\u932f\u7684\u984c\u76ee\";\n          else if (correctAdv >= Math.ceil(totalAdv * 0.6))\n            msg = \"\u5efa\u8b70\u91cd\u8b80\u76f8\u95dc\u6bb5\u843d\u5f8c\u518d\u8a66\u4e00\u6b21\";\n          else msg = \"\u5efa\u8b70\u91cd\u65b0\u95b1\u8b80\u6587\u4ef6\uff0c\u7279\u5225\u6ce8\u610f\u64cd\u4f5c\u6d41\u7a0b\";\n          scoreDiv.innerHTML =\n            \"\u6e2c\u9a57\u5b8c\u6210\uff01\u5f97\u5206\uff1a\" + correctAdv + \" \/ \" + totalAdv + \"<br>\" + msg;\n        }\n      }\n    \n<\/script>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>\u6e2c\u9a57\uff1aTanStack Query \u9032\u968e &#8211; useMutation \u8207\u6a02\u89c0\u66f4\u65b0 \u5171 5 \u984c\uff0c\u9ede\u9078 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rolpb_likes":0,"footnotes":""},"categories":[276],"tags":[286,269,278,284,285],"class_list":["post-285","post","type-post","status-publish","format-standard","hentry","category-tanstack-query-zustand","tag-invalidatequeries","tag-react","tag-tanstack-query","tag-usemutation","tag-285"],"blocksy_meta":{"styles_descriptor":{"styles":{"desktop":"","tablet":"","mobile":""},"google_fonts":[],"version":7}},"_links":{"self":[{"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/posts\/285","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/comments?post=285"}],"version-history":[{"count":0,"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/posts\/285\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/media?parent=285"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/categories?post=285"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/tags?post=285"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}