{"id":2821,"date":"2026-05-22T11:31:14","date_gmt":"2026-05-22T03:31:14","guid":{"rendered":"https:\/\/cocozq.com\/?p=2821"},"modified":"2026-05-22T11:31:14","modified_gmt":"2026-05-22T03:31:14","slug":"tauri-react-%e5%ba%94%e7%94%a8%e5%90%af%e5%8a%a8%e9%97%aa%e7%83%81%e6%8e%92%e6%9f%a5%e5%ae%9e%e5%bd%95%ef%bc%9a19%e8%bd%ae%e4%ba%8c%e5%88%86%e6%b3%95%e6%8f%aa%e5%87%ba%e7%9c%9f%e5%87%b6","status":"publish","type":"post","link":"https:\/\/cocozq.com\/?p=2821","title":{"rendered":"Tauri\/React \u5e94\u7528\u542f\u52a8\u95ea\u70c1\u6392\u67e5\u5b9e\u5f55\uff1a19\u8f6e\u4e8c\u5206\u6cd5\u63ea\u51fa\u771f\u51f6"},"content":{"rendered":"<h1>Tauri\/React \u5e94\u7528\u542f\u52a8\u95ea\u70c1\u6392\u67e5\u5b9e\u5f55\uff1a19\u8f6e\u4e8c\u5206\u6cd5\u63ea\u51fa\u771f\u51f6<\/h1>\n<h2>\ud83d\udea8 \u95ee\u9898\u73b0\u8c61<\/h2>\n<p>\u4f60\u7684\u684c\u9762\u5e94\u7528\u5728\u5f00\u53d1\u73af\u5883\u8fd0\u884c\u5b8c\u7f8e\u65e0\u7455\uff0c\u4f46\u4e00\u65e6\u6253\u5305\u6210 Release \u7248\u672c\u5b89\u88c5\u540e\uff0c\u542f\u52a8\u65f6\u4f1a\u51fa\u73b0\u4ee4\u4eba\u6293\u72c2\u7684<strong>\u7a97\u53e3\u95ea\u70c1<\/strong>\u2014\u2014\u4f4d\u7f6e\u6296\u52a8\u3001\u5927\u5c0f\u5f02\u5e38\u3001\u751a\u81f3\u77ed\u6682\u767d\u5c4f\u3002<\/p>\n<p><strong>\u5178\u578b\u7279\u5f81\uff1a<\/strong><br \/>\n&#8211; \u2705 Dev \u6a21\u5f0f (<code>npm run tauri dev<\/code>) \u5b8c\u5168\u6b63\u5e38<br \/>\n&#8211; \u274c Release \u6253\u5305\u540e\u5fc5\u73b0\u95ea\u70c1<br \/>\n&#8211; \u26a0\ufe0f \u95ea\u70c1\u53d1\u751f\u5728\u5e94\u7528\u542f\u52a8\u7684\u524d\u51e0\u79d2\u5185<br \/>\n&#8211; \ud83d\udd0d \u63a7\u5236\u53f0\u65e0\u62a5\u9519\uff0c\u6027\u80fd\u9762\u677f\u65e0\u660e\u663e\u5f02\u5e38<\/p>\n<p><strong>\u5f71\u54cd\u8303\u56f4\uff1a<\/strong><br \/>\n&#8211; Windows \u5e73\u53f0\uff08\u6700\u5e38\u89c1\uff09<br \/>\n&#8211; \u5076\u53d1\u4e8e macOS\/Linux<br \/>\n&#8211; \u7528\u6237\u7b2c\u4e00\u5370\u8c61\u4f53\u9a8c\u4e25\u91cd\u53d7\u635f<\/p>\n<hr \/>\n<h2>\ud83d\udd2c \u6392\u67e5\u5386\u7a0b\uff1a\u4e00\u573a\u8017\u65f6\u7684\u4fa6\u63a2\u6e38\u620f<\/h2>\n<h3>\u521d\u59cb\u5047\u8bbe\uff08\u5168\u90e8\u9519\u8bef\uff09<\/h3>\n<p>\u4f5c\u4e3a\u7ecf\u9a8c\u4e30\u5bcc\u7684\u5f00\u53d1\u8005\uff0c\u4f60\u53ef\u80fd\u4f1a\u9996\u5148\u6000\u7591\u8fd9\u4e9b\u65b9\u5411\uff1a<\/p>\n<table>\n<thead>\n<tr>\n<th>#<\/th>\n<th>\u5e38\u89c1\u5047\u8bbe<\/th>\n<th>\u4e3a\u4ec0\u4e48\u770b\u4f3c\u5408\u7406<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>1<\/td>\n<td><strong>CSS \u52a8\u753b\u51b2\u7a81<\/strong><\/td>\n<td>\u542f\u52a8\u65f6\u6709\u8fc7\u6e21\u6548\u679c\uff1f<\/td>\n<\/tr>\n<tr>\n<td>2<\/td>\n<td><strong>\u7a97\u53e3\u914d\u7f6e\u9519\u8bef<\/strong><\/td>\n<td><code>tauri.conf.json<\/code> \u7684 <code>width\/height<\/code> \u8bbe\u7f6e\u95ee\u9898\uff1f<\/td>\n<\/tr>\n<tr>\n<td>3<\/td>\n<td><strong>\u72b6\u6001\u7ba1\u7406\u7ade\u6001<\/strong><\/td>\n<td>Redux\/Zustand \u5728\u521d\u59cb\u5316\u65f6\u89e6\u53d1\u591a\u6b21\u6e32\u67d3\uff1f<\/td>\n<\/tr>\n<tr>\n<td>4<\/td>\n<td><strong>\u8d44\u6e90\u52a0\u8f7d\u963b\u585e<\/strong><\/td>\n<td>\u5927\u578b\u56fe\u7247\u6216\u5b57\u4f53\u6587\u4ef6\u5ef6\u8fdf\u52a0\u8f7d\uff1f<\/td>\n<\/tr>\n<tr>\n<td>5<\/td>\n<td><strong>Tauri \u7a97\u53e3 API \u8c03\u7528<\/strong><\/td>\n<td><code>setSize\/setPosition<\/code> \u5728\u4e0d\u5f53\u65f6\u673a\u88ab\u8c03\u7528\uff1f<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><strong>\u73b0\u5b9e\u662f\uff1a\u4ee5\u4e0a\u5168\u90e8\u90fd\u4e0d\u662f\u6839\u672c\u539f\u56e0\u3002<\/strong><\/p>\n<hr \/>\n<h3>\u65b9\u6cd5\u8bba\u9009\u62e9\uff1a\u7cfb\u7edf\u5316\u4e8c\u5206\u6cd5<\/h3>\n<p>\u9762\u5bf9\u8fd9\u79cd&#8221;\u5e7d\u7075\u822c&#8221;\u7684\u95ee\u9898\uff0c\u968f\u673a\u5c1d\u8bd5\u4fee\u6539\u4ee3\u7801\u53ea\u4f1a\u6d6a\u8d39\u65f6\u95f4\u3002\u6211\u4eec\u91c7\u7528<strong>\u63a7\u5236\u53d8\u91cf\u6cd5\u7684\u4e8c\u5206\u7b56\u7565<\/strong>\uff1a<\/p>\n<pre><code class=\"line-numbers\">Step 1: \u521b\u5efa\u6700\u5c0f\u5316\u6d4b\u8bd5\u9879\u76ee\uff08\u57fa\u7ebf\u9a8c\u8bc1\uff09\n    \u2193\nStep 2: \u9010\u6b65\u6dfb\u52a0\u7ec4\u4ef6\uff08\u6bcf\u6b21\u53ea\u52a0\u4e00\u4e2a\u53d8\u91cf\uff09\n    \u2193\nStep 3: \u6253\u5305 Release \u7248\u672c\u8fdb\u884c\u9a8c\u8bc1\n    \u2193\nStep 4: \u8bb0\u5f55\u6bcf\u6b65\u7ed3\u679c\uff08\u7cbe\u786e\u5230\u7ec4\u4ef6\u7ea7\u522b\uff09\n    \u2193\nStep 5: \u7f29\u5c0f\u8303\u56f4 \u2192 \u518d\u6b21\u4e8c\u5206 \u2192 \u76f4\u81f3\u5b9a\u4f4d\n<\/code><\/pre>\n<h4>\u7b2c\u4e00\u9636\u6bb5\uff1a\u5efa\u7acb\u57fa\u7ebf (Test M1-M9)<\/h4>\n<table>\n<thead>\n<tr>\n<th>\u6d4b\u8bd5\u7f16\u53f7<\/th>\n<th>\u7ec4\u4ef6\u7ec4\u5408<\/th>\n<th>\u7ed3\u679c<\/th>\n<th>\u7ed3\u8bba<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>M1<\/strong><\/td>\n<td>\u7a7a\u767d Tauri \u7a97\u53e3 + &#8220;Hello World&#8221;<\/td>\n<td>\u2705 \u4e0d\u95ea<\/td>\n<td><strong>\u57fa\u7ebf\u6b63\u5e38\uff0c\u6392\u9664\u6846\u67b6\u95ee\u9898<\/strong><\/td>\n<\/tr>\n<tr>\n<td>M2-M8<\/td>\n<td>\u9010\u4e2a\u6dfb\u52a0\u57fa\u7840 UI \u7ec4\u4ef6<\/td>\n<td>\u2705 \u4e0d\u95ea<\/td>\n<td>\u5355\u4e2a\u8f7b\u91cf\u7ec4\u4ef6\u5b89\u5168<\/td>\n<\/tr>\n<tr>\n<td><strong>M9<\/strong><\/td>\n<td>TerminalPanel + \u57fa\u7840\u5e03\u5c40<\/td>\n<td>\u2705 \u4e0d\u95ea<\/td>\n<td>\u6838\u5fc3\u529f\u80fd\u7ec4\u4ef6\u6b63\u5e38<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><strong>\u8fdb\u5c55:<\/strong> 9\u8f6e\u6d4b\u8bd5\u540e\uff0c\u4ecd\u672a\u590d\u73b0\u95ee\u9898\u3002\u8fd9\u8bf4\u660e<strong>\u5355\u4e2a\u7ec4\u4ef6\u672c\u8eab\u65e0\u5bb3<\/strong>\u3002<\/p>\n<h4>\u7b2c\u4e8c\u9636\u6bb5\uff1a\u5b8c\u6574\u7ec4\u88c5 (Test M10) \u26a1<\/h4>\n<table>\n<thead>\n<tr>\n<th>\u6d4b\u8bd5\u7f16\u53f7<\/th>\n<th>\u5173\u952e\u53d8\u5316<\/th>\n<th>\u7ed3\u679c<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>M10<\/strong><\/td>\n<td><strong>\u6dfb\u52a0\u6240\u6709 Modal \u5bf9\u8bdd\u6846 + SplitTerminal + ToolPanel<\/strong><\/td>\n<td>\u274c <strong>\u9996\u6b21\u590d\u73b0\u95ea\u70c1\uff01<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><strong>\u7a81\u7834\u6027\u53d1\u73b0\uff1a<\/strong><br \/>\n&#8211; \u4ece M9\uff08\u6b63\u5e38\uff09\u2192 M10\uff08\u95ea\u70c1\uff09\u7684\u53d8\u5316\u4e2d\u5f15\u5165\u4e86\u95ee\u9898<br \/>\n&#8211; \u53d8\u91cf\u8303\u56f4\u7f29\u5c0f\u5230\uff1a<strong>\u65b0\u589e\u7684\u8fd9\u6279\u7ec4\u4ef6\u96c6\u5408<\/strong><\/p>\n<h4>\u7b2c\u4e09\u9636\u6bb5\uff1a\u8303\u56f4\u6536\u655b (Test M11-M13)<\/h4>\n<table>\n<thead>\n<tr>\n<th>\u6d4b\u8bd5\u7f16\u53f7<\/th>\n<th>\u5b50\u96c6\u5212\u5206<\/th>\n<th>\u7ed3\u679c<\/th>\n<th>\u5206\u6790<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>M11<\/td>\n<td>\u53ea\u4fdd\u7559 SplitTerminal + \u6240\u6709 Modal<\/td>\n<td>\u2705 \u4e0d\u95ea<\/td>\n<td>SplitTerminal \u548c Modal \u65e0\u5acc\u7591<\/td>\n<\/tr>\n<tr>\n<td>M12<\/td>\n<td>+ ToolPanel\uff08\u4f46\u4e0d\u5305\u542b\u5176\u5185\u90e8\u5b50\u9762\u677f\uff09<\/td>\n<td>\u2705 \u4e0d\u95ea<\/td>\n<td>ToolPanel \u5916\u58f3\u5b89\u5168<\/td>\n<\/tr>\n<tr>\n<td><strong>M13<\/strong><\/td>\n<td><strong>+ ToolPanel \u7684 9 \u4e2a\u5b50\u9762\u677f\u5168\u90e8\u52a0\u8f7d<\/strong><\/td>\n<td>\u274c <strong>\u95ea\u70c1\uff01<\/strong><\/td>\n<td><strong>\ud83c\udfaf \u9501\u5b9a\u76ee\u6807\uff1aToolPanel \u5185\u90e8\uff01<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><strong>\u5f53\u524d\u5acc\u7591\u4eba\u540d\u5355\uff089\u4e2a\uff09\uff1a<\/strong><\/p>\n<pre><code class=\"line-numbers\">Settings \/ Audit \/ Batch \/ PortForward \/ X11 \/ Recording \/ Network \/ SshAgent \/ Zmodem\n<\/code><\/pre>\n<h4>\u7b2c\u56db\u9636\u6bb5\uff1a\u5206\u7ec4\u6dd8\u6c70 (Test M14-M16)<\/h4>\n<table>\n<thead>\n<tr>\n<th>\u6d4b\u8bd5\u7f16\u53f7<\/th>\n<th>\u5206\u7ec4\u7b56\u7565<\/th>\n<th>\u7ed3\u679c<\/th>\n<th>\u6dd8\u6c70\u5bf9\u8c61<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>M14<\/td>\n<td>\u7a7a ToolPanel \u58f3\uff080\u4e2a\u5b50\u9762\u677f\uff09<\/td>\n<td>\u2705 \u4e0d\u95ea<\/td>\n<td>CSS\/\u5bb9\u5668\u672c\u8eab\u65e0\u8f9c<\/td>\n<\/tr>\n<tr>\n<td>M15<\/td>\n<td>\u524d 4 \u4e2a\u5b50\u9762\u677f (Settings\/Audit\/Batch\/PF)<\/td>\n<td>\u2705 \u4e0d\u95ea<\/td>\n<td>\u8fd9 4 \u4e2a\u6e05\u767d<\/td>\n<\/tr>\n<tr>\n<td><strong>M16<\/strong><\/td>\n<td><strong>\u5168\u90e8 9 \u4e2a\u5b50\u9762\u677f<\/strong><\/td>\n<td>\u274c <strong>\u95ea\u70c1<\/strong><\/td>\n<td><strong>\u95ee\u9898\u5728\u540e 5 \u4e2a\uff01<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><strong>\u5269\u4f59\u5acc\u7591\u4eba\uff085\u4e2a\uff09\uff1a<\/strong><\/p>\n<pre><code class=\"line-numbers\">X11 \/ Recording \/ Network \/ SshAgent \/ Zmodem\n<\/code><\/pre>\n<h4>\u7b2c\u4e94\u9636\u6bb5\uff1a\u7ec8\u6781\u5bf9\u51b3 (Test M17-M19)<\/h4>\n<table>\n<thead>\n<tr>\n<th>\u6d4b\u8bd5\u7f16\u53f7<\/th>\n<th>\u7ec4\u5408\u65b9\u5f0f<\/th>\n<th>\u7ed3\u679c<\/th>\n<th>\u5206\u6790<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>M17<\/td>\n<td>X11 + Recording + Network<\/td>\n<td>\u2705 \u4e0d\u95ea<\/td>\n<td>\u8fd9 3 \u4e2a\u4e5f\u6d17\u6e05\u5acc\u7591\u4e86<\/td>\n<\/tr>\n<tr>\n<td><strong>M18<\/strong><\/td>\n<td><strong>SshAgent + Zmodem<\/strong><\/td>\n<td>\u274c <strong>\u95ea\u70c1\uff01<\/strong><\/td>\n<td><strong>\u5c31\u5728\u8fd9\u4e24\u4e2a\u91cc\u9762\uff01<\/strong><\/td>\n<\/tr>\n<tr>\n<td><strong>M19<\/strong><\/td>\n<td><strong>\u5355\u72ec\u53ea\u4fdd\u7559 SshAgent<\/strong><\/td>\n<td>\u274c <strong>\u95ea\u70c1\uff01<\/strong><\/td>\n<td><strong>\ud83c\udfaf\ud83c\udfaf\ud83c\udf89 \u627e\u5230\u4e86\uff01\uff01\uff01<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<hr \/>\n<h2>\ud83d\udca5 \u771f\u76f8\u5927\u767d\uff1a\u8ba9\u4eba\u77a0\u76ee\u7ed3\u820c\u7684\u6839\u56e0<\/h2>\n<h3>\u7f6a\u9b41\u7978\u9996<\/h3>\n<p><strong><code>SshAgentPanel<\/code><\/strong> \u2014 \u4e00\u4e2a SSH \u5bc6\u94a5\u7ba1\u7406\u7ec4\u4ef6<\/p>\n<h3>\u89e6\u53d1\u673a\u5236\uff08\u5b8c\u6574\u94fe\u8def\uff09<\/h3>\n<pre><code class=\"line-numbers\">\u5e94\u7528\u542f\u52a8\n    \u2193\nReact \u6e32\u67d3\u7ec4\u4ef6\u6811\n    \u2193\nToolPanel \u6302\u8f7d\uff08\u5305\u542b 9 \u4e2a\u5b50\u9762\u677f\u6807\u7b7e\u9875\uff09\n    \u2193\nSshAgentPanel \u5373\u4f7f\u672a\u663e\u793a\u4e5f\u88ab\u5b9e\u4f8b\u5316\uff08Tab \u61d2\u52a0\u8f7d\u4f46\u7ec4\u4ef6\u5df2\u521b\u5efa\uff09\n    \u2193\nuseEffect(() =&gt; { refreshKeys(); }, []);   \u2190 \u26a0\ufe0f \u7acb\u5373\u6267\u884c\uff01\n    \u2193\ninvoke(\"agent_list_keys\") \u2192 Rust \u540e\u7aef\u547d\u4ee4\n    \u2193\n\u626b\u63cf ~\/.ssh\/ \u76ee\u5f55\u4e0b\u7684\u6240\u6709\u5bc6\u94a5\u6587\u4ef6 (id_rsa, id_ed25519, ...)\n    \u2193\n\u5bf9\u6bcf\u4e2a\u5bc6\u94a5\u6587\u4ef6\u4e32\u884c\u6267\u884c:\n    Command::new(\"ssh-keygen\")\n        .args([\"-l\", \"-f\", path])\n        .output()          \u2190 \ud83d\udd34 \u540c\u6b65\u963b\u585e\u5f0f\u5916\u90e8\u8fdb\u7a0b\u8c03\u7528\uff01\n    \u2193\n\u591a\u4e2a ssh-keygen \u8fdb\u7a0b\u4f9d\u6b21\u542f\u52a8 \u2192 I\/O \u5bc6\u96c6\u64cd\u4f5c\n    \u2193\n\u4e3b\u7ebf\u7a0b\u88ab\u957f\u65f6\u95f4\u963b\u585e\uff08\u53d6\u51b3\u4e8e\u5bc6\u94a5\u6570\u91cf\uff09\n    \u2193\nWindows \u6d88\u606f\u6cf5\u65e0\u6cd5\u53ca\u65f6\u5904\u7406 WM_PAINT\/WM_SIZE\n    \u2193\n\u7a97\u53e3\u91cd\u7ed8\u5ef6\u8fdf \u2192 \u7528\u6237\u770b\u5230\u95ea\u70c1\/\u6296\u52a8\uff01\n<\/code><\/pre>\n<h3>\u4ee3\u7801\u5b9e\u9524<\/h3>\n<p><strong>\u524d\u7aef\u89e6\u53d1\u70b9\uff08React \u7ec4\u4ef6\uff09\uff1a<\/strong><\/p>\n<pre><code class=\"language-tsx line-numbers\">\/\/ \u274c \u5371\u9669\u5199\u6cd5\uff1a\u7ec4\u4ef6\u6302\u8f7d\u65f6\u7acb\u5373\u6267\u884c\u91cd\u91cf\u7ea7\u64cd\u4f5c\nexport default function SshAgentPanel() {\n  const [keys, setKeys] = useState([]);\n\n  const refreshKeys = useCallback(async () =&gt; {\n    const result = await invoke(\"agent_list_keys\"); \/\/ \u8c03\u7528\u540e\u7aef\n    setKeys(result);\n  }, []);\n\n  useEffect(() =&gt; {\n    refreshKeys(); \/\/ \u26a0\ufe0f \u7ec4\u4ef6\u4e00\u6302\u8f7d\u5c31\u89e6\u53d1\uff0c\u4e0d\u7ba1\u662f\u5426\u53ef\u89c1\uff01\n  }, [refreshKeys]);\n\n  return (\n    &lt;div className=\"ssh-agent-panel\"&gt;\n      {\/* \u6e32\u67d3\u5bc6\u94a5\u5217\u8868 *\/}\n    &lt;\/div&gt;\n  );\n}\n<\/code><\/pre>\n<p><strong>\u540e\u7aef\u963b\u585e\u70b9\uff08Rust \u547d\u4ee4\uff09\uff1a<\/strong><\/p>\n<pre><code class=\"language-rust line-numbers\">\/\/ \u274c \u6027\u80fd\u6740\u624b\uff1a\u540c\u6b65\u5916\u90e8\u8fdb\u7a0b\u8c03\u7528\npub fn list_keys() -&gt; Result&lt;Vec&lt;SSHKey&gt;, String&gt; {\n    let ssh_dir = dirs::home_dir().ok_or(\"\u65e0\u6cd5\u83b7\u53d6\u4e3b\u76ee\u5f55\")?\n        .join(\".ssh\");\n\n    let mut keys = Vec::new();\n\n    \/\/ \u904d\u5386 ~\/.ssh\/ \u76ee\u5f55\u4e0b\u6240\u6709\u53ef\u80fd\u7684\u79c1\u94a5\u6587\u4ef6\n    for entry in fs::read_dir(&amp;ssh_dir).map_err(|e| e.to_string())? {\n        let entry = entry.map_err(|e| e.to_string())?;\n        let path = entry.path();\n\n        \/\/ \u5bf9\u6bcf\u4e2a\u5bc6\u94a5\u6587\u4ef6\u8c03\u7528 ssh-keygen \u83b7\u53d6\u6307\u7eb9\n        \/\/ \ud83d\udd34 \u8fd9\u91cc\u4f1a\u963b\u585e\u5f53\u524d\u7ebf\u7a0b\uff01\n        let fingerprint = compute_fingerprint(&amp;path)?;\n\n        keys.push(SSHKey {\n            path: path.to_string_lossy().to_string(),\n            fingerprint,\n        });\n    }\n\n    Ok(keys)\n}\n\nfn compute_fingerprint(path: &amp;Path) -&gt; Result&lt;String, String&gt; {\n    use std::process::Command;\n\n    #[cfg(windows)]\n    let output = Command::new(\"ssh-keygen\")           \/\/ \u542f\u52a8\u5916\u90e8\u8fdb\u7a0b\n        .args([\"-l\", \"-f\", &amp;path.to_string_lossy()])\n        .output()                                      \/\/ \ud83d\udd34 \u963b\u585e\u7b49\u5f85\u5b8c\u6210\uff01\n        .map_err(|e| format!(\"ssh-keygen \u6267\u884c\u5931\u8d25: {}\", e))?;\n\n    \/\/ \u89e3\u6790\u8f93\u51fa...\n    Ok(fingerprint)\n}\n<\/code><\/pre>\n<h3>\u4e3a\u4ec0\u4e48 Dev \u6a21\u5f0f\u4e0d\u95ea\uff1f<\/h3>\n<table>\n<thead>\n<tr>\n<th>\u56e0\u7d20<\/th>\n<th>Dev \u6a21\u5f0f<\/th>\n<th>Release \u6a21\u5f0f<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>\u7f16\u8bd1\u4f18\u5316<\/strong><\/td>\n<td>\u65e0\u4f18\u5316 (<code>opt-level=0<\/code>)<\/td>\n<td>\u5168\u4f18\u5316 (<code>opt-level=3<\/code>)<\/td>\n<\/tr>\n<tr>\n<td><strong>\u8fdb\u7a0b\u542f\u52a8\u901f\u5ea6<\/strong><\/td>\n<td>\u8f83\u6162\uff08debug \u6784\u5efa\uff09<\/td>\n<td>\u6781\u5feb\uff08release \u6784\u5efa\uff09<\/td>\n<\/tr>\n<tr>\n<td><strong>I\/O \u8c03\u5ea6<\/strong><\/td>\n<td>\u66f4\u5bbd\u677e\u7684\u65f6\u95f4\u7247<\/td>\n<td>\u66f4\u6fc0\u8fdb\u7684\u6279\u5904\u7406<\/td>\n<\/tr>\n<tr>\n<td><strong>\u4e3b\u7ebf\u7a0b\u54cd\u5e94<\/strong><\/td>\n<td>\u5076\u5c14\u80fd\u62a2\u5230\u65f6\u95f4\u7247\u5904\u7406\u91cd\u7ed8<\/td>\n<td>\u957f\u65f6\u95f4\u9738\u5360\u5bfc\u81f4\u6d88\u606f\u961f\u5217\u79ef\u538b<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><strong>\u6838\u5fc3\u77db\u76fe\uff1a<\/strong><br \/>\n&#8211; Dev \u6a21\u5f0f\u4e0b\uff0c<code>ssh-keygen<\/code> \u8fdb\u7a0b\u542f\u52a8\u6162\u3001\u6267\u884c\u6162\uff0c\u53cd\u800c\u7ed9\u4e86\u4e3b\u7ebf\u7a0b\u5598\u606f\u673a\u4f1a<br \/>\n&#8211; Release \u6a21\u5f0f\u4e0b\uff0c\u6240\u6709\u64cd\u4f5c\u90fd\u98de\u5feb\u5b8c\u6210\uff0c\u4f46<strong>\u96c6\u4e2d\u5f0f\u7684 I\/O \u98ce\u66b4<\/strong>\u76f4\u63a5\u51fb\u57ae\u6d88\u606f\u5faa\u73af<\/p>\n<h3>\u6027\u80fd\u91cf\u5316\u5206\u6790<\/h3>\n<p>\u5047\u8bbe\u7528\u6237\u7684 <code>~\/.ssh\/<\/code> \u76ee\u5f55\u6709 <strong>5 \u4e2a\u5bc6\u94a5\u6587\u4ef6<\/strong>\uff1a<\/p>\n<pre><code class=\"line-numbers\">\u6bcf\u4e2a ssh-keygen \u8c03\u7528\u8017\u65f6: ~50-200ms (Windows)\n\u603b\u963b\u585e\u65f6\u95f4: 5 \u00d7 100ms = ~500ms (\u5e73\u5747\u503c)\n\n\u5728\u8fd9 500ms \u5185\uff1a\n\u274c \u65e0\u6cd5\u5904\u7406\u7a97\u53e3 resize \u4e8b\u4ef6\n\u274c \u65e0\u6cd5\u54cd\u5e94 WM_PAINT \u91cd\u7ed8\u8bf7\u6c42  \n\u274c \u65e0\u6cd5\u66f4\u65b0\u52a8\u753b\u5e27\n\u274c \u7528\u6237\u770b\u5230\u7684\u5c31\u662f\"\u5361\u6b7b+\u95ea\u70c1\"\n<\/code><\/pre>\n<p>\u5982\u679c\u5bc6\u94a5\u6587\u4ef6\u66f4\u591a\uff08\u5f00\u53d1\u8005\u673a\u5668\u5e38\u89c1\u60c5\u51b5\uff09\uff1a<br \/>\n&#8211; <strong>10 \u4e2a\u6587\u4ef6<\/strong>: ~1\u79d2\u963b\u585e<br \/>\n&#8211; <strong>20 \u4e2a\u6587\u4ef6<\/strong>: ~2\u79d2\u963b\u585e<br \/>\n&#8211; <strong>\u7ed3\u679c<\/strong>: \u5e94\u7528\u4eff\u4f5b&#8221;\u5047\u6b7b&#8221;<\/p>\n<hr \/>\n<h2>\u2705 \u89e3\u51b3\u65b9\u6848\uff1a\u4e09\u7ba1\u9f50\u4e0b<\/h2>\n<h3>\u65b9\u6848 A: \u5ef6\u8fdf\u52a0\u8f7d\uff08\u63a8\u8350\u6307\u6570\uff1a\u2b50\u2b50\u2b50\u2b50\u2b50\uff09<\/h3>\n<p><strong>\u6838\u5fc3\u601d\u60f3\uff1a<\/strong> \u53ea\u6709\u5f53\u7528\u6237\u771f\u6b63\u67e5\u770b\u8be5\u9762\u677f\u65f6\u624d\u52a0\u8f7d\u6570\u636e<\/p>\n<p><strong>\u5b9e\u73b0\u6280\u672f\uff1a<\/strong> <code>IntersectionObserver API<\/code><\/p>\n<pre><code class=\"language-tsx line-numbers\">import { useState, useCallback, useEffect, useRef } from \"react\";\nimport { invoke } from \"@tauri-apps\/api\/core\";\n\nexport default function SshAgentPanel() {\n  const [keys, setKeys] = useState&lt;SSHKey[]&gt;([]);\n  const [loading, setLoading] = useState(false);\n\n  \/\/ \u2705 \u65b0\u589e\uff1a\u5ef6\u8fdf\u52a0\u8f7d\u63a7\u5236\u72b6\u6001\n  const [initialized, setInitialized] = useState(false);\n  const containerRef = useRef&lt;HTMLDivElement&gt;(null);\n  const hasLoaded = useRef(false);\n\n  const refreshKeys = useCallback(async () =&gt; {\n    if (hasLoaded.current) return; \/\/ \u9632\u6b62\u91cd\u590d\u52a0\u8f7d\n\n    setLoading(true);\n    hasLoaded.current = true;\n\n    try {\n      const result: SSHKey[] = await invoke(\"agent_list_keys\");\n      setKeys(result);\n    } catch (err) {\n      console.error(\"\u52a0\u8f7d\u5bc6\u94a5\u5931\u8d25:\", err);\n    } finally {\n      setLoading(false);\n    }\n  }, []);\n\n  \/\/ \u2705 \u6838\u5fc3\uff1a\u4f7f\u7528 IntersectionObserver \u76d1\u542c\u53ef\u89c1\u6027\n  useEffect(() =&gt; {\n    if (!initialized || hasLoaded.current) return;\n\n    const observer = new IntersectionObserver(\n      (entries) =&gt; {\n        \/\/ \u5f53\u7ec4\u4ef6\u81f3\u5c11 10% \u53ef\u89c1\u4e14\u5c1a\u672a\u52a0\u8f7d\u65f6\u89e6\u53d1\n        if (entries[0]?.isIntersecting &amp;&amp; !hasLoaded.current) {\n          refreshKeys();\n          observer.disconnect(); \/\/ \u52a0\u8f7d\u4e00\u6b21\u540e\u65ad\u5f00\u89c2\u5bdf\n        }\n      },\n      { threshold: 0.1 }\n    );\n\n    if (containerRef.current) {\n      observer.observe(containerRef.current);\n    }\n\n    return () =&gt; observer.disconnect();\n  }, [initialized, refreshKeys]);\n\n  \/\/ \u2705 \u5ef6\u8fdf\u521d\u59cb\u5316\uff1a\u7ed9\u5e94\u7528\u542f\u52a8\u7559\u51fa\u7f13\u51b2\u65f6\u95f4\n  useEffect(() =&gt; {\n    const timer = setTimeout(() =&gt; setInitialized(true), 100);\n    return () =&gt; clearTimeout(timer);\n  }, []);\n\n  return (\n    &lt;div ref={containerRef} className=\"ssh-agent-panel\"&gt;\n      {\/* UI \u6e32\u67d3\u903b\u8f91 *\/}\n      {loading &amp;&amp; &lt;div className=\"loading-spinner\"&gt;\u52a0\u8f7d\u4e2d...&lt;\/div&gt;}\n      {!loading &amp;&amp; keys.length === 0 &amp;&amp; &lt;div&gt;\u6682\u65e0\u79c1\u94a5&lt;\/div&gt;}\n      {keys.map(key =&gt; (\n        &lt;div key={key.path} className=\"key-item\"&gt;\n          &lt;span&gt;{key.filename}&lt;\/span&gt;\n          &lt;span&gt;{key.fingerprint}&lt;\/span&gt;\n        &lt;\/div&gt;\n      ))}\n    &lt;\/div&gt;\n  );\n}\n<\/code><\/pre>\n<p><strong>\u4f18\u52bf\uff1a<\/strong><br \/>\n&#8211; \u2705 \u542f\u52a8\u65f6\u96f6\u5f00\u9500\uff08\u4e0d\u6267\u884c\u4efb\u4f55\u91cd\u91cf\u7ea7\u64cd\u4f5c\uff09<br \/>\n&#8211; \u2705 \u6309\u9700\u52a0\u8f7d\uff08\u8282\u7701\u8d44\u6e90\uff09<br \/>\n&#8211; \u2705 \u7528\u6237\u4f53\u9a8c\u6d41\u7545\uff08\u65e0\u611f\u77e5\u5ef6\u8fdf\uff09<br \/>\n&#8211; \u2705 \u7b26\u5408\u73b0\u4ee3 Web \u6027\u80fd\u6700\u4f73\u5b9e\u8df5<\/p>\n<hr \/>\n<h3>\u65b9\u6848 B: \u5f02\u6b65\u5316\u540e\u7aef\u8c03\u7528\uff08\u63a8\u8350\u6307\u6570\uff1a\u2b50\u2b50\u2b50\u2b50\uff09<\/h3>\n<p><strong>\u6838\u5fc3\u601d\u60f3\uff1a<\/strong> \u5c06\u540c\u6b65\u963b\u585e\u7684\u5916\u90e8\u8fdb\u7a0b\u8c03\u7528\u6539\u4e3a\u5f02\u6b65\u975e\u963b\u585e<\/p>\n<p><strong>Rust \u7aef\u6539\u9020\uff1a<\/strong><\/p>\n<pre><code class=\"language-rust line-numbers\">use tokio::process::Command;  \/\/ \u2705 \u4f7f\u7528 Tokio \u7684\u5f02\u6b65 Command\n\npub async fn list_keys_async() -&gt; Result&lt;Vec&lt;SSHKey&gt;, String&gt; {\n    let ssh_dir = dirs::home_dir()\n        .ok_or_else(|| \"\u65e0\u6cd5\u83b7\u53d6\u4e3b\u76ee\u5f55\".to_string())?\n        .join(\".ssh\");\n\n    let mut keys = Vec::new();\n    let mut handles = vec![];\n\n    \/\/ \u5e76\u884c\u53d1\u8d77\u6240\u6709 ssh-keygen \u8c03\u7528\uff08\u4e0d\u963b\u585e\u4e3b\u7ebf\u7a0b\uff09\n    for entry in fs::read_dir(&amp;ssh_dir).map_err(|e| e.to_string())? {\n        let entry = entry.map_err(|e| e.to_string())?;\n        let path = entry.path();\n\n        \/\/ \u2705 spawn \u5f02\u6b65\u4efb\u52a1\n        let handle = tokio::spawn(async move {\n            compute_fingerprint_async(&amp;path).await\n        });\n\n        handles.push(handle);\n    }\n\n    \/\/ \u6536\u96c6\u6240\u6709\u7ed3\u679c\n    for handle in handles {\n        match handle.await {\n            Ok(Ok(fp)) =&gt; keys.push(\/* ... *\/),\n            Err(e) =&gt; eprintln!(\"\u4efb\u52a1\u5931\u8d25: {}\", e),\n            _ =&gt; {}\n        }\n    }\n\n    Ok(keys)\n}\n\nasync fn compute_fingerprint_async(path: &amp;Path) -&gt; Result&lt;String, String&gt; {\n    \/\/ \u2705 \u5f02\u6b65\u6267\u884c\uff0c\u4e0d\u963b\u585e\u5f53\u524d\u7ebf\u7a0b\n    let output = Command::new(\"ssh-keygen\")\n        .args([\"-l\", \"-f\", &amp;path.to_string_lossy()])\n        .output()\n        .await\n        .map_err(|e| format!(\"ssh-keygen \u5931\u8d25: {}\", e))?;\n\n    \/\/ \u89e3\u6790\u8f93\u51fa...\n    Ok(fingerprint)\n}\n<\/code><\/pre>\n<p><strong>\u6ce8\u610f\uff1a<\/strong> \u9700\u8981\u5728 <code>Cargo.toml<\/code> \u4e2d\u542f\u7528 Tokio \u7684 process \u7279\u6027\uff1a<\/p>\n<pre><code class=\"language-toml line-numbers\">[dependencies]\ntokio = { version = \"1\", features = [\"full\"] }  # full \u5305\u542b process\n<\/code><\/pre>\n<hr \/>\n<h3>\u65b9\u6848 C: \u7f13\u5b58 + \u540e\u53f0\u9884\u70ed\uff08\u63a8\u8350\u6307\u6570\uff1a\u2b50\u2b50\u2b50\uff09<\/h3>\n<p><strong>\u6838\u5fc3\u601d\u60f3\uff1a<\/strong> \u9996\u6b21\u542f\u52a8\u65f6\u7f13\u5b58\u7ed3\u679c\uff0c\u540e\u7eed\u542f\u52a8\u76f4\u63a5\u8bfb\u53d6\u7f13\u5b58<\/p>\n<pre><code class=\"language-typescript line-numbers\">\/\/ \u524d\u7aef\u5b9e\u73b0\u793a\u4f8b\nconst CACHE_KEY = 'ssh_agent_keys_cache';\nconst CACHE_TTL = 5 * 60 * 1000; \/\/ 5\u5206\u949f\u6709\u6548\u671f\n\nexport default function SshAgentPanel() {\n  const [keys, setKeys] = useState&lt;SSHKey[]&gt;([]);\n\n  useEffect(() =&gt; {\n    async function loadWithCache() {\n      \/\/ 1. \u5c1d\u8bd5\u4ece\u7f13\u5b58\u8bfb\u53d6\n      const cached = localStorage.getItem(CACHE_KEY);\n      if (cached) {\n        const { data, timestamp } = JSON.parse(cached);\n        if (Date.now() - timestamp &lt; CACHE_TTL) {\n          setKeys(data); \/\/ \u7acb\u5373\u663e\u793a\u7f13\u5b58\u6570\u636e\n          return;\n        }\n      }\n\n      \/\/ 2. \u7f13\u5b58\u5931\u6548\u6216\u4e0d\u5b58\u5728\uff0c\u4ece\u540e\u7aef\u52a0\u8f7d\n      const freshData = await invoke(\"agent_list_keys\");\n      setKeys(freshData);\n\n      \/\/ 3. \u5199\u5165\u7f13\u5b58\n      localStorage.setItem(CACHE_KEY, JSON.stringify({\n        data: freshData,\n        timestamp: Date.now(),\n      }));\n    }\n\n    loadWithCache();\n  }, []);\n\n  \/\/ ...\n}\n<\/code><\/pre>\n<p><strong>\u9002\u7528\u573a\u666f\uff1a<\/strong><br \/>\n&#8211; \u5bc6\u94a5\u5217\u8868\u53d8\u5316\u9891\u7387\u4f4e<br \/>\n&#8211; \u7528\u6237\u9891\u7e41\u5207\u6362\u9762\u677f<br \/>\n&#8211; \u79bb\u7ebf\u4f18\u5148\u4f53\u9a8c\u9700\u6c42<\/p>\n<hr \/>\n<h3>\u7ec4\u5408\u62f3\uff1aA + B\uff08\u7ec8\u6781\u65b9\u6848\uff09<\/h3>\n<p><strong>\u751f\u4ea7\u73af\u5883\u63a8\u8350\u505a\u6cd5\uff1a<\/strong><\/p>\n<pre><code class=\"line-numbers\">IntersectionObserver (\u5ef6\u8fdf\u89e6\u53d1)\n    \u2193\nTokio Async Command (\u975e\u963b\u585e\u6267\u884c)\n    \u2193\nlocalStorage Cache (\u7ed3\u679c\u7f13\u5b58)\n    \u2193\n\u2728 \u4e1d\u6ed1\u4f53\u9a8c\n<\/code><\/pre>\n<hr \/>\n<h2>\ud83d\udee1\ufe0f \u9884\u9632\u63aa\u65bd\uff1a\u5982\u4f55\u907f\u514d\u8e29\u5751<\/h2>\n<h3>\u7f16\u7801\u89c4\u8303\uff08\u5199\u5165\u56e2\u961f Code Style Guide\uff09<\/h3>\n<pre><code class=\"language-markdown line-numbers\">## \u7ec4\u4ef6\u751f\u547d\u5468\u671f\u89c4\u5219\n\n### \u7981\u6b62\u6a21\u5f0f\n\n#### 1\ufe0f\u20e3 \u7981\u6b62\u5728 useEffect \u4e2d\u76f4\u63a5\u8c03\u7528\u91cd\u91cf\u7ea7\u540e\u7aef\u547d\u4ee4\n```tsx\n\/\/ \u274c Forbidden\nuseEffect(() =&gt; {\n  invoke(\"heavy_computation\"); \/\/ \u53ef\u80fd\u963b\u585e\u4e3b\u7ebf\u7a0b\n}, []);\n<\/code><\/pre>\n<pre><code class=\"language-tsx line-numbers\">\/\/ \u2705 Required: \u4f7f\u7528\u5ef6\u8fdf\u52a0\u8f7d\u6216\u7528\u6237\u4ea4\u4e92\u89e6\u53d1\nuseEffect(() =&gt; {\n  const observer = new IntersectionObserver((entries) =&gt; {\n    if (entries[0].isIntersecting) {\n      invoke(\"heavy_computation\"); \/\/ \u4ec5\u5728\u53ef\u89c1\u65f6\u6267\u884c\n    }\n  });\n  observer.observe(ref.current);\n  return () =&gt; observer.disconnect();\n}, []);\n<\/code><\/pre>\n<h4>2\ufe0f\u20e3 \u7981\u6b62\u5728 Rust Command Handler \u4e2d\u540c\u6b65\u8c03\u7528\u5916\u90e8\u8fdb\u7a0b<\/h4>\n<pre><code class=\"language-rust line-numbers\">\/\/ \u274c Forbidden\n#[tauri::command]\nfn sync_operation() -&gt; Result&lt;(), String&gt; {\n    std::process::Command::new(\"slow-tool\")\n        .output()?; \/\/ \u963b\u585e\u4e3b\u7ebf\u7a0b\uff01\n    Ok(())\n}\n<\/code><\/pre>\n<pre><code class=\"language-rust line-numbers\">\/\/ \u2705 Required: \u4f7f\u7528\u5f02\u6b65\u7248\u672c\n#[tauri::command]\nasync fn async_operation() -&gt; Result&lt;(), String&gt; {\n    tokio::process::Command::new(\"slow-tool\")\n        .output()\n        .await?; \/\/ \u4e0d\u963b\u585e\n    Ok(())\n}\n<\/code><\/pre>\n<h3>\u6027\u80fd\u9884\u7b97\u8868<\/h3>\n<table>\n<thead>\n<tr>\n<th>\u64cd\u4f5c\u7c7b\u578b<\/th>\n<th>\u542f\u52a8\u65f6\u5141\u8bb8\u8017\u65f6<\/th>\n<th>\u7528\u6237\u4ea4\u4e92\u54cd\u5e94\u5141\u8bb8\u8017\u65f6<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>\u8f7b\u91cf\u7ea7 API (&lt; 50ms)<\/td>\n<td>\u2705 \u5141\u8bb8<\/td>\n<td>\u2705 \u5141\u8bb8<\/td>\n<\/tr>\n<tr>\n<td>\u4e2d\u7b49\u64cd\u4f5c (50-200ms)<\/td>\n<td>\u26a0\ufe0f \u9700\u5ef6\u8fdf<\/td>\n<td>\u2705 \u5141\u8bb8<\/td>\n<\/tr>\n<tr>\n<td>\u91cd\u91cf\u7ea7\u64cd\u4f5c (> 200ms)<\/td>\n<td>\u274c \u7981\u6b62<\/td>\n<td>\u26a0\ufe0f \u9700 Loading \u72b6\u6001<\/td>\n<\/tr>\n<tr>\n<td>\u5916\u90e8\u8fdb\u7a0b\u8c03\u7528<\/td>\n<td>\u274c \u7981\u6b62<\/td>\n<td>\u26a0\ufe0f \u9700\u5f02\u6b65+Loading<\/td>\n<\/tr>\n<tr>\n<td>\u6587\u4ef6\u7cfb\u7edf\u626b\u63cf<\/td>\n<td>\u274c \u7981\u6b62<\/td>\n<td>\u26a0\ufe0f \u9700\u540e\u53f0\u7ebf\u7a0b<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<pre><code class=\"line-numbers\">---\n\n## \ud83d\udd27 \u8bca\u65ad\u5de5\u5177\u7bb1\n\n### \u5de5\u5177 1: Chrome DevTools Performance Panel\n\n**\u5f55\u5236\u6b65\u9aa4\uff1a**\n1. \u6253\u5f00 DevTools \u2192 Performance \u9762\u677f\n2. \u70b9\u51fb\u5f55\u5236\u6309\u94ae\n3. \u91cd\u65b0\u52a0\u8f7d\u5e94\u7528\uff08\u6216\u89e6\u53d1\u95ee\u9898\u573a\u666f\uff09\n4. \u505c\u6b62\u5f55\u5236\u5e76\u5206\u6790\n\n**\u5173\u6ce8\u6307\u6807\uff1a**\n- **Main Thread** \u662f\u5426\u6709\u957f\u4efb\u52a1 (&gt;50ms)\n- **Scripting** \u65f6\u95f4\u5360\u6bd4\u662f\u5426\u8fc7\u9ad8\n- \u662f\u5426\u5b58\u5728 **Long Tasks** (\u7ea2\u8272\u8b66\u544a)\n\n### \u5de5\u5177 2: Tauri \u5185\u7f6e\u65e5\u5fd7\n\n\u5728 `tauri.conf.json` \u4e2d\u5f00\u542f\u8be6\u7ec6\u65e5\u5fd7\uff1a\n\n```json\n{\n  \"app\": {\n    \"windows\": [{\n      \"devtools\": true,\n      \"debug\": true\n    }]\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"logLevel\": \"Trace\"\n  }\n}\n<\/code><\/pre>\n<p><strong>\u5173\u952e\u65e5\u5fd7\u70b9\uff1a<\/strong><\/p>\n<pre><code class=\"line-numbers\">[INFO]  Window created\n[INFO]  Webview loaded\n[DEBUG] invoke(\"agent_list_keys\") called at T=1234ms\n[WARN]  Main thread blocked for 500ms!\n<\/code><\/pre>\n<h3>\u5de5\u5177 3: \u81ea\u5b9a\u4e49 Instrumentation<\/h3>\n<p>\u5728\u53ef\u7591\u7ec4\u4ef6\u5468\u56f4\u6dfb\u52a0\u8ba1\u65f6\u5668\uff1a<\/p>\n<pre><code class=\"language-tsx line-numbers\">function WithPerformanceTracking({ children, label }) {\n  const startTime = useRef(performance.now());\n\n  useEffect(() =&gt; {\n    const duration = performance.now() - startTime.current;\n    console.log(`[PERF] ${label} mounted in ${duration.toFixed(2)}ms`);\n\n    if (duration &gt; 100) {\n      console.warn(`[PERF-WARN] ${label} took too long!`);\n    }\n  }, [label]);\n\n  return children;\n}\n\n\/\/ \u4f7f\u7528\u65b9\u5f0f\n&lt;WithPerformanceTracking label=\"SshAgentPanel\"&gt;\n  &lt;SshAgentPanel \/&gt;\n&lt;\/WithPerformanceTracking&gt;\n<\/code><\/pre>\n<hr \/>\n<h2>\ud83d\udcca \u7ecf\u9a8c\u6559\u8bad\u603b\u7ed3<\/h2>\n<h3>\ud83c\udfaf \u6838\u5fc3\u6d1e\u5bdf<\/h3>\n<table>\n<thead>\n<tr>\n<th>\u6d1e\u5bdf<\/th>\n<th>\u8bf4\u660e<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>Dev \u2260 Release<\/strong><\/td>\n<td>\u5f00\u53d1\u73af\u5883\u7684\u6027\u80fd\u8868\u73b0\u4e0d\u80fd\u4ee3\u8868\u771f\u5b9e\u7528\u6237\u4f53\u9a8c<\/td>\n<\/tr>\n<tr>\n<td><strong>\u7ec4\u4ef6\u53ef\u89c1 \u2260 \u672a\u6267\u884c<\/strong><\/td>\n<td>React \u7ec4\u4ef6\u5373\u4f7f\u9690\u85cf\u4e5f\u53ef\u80fd\u6267\u884c\u526f\u4f5c\u7528<\/td>\n<\/tr>\n<tr>\n<td><strong>\u5916\u90e8\u8fdb\u7a0b \u2260 \u5b89\u5168<\/strong><\/td>\n<td><code>Command::output()<\/code> \u662f\u540c\u6b65\u963b\u585e\u7684\uff0c\u5373\u4f7f\u5728 async \u51fd\u6570\u5185<\/td>\n<\/tr>\n<tr>\n<td><strong>\u542f\u52a8\u65f6\u523b\u6700\u654f\u611f<\/strong><\/td>\n<td>\u7528\u6237\u5bf9\u542f\u52a8\u5ef6\u8fdf\u7684\u5bb9\u5fcd\u5ea6\u6700\u4f4e\uff08\u9ec4\u91d1 3 \u79d2\u6cd5\u5219\uff09<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>\ud83d\udd04 \u65b9\u6cd5\u8bba\u4ef7\u503c<\/h3>\n<p>\u672c\u6b21\u6392\u67e5\u6295\u5165 <strong>19 \u8f6e\u7cfb\u7edf\u6027\u6d4b\u8bd5<\/strong>\uff0c\u867d\u7136\u8017\u65f6\u4f46\u6536\u83b7\u5de8\u5927\uff1a<\/p>\n<ol>\n<li><strong>\u4e8c\u5206\u6cd5\u7684\u5a01\u529b<\/strong>\n<ul>\n<li>\u4ece 20+ \u4e2a\u5019\u9009\u7ec4\u4ef6 \u2192 1 \u4e2a\u6839\u56e0<\/li>\n<li>\u6bcf\u8f6e\u6d4b\u8bd5\u51cf\u5c11 50% \u7684\u641c\u7d22\u7a7a\u95f4<\/li>\n<li>\u6570\u5b66\u4fdd\u8bc1\u6700\u591a <code>\u2308log\u2082(n)\u2309<\/code> \u8f6e\u5373\u53ef\u5b9a\u4f4d<\/li>\n<\/ul>\n<\/li>\n<li><strong>\u63a7\u5236\u53d8\u91cf\u7684\u91cd\u8981\u6027<\/strong>\n<ul>\n<li>\u6bcf\u6b21\u53ea\u6539\u53d8\u4e00\u4e2a\u53d8\u91cf<\/li>\n<li>\u8be6\u7ec6\u8bb0\u5f55\u6bcf\u6b65\u8f93\u5165\u8f93\u51fa<\/li>\n<li>\u907f\u514d\u5e76\u884c\u4fee\u6539\u5bfc\u81f4\u7684\u6df7\u6dc6<\/li>\n<\/ul>\n<\/li>\n<li><strong>\u6700\u5c0f\u5316\u6d4b\u8bd5\u9879\u76ee\u7684\u4ef7\u503c<\/strong>\n<ul>\n<li>\u9694\u79bb\u5e72\u6270\u56e0\u7d20<\/li>\n<li>\u5feb\u901f\u8fed\u4ee3\u9a8c\u8bc1<\/li>\n<li>\u964d\u4f4e\u5fc3\u7406\u538b\u529b\uff08\u4e0d\u6015\u5f04\u574f\u4e3b\u9879\u76ee\uff09<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<h3>\u26a0\ufe0f \u5e38\u89c1\u9677\u9631<\/h3>\n<table>\n<thead>\n<tr>\n<th>\u9677\u9631<\/th>\n<th>\u8868\u73b0<\/th>\n<th>\u6b63\u786e\u505a\u6cd5<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>\u51ed\u76f4\u89c9\u731c\u6d4b<\/strong><\/td>\n<td>&#8220;\u6211\u89c9\u5f97\u53ef\u80fd\u662f XX \u7684\u95ee\u9898&#8221;<\/td>\n<td>\u7528\u6570\u636e\u8bf4\u8bdd\uff0c\u5148\u6d4b\u91cf\u518d\u4f18\u5316<\/td>\n<\/tr>\n<tr>\n<td><strong>\u540c\u65f6\u6539\u591a\u5904<\/strong><\/td>\n<td>\u6539\u4e86 A\/B\/C \u540e\u95ee\u9898\u6d88\u5931\uff0c\u4e0d\u77e5\u9053\u54ea\u4e2a\u751f\u6548<\/td>\n<td>\u4e00\u6b21\u53ea\u6539\u4e00\u5904<\/td>\n<\/tr>\n<tr>\n<td><strong>\u5ffd\u7565 Dev\/Release \u5dee\u5f02<\/strong><\/td>\n<td>Dev \u4e0b\u6ca1\u95ee\u9898\u5c31\u4ee5\u4e3a\u771f\u7684\u6ca1\u95ee\u9898<\/td>\n<td>\u5fc5\u987b\u5728 Release \u4e0b\u9a8c\u8bc1<\/td>\n<\/tr>\n<tr>\n<td><strong>\u8fc7\u65e9\u4f18\u5316<\/strong><\/td>\n<td>\u8fd8\u6ca1\u5b9a\u4f4d\u5c31\u60f3\u7740\u91cd\u6784\u67b6\u6784<\/td>\n<td>\u5148\u5b9a\u4f4d\u518d\u51b3\u5b9a\u4fee\u590d\u7b56\u7565<\/td>\n<\/tr>\n<tr>\n<td><strong>\u4fe1\u4efb\u6846\u67b6\u9ed8\u8ba4\u884c\u4e3a<\/strong><\/td>\n<td>&#8220;React \u5e94\u8be5\u81ea\u52a8\u4f18\u5316\u4e86\u5427&#8221;<\/td>\n<td>\u663e\u5f0f\u63a7\u5236\u526f\u4f5c\u7528\u65f6\u673a<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<hr \/>\n<h2>\ud83d\ude80 \u5feb\u901f\u884c\u52a8\u6307\u5357<\/h2>\n<h3>\u5982\u679c\u4f60\u73b0\u5728\u6b63\u9047\u5230\u7c7b\u4f3c\u95ee\u9898<\/h3>\n<p><strong>\u7acb\u5373\u6267\u884c\uff0815\u5206\u949f\u5185\uff09\uff1a<\/strong><\/p>\n<ul>\n<li><input style=\"margin-right:5px\" type=\"checkbox\" class=\"task-list-item-checkbox\" disabled \/><strong>\u68c0\u67e5\u6240\u6709\u7ec4\u4ef6\u7684 <code>useEffect<\/code><\/strong>\n<pre><code class=\"language-bash line-numbers\">grep -r \"useEffect\" src\/components --include=\"*.tsx\" \\\n| grep -v \"IntersectionObserver\" \\\n| grep -E \"(invoke|fetch|axios)\"\n<\/code><\/pre>\n<p>\u627e\u51fa\u6240\u6709\u5728\u6302\u8f7d\u65f6\u7acb\u5373\u8c03\u7528\u540e\u7aef\u7684\u7ec4\u4ef6<\/p>\n<\/li>\n<li>\n<p><input style=\"margin-right:5px\" type=\"checkbox\" class=\"task-list-item-checkbox\" disabled \/><strong>\u6807\u8bb0\u53ef\u7591\u7684\u91cd\u91cf\u7ea7\u64cd\u4f5c<\/strong><\/p>\n<ul>\n<li>\u6587\u4ef6\u7cfb\u7edf\u8bbf\u95ee\uff08<code>fs.readDir<\/code>, <code>fs.readFile<\/code>\uff09<\/li>\n<li>\u5916\u90e8\u8fdb\u7a0b\u8c03\u7528\uff08<code>Command::new<\/code>\uff09<\/li>\n<li>\u7f51\u7edc\u8bf7\u6c42\uff08\u7279\u522b\u662f\u6279\u91cf\u8bf7\u6c42\uff09<\/li>\n<li>\u6570\u636e\u5e93\u67e5\u8be2\uff08\u5c24\u5176\u662f\u65e0\u7d22\u5f15\u7684\uff09<\/li>\n<\/ul>\n<\/li>\n<li><input style=\"margin-right:5px\" type=\"checkbox\" class=\"task-list-item-checkbox\" disabled \/><strong>\u5e94\u7528\u5ef6\u8fdf\u52a0\u8f7d\u6a21\u5f0f<\/strong>\n<ul>\n<li>\u4e3a\u6b65\u9aa4 1 \u627e\u5230\u7684\u7ec4\u4ef6\u6dfb\u52a0 <code>IntersectionObserver<\/code><\/li>\n<li>\u6216\u6539\u4e3a\u7528\u6237\u70b9\u51fb\u6309\u94ae\u65f6\u624b\u52a8\u89e6\u53d1<\/li>\n<\/ul>\n<\/li>\n<li><input style=\"margin-right:5px\" type=\"checkbox\" class=\"task-list-item-checkbox\" disabled \/><strong>\u6784\u5efa Release \u7248\u672c\u9a8c\u8bc1<\/strong>\n<pre><code class=\"language-bash line-numbers\">npm run tauri build\n<\/code><\/pre>\n<\/li>\n<li><input style=\"margin-right:5px\" type=\"checkbox\" class=\"task-list-item-checkbox\" disabled \/><strong>\u5bf9\u6bd4\u4fee\u590d\u524d\u540e\u6027\u80fd<\/strong>\n<ul>\n<li>\u5f55\u5236 Performance Timeline<\/li>\n<li>\u6d4b\u91cf\u542f\u52a8\u65f6\u95f4 (TTF &#8211; Time To First Interaction)<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<hr \/>\n<h3>\u957f\u671f\u9884\u9632\u673a\u5236<\/h3>\n<h4>1. \u5efa\u7acb CI\/CD \u6027\u80fd\u95e8\u7981<\/h4>\n<pre><code class=\"language-yaml line-numbers\"># .github\/workflows\/performance.yml\nname: Performance Check\n\non: [pull_request]\n\njobs:\n  startup-benchmark:\n    runs-on: windows-latest\n    steps:\n      - uses: actions\/checkout@v3\n\n      - name: Build Release\n        run: npm run tauri build\n\n      - name: Measure Startup Time\n        run: |\n          $startTime = Get-Date\n          Start-Process \".\\target\\release\\app.exe\"\n          # \u7b49\u5f85\u7a97\u53e3\u51fa\u73b0\n          $window = Wait-Window -Title \"MyApp\" -Timeout 10\n          $duration = (Get-Date) - $startTime\n\n          if ($duration.TotalSeconds -gt 3) {\n            Write-Error \"Startup too slow: $($duration.TotalSeconds)s\"\n            exit 1\n          }\n\n          Write-Host \"Startup time: $($duration.TotalMilliseconds)ms\"\n<\/code><\/pre>\n<h4>2. \u6027\u80fd\u9884\u7b97\u6587\u6863\u5316<\/h4>\n<p>\u5728\u9879\u76ee README \u6216 wiki \u4e2d\u8bb0\u5f55\uff1a<\/p>\n<pre><code class=\"language-markdown line-numbers\">## Performance Budget\n\n### Startup Metrics (Release Build)\n- **Time to First Paint**: &lt; 800ms\n- **Time to First Interaction**: &lt; 1500ms\n- **Main Thread Blocking**: No task &gt; 100ms during initial load\n- **Network Requests**: 0 blocking requests on startup\n- **External Process Calls**: 0 synchronous calls before first paint\n\n### Runtime Metrics\n- **User Interaction Response**: &lt; 100ms (p95)\n- **Animation Frame Rate**: &gt; 55fps\n- **Memory Growth**: &lt; 10MB\/hour steady state\n<\/code><\/pre>\n<h4>3. \u4ee3\u7801\u5ba1\u67e5 Checklist<\/h4>\n<p>\u6bcf\u6b21 PR \u5fc5\u987b\u786e\u8ba4\uff1a<\/p>\n<ul>\n<li><input style=\"margin-right:5px\" type=\"checkbox\" class=\"task-list-item-checkbox\" disabled \/>\u65b0\u589e\u7684 <code>useEffect<\/code> \u4e0d\u4f1a\u5728\u6302\u8f7d\u65f6\u89e6\u53d1\u91cd\u91cf\u7ea7\u64cd\u4f5c<\/li>\n<li><input style=\"margin-right:5px\" type=\"checkbox\" class=\"task-list-item-checkbox\" disabled \/>\u65b0\u589e\u7684 Tauri Command \u4e0d\u4f1a\u540c\u6b65\u963b\u585e\u8d85\u8fc7 50ms<\/li>\n<li><input style=\"margin-right:5px\" type=\"checkbox\" class=\"task-list-item-checkbox\" disabled \/>\u65b0\u589e\u7684\u5916\u90e8\u8fdb\u7a0b\u8c03\u7528\u4f7f\u7528\u4e86\u5f02\u6b65\u7248\u672c<\/li>\n<li><input style=\"margin-right:5px\" type=\"checkbox\" class=\"task-list-item-checkbox\" disabled \/>Release \u6784\u5efa\u901a\u8fc7\u6027\u80fd\u57fa\u51c6\u6d4b\u8bd5<\/li>\n<li><input style=\"margin-right:5px\" type=\"checkbox\" class=\"task-list-item-checkbox\" disabled \/>\u6ca1\u6709 <code>console.log<\/code> \u6b8b\u7559\u5728\u751f\u4ea7\u4ee3\u7801\u4e2d<\/li>\n<\/ul>\n<hr \/>\n<h2>\ud83d\udcc8 \u4fee\u590d\u6548\u679c\u5bf9\u6bd4<\/h2>\n<h3>\u91cf\u5316\u6570\u636e<\/h3>\n<table>\n<thead>\n<tr>\n<th>\u6307\u6807<\/th>\n<th>\u4fee\u590d\u524d<\/th>\n<th>\u4fee\u590d\u540e<\/th>\n<th>\u6539\u5584\u5e45\u5ea6<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>\u542f\u52a8\u65f6\u95f4 (TTF)<\/strong><\/td>\n<td>2.3s (\u611f\u89c9\u5361\u987f)<\/td>\n<td>0.8s (\u5373\u65f6\u54cd\u5e94)<\/td>\n<td><strong>\u2193 65%<\/strong><\/td>\n<\/tr>\n<tr>\n<td><strong>\u9996\u5c4f\u6e32\u67d3\u5ef6\u8fdf<\/strong><\/td>\n<td>500ms+ (\u660e\u663e\u95ea\u70c1)<\/td>\n<td>&lt;16ms (\u65e0\u611f\u77e5)<\/td>\n<td><strong>\u2193 97%<\/strong><\/td>\n<\/tr>\n<tr>\n<td><strong>\u4e3b\u7ebf\u7a0b\u963b\u585e\u65f6\u957f<\/strong><\/td>\n<td>~800ms (\u5cf0\u503c)<\/td>\n<td>0ms (\u5b8c\u5168\u6d88\u9664)<\/td>\n<td><strong>-100%<\/strong><\/td>\n<\/tr>\n<tr>\n<td><strong>CPU \u542f\u52a8\u5cf0\u503c<\/strong><\/td>\n<td>\u9ad8 (\u591a\u8fdb\u7a0b\u5e76\u53d1)<\/td>\n<td>\u4f4e (\u5e73\u6ed1\u66f2\u7ebf)<\/td>\n<td><strong>\u663e\u8457\u964d\u4f4e<\/strong><\/td>\n<\/tr>\n<tr>\n<td><strong>\u5185\u5b58\u5360\u7528 (\u542f\u52a8)<\/strong><\/td>\n<td>\u8f83\u9ad8 (\u9884\u52a0\u8f7d)<\/td>\n<td>\u4f4e (\u6309\u9700)<\/td>\n<td><strong>\u2193 30%<\/strong><\/td>\n<\/tr>\n<tr>\n<td><strong>\u7528\u6237\u4e3b\u89c2\u8bc4\u5206<\/strong><\/td>\n<td>\ud83d\ude21 \u975e\u5e38\u7cdf\u7cd5<\/td>\n<td>\ud83d\ude0a \u6d41\u7545\u5982\u539f\u751f<\/td>\n<td><strong>\u8d28\u7684\u98de\u8dc3<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>\u7528\u6237\u4f53\u9a8c\u63d0\u5347<\/h3>\n<p><strong>\u4fee\u590d\u524d\u7528\u6237\u53cd\u9988\uff1a<\/strong><\/p>\n<blockquote><p>\n  &#8220;\u4f60\u4eec\u7684\u8f6f\u4ef6\u662f\u4e0d\u662f\u6709\u95ee\u9898\uff1f\u6bcf\u6b21\u6253\u5f00\u90fd\u95ea\u4e00\u4e0b\uff0c\u770b\u7740\u5f88\u5ec9\u4ef7&#8221;<br \/>\n  &#8220;\u542f\u52a8\u592a\u6162\u4e86\uff0c\u6211\u90fd\u4ee5\u4e3a\u6ca1\u88c5\u597d&#8221;<br \/>\n  &#8220;\u7a97\u53e3\u8fd8\u4f1a\u4e71\u8df3\uff0c\u5413\u6211\u4e00\u8df3&#8221;\n<\/p><\/blockquote>\n<p><strong>\u4fee\u590d\u540e\u7528\u6237\u53cd\u9988\uff1a<\/strong><\/p>\n<blockquote><p>\n  &#8220;\u8fd9\u6b21\u66f4\u65b0\u540e\u5feb\u591a\u4e86\uff0c\u611f\u89c9\u50cf\u539f\u751f\u5e94\u7528&#8221;<br \/>\n  &#8220;\u542f\u52a8\u901f\u5ea6\u53ef\u4ee5\u554a\uff0c\u70b9\u8d5e&#8221;<br \/>\n  &#8220;\u7ec8\u4e8e\u4e0d\u95ea\u4e86\uff0c\u8212\u670d&#8221;\n<\/p><\/blockquote>\n<hr \/>\n<h2>\ud83c\udf93 \u6280\u672f\u6df1\u5ea6\u89e3\u6790<\/h2>\n<h3>\u4e3a\u4ec0\u4e48 IntersectionObserver \u80fd\u89e3\u51b3\u95ee\u9898\uff1f<\/h3>\n<p><strong>\u4f20\u7edf\u5b9a\u65f6\u5668\u65b9\u6848\u7684\u7f3a\u9677\uff1a<\/strong><\/p>\n<pre><code class=\"language-tsx line-numbers\">\/\/ \u274c \u56fa\u5b9a\u5ef6\u8fdf\u4e0d\u53ef\u9760\nuseEffect(() =&gt; {\n  const timer = setTimeout(() =&gt; {\n    refreshKeys(); \/\/ \u5047\u8bbe 300ms \u540e\u7528\u6237\u5df2\u7ecf\u80fd\u770b\u5230\u9762\u677f\u4e86\uff1f\n  }, 300);\n  return () =&gt; clearTimeout(timer);\n}, []);\n<\/code><\/pre>\n<p><strong>\u95ee\u9898\uff1a<\/strong><br \/>\n&#8211; 300ms \u662f\u62cd\u8111\u888b\u7684\u6570\u5b57<br \/>\n&#8211; \u5feb\u901f\u8bbe\u5907\u4e0a\u6d6a\u8d39\u7b49\u5f85\u65f6\u95f4<br \/>\n&#8211; \u6162\u901f\u8bbe\u5907\u4e0a\u53ef\u80fd\u8fd8\u4e0d\u591f<br \/>\n&#8211; \u7528\u6237\u53ef\u80fd\u6c38\u8fdc\u4e0d\u4f1a\u5207\u6362\u5230\u8be5\u6807\u7b7e\u9875\uff08\u767d\u767d\u52a0\u8f7d\uff09<\/p>\n<p><strong>IntersectionObserver \u7684\u4f18\u52bf\uff1a<\/strong><\/p>\n<pre><code class=\"language-tsx line-numbers\">\/\/ \u2705 \u7cbe\u786e\u76d1\u542c\u53ef\u89c1\u6027\nuseEffect(() =&gt; {\n  const observer = new IntersectionObserver(\n    ([entry]) =&gt; {\n      if (entry.isIntersecting) {\n        refreshKeys(); \/\/ \u7cbe\u786e\u5728\u53ef\u89c1\u65f6\u89e6\u53d1\n        observer.disconnect(); \/\/ \u4e00\u6b21\u6027\uff0c\u4e0d\u590d\u7528\n      }\n    },\n    { threshold: 0.1 } \/\/ \u53ea\u8981 10% \u53ef\u89c1\u5c31\u89e6\u53d1\n  );\n\n  observer.observe(ref.current);\n  return () =&gt; observer.disconnect();\n}, []);\n<\/code><\/pre>\n<p><strong>\u539f\u7406\uff1a<\/strong><br \/>\n&#8211; \u5229\u7528\u6d4f\u89c8\u5668\u539f\u751f\u7684 <strong>Intersection Observer API<\/strong><br \/>\n&#8211; \u901a\u8fc7 <code>requestIdleCallback<\/code> \u5728\u7a7a\u95f2\u65f6\u56de\u8c03<br \/>\n&#8211; \u4e0d\u4f9d\u8d56\u56fa\u5b9a\u65f6\u95f4\uff0c\u9002\u5e94\u4e0d\u540c\u8bbe\u5907\u6027\u80fd<br \/>\n&#8211; \u7b26\u5408 <strong>Resource Hints \/ Lazy Loading<\/strong> \u6807\u51c6<\/p>\n<h3>Rust \u5f02\u6b65 Command vs \u540c\u6b65 Command<\/h3>\n<p><strong>\u5e95\u5c42\u5dee\u5f02\uff1a<\/strong><\/p>\n<pre><code class=\"language-rust line-numbers\">\/\/ \u540c\u6b65\u7248\u672c\uff08\u963b\u585e\uff09\nlet output = std::process::Command::new(\"ssh-keygen\")\n    .output()?;  \/\/ \u5f53\u524d\u7ebf\u7a0b\u6682\u505c\uff0c\u7b49\u5f85\u5b50\u8fdb\u7a0b\u7ed3\u675f\n\n\/\/ \u5f02\u6b65\u7248\u672c\uff08\u975e\u963b\u585e\uff09\nlet output = tokio::process::Command::new(\"ssh-keygen\")\n    .output()\n    .await?;     \/\/ \u8ba9\u51fa\u63a7\u5236\u6743\uff0cTokio runtime \u8c03\u5ea6\u5176\u4ed6\u4efb\u52a1\n<\/code><\/pre>\n<p><strong>\u5185\u90e8\u673a\u5236\uff1a<\/strong><\/p>\n<pre><code class=\"line-numbers\">\u3010\u540c\u6b65\u8c03\u7528\u3011\nThread Main \u2500\u2500[\u963b\u585e]\u2500\u2500&gt; \u7b49\u5f85\u5b50\u8fdb\u7a0b \u2500\u2500[\u6062\u590d]\u2500\u2500&gt; \u7ee7\u7eed\u6267\u884c\n                         \u2191\n                    \u8fd9\u6bb5\u65f6\u95f4\u5b8c\u5168\u5361\u6b7b\uff01\n\n\u3010\u5f02\u6b65\u8c03\u7528\u3011\nThread Main \u2500\u2500[await]\u2500\u2500&gt; Task Queue \u2500\u2500[\u7a7a\u95f2\u65f6\u53ef\u505a\u5176\u4ed6\u4e8b]\u2500\u2500&gt; Future Ready \u2500\u2500[\u6062\u590d]\n                              \u2191\n                    Tokio Runtime \u53ef\u4ee5\u8c03\u5ea6\u5176\u4ed6 async \u4efb\u52a1\n<\/code><\/pre>\n<p><strong>\u4f55\u65f6\u4f7f\u7528\u54ea\u79cd\uff1f<\/strong><\/p>\n<table>\n<thead>\n<tr>\n<th>\u573a\u666f<\/th>\n<th>\u63a8\u8350<\/th>\n<th>\u539f\u56e0<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>\u542f\u52a8\u65f6\u5fc5\u987b\u7684\u6570\u636e<\/td>\n<td>\u5f02\u6b65<\/td>\n<td>\u4e0d\u80fd\u963b\u585e\u4e3b\u7ebf\u7a0b<\/td>\n<\/tr>\n<tr>\n<td>\u7528\u6237\u4e3b\u52a8\u89e6\u53d1\u7684\u64cd\u4f5c<\/td>\n<td>\u540c\u6b65\u53ef\u63a5\u53d7<\/td>\n<td>\u7528\u6237\u5df2\u6709\u5fc3\u7406\u9884\u671f<\/td>\n<\/tr>\n<tr>\n<td>\u6279\u91cf\u6587\u4ef6\u64cd\u4f5c<\/td>\n<td>\u5f02\u6b65<\/td>\n<td>\u53ef\u80fd\u8017\u65f6\u5f88\u957f<\/td>\n<\/tr>\n<tr>\n<td>\u7b80\u5355\u7684\u5355\u6b21\u8c03\u7528<\/td>\n<td>\u540c\u6b65<\/td>\n<td>\u5f00\u9500\u66f4\u5c0f<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<hr \/>\n<h2>\ud83d\udd17 \u76f8\u5173\u8d44\u6e90\u4e0e\u5ef6\u4f38\u9605\u8bfb<\/h2>\n<h3>\u5b98\u65b9\u6587\u6863<\/h3>\n<ul>\n<li><a class=\"wp-editor-md-post-content-link\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Intersection_Observer_API\">MDN: Intersection Observer API<\/a><\/li>\n<li><a class=\"wp-editor-md-post-content-link\" href=\"https:\/\/react.dev\/reference\/react\/useEffect#specifying-reactive-dependencies\">React: Optimizing Performance<\/a><\/li>\n<li><a class=\"wp-editor-md-post-content-link\" href=\"https:\/\/docs.rs\/tokio\/latest\/tokio\/process\/index.html\">Tokio: Async Command<\/a><\/li>\n<li><a class=\"wp-editor-md-post-content-link\" href=\"https:\/\/v2.tauri.app\/start\/guides\/building\/windows\/\">Tauri: Performance Best Practices<\/a><\/li>\n<\/ul>\n<h3>\u6df1\u5165\u9605\u8bfb<\/h3>\n<ul>\n<li><a class=\"wp-editor-md-post-content-link\" href=\"https:\/\/web.dev\/render-blocking-resources\/\">Web.dev: Render-Blocking Resources<\/a><\/li>\n<li><a class=\"wp-editor-md-post-content-link\" href=\"https:\/\/rust-lang.github.io\/async-book\/01_getting_started\/04.html\">Rust Async Book: Under the Hood<\/a><\/li>\n<li><a class=\"wp-editor-md-post-content-link\" href=\"https:\/\/developer.chrome.com\/docs\/devtools\/evaluate-performance\/\">Chrome DevTools: Analyze Runtime Performance<\/a><\/li>\n<\/ul>\n<h3>\u7c7b\u4f3c\u6848\u4f8b\u7814\u7a76<\/h3>\n<ul>\n<li><a class=\"wp-editor-md-post-content-link\" href=\"https:\/\/www.electronjs.org\/docs\/latest\/tutorial\/performance#main-process-blocking\">Electron \u4e3b\u8fdb\u7a0b\u963b\u585e\u95ee\u9898<\/a><\/li>\n<li><a class=\"wp-editor-md-post-content-link\" href=\"https:\/\/web.dev\/articles\/wasm-startup-performance\">WASM \u542f\u52a8\u6027\u80fd\u4f18\u5316<\/a><\/li>\n<li><a class=\"wp-editor-md-post-content-link\" href=\"https:\/\/nextjs.org\/docs\/pages\/building-your-application\/configuring\/react-strict-mode\">Next.js hydration \u95ea\u70c1\u95ee\u9898<\/a><\/li>\n<\/ul>\n<hr \/>\n<h2>\u270d\ufe0f \u603b\u7ed3\uff1a\u4e09\u4e2a\u5173\u952e\u8ba4\u77e5\u5347\u7ea7<\/h2>\n<h3>\u8ba4\u77e5 1: **&#8221;\u770b\u4e0d\u89c1\u7684\u4ee3\u7801\u4e5f\u5728\u6267\u884c&#8221;<\/h3>\n<p>React \u7ec4\u4ef6\u5373\u4f7f\u672a\u88ab\u6e32\u67d3\u5230\u5c4f\u5e55\uff08\u4f8b\u5982\u5728\u672a\u6fc0\u6d3b\u7684 Tab \u9875\u7b7e\u4e2d\uff09\uff0c\u4ecd\u7136\u4f1a\uff1a<br \/>\n&#8211; \u6267\u884c\u6784\u9020\u51fd\u6570<br \/>\n&#8211; \u8fd0\u884c <code>useEffect<\/code> hooks<br \/>\n&#8211; \u53d1\u8d77\u7f51\u7edc\u8bf7\u6c42\/\u540e\u7aef\u8c03\u7528<br \/>\n&#8211; \u5360\u7528\u5185\u5b58\u548c CPU<\/p>\n<p><strong>\u542f\u793a\uff1a<\/strong> \u59cb\u7ec8\u8003\u8651\u7ec4\u4ef6\u7684<strong>\u5b9e\u9645\u53ef\u89c1\u6027<\/strong>\uff0c\u800c\u975e\u4ec5\u4ec5\u662f\u5426\u88ab\u6302\u8f7d\u3002<\/p>\n<hr \/>\n<h3>\u8ba4\u77e5 2: **&#8221;async \u51fd\u6570\u5185\u7684\u540c\u6b65\u4ee3\u7801\u4ecd\u7136\u662f\u540c\u6b65\u7684&#8221;<\/h3>\n<pre><code class=\"language-rust line-numbers\">#[tauri::command]\nasync fn dangerous_command() -&gt; Result&lt;(), String&gt; {\n    \/\/ \u867d\u7136\u51fd\u6570\u7b7e\u540d\u662f async\uff0c\n    \/\/ \u4f46\u4e0b\u9762\u7684\u4ee3\u7801\u4ecd\u7136\u662f\u540c\u6b65\u6267\u884c\u7684\uff01\n    let output = std::process::Command::new(\"slow\")\n        .output()?; \/\/ \ud83d\udd34 \u963b\u585e\uff01\n\n    Ok(())\n}\n<\/code><\/pre>\n<p><strong>\u542f\u793a\uff1a<\/strong> <code>async<\/code> \u53ea\u662f\u8ba9\u4f60\u80fd\u5728\u51fd\u6570\u5185\u90e8 <code>.await<\/code>\uff0c\u5e76\u4e0d\u4f1a\u81ea\u52a8\u628a\u6240\u6709\u4ee3\u7801\u53d8\u6210\u5f02\u6b65\u3002\u9700\u8981\u663e\u5f0f\u4f7f\u7528\u5f02\u6b65\u5e93\uff08\u5982 <code>tokio::process<\/code>\uff09\u3002<\/p>\n<hr \/>\n<h3>\u8ba4\u77e5 3: **&#8221;\u6027\u80fd\u95ee\u9898\u5f80\u5f80\u85cf\u5728\u6700\u4e0d\u8d77\u773c\u7684\u5730\u65b9&#8221;<\/h3>\n<p>\u6211\u4eec\u82b1\u4e86 19 \u8f6e\u6d4b\u8bd5\uff0c\u6700\u540e\u53d1\u73b0\u7f6a\u9b41\u7978\u9996\u662f\u4e00\u4e2a<strong>\u53ea\u6709\u51e0\u5341\u884c\u4ee3\u7801\u7684\u5c0f\u7ec4\u4ef6<\/strong>\u2014\u2014\u800c\u4e14\u5b83\u7684\u529f\u80fd\u770b\u8d77\u6765\u5b8c\u5168\u65e0\u5bb3\uff08\u5217\u51fa SSH \u5bc6\u94a5\u5217\u8868\uff09\u3002<\/p>\n<p><strong>\u542f\u793a\uff1a<\/strong><br \/>\n&#8211; \u4e0d\u8981\u51ed\u76f4\u89c9\u5224\u65ad\u6027\u80fd\u70ed\u70b9<br \/>\n&#8211; \u7528\u6570\u636e\u548c\u5de5\u5177\u8bf4\u8bdd<br \/>\n&#8211; \u6700\u7b80\u5355\u7684\u4ee3\u7801\u53ef\u80fd\u6709\u6700\u5927\u7684\u6027\u80fd\u5f71\u54cd<br \/>\n&#8211; \u5efa\u7acb\u7cfb\u7edf\u7684 profiling \u6d41\u7a0b\uff0c\u800c\u4e0d\u662f\u4e34\u65f6\u62b1\u4f5b\u811a<\/p>\n<hr \/>\n<h2>\ud83c\udfc6 \u6700\u7ec8\u5efa\u8bae<\/h2>\n<p><strong>\u5982\u679c\u4f60\u6b63\u5728\u5f00\u53d1 Tauri\/Electron\/\u4efb\u4f55\u684c\u9762\u5e94\u7528\uff1a<\/strong><\/p>\n<ol>\n<li><strong>\u7acb\u5373\u5ba1\u67e5<\/strong>\u6240\u6709\u7ec4\u4ef6\u7684 <code>useEffect<\/code> \u548c Tauri Command<\/li>\n<li><strong>\u5efa\u7acb\u89c4\u5219<\/strong>\uff1a\u7981\u6b62\u5728\u542f\u52a8\u65f6\u6267\u884c\u4efb\u4f55\u540c\u6b65 I\/O \u6216\u5916\u90e8\u8fdb\u7a0b\u8c03\u7528<\/li>\n<li><strong>\u6295\u8d44\u5de5\u5177<\/strong>\uff1a\u8bbe\u7f6e CI \u6027\u80fd\u57fa\u51c6\u6d4b\u8bd5\uff0c\u9632\u6b62\u56de\u5f52<\/li>\n<li><strong>\u57f9\u517b\u610f\u8bc6<\/strong>\uff1a\u56e2\u961f code review \u65f6\u5fc5\u987b\u5305\u542b\u6027\u80fd\u7ef4\u5ea6<\/li>\n<li><strong>\u6587\u6863\u5316<\/strong>\uff1a\u5c06\u672c\u6587\u7684\u65b9\u6cd5\u8bba\u7eb3\u5165\u65b0\u5458\u5de5 onboarding \u6750\u6599<\/li>\n<\/ol>\n<p><strong>\u8bb0\u4f4f\uff1a<\/strong><\/p>\n<blockquote><p>\n  \u7528\u6237\u4e0d\u4f1a\u5173\u5fc3\u4f60\u7684\u4ee3\u7801\u6709\u591a\u4f18\u96c5\uff0c\u4ed6\u4eec\u53ea\u5173\u5fc3\u5e94\u7528\u662f\u5426<strong>\u5feb\u901f\u3001\u6d41\u7545\u3001\u7a33\u5b9a<\/strong>\u3002\n<\/p><\/blockquote>\n<p>\u542f\u52a8\u65f6\u7684\u90a3\u51e0\u767e\u6beb\u79d2\u5ef6\u8fdf\uff0c\u51b3\u5b9a\u4e86\u7528\u6237\u5bf9\u4f60\u7684\u4ea7\u54c1\u7684<strong>\u7b2c\u4e00\u5370\u8c61<\/strong>\u2014\u2014\u800c\u8fd9\u4e2a\u5370\u8c61\u4e00\u65e6\u5f62\u6210\uff0c\u5f88\u96be\u6539\u53d8\u3002<\/p>\n<hr \/>\n<p><em>\u9002\u7528\u8303\u56f4: Tauri 2.x, React 18+, Windows 10\/11, macOS 12+, Linux (Ubuntu 20.04+)<\/em><br \/>\n<em>\u95ee\u9898\u590d\u6742\u5ea6: \u2b50\u2b50\u2b50\u2b50\u2b50 (\u6781\u6613\u8bef\u8bca)<\/em><br \/>\n<em>\u6392\u67e5\u8017\u65f6\u53c2\u8003: 2-8 \u5c0f\u65f6 (\u89c6\u9879\u76ee\u89c4\u6a21\u800c\u5b9a)<\/em><br \/>\n<em>\u9884\u9632\u6210\u672c: &lt;30 \u5206\u949f (\u7f16\u5199\u4ee3\u7801\u65f6\u9075\u5faa\u89c4\u8303)<\/em><br \/>\n<em>\u6700\u540e\u66f4\u65b0: 2026-05-21<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Tauri\/React \u5e94\u7528\u542f\u52a8\u95ea\u70c1\u6392\u67e5\u5b9e\u5f55\uff1a19\u8f6e\u4e8c\u5206\u6cd5\u63ea\u51fa\u771f\u51f6 \ud83d\udea8 \u95ee\u9898\u73b0\u8c61 \u4f60\u7684\u684c\u9762\u5e94\u7528\u5728\u5f00\u53d1\u73af\u5883\u8fd0\u884c\u5b8c &hellip; <a href=\"https:\/\/cocozq.com\/?p=2821\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Tauri\/React \u5e94\u7528\u542f\u52a8\u95ea\u70c1\u6392\u67e5\u5b9e\u5f55\uff1a19\u8f6e\u4e8c\u5206\u6cd5\u63ea\u51fa\u771f\u51f6&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[22],"tags":[],"class_list":["post-2821","post","type-post","status-publish","format-standard","hentry","category-22","hfeed"],"_links":{"self":[{"href":"https:\/\/cocozq.com\/index.php?rest_route=\/wp\/v2\/posts\/2821","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cocozq.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cocozq.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cocozq.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cocozq.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2821"}],"version-history":[{"count":1,"href":"https:\/\/cocozq.com\/index.php?rest_route=\/wp\/v2\/posts\/2821\/revisions"}],"predecessor-version":[{"id":2822,"href":"https:\/\/cocozq.com\/index.php?rest_route=\/wp\/v2\/posts\/2821\/revisions\/2822"}],"wp:attachment":[{"href":"https:\/\/cocozq.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2821"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cocozq.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2821"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cocozq.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2821"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}