{"id":719,"date":"2026-02-08T00:41:25","date_gmt":"2026-02-07T16:41:25","guid":{"rendered":"https:\/\/blog.itsmygo.uk\/2026\/02\/08\/%e3%80%90react-%e7%b4%94%e5%89%8d%e7%ab%af%e5%af%a6%e6%88%b0%e3%80%9105-%e5%af%a6%e6%88%b0%ef%bc%9a%e7%94%a8-supabase-%e6%89%93%e9%80%a0%e5%ae%8c%e6%95%b4-crud-%e6%87%89%e7%94%a8\/"},"modified":"2026-02-08T00:41:25","modified_gmt":"2026-02-07T16:41:25","slug":"%e3%80%90react-%e7%b4%94%e5%89%8d%e7%ab%af%e5%af%a6%e6%88%b0%e3%80%9105-%e5%af%a6%e6%88%b0%ef%bc%9a%e7%94%a8-supabase-%e6%89%93%e9%80%a0%e5%ae%8c%e6%95%b4-crud-%e6%87%89%e7%94%a8","status":"publish","type":"post","link":"https:\/\/blog.itsmygo.uk\/wordpress\/2026\/02\/08\/%e3%80%90react-%e7%b4%94%e5%89%8d%e7%ab%af%e5%af%a6%e6%88%b0%e3%80%9105-%e5%af%a6%e6%88%b0%ef%bc%9a%e7%94%a8-supabase-%e6%89%93%e9%80%a0%e5%ae%8c%e6%95%b4-crud-%e6%87%89%e7%94%a8\/","title":{"rendered":"\u3010React \u7d14\u524d\u7aef\u5be6\u6230\u3011#05 \u5be6\u6230\uff1a\u7528 Supabase \u6253\u9020\u5b8c\u6574 CRUD \u61c9\u7528"},"content":{"rendered":"\n<div id=\"quiz-ab45dbaa\">\n<style>\n#quiz-ab45dbaa .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-ab45dbaa .question h3 {\n        margin-top: 0;\n        color: #333;\n      }#quiz-ab45dbaa .options {\n        list-style: none;\n        padding: 0;\n      }#quiz-ab45dbaa .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-ab45dbaa .options li:hover:not(.disabled) {\n        border-color: #4caf50;\n        background: #f0fff0;\n      }#quiz-ab45dbaa .options li.correct {\n        border-color: #4caf50;\n        background: #e8f5e9;\n      }#quiz-ab45dbaa .options li.wrong {\n        border-color: #f44336;\n        background: #ffebee;\n      }#quiz-ab45dbaa .options li.disabled {\n        cursor: default;\n        opacity: 0.7;\n      }#quiz-ab45dbaa .options li.show-answer {\n        border-color: #4caf50;\n        background: #e8f5e9;\n      }#quiz-ab45dbaa .feedback {\n        margin-top: 15px;\n        padding: 12px;\n        border-radius: 6px;\n        display: none;\n      }#quiz-ab45dbaa .feedback.correct {\n        background: #e8f5e9;\n        color: #2e7d32;\n        display: block;\n      }#quiz-ab45dbaa .feedback.wrong {\n        background: #ffebee;\n        color: #c62828;\n        display: block;\n      }#quiz-ab45dbaa .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-ab45dbaa .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\uff1a\u7528 Supabase \u6253\u9020\u5b8c\u6574 CRUD \u61c9\u7528<\/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 Supabase \u7684\u7d14\u524d\u7aef\u67b6\u69cb\u4e2d\uff0c\u70ba\u4ec0\u9ebc anon key \u653e\u5728\u524d\u7aef\u7a0b\u5f0f\u78bc\u88e1\u662f\u5b89\u5168\u7684\uff1f<\/h3>\n      <ul class=\"options\">\n        <li onclick=\"checkAnswer(1, 'A', 'C')\">A. \u56e0\u70ba anon key \u7d93\u904e\u52a0\u5bc6\uff0c\u7121\u6cd5\u88ab\u53cd\u7de8\u8b6f\u53d6\u5f97<\/li>\n        <li onclick=\"checkAnswer(1, 'B', 'C')\">B. \u56e0\u70ba Vite \u6703\u5728\u6253\u5305\u6642\u81ea\u52d5\u96b1\u85cf\u74b0\u5883\u8b8a\u6578\u7684\u503c<\/li>\n        <li onclick=\"checkAnswer(1, 'C', 'C')\">C. \u56e0\u70ba\u771f\u6b63\u7684\u5b89\u5168\u9632\u7dda\u662f\u8cc7\u6599\u5eab\u5c64\u7684 RLS \u653f\u7b56\uff0canon key \u53ea\u662f\u8b93\u4f60\u80fd\u9023\u4e0a Supabase<\/li>\n        <li onclick=\"checkAnswer(1, 'D', 'C')\">D. \u56e0\u70ba anon key \u53ea\u6709\u8b80\u53d6\u6b0a\u9650\uff0c\u7121\u6cd5\u505a\u4efb\u4f55\u5beb\u5165\u64cd\u4f5c<\/li>\n      <\/ul>\n      <div class=\"feedback\" id=\"fb1\"><\/div>\n    <\/div>\n\n    <div class=\"question\" id=\"q2\">\n      <h3>2. \u4ee5\u4e0b\u662f Supabase Client \u7684 CRUD \u51fd\u5f0f\u56de\u50b3\u7d50\u679c\uff0c\u6a19\u6e96\u7684\u8655\u7406\u6a21\u5f0f\u662f\u4ec0\u9ebc\uff1f<\/h3>\n      <div class=\"code-block\">const { data, error } = await supabase\n  .from(&#8216;todos&#8217;)\n  .select(&#8216;*&#8217;)<\/div>\n      <ul class=\"options\">\n        <li onclick=\"checkAnswer(2, 'A', 'B')\">A. \u76f4\u63a5\u56de\u50b3 data\uff0c\u4e0d\u9700\u8981\u6aa2\u67e5 error<\/li>\n        <li onclick=\"checkAnswer(2, 'B', 'B')\">B. \u5982\u679c error \u5b58\u5728\u5c31\u62cb\u51fa\uff0c\u6c92\u6709 error \u5c31\u56de\u50b3 data<\/li>\n        <li onclick=\"checkAnswer(2, 'C', 'B')\">C. \u7528 try-catch \u5305\u4f4f\u6574\u500b\u67e5\u8a62\u4f86\u8655\u7406\u932f\u8aa4<\/li>\n        <li onclick=\"checkAnswer(2, 'D', 'B')\">D. \u628a data \u548c error \u4e00\u8d77\u56de\u50b3\uff0c\u8b93\u547c\u53eb\u7aef\u81ea\u884c\u5224\u65b7<\/li>\n      <\/ul>\n      <div class=\"feedback\" id=\"fb2\"><\/div>\n    <\/div>\n\n    <div class=\"question\" id=\"q3\">\n      <h3>3. \u5728 useMutation \u7684 onSuccess \u4e2d\u547c\u53eb <code>invalidateQueries({ queryKey: ['todos', 'list'] })<\/code> \u7684\u76ee\u7684\u662f\u4ec0\u9ebc\uff1f<\/h3>\n      <ul class=\"options\">\n        <li onclick=\"checkAnswer(3, 'A', 'D')\">A. \u6e05\u9664\u6240\u6709\u5feb\u53d6\uff0c\u5f37\u5236\u4f7f\u7528\u8005\u91cd\u65b0\u767b\u5165<\/li>\n        <li onclick=\"checkAnswer(3, 'B', 'D')\">B. \u628a\u65b0\u589e\u7684\u8cc7\u6599\u76f4\u63a5\u5beb\u5165\u5feb\u53d6\uff0c\u907f\u514d\u984d\u5916\u7684 API \u8acb\u6c42<\/li>\n        <li onclick=\"checkAnswer(3, 'C', 'D')\">C. \u53d6\u6d88\u6b63\u5728\u9032\u884c\u7684 API \u8acb\u6c42\uff0c\u9632\u6b62\u8cc7\u6599\u885d\u7a81<\/li>\n        <li onclick=\"checkAnswer(3, 'D', 'D')\">D. \u5c07\u5c0d\u61c9\u7684\u5feb\u53d6\u6a19\u8a18\u70ba\u904e\u671f\uff0c\u89f8\u767c TanStack Query \u81ea\u52d5\u91cd\u65b0\u6293\u53d6\u5217\u8868<\/li>\n      <\/ul>\n      <div class=\"feedback\" id=\"fb3\"><\/div>\n    <\/div>\n\n    <div class=\"question\" id=\"q4\">\n      <h3>4. \u7d14\u524d\u7aef SPA \u90e8\u7f72\u5230 Vercel \u6216 Netlify \u6642\uff0c\u70ba\u4ec0\u9ebc\u9700\u8981\u8a2d\u5b9a\u300c\u6240\u6709\u8def\u7531\u90fd\u6307\u5411 index.html\u300d\u7684 rewrite \u898f\u5247\uff1f<\/h3>\n      <ul class=\"options\">\n        <li onclick=\"checkAnswer(4, 'A', 'B')\">A. \u56e0\u70ba Supabase \u7684 API \u8acb\u6c42\u9700\u8981\u900f\u904e index.html \u8f49\u767c<\/li>\n        <li onclick=\"checkAnswer(4, 'B', 'B')\">B. \u56e0\u70ba SPA \u7684\u8def\u7531\u7531\u524d\u7aef JavaScript \u8655\u7406\uff0c\u4f3a\u670d\u5668\u76f4\u63a5\u8a2a\u554f\u5b50\u8def\u5f91\u6703\u627e\u4e0d\u5230\u5c0d\u61c9\u7684\u6a94\u6848\u800c\u56de\u50b3 404<\/li>\n        <li onclick=\"checkAnswer(4, 'C', 'B')\">C. \u56e0\u70ba TanStack Router \u7684 loader \u5fc5\u9808\u5f9e index.html \u958b\u59cb\u8f09\u5165\u624d\u80fd\u904b\u4f5c<\/li>\n        <li onclick=\"checkAnswer(4, 'D', 'B')\">D. \u56e0\u70ba\u74b0\u5883\u8b8a\u6578\u53ea\u6709\u5728 index.html \u4e2d\u624d\u80fd\u88ab\u8b80\u53d6<\/li>\n      <\/ul>\n      <div class=\"feedback\" id=\"fb4\"><\/div>\n    <\/div>\n\n    <div class=\"question\" id=\"q5\">\n      <h3>5. \u5728 Optimistic Update \u4e2d\uff0c<code>onMutate<\/code> \u88e1\u547c\u53eb <code>cancelQueries<\/code> \u7684\u76ee\u7684\u662f\u4ec0\u9ebc\uff1f<\/h3>\n      <div class=\"code-block\">onMutate: async () => {\n  await queryClient.cancelQueries({ queryKey: [&#8216;todos&#8217;, &#8216;list&#8217;] })\n  const previousTodos = queryClient.getQueryData([&#8216;todos&#8217;, &#8216;list&#8217;])\n  queryClient.setQueryData([&#8216;todos&#8217;, &#8216;list&#8217;], (old) => \/* \u6a02\u89c0\u66f4\u65b0 *\/)\n  return { previousTodos }\n}<\/div>\n      <ul class=\"options\">\n        <li onclick=\"checkAnswer(5, 'A', 'C')\">A. \u53d6\u6d88 mutation \u7684 API \u8acb\u6c42\uff0c\u6539\u7528\u672c\u5730\u8cc7\u6599<\/li>\n        <li onclick=\"checkAnswer(5, 'B', 'C')\">B. \u505c\u6b62\u6240\u6709\u8ddf todos \u76f8\u95dc\u7684 mutation \u64cd\u4f5c<\/li>\n        <li onclick=\"checkAnswer(5, 'C', 'C')\">C. \u53d6\u6d88\u6b63\u5728\u9032\u884c\u7684\u67e5\u8a62\u8acb\u6c42\uff0c\u907f\u514d\u5176\u56de\u50b3\u7d50\u679c\u8986\u84cb\u6389\u6211\u5011\u5373\u5c07\u505a\u7684\u6a02\u89c0\u66f4\u65b0<\/li>\n        <li onclick=\"checkAnswer(5, 'D', 'C')\">D. \u6e05\u9664\u5feb\u53d6\u4e2d\u7684\u820a\u8cc7\u6599\uff0c\u70ba\u6a02\u89c0\u66f4\u65b0\u9a30\u51fa\u7a7a\u9593<\/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: \"Supabase \u7684\u5b89\u5168\u6a21\u578b\u4e0d\u9760\u96b1\u85cf key\uff0c\u800c\u662f\u9760 RLS\uff08Row Level Security\uff09\u3002anon key \u53ea\u662f\u8b93\u524d\u7aef\u80fd\u9023\u4e0a Supabase\uff0c\u771f\u6b63\u7684\u5b89\u5168\u9632\u7dda\u662f\u8cc7\u6599\u5eab\u5c64\u7684 RLS \u653f\u7b56\u2014\u2014\u6bcf\u6b21\u64cd\u4f5c\u6642 PostgreSQL \u6703\u6aa2\u67e5\u300c\u9019\u500b\u4f7f\u7528\u8005\u6709\u6b0a\u9650\u78b0\u9019\u7b46\u8cc7\u6599\u55ce\uff1f\u300d\u3002\u5c31\u50cf\u5927\u6a13\u5927\u9580\u5c0d\u6240\u6709\u4f4f\u6236\u958b\u653e\uff0c\u4f46\u6bcf\u9593\u623f\u9593\u6709\u5404\u81ea\u7684\u9396\u3002\",\n        2: \"\u6587\u7ae0\u4e2d\u6240\u6709 CRUD \u51fd\u5f0f\u90fd\u9075\u5faa\u76f8\u540c\u7684\u6a19\u6e96\u6a21\u5f0f\uff1a\u89e3\u69cb\u51fa <code>{ data, error }<\/code>\uff0c\u5982\u679c error \u5b58\u5728\u5c31\u7528 <code>throw error<\/code> \u62cb\u51fa\uff08\u8b93 TanStack Query \u63a5\u4f4f\uff09\uff0c\u6c92\u6709 error \u5c31\u56de\u50b3 data\u3002\u9019\u662f Supabase Client \u7684\u6a19\u6e96\u8655\u7406\u65b9\u5f0f\u3002\",\n        3: \"<code>invalidateQueries<\/code> \u7684\u4f5c\u7528\u662f\u628a\u7279\u5b9a queryKey \u7684\u5feb\u53d6\u6a19\u8a18\u70ba\u300c\u904e\u671f\u300d\uff0c\u9019\u6703\u89f8\u767c TanStack Query \u81ea\u52d5\u91cd\u65b0\u57f7\u884c\u5c0d\u61c9\u7684 queryFn \u4f86\u6293\u53d6\u6700\u65b0\u8cc7\u6599\u3002\u9019\u6a23\u65b0\u589e\/\u4fee\u6539\/\u522a\u9664\u5f8c\uff0c\u5217\u8868\u5c31\u6703\u81ea\u52d5\u66f4\u65b0\u5230\u6700\u65b0\u72c0\u614b\u3002\",\n        4: \"SPA \u53ea\u6709\u4e00\u500b index.html \u5165\u53e3\uff0c\u6240\u6709\u8def\u7531\u5207\u63db\u90fd\u7531\u524d\u7aef JavaScript\uff08TanStack Router\uff09\u8655\u7406\u3002\u5982\u679c\u4f7f\u7528\u8005\u76f4\u63a5\u5728\u700f\u89bd\u5668\u8a2a\u554f <code>\/todos\/123<\/code>\uff0c\u975c\u614b\u4f3a\u670d\u5668\u6703\u5617\u8a66\u5c0b\u627e <code>\/todos\/123\/index.html<\/code> \u9019\u500b\u5be6\u969b\u6a94\u6848\uff0c\u627e\u4e0d\u5230\u5c31\u56de\u50b3 404\u3002\u8a2d\u5b9a rewrite \u898f\u5247\u8b93\u6240\u6709\u8def\u5f91\u90fd\u56de\u50b3 index.html\uff0c\u8def\u7531\u908f\u8f2f\u5c31\u80fd\u4ea4\u7d66\u524d\u7aef JavaScript \u8655\u7406\u3002\",\n        5: \"\u5728 Optimistic Update \u4e2d\uff0c<code>cancelQueries<\/code> \u662f\u53d6\u6d88\u300c\u6b63\u5728\u9032\u884c\u7684\u67e5\u8a62\u8acb\u6c42\u300d\uff08\u4e0d\u662f mutation\uff09\u3002\u56e0\u70ba\u5982\u679c\u6b64\u6642\u525b\u597d\u6709\u4e00\u500b\u5217\u8868\u7684 refetch \u6b63\u5728\u9032\u884c\uff0c\u5b83\u7684\u56de\u50b3\u7d50\u679c\u6703\u8986\u84cb\u6389\u6211\u5011\u7528 <code>setQueryData<\/code> \u505a\u7684\u6a02\u89c0\u66f4\u65b0\uff0c\u5c0e\u81f4\u756b\u9762\u9583\u56de\u820a\u72c0\u614b\u3002\u5148\u53d6\u6d88\u5b83\uff0c\u624d\u80fd\u78ba\u4fdd\u6a02\u89c0\u66f4\u65b0\u7684\u7d50\u679c\u4e0d\u6703\u88ab\u84cb\u6389\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              ? \" &#127881; \u5168\u5c0d\uff01\"\n              : correct >= Math.ceil(total * 0.6)\n                ? \" &#128077; \u4e0d\u932f\uff01\"\n                : \" &#128218; \u518d\u8907\u7fd2\u4e00\u4e0b\u5427\");\n        }\n      }\n    \n<\/script>\n<\/div>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">**\u7cfb\u5217**\uff1aReact \u7d14\u524d\u7aef\u5be6\u6230\uff08\u7b2c 5 \u7bc7\uff0c\u5171 5 \u7bc7\u2014\u2014\u5b8c\u7d50\u7bc7\uff09<br>**\u96e3\u5ea6**\uff1aL1-\u8d77\u6b65<br>**\u524d\u7f6e\u77e5\u8b58**\uff1a\u8b80\u5b8c\u672c\u7cfb\u5217\u7b2c 1-4 \u7bc7\u3001\u5c0d Supabase \u6709\u57fa\u672c\u8a8d\u8b58\uff08\u77e5\u9053\u5b83\u662f\u4ec0\u9ebc\u5c31\u597d\uff09<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">\u4e00\u53e5\u8a71\u8aaa\u660e<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u628a\u524d\u56db\u7bc7\u5b78\u5230\u7684 Router + Query \u5168\u90e8\u4e32\u8d77\u4f86\uff0c\u642d\u914d Supabase \u4f5c\u70ba\u5f8c\u7aef\uff0c\u5b8c\u6210\u4e00\u500b\u53ef\u90e8\u7f72\u7684\u7d14\u524d\u7aef CRUD \u61c9\u7528\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u9019\u7bc7\u5728\u8b1b\u4ec0\u9ebc<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u524d\u56db\u7bc7\u6211\u5011\u5206\u5225\u770b\u61c2\u4e86 TanStack Router\uff08\u8def\u7531\uff09\u548c TanStack Query\uff08\u8cc7\u6599\u7ba1\u7406\uff09\uff0c\u4e5f\u770b\u4e86\u5b83\u5011\u600e\u9ebc\u6574\u5408\u3002\u4f46\u90a3\u4e9b\u7bc4\u4f8b\u90fd\u7528\u5047\u7684 <code>fetch('\/api\/...')<\/code>\u2014\u2014\u5be6\u969b\u5c08\u6848\u4e2d\uff0c\u4f60\u7684\u8cc7\u6599\u8981\u5b58\u5728\u54ea\u88e1\uff1f<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u9019\u7bc7\u7528\u4e00\u500b\u300c\u5f85\u8fa6\u6e05\u55ae\u300d\u61c9\u7528\uff0c\u628a\u6240\u6709\u6982\u5ff5\u4e32\u5728\u4e00\u8d77\uff0c\u4e26\u7528 <strong>Supabase<\/strong> \u4f5c\u70ba\u771f\u6b63\u7684\u5f8c\u7aef\u3002\u4f60\u6703\u770b\u5230\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>\u4e00\u500b\u5b8c\u6574\u5c08\u6848\u7684\u6a94\u6848\u7d50\u69cb\u9577\u4ec0\u9ebc\u6a23<\/li>\n\n\n<li>Supabase \u600e\u9ebc\u53d6\u4ee3\u50b3\u7d71\u7684\u5f8c\u7aef API<\/li>\n\n\n<li>\u524d\u9762\u5b78\u7684 loader\u3001queryOptions\u3001useMutation \u600e\u9ebc\u5728\u771f\u5be6\u5c08\u6848\u4e2d\u7d44\u5408<\/li>\n\n\n<li>\u70ba\u4ec0\u9ebc\u7d14\u524d\u7aef\u4e5f\u53ef\u4ee5\u5b89\u5168\u5730\u64cd\u4f5c\u8cc7\u6599\u5eab<\/li>\n\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Supabase \u662f\u4ec0\u9ebc\uff1f30 \u79d2\u641e\u61c2<\/h2>\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\">\u50b3\u7d71\u67b6\u69cb                         Supabase \u67b6\u69cb\uff08\u7d14\u524d\u7aef\uff09\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510                    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502  React   \u2502                    \u2502  React   \u2502\n\u2502  \u524d\u7aef     \u2502                    \u2502  \u524d\u7aef     \u2502\n\u2514\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2518                    \u2514\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2518\n     \u2502 fetch(<span class=\"hljs-string\">'\/api\/todos'<\/span>)           \u2502 supabase.from(<span class=\"hljs-string\">'todos'<\/span>).select()\n     \u2193                               \u2193\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510                    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Express  \u2502                    \u2502 Supabase \u2502 \u2190 \u96f2\u7aef\u670d\u52d9\n\u2502 \u5f8c\u7aef API  \u2502                    \u2502 (\u81ea\u5e36 DB  \u2502\n\u2514\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2518                    \u2502  + Auth   \u2502\n     \u2502 SQL query                \u2502  + API)   \u2502\n     \u2193                          \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 PostgreSQL\u2502\n\u2502 \u8cc7\u6599\u5eab    \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\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<p class=\"wp-block-paragraph\"><strong>\u7ffb\u8b6f<\/strong>\uff1aSupabase \u662f\u4e00\u500b\u300c\u5f8c\u7aef\u5373\u670d\u52d9\u300d\uff08BaaS\uff09\uff0c\u5b83\u5e6b\u4f60\u628a\u8cc7\u6599\u5eab\u3001\u8eab\u4efd\u9a57\u8b49\u3001API \u5168\u90e8\u6253\u5305\u597d\u3002\u4f60\u7684 React \u524d\u7aef\u53ef\u4ee5\u76f4\u63a5\u547c\u53eb Supabase\uff0c\u4e0d\u9700\u8981\u81ea\u5df1\u5beb\u5f8c\u7aef\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u5c08\u6848\u7d50\u69cb\u7e3d\u89bd<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">AI \u5e6b\u4f60\u751f\u6210\u4e00\u500b React + TanStack + Supabase \u7684\u5f85\u8fa6\u6e05\u55ae\u61c9\u7528\uff0c\u6a94\u6848\u7d50\u69cb\u5927\u6982\u9577\u9019\u6a23\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-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">todo-app\/\n\u251c\u2500\u2500 .env                         \u2190 Supabase \u9023\u7dda\u8cc7\u8a0a\uff08\u74b0\u5883\u8b8a\u6578\uff09\n\u251c\u2500\u2500 package.json\n\u251c\u2500\u2500 vite.config.ts\n\u251c\u2500\u2500 src\/\n\u2502   \u251c\u2500\u2500 main.tsx                 \u2190 \u61c9\u7528\u5165\u53e3\uff08Router + Query \u521d\u59cb\u5316\uff09\n\u2502   \u251c\u2500\u2500 lib\/\n\u2502   \u2502   \u2514\u2500\u2500 supabase.ts          \u2190 Supabase Client \u521d\u59cb\u5316\n\u2502   \u251c\u2500\u2500 types\/\n\u2502   \u2502   \u2514\u2500\u2500 database.types.ts    \u2190 \u8cc7\u6599\u5eab\u578b\u5225\u5b9a\u7fa9\uff08\u81ea\u52d5\u751f\u6210\u7684\uff09\n\u2502   \u251c\u2500\u2500 api\/\n\u2502   \u2502   \u2514\u2500\u2500 todos.ts             \u2190 CRUD \u51fd\u5f0f + Query Options\n\u2502   \u251c\u2500\u2500 routes\/\n\u2502   \u2502   \u251c\u2500\u2500 __root.tsx           \u2190 \u6839\u8def\u7531\uff08Layout + Context\uff09\n\u2502   \u2502   \u251c\u2500\u2500 index.tsx            \u2190 \u9996\u9801 \/\n\u2502   \u2502   \u251c\u2500\u2500 login.tsx            \u2190 \u767b\u5165\u9801 \/login\n\u2502   \u2502   \u2514\u2500\u2500 todos\/\n\u2502   \u2502       \u251c\u2500\u2500 index.tsx        \u2190 \u5f85\u8fa6\u5217\u8868 \/todos\n\u2502   \u2502       \u2514\u2500\u2500 $todoId.tsx      \u2190 \u5f85\u8fa6\u8a73\u60c5 \/todos\/<span class=\"hljs-number\">123<\/span>\n\u2502   \u2514\u2500\u2500 components\/\n\u2502       \u251c\u2500\u2500 TodoList.tsx\n\u2502       \u251c\u2500\u2500 TodoItem.tsx\n\u2502       \u2514\u2500\u2500 CreateTodoForm.tsx\n\u2514\u2500\u2500 supabase\/\n    \u2514\u2500\u2500 migrations\/              \u2190 \u8cc7\u6599\u5eab Schema\uff08SQL \u6a94\u6848\uff09\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><strong>\u7ffb\u8b6f\u5c0d\u7167<\/strong>\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>\u8cc7\u6599\u593e\/\u6a94\u6848<\/th>\n<th>\u89d2\u8272<\/th>\n<th>\u5c0d\u61c9\u524d\u5e7e\u7bc7\u5b78\u7684<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>lib\/supabase.ts<\/code><\/td>\n<td>Supabase \u9023\u7dda\u8a2d\u5b9a<\/td>\n<td>\u672c\u7bc7\u65b0\u6982\u5ff5<\/td>\n<\/tr>\n<tr>\n<td><code>types\/database.types.ts<\/code><\/td>\n<td>\u8cc7\u6599\u5eab\u7684 TypeScript \u578b\u5225<\/td>\n<td>\u672c\u7bc7\u65b0\u6982\u5ff5<\/td>\n<\/tr>\n<tr>\n<td><code>api\/todos.ts<\/code><\/td>\n<td>CRUD \u51fd\u5f0f + queryOptions<\/td>\n<td>\u7b2c 3\u30014 \u7bc7<\/td>\n<\/tr>\n<tr>\n<td><code>routes\/<\/code><\/td>\n<td>\u9801\u9762\u8def\u7531<\/td>\n<td>\u7b2c 2\u30014 \u7bc7<\/td>\n<\/tr>\n<tr>\n<td><code>routes\/__root.tsx<\/code><\/td>\n<td>\u6839\u8def\u7531 + context<\/td>\n<td>\u7b2c 4 \u7bc7<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">\u95dc\u9375\u89c0\u5bdf\uff1a\u6574\u500b\u5c08\u6848\u6c92\u6709 <code>server\/<\/code> \u6216 <code>backend\/<\/code> \u8cc7\u6599\u593e\u3002\u6240\u6709\u8ddf\u5f8c\u7aef\u7684\u6e9d\u901a\u90fd\u900f\u904e <code>lib\/supabase.ts<\/code> \u88e1\u7684 Supabase Client \u5b8c\u6210\u3002<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Supabase \u8a2d\u5b9a\uff1a\u521d\u59cb\u5316 Client<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u74b0\u5883\u8b8a\u6578<\/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-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\"># .env<\/span>\nVITE_SUPABASE_URL=https:<span class=\"hljs-comment\">\/\/abcdefg.supabase.co<\/span>\nVITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><strong>\u7ffb\u8b6f<\/strong>\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li><code>VITE_SUPABASE_URL<\/code>\uff1a\u4f60\u7684 Supabase \u5c08\u6848\u7db2\u5740\uff08\u6bcf\u500b\u5c08\u6848\u4e0d\u540c\uff09<\/li>\n\n\n<li><code>VITE_SUPABASE_ANON_KEY<\/code>\uff1a\u516c\u958b\u7684 API \u91d1\u9470\uff08\u7b49\u7b49\u6703\u89e3\u91cb\u70ba\u4ec0\u9ebc\u516c\u958b\u4e5f\u5b89\u5168\uff09<\/li>\n\n\n<li><code>VITE_<\/code> \u524d\u7db4\uff1aVite \u7684\u898f\u5247\u2014\u2014\u53ea\u6709 <code>VITE_<\/code> \u958b\u982d\u7684\u74b0\u5883\u8b8a\u6578\u624d\u6703\u88ab\u6253\u5305\u9032\u524d\u7aef\u7a0b\u5f0f\u78bc<\/li>\n\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\u521d\u59cb\u5316 Supabase Client<\/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-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/lib\/supabase.ts<\/span>\n<span class=\"hljs-keyword\">import<\/span> { createClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@supabase\/supabase-js'<\/span>\n<span class=\"hljs-keyword\">import<\/span> type { Database } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/types\/database.types'<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> supabaseUrl = <span class=\"hljs-keyword\">import<\/span>.meta.env.VITE_SUPABASE_URL\n<span class=\"hljs-keyword\">const<\/span> supabaseAnonKey = <span class=\"hljs-keyword\">import<\/span>.meta.env.VITE_SUPABASE_ANON_KEY\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> supabase = createClient&lt;Database&gt;(supabaseUrl, supabaseAnonKey)\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<p class=\"wp-block-paragraph\"><strong>\u9010\u884c\u7ffb\u8b6f<\/strong>\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>\u7a0b\u5f0f\u78bc<\/th>\n<th>\u7ffb\u8b6f<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>createClient<\/code><\/td>\n<td>\u5efa\u7acb\u4e00\u500b Supabase \u9023\u7dda\u7269\u4ef6<\/td>\n<\/tr>\n<tr>\n<td><code>&lt;Database&gt;<\/code><\/td>\n<td>\u544a\u8a34 TypeScript \u8cc7\u6599\u5eab\u7684\u7d50\u69cb\u9577\u4ec0\u9ebc\u6a23\uff08\u578b\u5225\u5b89\u5168\uff09<\/td>\n<\/tr>\n<tr>\n<td><code>import.meta.env.VITE_SUPABASE_URL<\/code><\/td>\n<td>\u5f9e\u74b0\u5883\u8b8a\u6578\u8b80\u53d6 Supabase \u7db2\u5740<\/td>\n<\/tr>\n<tr>\n<td><code>export const supabase<\/code><\/td>\n<td>\u532f\u51fa\u9019\u500b\u9023\u7dda\u7269\u4ef6\uff0c\u6574\u500b\u5c08\u6848\u90fd\u7528\u9019\u4e00\u500b<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">\u9019\u6bb5\u7a0b\u5f0f\u5f88\u77ed\uff0c\u4f46\u5b83\u662f\u6574\u500b\u5c08\u6848\u8ddf Supabase \u6e9d\u901a\u7684\u57fa\u790e\u3002\u6574\u500b\u5c08\u6848\u53ea\u9700\u8981\u9019\u4e00\u500b <code>supabase<\/code> \u7269\u4ef6\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u8cc7\u6599\u5eab Schema \u8207 RLS<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u8cc7\u6599\u8868\u8a2d\u8a08<\/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\">-- supabase\/migrations\/<span class=\"hljs-number\">001<\/span>_create_todos.sql\ncreate table todos (\n  id          uuid <span class=\"hljs-keyword\">default<\/span> gen_random_uuid() primary key,\n  user_id     uuid references auth.users(id) not <span class=\"hljs-literal\">null<\/span>,\n  title       text not <span class=\"hljs-literal\">null<\/span>,\n  completed   boolean <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-literal\">false<\/span>,\n  created_at  timestamptz <span class=\"hljs-keyword\">default<\/span> now()\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<p class=\"wp-block-paragraph\"><strong>\u7ffb\u8b6f<\/strong>\uff1a\u5efa\u7acb\u4e00\u5f35 <code>todos<\/code> \u8868\uff0c\u6bcf\u7b46\u5f85\u8fa6\u6709 id\u3001\u64c1\u6709\u8005\uff08user_id\uff09\u3001\u6a19\u984c\u3001\u5b8c\u6210\u72c0\u614b\u3001\u5efa\u7acb\u6642\u9593\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">RLS\uff1a\u70ba\u4ec0\u9ebc anon key \u653e\u5728\u524d\u7aef\u662f\u5b89\u5168\u7684<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u4f60\u53ef\u80fd\u6703\u60f3\uff1a\u300c\u628a API key \u653e\u5728\u524d\u7aef\u7a0b\u5f0f\u78bc\u88e1\uff0c\u4efb\u4f55\u4eba\u90fd\u770b\u5f97\u5230\uff0c\u9019\u4e0d\u5371\u96aa\u55ce\uff1f\u300d<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u7b54\u6848\u662f\uff1a<strong>Supabase \u7684\u5b89\u5168\u6a21\u578b\u4e0d\u9760\u96b1\u85cf key\uff0c\u800c\u662f\u9760 RLS\uff08Row Level Security\uff09\u3002<\/strong><\/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\">-- \u555f\u7528 RLS\nalter table todos enable row level security;\n\n-- \u653f\u7b56\uff1a\u4f7f\u7528\u8005\u53ea\u80fd\u770b\u81ea\u5df1\u7684\u5f85\u8fa6\ncreate policy <span class=\"hljs-string\">\"Users can view own todos\"<\/span>\n  on todos <span class=\"hljs-keyword\">for<\/span> select\n  using (auth.uid() = user_id);\n\n-- \u653f\u7b56\uff1a\u4f7f\u7528\u8005\u53ea\u80fd\u65b0\u589e\u81ea\u5df1\u7684\u5f85\u8fa6\ncreate policy <span class=\"hljs-string\">\"Users can insert own todos\"<\/span>\n  on todos <span class=\"hljs-keyword\">for<\/span> insert\n  <span class=\"hljs-keyword\">with<\/span> check (auth.uid() = user_id);\n\n-- \u653f\u7b56\uff1a\u4f7f\u7528\u8005\u53ea\u80fd\u4fee\u6539\u81ea\u5df1\u7684\u5f85\u8fa6\ncreate policy <span class=\"hljs-string\">\"Users can update own todos\"<\/span>\n  on todos <span class=\"hljs-keyword\">for<\/span> update\n  using (auth.uid() = user_id);\n\n-- \u653f\u7b56\uff1a\u4f7f\u7528\u8005\u53ea\u80fd\u522a\u9664\u81ea\u5df1\u7684\u5f85\u8fa6\ncreate policy <span class=\"hljs-string\">\"Users can delete own todos\"<\/span>\n  on todos <span class=\"hljs-keyword\">for<\/span> <span class=\"hljs-keyword\">delete<\/span>\n  using (auth.uid() = user_id);\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<p class=\"wp-block-paragraph\"><strong>\u7ffb\u8b6f<\/strong>\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\">\u6c92\u6709 RLS \u7684\u60c5\u6cc1\uff1a\n  \u4efb\u4f55\u4eba\u62ff\u5230 anon key \u2192 \u53ef\u4ee5\u8b80\/\u5beb\u6240\u6709\u4eba\u7684\u8cc7\u6599 \u2192 \u5371\u96aa\uff01\n\n\u6709 RLS \u7684\u60c5\u6cc1\uff1a\n  \u4efb\u4f55\u4eba\u62ff\u5230 anon key \u2192 \u53ef\u4ee5\u9023\u7dda\n  \u4f46\u6bcf\u6b21\u64cd\u4f5c\u8cc7\u6599\u5eab\u6642 \u2192 PostgreSQL \u6703\u6aa2\u67e5\uff1a\n    \u300c\u9019\u500b\u8acb\u6c42\u7684\u4f7f\u7528\u8005\u662f\u8ab0\uff1f\u300d\uff08auth.uid()\uff09\n    \u300c\u4ed6\u6709\u6b0a\u9650\u78b0\u9019\u7b46\u8cc7\u6599\u55ce\uff1f\u300d\uff08user_id \u8981\u5339\u914d\uff09\n    \u2192 \u53ea\u80fd\u78b0\u81ea\u5df1\u7684\u8cc7\u6599 \u2192 \u5b89\u5168\uff01\n<\/code><\/span><\/pre><\/div>\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">**\u95dc\u9375\u7406\u89e3**\uff1aanon key \u53ea\u662f\u8b93\u4f60\u300c\u80fd\u9023\u4e0a Supabase\u300d\u3002\u771f\u6b63\u7684\u5b89\u5168\u9632\u7dda\u662f\u8cc7\u6599\u5eab\u5c64\u7684 RLS \u653f\u7b56\u3002\u5c31\u50cf\u5927\u6a13\u7684\u5927\u9580\u5c0d\u6240\u6709\u4f4f\u6236\u958b\u653e\uff0c\u4f46\u6bcf\u9593\u623f\u9593\u6709\u5404\u81ea\u7684\u9396\u3002<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">TypeScript \u578b\u5225\u751f\u6210<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Supabase \u53ef\u4ee5\u6839\u64da\u4f60\u7684\u8cc7\u6599\u5eab Schema \u81ea\u52d5\u751f\u6210 TypeScript \u578b\u5225\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\">npx supabase gen types typescript --project-id abcdefg &gt; src\/types\/database.types.ts\n<\/code><\/span><\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">\u751f\u6210\u7684\u6a94\u6848\u9577\u9019\u6a23\uff08\u7bc0\u9304\uff09\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-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ src\/types\/database.types.ts\uff08\u81ea\u52d5\u751f\u6210\uff0c\u4e0d\u8981\u624b\u52d5\u4fee\u6539\uff09<\/span>\nexport <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">Database<\/span> <\/span>{\n  <span class=\"hljs-keyword\">public<\/span>: {\n    Tables: {\n      todos: {\n        Row: {                           <span class=\"hljs-comment\">\/\/ \u8b80\u53d6\u6642\u7684\u578b\u5225<\/span>\n          id: string\n          user_id: string\n          title: string\n          completed: boolean\n          created_at: string\n        }\n        Insert: {                        <span class=\"hljs-comment\">\/\/ \u65b0\u589e\u6642\u7684\u578b\u5225<\/span>\n          id?: string                    <span class=\"hljs-comment\">\/\/ \u6709\u9810\u8a2d\u503c\u7684\u6b04\u4f4d\u8b8a\u6210\u53ef\u9078<\/span>\n          user_id: string\n          title: string\n          completed?: boolean\n          created_at?: string\n        }\n        Update: {                        <span class=\"hljs-comment\">\/\/ \u66f4\u65b0\u6642\u7684\u578b\u5225<\/span>\n          id?: string                    <span class=\"hljs-comment\">\/\/ \u6240\u6709\u6b04\u4f4d\u90fd\u662f\u53ef\u9078\u7684<\/span>\n          user_id?: string\n          title?: string\n          completed?: boolean\n          created_at?: string\n        }\n      }\n    }\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><strong>\u7ffb\u8b6f<\/strong>\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>\u578b\u5225<\/th>\n<th>\u610f\u601d<\/th>\n<th>\u4ec0\u9ebc\u6642\u5019\u7528<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>Row<\/code><\/td>\n<td>\u5f9e\u8cc7\u6599\u5eab\u8b80\u51fa\u4f86\u7684\u5b8c\u6574\u8cc7\u6599<\/td>\n<td>select \u67e5\u8a62\u7684\u56de\u50b3\u503c<\/td>\n<\/tr>\n<tr>\n<td><code>Insert<\/code><\/td>\n<td>\u65b0\u589e\u6642\u8981\u7d66\u7684\u6b04\u4f4d\uff08\u6709\u9810\u8a2d\u503c\u7684\u53ef\u4ee5\u4e0d\u7d66\uff09<\/td>\n<td>insert \u64cd\u4f5c\u7684\u53c3\u6578<\/td>\n<\/tr>\n<tr>\n<td><code>Update<\/code><\/td>\n<td>\u66f4\u65b0\u6642\u8981\u6539\u7684\u6b04\u4f4d\uff08\u53ea\u7d66\u8981\u6539\u7684\u5c31\u597d\uff09<\/td>\n<td>update \u64cd\u4f5c\u7684\u53c3\u6578<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">\u6709\u4e86\u9019\u4e9b\u578b\u5225\uff0c\u7576\u4f60\u5beb <code>supabase.from('todos').select('*')<\/code> \u6642\uff0cTypeScript \u6703\u81ea\u52d5\u77e5\u9053\u56de\u50b3\u7684\u8cc7\u6599\u6709\u54ea\u4e9b\u6b04\u4f4d\u3001\u662f\u4ec0\u9ebc\u578b\u5225\u3002\u6253\u932f\u6b04\u4f4d\u540d\u7a31\u7de8\u8f2f\u5668\u6703\u7acb\u523b\u5831\u932f\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">API \u5c64\u5c01\u88dd\uff1aCRUD \u51fd\u5f0f + Query Options<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u9019\u662f\u6574\u500b\u5c08\u6848\u7684\u6838\u5fc3\u6a94\u6848\u2014\u2014\u628a Supabase \u7684 CRUD \u64cd\u4f5c\u548c TanStack Query \u7684\u8a2d\u5b9a\u5168\u90e8\u96c6\u4e2d\u5728\u4e00\u8d77\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">CRUD \u51fd\u5f0f<\/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-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/api\/todos.ts<\/span>\n<span class=\"hljs-keyword\">import<\/span> { supabase } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/lib\/supabase'<\/span>\n<span class=\"hljs-keyword\">import<\/span> type { Database } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/types\/database.types'<\/span>\n\n<span class=\"hljs-comment\">\/\/ \u5f9e\u81ea\u52d5\u751f\u6210\u7684\u578b\u5225\u4e2d\u53d6\u51fa\u9700\u8981\u7684\u578b\u5225<\/span>\ntype Todo = Database&#91;<span class=\"hljs-string\">'public'<\/span>]&#91;<span class=\"hljs-string\">'Tables'<\/span>]&#91;<span class=\"hljs-string\">'todos'<\/span>]&#91;<span class=\"hljs-string\">'Row'<\/span>]\ntype TodoInsert = Database&#91;<span class=\"hljs-string\">'public'<\/span>]&#91;<span class=\"hljs-string\">'Tables'<\/span>]&#91;<span class=\"hljs-string\">'todos'<\/span>]&#91;<span class=\"hljs-string\">'Insert'<\/span>]\ntype TodoUpdate = Database&#91;<span class=\"hljs-string\">'public'<\/span>]&#91;<span class=\"hljs-string\">'Tables'<\/span>]&#91;<span class=\"hljs-string\">'todos'<\/span>]&#91;<span class=\"hljs-string\">'Update'<\/span>]\n\n<span class=\"hljs-comment\">\/\/ ===== \u67e5\u8a62\uff08Read\uff09=====<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getTodos<\/span>(<span class=\"hljs-params\">status?: <span class=\"hljs-string\">'completed'<\/span> | <span class=\"hljs-string\">'active'<\/span><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">let<\/span> query = supabase\n    .from(<span class=\"hljs-string\">'todos'<\/span>)           <span class=\"hljs-comment\">\/\/ \u5f9e todos \u8868<\/span>\n    .select(<span class=\"hljs-string\">'*'<\/span>)             <span class=\"hljs-comment\">\/\/ \u9078\u6240\u6709\u6b04\u4f4d<\/span>\n    .order(<span class=\"hljs-string\">'created_at'<\/span>, { <span class=\"hljs-attr\">ascending<\/span>: <span class=\"hljs-literal\">false<\/span> })  <span class=\"hljs-comment\">\/\/ \u65b0\u7684\u5728\u524d<\/span>\n\n  <span class=\"hljs-comment\">\/\/ \u5982\u679c\u6709\u7be9\u9078\u689d\u4ef6<\/span>\n  <span class=\"hljs-keyword\">if<\/span> (status === <span class=\"hljs-string\">'completed'<\/span>) {\n    query = query.eq(<span class=\"hljs-string\">'completed'<\/span>, <span class=\"hljs-literal\">true<\/span>)\n  } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (status === <span class=\"hljs-string\">'active'<\/span>) {\n    query = query.eq(<span class=\"hljs-string\">'completed'<\/span>, <span class=\"hljs-literal\">false<\/span>)\n  }\n\n  <span class=\"hljs-keyword\">const<\/span> { data, error } = <span class=\"hljs-keyword\">await<\/span> query\n\n  <span class=\"hljs-keyword\">if<\/span> (error) <span class=\"hljs-keyword\">throw<\/span> error     <span class=\"hljs-comment\">\/\/ \u6709\u932f\u5c31\u62cb\u51fa\uff0c\u8b93 TanStack Query \u63a5\u4f4f<\/span>\n  <span class=\"hljs-keyword\">return<\/span> data                <span class=\"hljs-comment\">\/\/ \u56de\u50b3 Todo&#91;]<\/span>\n}\n\n<span class=\"hljs-comment\">\/\/ ===== \u67e5\u8a62\u55ae\u7b46\uff08Read\uff09=====<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getTodoById<\/span>(<span class=\"hljs-params\">id: string<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> { data, error } = <span class=\"hljs-keyword\">await<\/span> supabase\n    .from(<span class=\"hljs-string\">'todos'<\/span>)\n    .select(<span class=\"hljs-string\">'*'<\/span>)\n    .eq(<span class=\"hljs-string\">'id'<\/span>, id)            <span class=\"hljs-comment\">\/\/ WHERE id = ?<\/span>\n    .single()                <span class=\"hljs-comment\">\/\/ \u53ea\u8981\u4e00\u7b46\uff08\u56de\u50b3\u7269\u4ef6\u800c\u975e\u9663\u5217\uff09<\/span>\n\n  <span class=\"hljs-keyword\">if<\/span> (error) <span class=\"hljs-keyword\">throw<\/span> error\n  <span class=\"hljs-keyword\">return<\/span> data                <span class=\"hljs-comment\">\/\/ \u56de\u50b3 Todo<\/span>\n}\n\n<span class=\"hljs-comment\">\/\/ ===== \u65b0\u589e\uff08Create\uff09=====<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">createTodo<\/span>(<span class=\"hljs-params\">todo: TodoInsert<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> { data, error } = <span class=\"hljs-keyword\">await<\/span> supabase\n    .from(<span class=\"hljs-string\">'todos'<\/span>)\n    .insert(todo)            <span class=\"hljs-comment\">\/\/ \u63d2\u5165\u4e00\u7b46<\/span>\n    .select()                <span class=\"hljs-comment\">\/\/ \u56de\u50b3\u63d2\u5165\u5f8c\u7684\u5b8c\u6574\u8cc7\u6599<\/span>\n    .single()\n\n  <span class=\"hljs-keyword\">if<\/span> (error) <span class=\"hljs-keyword\">throw<\/span> error\n  <span class=\"hljs-keyword\">return<\/span> data\n}\n\n<span class=\"hljs-comment\">\/\/ ===== \u4fee\u6539\uff08Update\uff09=====<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">updateTodo<\/span>(<span class=\"hljs-params\">id: string, updates: TodoUpdate<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> { data, error } = <span class=\"hljs-keyword\">await<\/span> supabase\n    .from(<span class=\"hljs-string\">'todos'<\/span>)\n    .update(updates)         <span class=\"hljs-comment\">\/\/ \u66f4\u65b0\u6307\u5b9a\u6b04\u4f4d<\/span>\n    .eq(<span class=\"hljs-string\">'id'<\/span>, id)            <span class=\"hljs-comment\">\/\/ WHERE id = ?<\/span>\n    .select()\n    .single()\n\n  <span class=\"hljs-keyword\">if<\/span> (error) <span class=\"hljs-keyword\">throw<\/span> error\n  <span class=\"hljs-keyword\">return<\/span> data\n}\n\n<span class=\"hljs-comment\">\/\/ ===== \u522a\u9664\uff08Delete\uff09=====<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">deleteTodo<\/span>(<span class=\"hljs-params\">id: string<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> { error } = <span class=\"hljs-keyword\">await<\/span> supabase\n    .from(<span class=\"hljs-string\">'todos'<\/span>)\n    .delete()                <span class=\"hljs-comment\">\/\/ \u522a\u9664<\/span>\n    .eq(<span class=\"hljs-string\">'id'<\/span>, id)            <span class=\"hljs-comment\">\/\/ WHERE id = ?<\/span>\n\n  <span class=\"hljs-keyword\">if<\/span> (error) <span class=\"hljs-keyword\">throw<\/span> error\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><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<p class=\"wp-block-paragraph\"><strong>\u6838\u5fc3\u6982\u5ff5\u7ffb\u8b6f<\/strong>\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Supabase \u5beb\u6cd5<\/th>\n<th>\u5c0d\u61c9\u7684 SQL<\/th>\n<th>\u7ffb\u8b6f<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>.from('todos')<\/code><\/td>\n<td><code>FROM todos<\/code><\/td>\n<td>\u6307\u5b9a\u64cd\u4f5c\u54ea\u5f35\u8868<\/td>\n<\/tr>\n<tr>\n<td><code>.select('*')<\/code><\/td>\n<td><code>SELECT *<\/code><\/td>\n<td>\u53d6\u6240\u6709\u6b04\u4f4d<\/td>\n<\/tr>\n<tr>\n<td><code>.eq('id', id)<\/code><\/td>\n<td><code>WHERE id = ?<\/code><\/td>\n<td>\u7be9\u9078\u689d\u4ef6\uff1aid \u7b49\u65bc\u67d0\u500b\u503c<\/td>\n<\/tr>\n<tr>\n<td><code>.order('created_at', { ascending: false })<\/code><\/td>\n<td><code>ORDER BY created_at DESC<\/code><\/td>\n<td>\u6309\u5efa\u7acb\u6642\u9593\u5012\u5e8f\u6392\u5217<\/td>\n<\/tr>\n<tr>\n<td><code>.single()<\/code><\/td>\n<td><code>LIMIT 1<\/code><\/td>\n<td>\u53ea\u56de\u50b3\u4e00\u7b46\uff08\u56de\u50b3\u7269\u4ef6\u800c\u975e\u9663\u5217\uff09<\/td>\n<\/tr>\n<tr>\n<td><code>.insert(todo)<\/code><\/td>\n<td><code>INSERT INTO todos ...<\/code><\/td>\n<td>\u63d2\u5165\u4e00\u7b46\u8cc7\u6599<\/td>\n<\/tr>\n<tr>\n<td><code>.update(updates)<\/code><\/td>\n<td><code>UPDATE todos SET ...<\/code><\/td>\n<td>\u66f4\u65b0\u6307\u5b9a\u6b04\u4f4d<\/td>\n<\/tr>\n<tr>\n<td><code>.delete()<\/code><\/td>\n<td><code>DELETE FROM todos<\/code><\/td>\n<td>\u522a\u9664\u8cc7\u6599<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">**\u89c0\u5bdf\u91cd\u9ede**\uff1a\u6bcf\u500b\u51fd\u5f0f\u7684\u7d50\u69cb\u90fd\u4e00\u6a23\u2014\u2014\u547c\u53eb Supabase\u3001\u89e3\u69cb\u51fa <code>{ data, error }<\/code>\u3001\u6709\u932f\u5c31\u62cb\u51fa\u3001\u6c92\u932f\u5c31\u56de\u50b3 data\u3002\u9019\u662f Supabase Client \u7684\u6a19\u6e96\u6a21\u5f0f\u3002<\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">Query Options \u5b9a\u7fa9<\/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-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/api\/todos.ts\uff08\u63a5\u7e8c\u4e0a\u9762\uff09<\/span>\n<span class=\"hljs-keyword\">import<\/span> { queryOptions } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@tanstack\/react-query'<\/span>\n\n<span class=\"hljs-comment\">\/\/ \u5217\u8868\u7684 query options<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> todosQueryOptions = <span class=\"hljs-function\">(<span class=\"hljs-params\">status?: <span class=\"hljs-string\">'completed'<\/span> | <span class=\"hljs-string\">'active'<\/span><\/span>) =&gt;<\/span>\n  queryOptions({\n    <span class=\"hljs-attr\">queryKey<\/span>: &#91;<span class=\"hljs-string\">'todos'<\/span>, <span class=\"hljs-string\">'list'<\/span>, { status }],   <span class=\"hljs-comment\">\/\/ status \u4e0d\u540c \u2192 \u5feb\u53d6\u4e0d\u540c<\/span>\n    <span class=\"hljs-attr\">queryFn<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> getTodos(status),\n  })\n\n<span class=\"hljs-comment\">\/\/ \u55ae\u7b46\u7684 query options<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> todoDetailQueryOptions = <span class=\"hljs-function\">(<span class=\"hljs-params\">todoId: string<\/span>) =&gt;<\/span>\n  queryOptions({\n    <span class=\"hljs-attr\">queryKey<\/span>: &#91;<span class=\"hljs-string\">'todos'<\/span>, <span class=\"hljs-string\">'detail'<\/span>, todoId],\n    <span class=\"hljs-attr\">queryFn<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> getTodoById(todoId),\n  })\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><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<p class=\"wp-block-paragraph\"><strong>\u7ffb\u8b6f<\/strong>\uff1a\u9019\u8ddf\u7b2c 4 \u7bc7\u5b78\u7684\u5b8c\u5168\u4e00\u6a23\u2014\u2014\u628a <code>queryKey<\/code> \u548c <code>queryFn<\/code> \u6253\u5305\u5728\u4e00\u8d77\uff0c\u8b93 loader \u548c\u5143\u4ef6\u5171\u7528\u3002\u552f\u4e00\u7684\u5dee\u5225\u662f <code>queryFn<\/code> \u88e1\u9762\u4e0d\u518d\u662f <code>fetch('\/api\/...')<\/code>\uff0c\u800c\u662f\u6211\u5011\u4e0a\u9762\u5beb\u7684 Supabase CRUD \u51fd\u5f0f\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">TanStack Query \u6574\u5408\uff1aMutation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u7b2c 3 \u7bc7\u5b78\u4e86 <code>useQuery<\/code>\uff08\u8b80\u53d6\u8cc7\u6599\uff09\uff0c\u73fe\u5728\u8981\u7528 <code>useMutation<\/code>\uff08\u4fee\u6539\u8cc7\u6599\uff09\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u57fa\u672c\u7684 Mutation\uff1a\u65b0\u589e\u5f85\u8fa6<\/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-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/components\/CreateTodoForm.tsx<\/span>\n<span class=\"hljs-keyword\">import<\/span> { useMutation, useQueryClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@tanstack\/react-query'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { createTodo } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/api\/todos'<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">CreateTodoForm<\/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>: createTodo,                      <span class=\"hljs-comment\">\/\/ \u8981\u57f7\u884c\u7684\u51fd\u5f0f<\/span>\n    <span class=\"hljs-attr\">onSuccess<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n      queryClient.invalidateQueries({            <span class=\"hljs-comment\">\/\/ \u6210\u529f\u5f8c\uff0c\u628a\u5217\u8868\u5feb\u53d6\u6a19\u8a18\u70ba\u300c\u904e\u671f\u300d<\/span>\n        <span class=\"hljs-attr\">queryKey<\/span>: &#91;<span class=\"hljs-string\">'todos'<\/span>, <span class=\"hljs-string\">'list'<\/span>],\n      })\n    },\n  })\n\n  <span class=\"hljs-keyword\">const<\/span> handleSubmit = <span class=\"hljs-function\">(<span class=\"hljs-params\">e: React.FormEvent&lt;HTMLFormElement&gt;<\/span>) =&gt;<\/span> {\n    e.preventDefault()\n    <span class=\"hljs-keyword\">const<\/span> formData = <span class=\"hljs-keyword\">new<\/span> FormData(e.currentTarget)\n    mutation.mutate({                            <span class=\"hljs-comment\">\/\/ \u89f8\u767c mutation<\/span>\n      <span class=\"hljs-attr\">title<\/span>: formData.get(<span class=\"hljs-string\">'title'<\/span>) <span class=\"hljs-keyword\">as<\/span> string,\n      <span class=\"hljs-attr\">user_id<\/span>: <span class=\"hljs-string\">'...'<\/span>,                            <span class=\"hljs-comment\">\/\/ \u5f9e\u8a8d\u8b49\u72c0\u614b\u53d6\u5f97<\/span>\n    })\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\">input<\/span> <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"title\"<\/span> <span class=\"hljs-attr\">placeholder<\/span>=<span class=\"hljs-string\">\"\u65b0\u589e\u5f85\u8fa6...\"<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n        <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"submit\"<\/span>\n        <span class=\"hljs-attr\">disabled<\/span>=<span class=\"hljs-string\">{mutation.isPending}<\/span>            \/\/ \u9001\u51fa\u4e2d\u6642\u505c\u7528\u6309\u9215\n      &gt;<\/span>\n        {mutation.isPending ? '\u65b0\u589e\u4e2d...' : '\u65b0\u589e'}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n      {mutation.isError &amp;&amp; (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>\u65b0\u589e\u5931\u6557\uff1a{mutation.error.message}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      )}\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-10\"><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<p class=\"wp-block-paragraph\"><strong>\u9010\u884c\u7ffb\u8b6f<\/strong>\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>\u7a0b\u5f0f\u78bc<\/th>\n<th>\u7ffb\u8b6f<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>useMutation({ mutationFn })<\/code><\/td>\n<td>\u5efa\u7acb\u4e00\u500b\u300c\u4fee\u6539\u8cc7\u6599\u300d\u7684\u64cd\u4f5c\uff08\u5c0d\u61c9 useQuery \u7684\u300c\u8b80\u53d6\u8cc7\u6599\u300d\uff09<\/td>\n<\/tr>\n<tr>\n<td><code>mutationFn: createTodo<\/code><\/td>\n<td>\u7576\u89f8\u767c mutation \u6642\uff0c\u57f7\u884c createTodo \u51fd\u5f0f<\/td>\n<\/tr>\n<tr>\n<td><code>onSuccess<\/code><\/td>\n<td>\u64cd\u4f5c\u6210\u529f\u5f8c\u8981\u505a\u4ec0\u9ebc<\/td>\n<\/tr>\n<tr>\n<td><code>invalidateQueries({ queryKey: ['todos', 'list'] })<\/code><\/td>\n<td>\u628a\u6240\u6709\u4ee5 <code>['todos', 'list']<\/code> \u958b\u982d\u7684\u5feb\u53d6\u6a19\u8a18\u70ba\u904e\u671f\uff0c\u81ea\u52d5\u91cd\u65b0\u6293\u53d6<\/td>\n<\/tr>\n<tr>\n<td><code>mutation.mutate({ ... })<\/code><\/td>\n<td>\u89f8\u767c mutation\uff0c\u50b3\u5165\u53c3\u6578<\/td>\n<\/tr>\n<tr>\n<td><code>mutation.isPending<\/code><\/td>\n<td>mutation \u6b63\u5728\u57f7\u884c\u4e2d\u55ce\uff1f<\/td>\n<\/tr>\n<tr>\n<td><code>mutation.isError<\/code><\/td>\n<td>mutation \u5931\u6557\u4e86\u55ce\uff1f<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Mutation \u7684\u6d41\u7a0b<\/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-11\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\">\u4f7f\u7528\u8005\u9ede\u300c\u65b0\u589e\u300d\u6309\u9215\n  \u2193\n<span class=\"hljs-selector-tag\">mutation<\/span><span class=\"hljs-selector-class\">.mutate<\/span>({ <span class=\"hljs-attribute\">title<\/span>: <span class=\"hljs-string\">'\u8cb7\u725b\u5976'<\/span> })\n  \u2193\n\u547c\u53eb <span class=\"hljs-selector-tag\">createTodo<\/span> \u2192 <span class=\"hljs-selector-tag\">Supabase<\/span> \u63d2\u5165\u8cc7\u6599\n  \u2193 \u6210\u529f\n<span class=\"hljs-selector-tag\">onSuccess<\/span> \u57f7\u884c\n  \u2193\n<span class=\"hljs-selector-tag\">invalidateQueries<\/span>(<span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">'todos'<\/span>, <span class=\"hljs-string\">'list'<\/span>]<\/span>)\n  \u2193\n<span class=\"hljs-selector-tag\">TanStack<\/span> <span class=\"hljs-selector-tag\">Query<\/span> \u81ea\u52d5\u91cd\u65b0\u6293\u53d6\u5217\u8868 \u2192 \u756b\u9762\u66f4\u65b0\uff01\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u522a\u9664 + \u5b8c\u6210\u5207\u63db<\/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-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/components\/TodoItem.tsx<\/span>\n<span class=\"hljs-keyword\">import<\/span> { useMutation, useQueryClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@tanstack\/react-query'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { updateTodo, deleteTodo } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/api\/todos'<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">TodoItem<\/span>(<span class=\"hljs-params\">{ todo }: { todo: Todo }<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> queryClient = useQueryClient()\n\n  <span class=\"hljs-comment\">\/\/ \u5207\u63db\u5b8c\u6210\u72c0\u614b<\/span>\n  <span class=\"hljs-keyword\">const<\/span> toggleMutation = useMutation({\n    <span class=\"hljs-attr\">mutationFn<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> updateTodo(todo.id, { <span class=\"hljs-attr\">completed<\/span>: !todo.completed }),\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\">\/\/ \u522a\u9664<\/span>\n  <span class=\"hljs-keyword\">const<\/span> deleteMutation = useMutation({\n    <span class=\"hljs-attr\">mutationFn<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> deleteTodo(todo.id),\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-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/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> toggleMutation.mutate()}\n        disabled={toggleMutation.isPending}\n      \/&gt;\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span>\n        <span class=\"hljs-attr\">textDecoration:<\/span> <span class=\"hljs-attr\">todo.completed<\/span> ? '<span class=\"hljs-attr\">line-through<\/span>' <span class=\"hljs-attr\">:<\/span> '<span class=\"hljs-attr\">none<\/span>',\n      }}&gt;<\/span>\n        {todo.title}\n      <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>\n        <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> deleteMutation.mutate()}\n        disabled={deleteMutation.isPending}\n      &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><\/span>\n  )\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><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<p class=\"wp-block-paragraph\"><strong>\u7ffb\u8b6f<\/strong>\uff1a\u6bcf\u500b\u5f85\u8fa6\u9805\u76ee\u6709\u5169\u500b mutation\u2014\u2014\u5207\u63db\u5b8c\u6210\u72c0\u614b\u548c\u522a\u9664\u3002\u5169\u8005\u7684\u6a21\u5f0f\u4e00\u6a21\u4e00\u6a23\uff1a<code>mutationFn<\/code> \u505a\u4e8b\uff0c<code>onSuccess<\/code> \u88e1\u9762 invalidate \u8b93\u5217\u8868\u91cd\u65b0\u6293\u53d6\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Optimistic Updates\uff1a\u5373\u6642\u56de\u994b<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u4e0a\u9762\u7684\u505a\u6cd5\u6709\u4e00\u500b\u5c0f\u554f\u984c\uff1a\u4f7f\u7528\u8005\u52fe\u9078\u300c\u5b8c\u6210\u300d\u5f8c\uff0c\u8981\u7b49 Supabase \u56de\u61c9\u3001\u518d\u7b49\u5217\u8868\u91cd\u65b0\u6293\u53d6\uff0c\u624d\u6703\u770b\u5230\u756b\u9762\u66f4\u65b0\u3002\u5982\u679c\u7db2\u8def\u6162\uff0c\u6703\u6709\u660e\u986f\u7684\u5ef6\u9072\u611f\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Optimistic Update<\/strong>\uff08\u6a02\u89c0\u66f4\u65b0\uff09\u7684\u60f3\u6cd5\u662f\uff1a\u300c\u5148\u5047\u8a2d\u6210\u529f\uff0c\u7acb\u523b\u66f4\u65b0\u756b\u9762\u3002\u5982\u679c\u5931\u6557\u518d\u9000\u56de\u53bb\u3002\u300d<\/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-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> toggleMutation = useMutation({\n  <span class=\"hljs-attr\">mutationFn<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> updateTodo(todo.id, { <span class=\"hljs-attr\">completed<\/span>: !todo.completed }),\n\n  <span class=\"hljs-comment\">\/\/ \u5728 API \u56de\u61c9\u4e4b\u524d\u5c31\u5148\u66f4\u65b0\u756b\u9762<\/span>\n  <span class=\"hljs-attr\">onMutate<\/span>: <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n    <span class=\"hljs-comment\">\/\/ 1. \u53d6\u6d88\u6b63\u5728\u9032\u884c\u7684\u67e5\u8a62\uff08\u907f\u514d\u8986\u84cb\u6211\u5011\u7684\u6a02\u89c0\u66f4\u65b0\uff09<\/span>\n    <span class=\"hljs-keyword\">await<\/span> queryClient.cancelQueries({ <span class=\"hljs-attr\">queryKey<\/span>: &#91;<span class=\"hljs-string\">'todos'<\/span>, <span class=\"hljs-string\">'list'<\/span>] })\n\n    <span class=\"hljs-comment\">\/\/ 2. \u4fdd\u5b58\u76ee\u524d\u7684\u5feb\u53d6\uff08\u842c\u4e00\u5931\u6557\u8981\u56de\u5fa9\uff09<\/span>\n    <span class=\"hljs-keyword\">const<\/span> previousTodos = queryClient.getQueryData(&#91;<span class=\"hljs-string\">'todos'<\/span>, <span class=\"hljs-string\">'list'<\/span>])\n\n    <span class=\"hljs-comment\">\/\/ 3. \u6a02\u89c0\u66f4\u65b0\u5feb\u53d6<\/span>\n    queryClient.setQueryData(\n      &#91;<span class=\"hljs-string\">'todos'<\/span>, <span class=\"hljs-string\">'list'<\/span>],\n      (old: Todo&#91;] | <span class=\"hljs-literal\">undefined<\/span>) =&gt;\n        old?.map(<span class=\"hljs-function\"><span class=\"hljs-params\">t<\/span> =&gt;<\/span>\n          t.id === todo.id\n            ? { ...t, <span class=\"hljs-attr\">completed<\/span>: !t.completed }   <span class=\"hljs-comment\">\/\/ \u5148\u628a\u9019\u7b46\u6539\u6389<\/span>\n            : t\n        )\n    )\n\n    <span class=\"hljs-keyword\">return<\/span> { previousTodos }   <span class=\"hljs-comment\">\/\/ \u56de\u50b3\u820a\u8cc7\u6599\uff08\u7d66 onError \u7528\uff09<\/span>\n  },\n\n  <span class=\"hljs-comment\">\/\/ \u5931\u6557\u6642\u56de\u5fa9\u5230\u4e4b\u524d\u7684\u72c0\u614b<\/span>\n  <span class=\"hljs-attr\">onError<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">_err, _vars, context<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-keyword\">if<\/span> (context?.previousTodos) {\n      queryClient.setQueryData(&#91;<span class=\"hljs-string\">'todos'<\/span>, <span class=\"hljs-string\">'list'<\/span>], context.previousTodos)\n    }\n  },\n\n  <span class=\"hljs-comment\">\/\/ \u4e0d\u7ba1\u6210\u529f\u5931\u6557\uff0c\u6700\u5f8c\u90fd\u91cd\u65b0\u6293\u4e00\u6b21\u78ba\u4fdd\u8cc7\u6599\u6b63\u78ba<\/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>, <span class=\"hljs-string\">'list'<\/span>] })\n  },\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><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<p class=\"wp-block-paragraph\"><strong>\u9010\u884c\u7ffb\u8b6f<\/strong>\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>\u7a0b\u5f0f\u78bc<\/th>\n<th>\u7ffb\u8b6f<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>onMutate<\/code><\/td>\n<td>\u5728 API \u8acb\u6c42\u9001\u51fa\u7684<strong>\u540c\u6642<\/strong>\u57f7\u884c\uff08\u4e0d\u7b49\u56de\u61c9\uff09<\/td>\n<\/tr>\n<tr>\n<td><code>cancelQueries<\/code><\/td>\n<td>\u5982\u679c\u6b63\u5728\u91cd\u65b0\u6293\u5217\u8868\uff0c\u5148\u53d6\u6d88\uff08\u4e0d\u7136\u6703\u628a\u6211\u5011\u7684\u6a02\u89c0\u66f4\u65b0\u84cb\u6389\uff09<\/td>\n<\/tr>\n<tr>\n<td><code>getQueryData<\/code><\/td>\n<td>\u5f9e\u5feb\u53d6\u62ff\u51fa\u76ee\u524d\u7684\u8cc7\u6599\uff08\u5099\u4efd\uff09<\/td>\n<\/tr>\n<tr>\n<td><code>setQueryData<\/code><\/td>\n<td>\u76f4\u63a5\u4fee\u6539\u5feb\u53d6\u88e1\u7684\u8cc7\u6599\uff08\u4e0d\u7d93\u904e API\uff09<\/td>\n<\/tr>\n<tr>\n<td><code>return { previousTodos }<\/code><\/td>\n<td>\u628a\u5099\u4efd\u50b3\u7d66 onError<\/td>\n<\/tr>\n<tr>\n<td><code>onError<\/code><\/td>\n<td>API \u5931\u6557\u6642\uff0c\u7528\u5099\u4efd\u628a\u5feb\u53d6\u9084\u539f<\/td>\n<\/tr>\n<tr>\n<td><code>onSettled<\/code><\/td>\n<td>\u4e0d\u7ba1\u6210\u529f\u6216\u5931\u6557\uff0c\u6700\u5f8c\u90fd invalidate \u4e00\u6b21\uff0c\u78ba\u4fdd\u5feb\u53d6\u8ddf\u8cc7\u6599\u5eab\u540c\u6b65<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Optimistic Update \u7684\u6d41\u7a0b<\/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\u52fe\u9078\u300c\u5b8c\u6210\u300d\n  \u2193\nonMutate \u57f7\u884c\uff08API \u9084\u6c92\u56de\u61c9\uff09\n  \u2192 \u5099\u4efd\u820a\u5feb\u53d6\n  \u2192 \u76f4\u63a5\u6539\u5feb\u53d6\uff08\u756b\u9762\u7acb\u523b\u66f4\u65b0\uff01\uff09\n  \u2193\n\u540c\u6642\uff0cAPI \u8acb\u6c42\u9001\u51fa...\n  \u2193\nAPI \u6210\u529f \u2192 onSettled \u2192 invalidate \u2192 \u91cd\u65b0\u6293\u53d6\u78ba\u8a8d\nAPI \u5931\u6557 \u2192 onError \u2192 \u7528\u5099\u4efd\u9084\u539f\u5feb\u53d6\uff08\u756b\u9762\u56de\u5fa9\uff09\n           \u2192 onSettled \u2192 invalidate \u2192 \u91cd\u65b0\u6293\u53d6\n<\/code><\/span><\/pre><\/div>\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">**\u4ec0\u9ebc\u6642\u5019\u8a72\u7528 Optimistic Update\uff1f** \u9069\u5408\u300c\u4f7f\u7528\u8005\u9810\u671f\u7acb\u523b\u770b\u5230\u53cd\u61c9\u300d\u7684\u64cd\u4f5c\uff0c\u50cf\u662f\u52fe\u9078\u5b8c\u6210\u3001\u522a\u9664\u3002\u4e0d\u9069\u5408\u300c\u7d50\u679c\u4e0d\u78ba\u5b9a\u300d\u7684\u64cd\u4f5c\uff0c\u50cf\u662f\u63d0\u4ea4\u8868\u55ae\uff08\u53ef\u80fd\u6703\u9a57\u8b49\u5931\u6557\uff09\u3002<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">TanStack Router \u6574\u5408\uff1a\u8def\u7531\u7d50\u69cb<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u6839\u8def\u7531\uff1a\u8a2d\u5b9a Context<\/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-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/routes\/__root.tsx<\/span>\n<span class=\"hljs-keyword\">import<\/span> { createRootRouteWithContext, Outlet } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@tanstack\/react-router'<\/span>\n<span class=\"hljs-keyword\">import<\/span> type { QueryClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@tanstack\/react-query'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { supabase } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/lib\/supabase'<\/span>\n<span class=\"hljs-keyword\">import<\/span> type { Session } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@supabase\/supabase-js'<\/span>\n\ninterface RouterContext {\n  <span class=\"hljs-attr\">queryClient<\/span>: QueryClient\n  <span class=\"hljs-attr\">session<\/span>: Session | <span class=\"hljs-literal\">null<\/span>       <span class=\"hljs-comment\">\/\/ \u8a8d\u8b49\u72c0\u614b\u4e5f\u653e\u9032 context<\/span>\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> Route = createRootRouteWithContext&lt;RouterContext&gt;()({\n  <span class=\"hljs-attr\">component<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/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\">nav<\/span>&gt;<\/span>{\/* \u5c0e\u89bd\u5217 *\/}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">nav<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Outlet<\/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-14\"><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<p class=\"wp-block-paragraph\"><strong>\u7ffb\u8b6f<\/strong>\uff1a\u8ddf\u7b2c 4 \u7bc7\u4e00\u6a23\uff0c\u628a <code>queryClient<\/code> \u653e\u9032 Router \u7684 context\u3002\u9019\u88e1\u984d\u5916\u52a0\u4e86 <code>session<\/code>\uff08\u4f7f\u7528\u8005\u767b\u5165\u72c0\u614b\uff09\uff0c\u8b93\u6240\u6709\u8def\u7531\u90fd\u80fd\u77e5\u9053\u300c\u73fe\u5728\u6709\u6c92\u6709\u4eba\u767b\u5165\u300d\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u5f85\u8fa6\u5217\u8868\u9801\uff1aloader + search params<\/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-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/routes\/todos\/index.tsx<\/span>\n<span class=\"hljs-keyword\">import<\/span> { createFileRoute } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@tanstack\/react-router'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { useQuery } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@tanstack\/react-query'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { z } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'zod'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { todosQueryOptions } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/..\/api\/todos'<\/span>\n\n<span class=\"hljs-comment\">\/\/ search params \u9a57\u8b49<\/span>\n<span class=\"hljs-keyword\">const<\/span> todosSearchSchema = z.object({\n  <span class=\"hljs-attr\">status<\/span>: z.enum(&#91;<span class=\"hljs-string\">'all'<\/span>, <span class=\"hljs-string\">'completed'<\/span>, <span class=\"hljs-string\">'active'<\/span>]).optional(),\n})\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> Route = createFileRoute(<span class=\"hljs-string\">'\/todos\/'<\/span>)({\n  <span class=\"hljs-attr\">validateSearch<\/span>: todosSearchSchema,\n\n  <span class=\"hljs-attr\">loader<\/span>: <span class=\"hljs-keyword\">async<\/span> ({ search, context }) =&gt; {\n    <span class=\"hljs-comment\">\/\/ \u7528 search.status \u6c7a\u5b9a\u8981\u6293\u4ec0\u9ebc\u8cc7\u6599<\/span>\n    <span class=\"hljs-keyword\">await<\/span> context.queryClient.ensureQueryData(\n      todosQueryOptions(\n        search.status === <span class=\"hljs-string\">'all'<\/span> ? <span class=\"hljs-literal\">undefined<\/span> : search.status\n      )\n    )\n  },\n\n  <span class=\"hljs-attr\">component<\/span>: TodoListPage,\n})\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">TodoListPage<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> { status } = Route.useSearch()\n  <span class=\"hljs-keyword\">const<\/span> { <span class=\"hljs-attr\">data<\/span>: todos } = useQuery(\n    todosQueryOptions(status === <span class=\"hljs-string\">'all'<\/span> ? <span class=\"hljs-literal\">undefined<\/span> : status)\n  )\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\">h1<\/span>&gt;<\/span>\u6211\u7684\u5f85\u8fa6<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">FilterTabs<\/span> <span class=\"hljs-attr\">currentStatus<\/span>=<span class=\"hljs-string\">{status}<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CreateTodoForm<\/span> \/&gt;<\/span>\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\">TodoItem<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{todo.id}<\/span> <span class=\"hljs-attr\">todo<\/span>=<span class=\"hljs-string\">{todo}<\/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-15\"><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<p class=\"wp-block-paragraph\"><strong>\u7ffb\u8b6f<\/strong>\uff1a\u9019\u5c31\u662f\u7b2c 4 \u7bc7\u5b78\u7684\u300cSearch Params \u9a45\u52d5\u67e5\u8a62\u300d\u5728\u5be6\u969b\u5c08\u6848\u4e2d\u7684\u61c9\u7528\u3002URL <code>\/todos?status=completed<\/code> \u6703\u53ea\u986f\u793a\u5df2\u5b8c\u6210\u7684\u5f85\u8fa6\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u5f85\u8fa6\u8a73\u60c5\u9801<\/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-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/routes\/todos\/$todoId.tsx<\/span>\n<span class=\"hljs-keyword\">import<\/span> { createFileRoute } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@tanstack\/react-router'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { useQuery } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@tanstack\/react-query'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { todoDetailQueryOptions } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/..\/api\/todos'<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> Route = createFileRoute(<span class=\"hljs-string\">'\/todos\/$todoId'<\/span>)({\n  <span class=\"hljs-attr\">loader<\/span>: <span class=\"hljs-keyword\">async<\/span> ({ params, context }) =&gt; {\n    <span class=\"hljs-keyword\">await<\/span> context.queryClient.ensureQueryData(\n      todoDetailQueryOptions(params.todoId)\n    )\n  },\n  <span class=\"hljs-attr\">pendingComponent<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/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  <span class=\"hljs-attr\">errorComponent<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">{ error }<\/span>) =&gt;<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\u627e\u4e0d\u5230\u9019\u7b46\u5f85\u8fa6\uff1a{error.message}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>,\n  <span class=\"hljs-attr\">component<\/span>: TodoDetailPage,\n})\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">TodoDetailPage<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> { todoId } = Route.useParams()\n  <span class=\"hljs-keyword\">const<\/span> { <span class=\"hljs-attr\">data<\/span>: todo } = useQuery(todoDetailQueryOptions(todoId))\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">article<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>{todo?.title}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>\u72c0\u614b\uff1a{todo?.completed ? '\u5df2\u5b8c\u6210' : '\u672a\u5b8c\u6210'}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>\u5efa\u7acb\u6642\u9593\uff1a{todo?.created_at}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">article<\/span>&gt;<\/span><\/span>\n  )\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><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<p class=\"wp-block-paragraph\"><strong>\u7ffb\u8b6f<\/strong>\uff1a\u9019\u8ddf\u7b2c 4 \u7bc7\u7684 <code>postDetailQueryOptions<\/code> \u7bc4\u4f8b\u5e7e\u4e4e\u4e00\u6a21\u4e00\u6a23\uff0c\u53ea\u662f\u628a <code>fetch<\/code> \u63db\u6210\u4e86 Supabase \u7684 <code>getTodoById<\/code>\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u8a8d\u8b49\u6d41\u7a0b\u7c21\u4ecb<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u7d14\u524d\u7aef\u61c9\u7528\u4e2d\uff0cSupabase Auth \u8ca0\u8cac\u4f7f\u7528\u8005\u767b\u5165\/\u767b\u51fa\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u767b\u5165<\/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-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/routes\/login.tsx<\/span>\n<span class=\"hljs-keyword\">import<\/span> { supabase } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/lib\/supabase'<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">LoginPage<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> handleEmailLogin = <span class=\"hljs-keyword\">async<\/span> (email: string, <span class=\"hljs-attr\">password<\/span>: string) =&gt; {\n    <span class=\"hljs-keyword\">const<\/span> { error } = <span class=\"hljs-keyword\">await<\/span> supabase.auth.signInWithPassword({\n      email,\n      password,\n    })\n    <span class=\"hljs-keyword\">if<\/span> (error) alert(error.message)\n  }\n\n  <span class=\"hljs-keyword\">const<\/span> handleGoogleLogin = <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n    <span class=\"hljs-keyword\">await<\/span> supabase.auth.signInWithOAuth({\n      <span class=\"hljs-attr\">provider<\/span>: <span class=\"hljs-string\">'google'<\/span>,\n    })\n  }\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      {\/* \u5e33\u865f\u5bc6\u78bc\u767b\u5165\u8868\u55ae *\/}\n      {\/* Google \u767b\u5165\u6309\u9215 *\/}\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-17\"><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<p class=\"wp-block-paragraph\"><strong>\u7ffb\u8b6f<\/strong>\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>\u7a0b\u5f0f\u78bc<\/th>\n<th>\u7ffb\u8b6f<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>signInWithPassword<\/code><\/td>\n<td>\u7528\u5e33\u865f\u5bc6\u78bc\u767b\u5165<\/td>\n<\/tr>\n<tr>\n<td><code>signInWithOAuth({ provider: 'google' })<\/code><\/td>\n<td>\u8df3\u8f49\u5230 Google \u767b\u5165\u9801\u9762<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">\u8a8d\u8b49\u72c0\u614b\u7ba1\u7406\uff1aSession Listener<\/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-18\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/main.tsx<\/span>\n<span class=\"hljs-keyword\">import<\/span> { useState, useEffect } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { supabase } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/lib\/supabase'<\/span>\n<span class=\"hljs-keyword\">import<\/span> type { Session } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@supabase\/supabase-js'<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">App<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> &#91;session, setSession] = useState&lt;Session | <span class=\"hljs-literal\">null<\/span>&gt;(<span class=\"hljs-literal\">null<\/span>)\n\n  useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    <span class=\"hljs-comment\">\/\/ \u53d6\u5f97\u76ee\u524d\u7684 session<\/span>\n    supabase.auth.getSession().then(<span class=\"hljs-function\">(<span class=\"hljs-params\">{ data: { session } }<\/span>) =&gt;<\/span> {\n      setSession(session)\n    })\n\n    <span class=\"hljs-comment\">\/\/ \u76e3\u807d session \u8b8a\u5316\uff08\u767b\u5165\u3001\u767b\u51fa\u3001token \u66f4\u65b0\uff09<\/span>\n    <span class=\"hljs-keyword\">const<\/span> { <span class=\"hljs-attr\">data<\/span>: { subscription } } = supabase.auth.onAuthStateChange(\n      <span class=\"hljs-function\">(<span class=\"hljs-params\">_event, session<\/span>) =&gt;<\/span> {\n        setSession(session)\n      }\n    )\n\n    <span class=\"hljs-comment\">\/\/ \u5143\u4ef6\u5378\u8f09\u6642\u53d6\u6d88\u76e3\u807d<\/span>\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> subscription.unsubscribe()\n  }, &#91;])\n\n  <span class=\"hljs-comment\">\/\/ \u628a session \u50b3\u9032 Router context<\/span>\n  <span class=\"hljs-keyword\">const<\/span> router = createRouter({\n    routeTree,\n    <span class=\"hljs-attr\">context<\/span>: { queryClient, session },\n  })\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">QueryClientProvider<\/span> <span class=\"hljs-attr\">client<\/span>=<span class=\"hljs-string\">{queryClient}<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">RouterProvider<\/span> <span class=\"hljs-attr\">router<\/span>=<span class=\"hljs-string\">{router}<\/span> \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">QueryClientProvider<\/span>&gt;<\/span><\/span>\n  )\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><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<p class=\"wp-block-paragraph\"><strong>\u7ffb\u8b6f<\/strong>\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>\u7a0b\u5f0f\u78bc<\/th>\n<th>\u7ffb\u8b6f<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>getSession()<\/code><\/td>\n<td>\u7a0b\u5f0f\u555f\u52d5\u6642\uff0c\u6aa2\u67e5\u6709\u6c92\u6709\u5df2\u767b\u5165\u7684 session<\/td>\n<\/tr>\n<tr>\n<td><code>onAuthStateChange<\/code><\/td>\n<td>\u6301\u7e8c\u76e3\u807d\u2014\u2014\u7576\u4f7f\u7528\u8005\u767b\u5165\u3001\u767b\u51fa\u3001token \u81ea\u52d5\u66f4\u65b0\u6642\uff0c\u901a\u77e5\u6211<\/td>\n<\/tr>\n<tr>\n<td><code>subscription.unsubscribe()<\/code><\/td>\n<td>\u5143\u4ef6\u5378\u8f09\u6642\u505c\u6b62\u76e3\u807d\uff08\u907f\u514d\u8a18\u61b6\u9ad4\u6d29\u6f0f\uff09<\/td>\n<\/tr>\n<tr>\n<td><code>context: { queryClient, session }<\/code><\/td>\n<td>\u628a session \u653e\u9032 Router \u7684 context\uff0c\u6240\u6709\u8def\u7531\u90fd\u80fd\u5b58\u53d6<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Route Guard\uff1a\u4fdd\u8b77\u9700\u8981\u767b\u5165\u7684\u9801\u9762<\/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-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/routes\/todos\/index.tsx\uff08\u52a0\u4e0a beforeLoad\uff09<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> Route = createFileRoute(<span class=\"hljs-string\">'\/todos\/'<\/span>)({\n  <span class=\"hljs-attr\">beforeLoad<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">{ context }<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-keyword\">if<\/span> (!context.session) {\n      <span class=\"hljs-keyword\">throw<\/span> redirect({ <span class=\"hljs-attr\">to<\/span>: <span class=\"hljs-string\">'\/login'<\/span> })     <span class=\"hljs-comment\">\/\/ \u6c92\u767b\u5165\u5c31\u8df3\u8f49\u5230\u767b\u5165\u9801<\/span>\n    }\n  },\n  <span class=\"hljs-comment\">\/\/ ... loader, component \u7b49<\/span>\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><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<p class=\"wp-block-paragraph\"><strong>\u7ffb\u8b6f<\/strong>\uff1a<code>beforeLoad<\/code> \u5728 loader \u4e4b\u524d\u57f7\u884c\u3002\u5982\u679c context \u88e1\u9762\u6c92\u6709 session\uff08\u4ee3\u8868\u6c92\u767b\u5165\uff09\uff0c\u5c31 redirect \u5230\u767b\u5165\u9801\u3002\u9019\u5c31\u662f\u7d14\u524d\u7aef\u7684\u300c\u8def\u7531\u5b88\u885b\u300d\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u8a8d\u8b49\u6d41\u7a0b\u5168\u8c8c<\/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-20\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">\u4f7f\u7528\u8005\u6253\u958b \/todos\n  \u2193\nRouter \u5339\u914d\u5230 \/todos \u8def\u7531\n  \u2193\nbeforeLoad \u57f7\u884c\n  \u2192 \u6aa2\u67e5 context.session\n  \u2192 \u6709 session \u2192 \u7e7c\u7e8c\n  \u2192 \u6c92 session \u2192 redirect(<span class=\"hljs-string\">'\/login'<\/span>)\n  \u2193\nloader \u57f7\u884c\n  \u2192 ensureQueryData(todosQueryOptions)\n  \u2192 Supabase Client \u767c\u51fa\u8acb\u6c42\u6642\u81ea\u52d5\u5e36\u4e0a session \u7684 token\n  \u2192 RLS \u6839\u64da token \u88e1\u7684 user_id \u7be9\u9078\u8cc7\u6599\n  \u2193\ncomponent \u6e32\u67d3 \u2192 \u53ea\u770b\u5230\u81ea\u5df1\u7684\u5f85\u8fa6\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><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<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u90e8\u7f72\uff1aVite Build + \u975c\u614b\u8a17\u7ba1<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u6574\u500b\u61c9\u7528\u662f\u7d14\u524d\u7aef\u2014\u2014\u6c92\u6709 Node.js \u4f3a\u670d\u5668\uff0c\u53ea\u6709 HTML + JS + CSS\u3002\u90e8\u7f72\u975e\u5e38\u7c21\u55ae\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Build<\/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\">npm run build\n<\/code><\/span><\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Vite \u6703\u5728 <code>dist\/<\/code> \u8cc7\u6599\u593e\u7522\u51fa\u975c\u614b\u6a94\u6848\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\">dist\/\n\u251c\u2500\u2500 index.html          \u2190 \u55ae\u4e00 HTML \u5165\u53e3\n\u251c\u2500\u2500 assets\/\n\u2502   \u251c\u2500\u2500 index-abc123.js  \u2190 \u6253\u5305\u5f8c\u7684 JavaScript\n\u2502   \u2514\u2500\u2500 index-def456.css \u2190 \u6253\u5305\u5f8c\u7684 CSS\n<\/code><\/span><\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">\u90e8\u7f72\u5230\u975c\u614b\u8a17\u7ba1<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>\u5e73\u53f0<\/th>\n<th>\u90e8\u7f72\u65b9\u5f0f<\/th>\n<th>\u91cd\u9ede\u8a2d\u5b9a<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>Vercel<\/strong><\/td>\n<td>\u9023\u63a5 GitHub repo\uff0c\u81ea\u52d5\u90e8\u7f72<\/td>\n<td>Framework Preset \u9078 Vite<\/td>\n<\/tr>\n<tr>\n<td><strong>Netlify<\/strong><\/td>\n<td>\u9023\u63a5 GitHub repo\uff0c\u81ea\u52d5\u90e8\u7f72<\/td>\n<td>Build command: <code>npm run build<\/code>\uff0cPublish directory: <code>dist<\/code><\/td>\n<\/tr>\n<tr>\n<td><strong>Cloudflare Pages<\/strong><\/td>\n<td>\u9023\u63a5 GitHub repo\uff0c\u81ea\u52d5\u90e8\u7f72<\/td>\n<td>\u540c\u4e0a<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">SPA \u8def\u7531\u7684\u91cd\u9ede\u8a2d\u5b9a<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u56e0\u70ba\u662f SPA\uff0c\u6240\u6709\u8def\u7531\u90fd\u9760\u524d\u7aef JavaScript \u8655\u7406\u3002\u5982\u679c\u4f7f\u7528\u8005\u76f4\u63a5\u8a2a\u554f <code>\/todos\/123<\/code>\uff0c\u4f3a\u670d\u5668\u6703\u627e\u4e0d\u5230\u9019\u500b\u6a94\u6848\u800c\u56de\u50b3 404\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u89e3\u6cd5<\/strong>\uff1a\u8a2d\u5b9a\u300c\u6240\u6709\u8def\u7531\u90fd\u6307\u5411 index.html\u300d\u3002<\/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-21\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\"># Netlify\uff1a\u5728 dist\/ \u6216\u5c08\u6848\u6839\u76ee\u9304\u653e\u4e00\u500b _redirects \u6a94\u6848<\/span>\n<span class=\"hljs-comment\">\/*    \/index.html   200\n<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\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-22\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ Vercel\uff1avercel.json<\/span>\n{\n  <span class=\"hljs-attr\">\"rewrites\"<\/span>: &#91;\n    { <span class=\"hljs-attr\">\"source\"<\/span>: <span class=\"hljs-string\">\"\/(.*)\"<\/span>, <span class=\"hljs-attr\">\"destination\"<\/span>: <span class=\"hljs-string\">\"\/index.html\"<\/span> }\n  ]\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><strong>\u7ffb\u8b6f<\/strong>\uff1a\u544a\u8a34\u975c\u614b\u4f3a\u670d\u5668\u2014\u2014\u300c\u4e0d\u7ba1\u4f7f\u7528\u8005\u8a2a\u554f\u4ec0\u9ebc\u8def\u5f91\uff0c\u90fd\u56de\u50b3 index.html\u3002\u8def\u7531\u4ea4\u7d66\u524d\u7aef JavaScript \u8655\u7406\u3002\u300d<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u74b0\u5883\u8b8a\u6578\u8a2d\u5b9a<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u5728\u8a17\u7ba1\u5e73\u53f0\u7684\u8a2d\u5b9a\u9801\u9762\uff0c\u52a0\u5165\u74b0\u5883\u8b8a\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-23\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">VITE_SUPABASE_URL=https:<span class=\"hljs-comment\">\/\/abcdefg.supabase.co<\/span>\nVITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIs...\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><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<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">\u9019\u4e9b\u503c\u6703\u5728 <code>npm run build<\/code> \u6642\u88ab\u6ce8\u5165\u5230 JavaScript \u4e2d\u3002\u90e8\u7f72\u5f8c\u4e0d\u80fd\u900f\u904e\u6539\u74b0\u5883\u8b8a\u6578\u4f86\u5207\u63db\u2014\u2014\u8981\u6539\u5c31\u8981\u91cd\u65b0 build\u3002<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u5168\u90e8\u4e32\u8d77\u4f86\uff1a\u8cc7\u6599\u6d41\u5168\u8c8c<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u4ee5\u4e0b\u662f\u4f7f\u7528\u8005\u300c\u65b0\u589e\u4e00\u7b46\u5f85\u8fa6\u300d\u7684\u5b8c\u6574\u8cc7\u6599\u6d41\uff0c\u628a\u672c\u7cfb\u5217\u4e94\u7bc7\u7684\u6982\u5ff5\u5168\u90e8\u4e32\u5728\u4e00\u8d77\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-24\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">\u4f7f\u7528\u8005\u5728 \/todos \u9801\u9762\uff0c\u9ede\u4e86\u300c\u65b0\u589e\u300d\u6309\u9215\n  \u2502\n  \u2502  \u2460 React \u5143\u4ef6\uff08\u7b2c <span class=\"hljs-number\">1<\/span> \u7bc7\uff09\n  \u2193\nCreateTodoForm \u7684 handleSubmit \u57f7\u884c\n  \u2192 mutation.mutate({ <span class=\"hljs-attr\">title<\/span>: <span class=\"hljs-string\">'\u8cb7\u725b\u5976'<\/span>, <span class=\"hljs-attr\">user_id<\/span>: <span class=\"hljs-string\">'...'<\/span> })\n  \u2502\n  \u2502  \u2461 TanStack Query Mutation\uff08\u7b2c <span class=\"hljs-number\">3<\/span> \u7bc7 + \u672c\u7bc7\uff09\n  \u2193\nmutationFn \u57f7\u884c \u2192 createTodo()\n  \u2192 supabase.from(<span class=\"hljs-string\">'todos'<\/span>).insert(...)\n  \u2502\n  \u2502  \u2462 Supabase Client\uff08\u672c\u7bc7\uff09\n  \u2193\n\u8acb\u6c42\u9001\u5230 Supabase\uff08\u5e36\u8457 session token\uff09\n  \u2192 RLS \u6aa2\u67e5\uff1aauth.uid() = user_id\uff1f \u2192 \u901a\u904e\n  \u2192 PostgreSQL \u63d2\u5165\u8cc7\u6599\n  \u2192 \u56de\u50b3\u65b0\u589e\u7684\u8cc7\u6599\n  \u2502\n  \u2502  \u2463 Mutation \u6210\u529f\uff08\u7b2c <span class=\"hljs-number\">3<\/span> \u7bc7 + \u672c\u7bc7\uff09\n  \u2193\nonSuccess \u2192 invalidateQueries(&#91;<span class=\"hljs-string\">'todos'<\/span>, <span class=\"hljs-string\">'list'<\/span>])\n  \u2502\n  \u2502  \u2464 TanStack Query \u81ea\u52d5\u91cd\u65b0\u6293\u53d6\uff08\u7b2c <span class=\"hljs-number\">3<\/span> \u7bc7\uff09\n  \u2193\ngetTodos() \u91cd\u65b0\u57f7\u884c\n  \u2192 supabase.from(<span class=\"hljs-string\">'todos'<\/span>).select(<span class=\"hljs-string\">'*'<\/span>)\n  \u2192 \u56de\u50b3\u66f4\u65b0\u5f8c\u7684\u5217\u8868\n  \u2502\n  \u2502  \u2465 React \u81ea\u52d5\u91cd\u65b0\u6e32\u67d3\uff08\u7b2c <span class=\"hljs-number\">1<\/span> \u7bc7\uff09\n  \u2193\nTodoList \u5143\u4ef6\u66f4\u65b0 \u2192 \u756b\u9762\u986f\u793a\u65b0\u7684\u5f85\u8fa6\uff01\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><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<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u6838\u5fc3\u6982\u5ff5\u7ffb\u8b6f<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>\u4f60\u6703\u770b\u5230<\/th>\n<th>\u610f\u601d<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>createClient&lt;Database&gt;(url, key)<\/code><\/td>\n<td>\u5efa\u7acb Supabase \u9023\u7dda\uff0c\u5e36\u578b\u5225\u5b9a\u7fa9<\/td>\n<\/tr>\n<tr>\n<td><code>supabase.from('todos').select('*')<\/code><\/td>\n<td>\u5f9e todos \u8868\u67e5\u6240\u6709\u6b04\u4f4d<\/td>\n<\/tr>\n<tr>\n<td><code>.eq('id', id)<\/code><\/td>\n<td>WHERE \u689d\u4ef6<\/td>\n<\/tr>\n<tr>\n<td><code>.single()<\/code><\/td>\n<td>\u53ea\u8981\u4e00\u7b46\u7d50\u679c\uff08\u56de\u50b3\u7269\u4ef6\uff0c\u4e0d\u662f\u9663\u5217\uff09<\/td>\n<\/tr>\n<tr>\n<td><code>{ data, error } = await ...<\/code><\/td>\n<td>Supabase \u6a19\u6e96\u56de\u50b3\u683c\u5f0f\uff1a\u8cc7\u6599\u548c\u932f\u8aa4\u5206\u958b<\/td>\n<\/tr>\n<tr>\n<td><code>useMutation({ mutationFn, onSuccess })<\/code><\/td>\n<td>\u4fee\u6539\u8cc7\u6599\u7684\u64cd\u4f5c\uff0c\u6210\u529f\u5f8c\u505a\u67d0\u4e8b<\/td>\n<\/tr>\n<tr>\n<td><code>invalidateQueries({ queryKey })<\/code><\/td>\n<td>\u8b93\u7279\u5b9a\u5feb\u53d6\u904e\u671f\uff0c\u89f8\u767c\u91cd\u65b0\u6293\u53d6<\/td>\n<\/tr>\n<tr>\n<td><code>onMutate<\/code> \u2192 <code>setQueryData<\/code><\/td>\n<td>Optimistic Update\uff1a\u4e0d\u7b49 API\uff0c\u5148\u6539\u5feb\u53d6<\/td>\n<\/tr>\n<tr>\n<td><code>supabase.auth.signInWithPassword<\/code><\/td>\n<td>\u5e33\u865f\u5bc6\u78bc\u767b\u5165<\/td>\n<\/tr>\n<tr>\n<td><code>supabase.auth.onAuthStateChange<\/code><\/td>\n<td>\u76e3\u807d\u767b\u5165\/\u767b\u51fa\u72c0\u614b\u8b8a\u5316<\/td>\n<\/tr>\n<tr>\n<td><code>beforeLoad<\/code> + <code>redirect<\/code><\/td>\n<td>\u8def\u7531\u5b88\u885b\uff1a\u6c92\u767b\u5165\u5c31\u8df3\u8f49<\/td>\n<\/tr>\n<tr>\n<td>RLS policy + <code>auth.uid()<\/code><\/td>\n<td>\u8cc7\u6599\u5eab\u5c64\u7684\u6b0a\u9650\u63a7\u5236\uff1a\u53ea\u80fd\u78b0\u81ea\u5df1\u7684\u8cc7\u6599<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Vibe Coder \u6aa2\u67e5\u9ede<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u770b\u5230 AI \u751f\u6210\u4e86\u4e00\u500b React + Supabase \u7684\u5b8c\u6574\u5c08\u6848\u6642\uff0c\u78ba\u8a8d\u9019\u4e9b\u4e8b\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>[ ] <strong>\u6709 <code>.env<\/code> \u6a94\u6848\u55ce\uff1f<\/strong> Supabase URL \u548c anon key \u4e0d\u61c9\u8a72\u786c\u5beb\u5728\u7a0b\u5f0f\u78bc\u88e1<\/li>\n\n\n<li>[ ] <strong>\u6709\u555f\u7528 RLS \u55ce\uff1f<\/strong> \u5982\u679c Supabase \u7684 table \u6c92\u6709\u555f\u7528 RLS\uff0c\u4efb\u4f55\u4eba\u90fd\u80fd\u8b80\u5beb\u6240\u6709\u8cc7\u6599<\/li>\n\n\n<li>[ ] <strong>CRUD \u51fd\u5f0f\u6709\u8655\u7406 error \u55ce\uff1f<\/strong> Supabase \u56de\u50b3\u7684 <code>{ data, error }<\/code> \u4e2d\uff0cerror \u8981\u6aa2\u67e5<\/li>\n\n\n<li>[ ] <strong>Mutation \u7684 onSuccess \u6709 invalidate \u5feb\u53d6\u55ce\uff1f<\/strong> \u4e0d\u7136\u65b0\u589e\/\u4fee\u6539\u5f8c\u5217\u8868\u4e0d\u6703\u66f4\u65b0<\/li>\n\n\n<li>[ ] <strong>queryOptions \u6709\u5728 loader \u548c\u5143\u4ef6\u4e4b\u9593\u5171\u7528\u55ce\uff1f<\/strong> \u78ba\u4fdd queryKey \u4e00\u81f4\uff08\u7b2c 4 \u7bc7\u7684\u91cd\u9ede\uff09<\/li>\n\n\n<li>[ ] <strong>\u9700\u8981\u767b\u5165\u7684\u9801\u9762\u6709 beforeLoad \u6aa2\u67e5\u55ce\uff1f<\/strong> \u6c92\u6709\u7684\u8a71\u672a\u767b\u5165\u4f7f\u7528\u8005\u80fd\u76f4\u63a5\u8a2a\u554f<\/li>\n\n\n<li>[ ] <strong>\u90e8\u7f72\u8a2d\u5b9a\u6709\u8655\u7406 SPA \u8def\u7531\u55ce\uff1f<\/strong> \u9700\u8981\u300c\u6240\u6709\u8def\u5f91\u90fd\u6307\u5411 index.html\u300d\u7684 rewrite \u898f\u5247<\/li>\n\n\n<li>[ ] <strong>TypeScript \u578b\u5225\u662f\u81ea\u52d5\u751f\u6210\u7684\u55ce\uff1f<\/strong> <code>database.types.ts<\/code> \u61c9\u8a72\u7528 <code>supabase gen types<\/code> \u7522\u51fa\uff0c\u4e0d\u8981\u624b\u52d5\u5beb<\/li>\n\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u5fc5\u770b\u61c2 vs \u77e5\u9053\u5c31\u597d<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u5fc5\u770b\u61c2\uff08\u9019\u5957\u6280\u8853\u68e7\u7684\u6838\u5fc3\uff09<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>\u6982\u5ff5<\/th>\n<th>\u70ba\u4ec0\u9ebc\u91cd\u8981<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Supabase Client \u521d\u59cb\u5316<\/td>\n<td>\u6574\u500b\u5c08\u6848\u53ea\u505a\u4e00\u6b21\uff0c\u4f46\u8981\u77e5\u9053\u5b83\u5728\u54ea\u3001\u600e\u9ebc\u904b\u4f5c<\/td>\n<\/tr>\n<tr>\n<td><code>.from().select().eq()<\/code> \u67e5\u8a62\u93c8<\/td>\n<td>\u6bcf\u500b CRUD \u51fd\u5f0f\u90fd\u9577\u9019\u6a23<\/td>\n<\/tr>\n<tr>\n<td><code>{ data, error }<\/code> \u89e3\u69cb<\/td>\n<td>Supabase \u7684\u6a19\u6e96\u56de\u50b3\u6a21\u5f0f<\/td>\n<\/tr>\n<tr>\n<td><code>useMutation<\/code> + <code>invalidateQueries<\/code><\/td>\n<td>\u4fee\u6539\u8cc7\u6599\u5f8c\u66f4\u65b0\u756b\u9762\u7684\u6a19\u6e96\u6d41\u7a0b<\/td>\n<\/tr>\n<tr>\n<td>RLS \u7684\u57fa\u672c\u6982\u5ff5<\/td>\n<td>\u7406\u89e3\u70ba\u4ec0\u9ebc\u7d14\u524d\u7aef\u4e5f\u80fd\u5b89\u5168<\/td>\n<\/tr>\n<tr>\n<td><code>beforeLoad<\/code> \u505a\u8a8d\u8b49\u6aa2\u67e5<\/td>\n<td>\u4fdd\u8b77\u8def\u7531\u7684\u6a19\u6e96\u505a\u6cd5<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">\u77e5\u9053\u5c31\u597d\uff08\u9047\u5230\u518d\u67e5\uff09<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li><strong>Optimistic Updates<\/strong>\uff1a\u8b93\u64cd\u4f5c\u611f\u89ba\u66f4\u5feb\uff0c\u4f46\u7a0b\u5f0f\u78bc\u8f03\u8907\u96dc\u3002\u5148\u7528 invalidate \u5c31\u597d\uff0c\u9ad4\u9a57\u4e0d\u597d\u518d\u52a0<\/li>\n\n\n<li><strong>Supabase Realtime<\/strong>\uff1a\u5373\u6642\u8a02\u95b1\u8cc7\u6599\u8b8a\u5316\uff08\u50cf\u804a\u5929\u5ba4\uff09\uff0c\u4e00\u822c CRUD \u61c9\u7528\u4e0d\u9700\u8981<\/li>\n\n\n<li><strong>Supabase Edge Functions<\/strong>\uff1a\u9700\u8981\u5f8c\u7aef\u908f\u8f2f\u6642\u624d\u7528\uff08\u4f8b\u5982\u5bc4\u4fe1\u3001\u4e32\u63a5\u7b2c\u4e09\u65b9 API\uff09<\/li>\n\n\n<li><strong>Supabase Storage<\/strong>\uff1a\u6a94\u6848\u4e0a\u50b3\u529f\u80fd<\/li>\n\n\n<li><strong>RLS \u9032\u968e\u653f\u7b56<\/strong>\uff1a\u4f8b\u5982\u300c\u7ba1\u7406\u54e1\u53ef\u4ee5\u770b\u6240\u6709\u4eba\u7684\u8cc7\u6599\u300d\u9019\u7a2e\u8907\u96dc\u898f\u5247<\/li>\n\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u672c\u7cfb\u5217\u56de\u9867<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u606d\u559c\u4f60\u8b80\u5b8c\u6574\u500b\u7cfb\u5217\uff01\u8b93\u6211\u5011\u56de\u9867\u4e00\u4e0b\u6bcf\u7bc7\u5b78\u4e86\u4ec0\u9ebc\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>\u7bc7\u6578<\/th>\n<th>\u4e3b\u984c<\/th>\n<th>\u4f60\u5b78\u6703\u770b\u61c2\u4ec0\u9ebc<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>#01<\/td>\n<td>\u67b6\u69cb\u7e3d\u89bd<\/td>\n<td>SPA vs SSR \u7684\u5dee\u7570\uff0c\u70ba\u4ec0\u9ebc\u9078 React + TanStack<\/td>\n<\/tr>\n<tr>\n<td>#02<\/td>\n<td>TanStack Router<\/td>\n<td>\u6a94\u6848\u8def\u7531\u3001\u52d5\u614b\u53c3\u6578\u3001search params\u3001\u578b\u5225\u5b89\u5168<\/td>\n<\/tr>\n<tr>\n<td>#03<\/td>\n<td>TanStack Query<\/td>\n<td>useQuery\u3001queryKey\u3001\u5feb\u53d6\u6a5f\u5236\u3001useMutation \u57fa\u790e<\/td>\n<\/tr>\n<tr>\n<td>#04<\/td>\n<td>Router + Query \u6574\u5408<\/td>\n<td>loader\u3001ensureQueryData\u3001Pending UI\u3001Error \u8655\u7406<\/td>\n<\/tr>\n<tr>\n<td>#05<\/td>\n<td>Supabase \u5be6\u6230<\/td>\n<td>Supabase CRUD\u3001RLS\u3001\u8a8d\u8b49\u3001Optimistic Updates\u3001\u90e8\u7f72<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">\u9019\u5957\u6280\u8853\u68e7\u7684\u5b8c\u6574\u67b6\u69cb<\/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\">\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502                 \u4f7f\u7528\u8005\u700f\u89bd\u5668                    \u2502\n\u2502                                             \u2502\n\u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510   \u2502\n\u2502  \u2502 TanStack     \u2502    \u2502 TanStack Query   \u2502   \u2502\n\u2502  \u2502 Router       \u2502    \u2502                  \u2502   \u2502\n\u2502  \u2502              \u2502    \u2502 useQuery  \u5feb\u53d6\u7ba1\u7406  \u2502   \u2502\n\u2502  \u2502 \u8def\u7531\u5339\u914d      \u2502\u2500\u2500\u2500\u2192\u2502 useMutation      \u2502   \u2502\n\u2502  \u2502 loader \u9810\u53d6   \u2502    \u2502 invalidate       \u2502   \u2502\n\u2502  \u2502 beforeLoad   \u2502    \u2502 optimistic update\u2502   \u2502\n\u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518   \u2502\n\u2502                               \u2502              \u2502\n\u2502                    Supabase Client           \u2502\n\u2502                               \u2502              \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n                                \u2502\n                                \u2193\n                    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n                    \u2502    Supabase      \u2502\n                    \u2502                  \u2502\n                    \u2502  PostgreSQL + RLS\u2502\n                    \u2502  Auth            \u2502\n                    \u2502  Storage         \u2502\n                    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n<\/code><\/span><\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">\u7576\u4f60\u770b\u5230 AI \u5e6b\u4f60\u751f\u6210\u4e86\u4e00\u500b React \u5c08\u6848\uff0c\u88e1\u9762\u6709 <code>@tanstack\/react-router<\/code>\u3001<code>@tanstack\/react-query<\/code>\u3001<code>@supabase\/supabase-js<\/code> \u9019\u4e09\u500b\u5957\u4ef6\u6642\u2014\u2014\u4f60\u73fe\u5728\u77e5\u9053\u6bcf\u4e00\u5c64\u5728\u505a\u4ec0\u9ebc\u4e86\u3002<\/p>\n\n\n\n<div id=\"quiz-967e21b1\">\n<style>\n#quiz-967e21b1 .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-967e21b1 .badge.scenario {\n        background: #ebf8ff;\n        color: #2b6cb0;\n      }#quiz-967e21b1 .badge.debug {\n        background: #fef3c7;\n        color: #92400e;\n      }#quiz-967e21b1 .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-967e21b1 .question-adv h3 {\n        margin-top: 0;\n        color: #2d3748;\n      }#quiz-967e21b1 .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-967e21b1 .options-adv {\n        list-style: none;\n        padding: 0;\n      }#quiz-967e21b1 .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-967e21b1 .options-adv li:hover:not(.disabled) {\n        border-color: #ed8936;\n        background: #fffaf0;\n      }#quiz-967e21b1 .options-adv li.disabled {\n        cursor: default;\n        opacity: 0.7;\n      }#quiz-967e21b1 .options-adv li.show-answer {\n        border-color: #48bb78;\n        background: #f0fff4;\n      }#quiz-967e21b1 .options-adv li.wrong {\n        border-color: #fc8181;\n        background: #fff5f5;\n      }#quiz-967e21b1 .feedback-adv {\n        margin-top: 16px;\n        padding: 14px;\n        border-radius: 8px;\n        display: none;\n        line-height: 1.6;\n      }#quiz-967e21b1 .feedback-adv.correct {\n        background: #f0fff4;\n        color: #276749;\n        display: block;\n      }#quiz-967e21b1 .feedback-adv.wrong {\n        background: #fff5f5;\n        color: #c53030;\n        display: block;\n      }#quiz-967e21b1 .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-967e21b1 .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\uff1a\u7528 Supabase \u6253\u9020\u5b8c\u6574 CRUD \u61c9\u7528<\/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\u7528 AI \u751f\u6210\u4e86\u4e00\u500b React + Supabase \u7684\u5f85\u8fa6\u6e05\u55ae\u61c9\u7528\u3002\u65b0\u589e\u5f85\u8fa6\u5f8c\uff0c\u756b\u9762\u4e0a\u7684\u5217\u8868\u6c92\u6709\u81ea\u52d5\u66f4\u65b0\uff0c\u5fc5\u9808\u624b\u52d5\u91cd\u65b0\u6574\u7406\u9801\u9762\u624d\u6703\u770b\u5230\u65b0\u8cc7\u6599\u3002\u4f60\u6703\u5148\u6aa2\u67e5\u54ea\u88e1\uff1f <span class=\"badge scenario\">\u60c5\u5883\u984c<\/span><\/h3>\n      <ul class=\"options-adv\">\n        <li onclick=\"checkAnswerAdv(1, 'A', 'B')\">A. \u6aa2\u67e5 Supabase \u7684 RLS \u653f\u7b56\u662f\u5426\u6b63\u78ba\u8a2d\u5b9a<\/li>\n        <li onclick=\"checkAnswerAdv(1, 'B', 'B')\">B. \u6aa2\u67e5 <code>useMutation<\/code> \u7684 <code>onSuccess<\/code> \u6709\u6c92\u6709\u547c\u53eb <code>invalidateQueries<\/code><\/li>\n        <li onclick=\"checkAnswerAdv(1, 'C', 'B')\">C. \u6aa2\u67e5 <code>createClient<\/code> \u6709\u6c92\u6709\u5e36\u5165 <code>&lt;Database&gt;<\/code> \u578b\u5225\u53c3\u6578<\/li>\n        <li onclick=\"checkAnswerAdv(1, 'D', 'B')\">D. \u6aa2\u67e5 Supabase \u7684 Realtime \u529f\u80fd\u6709\u6c92\u6709\u555f\u7528<\/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\u6b63\u5728\u958b\u767c\u4e00\u500b\u5f85\u8fa6\u6e05\u55ae\u7684\u300c\u52fe\u9078\u5b8c\u6210\u300d\u529f\u80fd\u3002\u4f7f\u7528\u8005\u53cd\u6620\u52fe\u9078\u5f8c\u8981\u7b49\u4e00\u5169\u79d2\u756b\u9762\u624d\u6703\u66f4\u65b0\uff0c\u9ad4\u9a57\u4e0d\u597d\u3002\u5728\u4e0d\u589e\u52a0\u592a\u591a\u7a0b\u5f0f\u78bc\u8907\u96dc\u5ea6\u7684\u524d\u63d0\u4e0b\uff0c\u4f60\u61c9\u8a72\u63a1\u7528\u4ec0\u9ebc\u7b56\u7565\uff1f <span class=\"badge scenario\">\u60c5\u5883\u984c<\/span><\/h3>\n      <ul class=\"options-adv\">\n        <li onclick=\"checkAnswerAdv(2, 'A', 'C')\">A. \u555f\u7528 Supabase Realtime \u4f86\u8a02\u95b1\u8cc7\u6599\u8b8a\u5316\uff0c\u5373\u6642\u66f4\u65b0\u756b\u9762<\/li>\n        <li onclick=\"checkAnswerAdv(2, 'B', 'C')\">B. \u628a TanStack Query \u7684 <code>staleTime<\/code> \u8a2d\u70ba 0\uff0c\u8b93\u5feb\u53d6\u6c38\u9060\u904e\u671f\u4ee5\u52a0\u901f\u91cd\u65b0\u6293\u53d6<\/li>\n        <li onclick=\"checkAnswerAdv(2, 'C', 'C')\">C. \u4f7f\u7528 Optimistic Update\uff1a\u5728 <code>onMutate<\/code> \u4e2d\u5148\u7528 <code>setQueryData<\/code> \u66f4\u65b0\u5feb\u53d6\uff0c\u5931\u6557\u6642\u7528 <code>onError<\/code> \u56de\u5fa9<\/li>\n        <li onclick=\"checkAnswerAdv(2, 'D', 'C')\">D. \u5728\u5143\u4ef6\u4e2d\u7528 <code>useState<\/code> \u7dad\u8b77\u4e00\u4efd\u672c\u5730\u7684\u5f85\u8fa6\u5217\u8868\uff0c\u52fe\u9078\u6642\u76f4\u63a5\u4fee\u6539\u672c\u5730\u72c0\u614b<\/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\u628a React + Supabase \u7684 SPA \u90e8\u7f72\u5230 Netlify \u5f8c\uff0c\u9996\u9801\u53ef\u4ee5\u6b63\u5e38\u986f\u793a\uff0c\u4f46\u76f4\u63a5\u5728\u700f\u89bd\u5668\u8f38\u5165 <code>\/todos\/abc123<\/code> \u6703\u51fa\u73fe 404 \u9801\u9762\u3002\u6700\u53ef\u80fd\u7684\u539f\u56e0\u548c\u89e3\u6cd5\u662f\u4ec0\u9ebc\uff1f <span class=\"badge scenario\">\u60c5\u5883\u984c<\/span><\/h3>\n      <ul class=\"options-adv\">\n        <li onclick=\"checkAnswerAdv(3, 'A', 'D')\">A. TanStack Router \u6c92\u6709\u6b63\u78ba\u8a2d\u5b9a <code>\/todos\/$todoId<\/code> \u8def\u7531\uff0c\u9700\u8981\u4fee\u6539\u8def\u7531\u5b9a\u7fa9<\/li>\n        <li onclick=\"checkAnswerAdv(3, 'B', 'D')\">B. Supabase \u7684 RLS \u653f\u7b56\u963b\u64cb\u4e86\u5c0d\u8a72\u7b46\u5f85\u8fa6\u7684\u5b58\u53d6<\/li>\n        <li onclick=\"checkAnswerAdv(3, 'C', 'D')\">C. Vite \u7684 build \u8a2d\u5b9a\u6709\u8aa4\uff0c\u9700\u8981\u5728 <code>vite.config.ts<\/code> \u4e2d\u52a0\u5165 <code>base: '\/'<\/code><\/li>\n        <li onclick=\"checkAnswerAdv(3, 'D', 'D')\">D. Netlify \u7f3a\u5c11 SPA \u7684 rewrite \u898f\u5247\uff0c\u9700\u8981\u5728\u5c08\u6848\u4e2d\u52a0\u5165 <code>_redirects<\/code> \u6a94\u6848\uff0c\u5167\u5bb9\u70ba <code>\/*  \/index.html  200<\/code><\/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. \u540c\u4e8b\u5beb\u4e86\u4ee5\u4e0b\u7684 Supabase CRUD \u51fd\u5f0f\uff0c\u4f46\u4f7f\u7528\u6642\u5076\u723e\u6703\u51fa\u73fe\u300cCannot read properties of null\u300d\u7684\u932f\u8aa4\u3002\u554f\u984c\u51fa\u5728\u54ea\u88e1\uff1f <span class=\"badge debug\">\u932f\u8aa4\u8a3a\u65b7<\/span><\/h3>\n      <div class=\"code-block\">export async function getTodoById(id: string) {\n  const { data } = await supabase\n    .from(&#8216;todos&#8217;)\n    .select(&#8216;*&#8217;)\n    .eq(&#8216;id&#8217;, id)\n    .single()\n\n  return data\n}<\/div>\n      <ul class=\"options-adv\">\n        <li onclick=\"checkAnswerAdv(4, 'A', 'B')\">A. <code>.single()<\/code> \u4e0d\u80fd\u548c <code>.eq()<\/code> \u4e00\u8d77\u4f7f\u7528\uff0c\u61c9\u8a72\u79fb\u9664 <code>.single()<\/code><\/li>\n        <li onclick=\"checkAnswerAdv(4, 'B', 'B')\">B. \u6c92\u6709\u89e3\u69cb <code>error<\/code> \u4e26\u6aa2\u67e5\u932f\u8aa4\u2014\u2014\u7576\u67e5\u8a62\u5931\u6557\u6642 <code>data<\/code> \u6703\u662f <code>null<\/code>\uff0c\u76f4\u63a5\u56de\u50b3\u6703\u5c0e\u81f4\u547c\u53eb\u7aef\u5b58\u53d6 null \u7684\u5c6c\u6027<\/li>\n        <li onclick=\"checkAnswerAdv(4, 'C', 'B')\">C. \u61c9\u8a72\u7528 <code>.maybeSingle()<\/code> \u53d6\u4ee3 <code>.single()<\/code>\uff0c\u56e0\u70ba\u8cc7\u6599\u53ef\u80fd\u4e0d\u5b58\u5728<\/li>\n        <li onclick=\"checkAnswerAdv(4, 'D', 'B')\">D. \u7f3a\u5c11 <code>.order()<\/code> \u6392\u5e8f\uff0c\u5c0e\u81f4 Supabase \u4e0d\u77e5\u9053\u8981\u56de\u50b3\u54ea\u4e00\u7b46<\/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. \u4ee5\u4e0b\u7a0b\u5f0f\u78bc\u7684 loader \u6709\u6b63\u5e38\u6293\u8cc7\u6599\uff0c\u4f46\u5207\u63db\u5230 <code>\/todos<\/code> \u9801\u9762\u6642\uff0c\u5143\u4ef6\u88e1\u7684 <code>useQuery<\/code> \u537b\u91cd\u65b0\u767c\u51fa\u4e86\u4e00\u6b21 API \u8acb\u6c42\uff0c\u6c92\u6709\u4f7f\u7528 loader \u9810\u53d6\u7684\u5feb\u53d6\u3002\u6700\u53ef\u80fd\u7684\u539f\u56e0\u662f\u4ec0\u9ebc\uff1f <span class=\"badge debug\">\u932f\u8aa4\u8a3a\u65b7<\/span><\/h3>\n      <div class=\"code-block\">\/\/ api\/todos.ts\nconst todosListOptions = (status?: string) =>\n  queryOptions({\n    queryKey: [&#8216;todos&#8217;, { status }],\n    queryFn: () => getTodos(status),\n  })\n\n\/\/ routes\/todos\/index.tsx\nexport const Route = createFileRoute(&#8216;\/todos\/&#8217;)({\n  validateSearch: todosSearchSchema,\n  loader: async ({ search, context }) => {\n    await context.queryClient.ensureQueryData(\n      queryOptions({\n        queryKey: [&#8216;todos&#8217;, &#8216;list&#8217;, { status: search.status }],\n        queryFn: () => getTodos(search.status),\n      })\n    )\n  },\n  component: TodoListPage,\n})\n\nfunction TodoListPage() {\n  const { status } = Route.useSearch()\n  const { data: todos } = useQuery(todosListOptions(status))\n  \/\/ &#8230;\n}<\/div>\n      <ul class=\"options-adv\">\n        <li onclick=\"checkAnswerAdv(5, 'A', 'C')\">A. <code>ensureQueryData<\/code> \u61c9\u8a72\u6539\u7528 <code>prefetchQuery<\/code>\uff0c\u524d\u8005\u4e0d\u6703\u5b58\u5165\u5feb\u53d6<\/li>\n        <li onclick=\"checkAnswerAdv(5, 'B', 'C')\">B. <code>validateSearch<\/code> \u7684 schema \u5b9a\u7fa9\u6709\u8aa4\uff0c\u5c0e\u81f4 search params \u9a57\u8b49\u5931\u6557<\/li>\n        <li onclick=\"checkAnswerAdv(5, 'C', 'C')\">C. loader \u548c\u5143\u4ef6\u4f7f\u7528\u7684 <code>queryKey<\/code> \u4e0d\u4e00\u81f4\u2014\u2014loader \u7528 <code>['todos', 'list', { status }]<\/code>\uff0c\u5143\u4ef6\u7528 <code>['todos', { status }]<\/code>\uff0c\u5feb\u53d6\u5b58\u5728\u4e0d\u540c\u7684 key \u5e95\u4e0b<\/li>\n        <li onclick=\"checkAnswerAdv(5, 'D', 'C')\">D. \u5143\u4ef6\u4e2d\u61c9\u8a72\u7528 <code>useSuspenseQuery<\/code> \u800c\u975e <code>useQuery<\/code>\uff0c\u624d\u80fd\u8b80\u53d6 loader \u9810\u53d6\u7684\u8cc7\u6599<\/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: \"\u65b0\u589e\u8cc7\u6599\u5f8c\u756b\u9762\u4e0d\u66f4\u65b0\uff0c\u6700\u5e38\u898b\u7684\u539f\u56e0\u662f <code>useMutation<\/code> \u7684 <code>onSuccess<\/code> \u6c92\u6709\u547c\u53eb <code>invalidateQueries<\/code>\u3002\u6c92\u6709 invalidate \u7684\u8a71\uff0cTanStack Query \u4e0d\u77e5\u9053\u5feb\u53d6\u5df2\u7d93\u904e\u671f\uff0c\u4e0d\u6703\u91cd\u65b0\u6293\u53d6\u5217\u8868\u3002RLS \u554f\u984c\u6703\u5c0e\u81f4\u8cc7\u6599\u5beb\u5165\u5931\u6557\uff08\u800c\u975e\u756b\u9762\u4e0d\u66f4\u65b0\uff09\uff0c\u578b\u5225\u53c3\u6578\u53ea\u5f71\u97ff TypeScript \u7684\u578b\u5225\u6aa2\u67e5\uff0cRealtime \u662f\u5373\u6642\u8a02\u95b1\u529f\u80fd\uff0c\u4e00\u822c CRUD \u4e0d\u9700\u8981\u3002\",\n        2: \"Optimistic Update \u662f\u6587\u7ae0\u4e2d\u4ecb\u7d39\u7684\u6a19\u6e96\u505a\u6cd5\u2014\u2014\u5728 <code>onMutate<\/code> \u4e2d\u5148\u7528 <code>setQueryData<\/code> \u76f4\u63a5\u6539\u5feb\u53d6\u8b93\u756b\u9762\u7acb\u523b\u66f4\u65b0\uff0c\u5931\u6557\u6642\u7528 <code>onError<\/code> \u642d\u914d\u5099\u4efd\u9084\u539f\uff0c\u6700\u5f8c\u5728 <code>onSettled<\/code> \u505a invalidate \u78ba\u4fdd\u8cc7\u6599\u6b63\u78ba\u3002Realtime \u5c0d\u6b64\u5834\u666f\u592a\u91cd\u4e86\uff1b\u628a staleTime \u8a2d\u70ba 0 \u4e0d\u6703\u52a0\u901f mutation \u5f8c\u7684\u66f4\u65b0\uff1b\u7528 useState \u7dad\u8b77\u672c\u5730\u72c0\u614b\u6703\u5c0e\u81f4\u8cc7\u6599\u8ddf Supabase \u4e0d\u540c\u6b65\u3002\",\n        3: \"\u9019\u662f SPA \u90e8\u7f72\u7684\u7d93\u5178\u554f\u984c\u3002SPA \u53ea\u6709\u4e00\u500b <code>index.html<\/code> \u5165\u53e3\uff0c\u6240\u6709\u8def\u7531\u7531\u524d\u7aef JavaScript \u8655\u7406\u3002\u7576\u4f7f\u7528\u8005\u76f4\u63a5\u8a2a\u554f <code>\/todos\/abc123<\/code> \u6642\uff0cNetlify \u6703\u5617\u8a66\u627e\u5c0d\u61c9\u7684\u975c\u614b\u6a94\u6848\uff0c\u627e\u4e0d\u5230\u5c31\u56de\u50b3 404\u3002\u89e3\u6cd5\u662f\u5728\u5c08\u6848\u4e2d\u52a0\u5165 <code>_redirects<\/code> \u6a94\u6848\uff0c\u8a2d\u5b9a <code>\/* \/index.html 200<\/code>\uff0c\u8b93\u6240\u6709\u8def\u5f91\u90fd\u56de\u50b3 index.html\uff0c\u7531\u524d\u7aef Router \u63a5\u624b\u8655\u7406\u3002\",\n        4: \"\u6587\u7ae0\u4e2d\u660e\u78ba\u6307\u51fa Supabase CRUD \u51fd\u5f0f\u7684\u6a19\u6e96\u6a21\u5f0f\u662f\uff1a\u89e3\u69cb\u51fa <code>{ data, error }<\/code>\uff0c\u7136\u5f8c <code>if (error) throw error<\/code>\u3002\u9019\u6bb5\u7a0b\u5f0f\u78bc\u53ea\u89e3\u69cb\u4e86 <code>data<\/code> \u537b\u6c92\u6709\u6aa2\u67e5 <code>error<\/code>\u3002\u7576\u67e5\u8a62\u5931\u6557\u6642\uff08\u4f8b\u5982\u7db2\u8def\u932f\u8aa4\u3001\u627e\u4e0d\u5230\u8cc7\u6599\uff09\uff0c<code>data<\/code> \u6703\u662f <code>null<\/code>\uff0c\u76f4\u63a5\u56de\u50b3 null \u6703\u5c0e\u81f4\u547c\u53eb\u7aef\u5617\u8a66\u5b58\u53d6 null \u7684\u5c6c\u6027\u800c\u5831\u932f\u3002\u6b63\u78ba\u505a\u6cd5\u662f\u52a0\u4e0a <code>if (error) throw error<\/code>\uff0c\u8b93 TanStack Query \u63a5\u4f4f\u932f\u8aa4\u4e26\u986f\u793a errorComponent\u3002\",\n        5: \"\u9019\u6b63\u662f\u6587\u7ae0\u4e2d\u300c\u5171\u7528 Query Options\u300d\u6bb5\u843d\u7279\u5225\u5f37\u8abf\u7684\u554f\u984c\u3002loader \u88e1\u9762\u76f4\u63a5\u5beb\u4e86 <code>queryKey: ['todos', 'list', { status }]<\/code>\uff0c\u4f46\u5143\u4ef6\u4f7f\u7528\u7684 <code>todosListOptions<\/code> \u51fd\u5f0f\u7522\u751f\u7684 queryKey \u662f <code>['todos', { status }]<\/code>\uff08\u6c92\u6709 'list'\uff09\u3002\u5169\u908a\u7684 key \u4e0d\u4e00\u81f4\uff0c\u6240\u4ee5 loader \u5b58\u9032\u5feb\u53d6\u7684\u8cc7\u6599\u5b58\u5728\u4e00\u500b key \u4e0b\uff0c\u5143\u4ef6\u537b\u7528\u53e6\u4e00\u500b key \u53bb\u627e\uff0c\u627e\u4e0d\u5230\u5c31\u91cd\u65b0\u767c\u8acb\u6c42\u3002\u89e3\u6cd5\u662f loader \u548c\u5143\u4ef6\u90fd\u4f7f\u7528\u540c\u4e00\u500b <code>todosListOptions<\/code> \u51fd\u5f0f\uff0c\u78ba\u4fdd queryKey \u4e00\u81f4\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 = \"&#127919; \u5b8c\u7f8e\uff01\u4f60\u5df2\u7d93\u6e96\u5099\u597d\u5be6\u6230\u4e86\uff01\";\n          else if (correctAdv >= Math.ceil(totalAdv * 0.8))\n            msg = \"&#128170; \u5f88\u68d2\uff01\u518d\u8907\u7fd2\u4e00\u4e0b\u932f\u7684\u984c\u76ee\";\n          else if (correctAdv >= Math.ceil(totalAdv * 0.6))\n            msg = \"&#128214; \u5efa\u8b70\u91cd\u8b80\u76f8\u95dc\u6bb5\u843d\u5f8c\u518d\u8a66\u4e00\u6b21\";\n          else msg = \"&#128260; \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\uff1a\u7528 Supabase \u6253\u9020\u5b8c\u6574 CRUD \u61c9\u7528 \u5171 5 \u984c\uff0c\u9ede\u9078\u7b54\u6848\u5f8c\u6703\u7acb\u5373\u986f\u793a\u7d50\u679c 1. \u5728 Supa [&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":[639],"tags":[299,309,301,284,650],"class_list":["post-719","post","type-post","status-publish","format-standard","hentry","category-react-tanstack","tag-crud","tag-rls","tag-supabase","tag-usemutation","tag-650"],"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\/719","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=719"}],"version-history":[{"count":0,"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/posts\/719\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/media?parent=719"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/categories?post=719"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.itsmygo.uk\/wordpress\/wp-json\/wp\/v2\/tags?post=719"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}