<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>sukha</title><description>A coder-ready Astro blog theme with 59 of your favorite color schemes to choose from</description><link>https://sukha.dev</link><item><title>飘</title><link>https://sukha.dev/posts/%E9%A3%98</link><guid isPermaLink="true">https://sukha.dev/posts/%E9%A3%98</guid><pubDate>Thu, 04 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;text-align: right; white-space: pre-line&quot;&amp;gt;
我在梦里飘，
很轻。
我在醉酒里飘，
穿了靴子的矮人在跟我讲话，
很轻。
我在与人相处的时光里飘，
记忆像子弹一样穿透我，
我倒在地上，
我又起来了，
可是还是在地上躺着，
我离自己越来越远，
可还是在躺着，
我感觉不到自己了，
很轻。
我大胆的问天边的飞鸟，
这里是哪里，
还是听不清飞鸟的呼啸，
飘在风声里，
我释然了。
我命名为自己看到的一切为真实，
我掌握自己的生命而感到自豪，
我支持一切为自由而献身的人们，
清楚而不张扬的飘在安静的纯白世界里，
是我的一个梦想。
我想要翅膀，
越过绝命谷和獠牙山，
与见过的每一个精灵打招呼。&lt;/p&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;:::note
生活之于我犹如书之于我，很重要也很必要，我希望自己能花足够的时间做自己喜欢做的事，这件事现在看起来很简单，但是以后可能会很难，甚至可能会被现实击垮，但是我这个人是不会放弃的，比如我看一本书，在我还没有看完之前，我是不会摔书说这是本烂书，我希望每个人都能如此，都能看到生活里的花朵和蜂蜜，小猫和狗狗。我们读完书不能想太多，这会使我们陷入文学的毒，后果很难想象，比如不求甚解，也许是对于复杂书籍的最好解决方式。
:::&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>枯的心声</title><link>https://sukha.dev/posts/%E6%9E%AF%E7%9A%84%E5%BF%83%E5%A3%B0</link><guid isPermaLink="true">https://sukha.dev/posts/%E6%9E%AF%E7%9A%84%E5%BF%83%E5%A3%B0</guid><pubDate>Sat, 06 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;text-align: right; white-space: pre-line&quot;&amp;gt;
枯爬到墙上，
爬山虎黄了。
枯跑过花海，
花海凋了。
枯藏在人心里，
心死了。
枯爬到头上，
头发白了。
枯逃进岁月里，
岁月沉了。
终于，
枯失去了热爱，
对一切的热爱，
她没有看到向往的生活，
但，
她还在寻找，
寻找能陪她的一切，
即使不能陪伴，
也要看一眼，
看看那个她熟悉又陌生的人，
她看见了，
她很满意。
但她还是沉进了岁月的深谷里。
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;:::note
最近读了一本书：房思琪的初恋乐园，感触很深，也有很多不能理解的地方，在看到思琪沉沦以后，我真的很难过，她最终还是没能克服一切用力将她拖进深渊的力量，她到底为什么会消失？是因为怡婷对她的一再否认和误解，还是她撑不下去了。压死骆驼的最后一根稻草最终压在了她的身上——她选择保护自己，再也不要出去看这个满目疮痍的世界，我很遗憾。我对李国华的人格有两种观点，第一种是他就是个变态，他做了人世间最最不可原谅的事情——奸污，他毁掉了一类人——曾经相信生活和世界是友好的女孩们，他是一个精神杀手。第二种：作者在采访中说，他这个人在现实世界中是真实存在的，这个人便是李国华的的原型，而这个人也有一个原型，这个人便是胡兰成。你可以去读一下胡兰成的书，他有些书，写的很好，但在做人方面他真的很差，正如李国华，李国华所说的某些情话是很动听的，是很有艺术感的，甚至把这些话用在毛毛跟伊纹身上是很好的，但是他在做人方面很差劲，世界上有千千万万个李国华，但我们本身并不能制裁他，只能默默为别人难过，这也是我们应该，甚至是世界应该改变的地方，我觉得世界（世界并不是具象化的东西，但这里指的是可以真正制止这种事情的人）应该想办法惩处这些人，因为他们做了绝对错误的事情。“知其不可为而为之。”是形容看这本书过程的最契合的一句论语，作者说这本书是不应该去看的，但是你还要去看，这就是艺术带来的快感，很矛盾，但也很清楚。我记得书中最清楚的一句话：“人只能一活，却可以常死。” 我很抱歉作者就这样离开了人世，也许带着遗憾，但是她大概留下了一本书替她完成这个任务，希望世界上再也没有“李国华”。
有一些关于这个的电影推荐大家:《熔炉》《素媛》《嘉年华》
谢谢你能看到这里。
晚安。
:::&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>星</title><link>https://sukha.dev/posts/%E6%98%9F</link><guid isPermaLink="true">https://sukha.dev/posts/%E6%98%9F</guid><pubDate>Mon, 08 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;text-align: left; white-space: pre-line&quot;&amp;gt;
星在地上慢慢爬著，
不曾仰望看看天空，
不知為何落下蒼穹，
失憶，
失落。
思考，
終於在星河閃耀的一瞬，
在尋找的罅隙里記起自己的歸宿，
即是天空，
在河邊低頭看星河映出的壯麗，
無限，
無窮，
無垠。
他等著，
夥伴接他同回。
從千年前黎明，
至千年後黑夜。
說思君如明燭，
但，
話語無法填補孤獨。
與宇宙中瞥見軌道，
與睡夢中回歸家鄉。
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;:::note
今天的心情比较好，或者说，今天没有遇到不好的事情，但大部分时间都溜走了，或与游戏，或与书籍。我在思考一个问题，为什么人们在生活中逐渐失去了热情，至少我身边的人大概是这样的，或者说我生活的地方是这样的，老师没有在认真讲课，真的看得出来，原因之一就是学生没有认真听课，但这又有一原因：晚上熬夜。理由就是人本身没有完成今天的意义，人的深层意识里有一种东西叫做意义感，如心理学家罗伯特·凯恩说：“人生作为一种活动本身，就是创造意义的活动。建构意义感这件事不仅是我们的生物本能，甚至可以等同于生命活动的全部。”这种意义感与人之间的羁绊并不是一言两语就可以道出的。用简单的话来说，你的潜意识里有一种根深蒂固的认识：没有好好享受今天，这样就睡下简直太差劲了！“于是尽力用夜晚时间来享受今天剩余的时光，其实这在某方面来说是正确的，因为人在某一方面来说就是由时间决定的物种，无法跨越时间的物种是无法征服时间的。
就当下情况来说，我们应该慢慢摆脱这种利用夜晚来填补空缺的错误做法，学生好好听课，老师好好讲课，各司其职，附加热情，学习有了动力，生活大概也有动力，便可以在无法跨越的时间里慢慢享受自己创造的美好。
:::&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>霞</title><link>https://sukha.dev/posts/%E9%9C%9E</link><guid isPermaLink="true">https://sukha.dev/posts/%E9%9C%9E</guid><pubDate>Wed, 10 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;text-align: right; white-space: pre-line&quot;&amp;gt;
窩在云里，
不慾出現，
飽含光明，
飽含溫暖，
於是看到如撒旦一般的獵人在奔跑捕獵，
於是看到如天使一樣的醫生在悉心治療，
於是看到但丁用擎天柱之筆寫人世醜惡。
逐漸的，
惡減少，
云飄開，
變薄，
趨淡，
顯露猩猩紅色，
逐深，
漸濃，
覆蓋於整個浩浩湯湯之天空，
一切都顯現原形—
邪惡，善良，與無畏。
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;:::note
今天是美好的一天，但是还是阴雨连绵，我很搞不清楚的一点是为什么人可以这样：发消息不回复，但在发动态，这是很可笑的，很对不起别人的等待，真的。
我今天看到了一句话分享给大家：&lt;br /&gt;
&lt;em&gt;&lt;strong&gt;Gravitation is not responsible for people falling in love. - Albert Einstein&lt;/strong&gt;&lt;/em&gt;&lt;br /&gt;
我在某些时候不由自主的想，为什么人和人之间的差距会这样大，毋宁说是你把一本書放在不同的人面前他们的評論各不相同，就算是看一个人的走路姿势就可以大概判断一个人的为人，我喜欢观察，身边的一切都可以观察，譬如走路姿势，有的人走路方正直板，有的人走路两只脚像弹簧（看起来真的很好玩），还有些人是普通的走路。譬如做事，有些人就是喜欢找茬，事事如此。我平常喜欢靠直觉判斷一个人的品格（大概是他在人前的品格），就会发现很多有趣的问题，比如你会猜到他下一步怎么做，甚至具体到说的话，
可以在脑中先于他说出。
某些时候人是很脆弱的，真的很脆弱，你说的某句话，做的某个动作，可能是压死骆驼的最后一根稻草，请大家谨言慎行。
:::&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>輪迴</title><link>https://sukha.dev/posts/%E8%BC%AA%E8%BF%B4</link><guid isPermaLink="true">https://sukha.dev/posts/%E8%BC%AA%E8%BF%B4</guid><pubDate>Fri, 12 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;text-align: right; white-space: pre-line&quot;&amp;gt;
在我自殺輪入餓鬼道之後，
我試著掙脫，
無能為力。
惡鬼敲打我的身體，
身上滿是傷痕，
靈魂滿是傷痕，
我已經不寄予任何希望。
我輕輕拍滅火焰。
我把身上受到的每一次傷害 ，
記錄在腦海裡，
記錄在我的靈魂裡。
我不知道為何輪入餓鬼道，
還會感到疼痛。
這並不是簡單懲罰，
並不是直接傷害靈魂，
是禁錮在虛無縹緲軀殼裡的靈魂。
我無法掙脫桎梏，
受到夜以繼日的懲罰。
我並不後悔，
我來不及後悔。
我很高興經歷前生的一切。
請將一切斬斷，
連同我傷痕的記憶。
謝謝你，
我已重見光明。
我已再次看到美麗萬物。
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;:::note
今天又是好快的過去，有時候就會覺得生命真是被時間欺負的太慘了，有機會我一定替生命好好教訓時間一頓，要它知道生命珍貴。閒下來的時候太無聊了，我就想玩會遊戲，但是遊戲也是浪費時間（當然玩遊戲也可以放鬆），所以就想看書，看書是最適合的方式，處理空閒所有時間的方式，睡覺也是，睡覺可以幫你把一切都放下，就像把你放進了一個最最安全的地方，但噩夢是最可惡的，替大家祈禱一下不要做噩夢。
近來真的好冷，大家那裡不知道冷不冷，假若冷的話，一定一定注意保暖！
下面給大家分享一段布朗肖“récit”型小說（即純小說）《死刑判決》的片段：
|&quot;墜入愛河&quot;是有道理的，真正的愛開始於一種失控，一種無法抑制的墜落，開始於主體放棄主體地位的那一刻。我們不是靠意志和決定愛上一個人的。&quot;我決定愛上他，我會努力愛上他，我能夠愛上他&quot;，實際只會使我們離愛越來越遠。不是我能愛，而是我不能不愛。愛情的激情對主體來說，是一種毀滅性的體驗，主體&quot;我思故我在&quot;的自持和自足假象被摧毀，存在者真切感受到了自身存在的不完整性。|
我真的太難理解這段話了，所以才分享給大家解讀一下。
安好。
:::&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>種</title><link>https://sukha.dev/posts/%E7%A8%AE</link><guid isPermaLink="true">https://sukha.dev/posts/%E7%A8%AE</guid><pubDate>Sun, 14 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;text-align: right; white-space: pre-line&quot;&amp;gt;
看到絕望，
便要希望。
襟懷希望，
種下希望。
在它轉生成種子之前，
便要迫不及待的活埋般種下，
在看到種子種下的時候，
全部的人發瘋似的笑，
像是病態的怪獸。
縮在一角，
她連同種子一起縮在一角。
她不是她，
種子不是種子。
種子種下，
便是靈魂種下，
靈魂便是希望。
認鬼神為希望，
認希望為鑰匙。
開啟黎明第一縷光，
黑得發白的光，
真是可怖。
為私慾便放棄生命，
實為人經常做到的。
實為人毫不猶豫實現的。
與其永生痛苦，
不如短暫幸福，
這些人把這話用精神刻在腦裡，
刻在心肺里。
將它傳播，
呼出的氣息是傳播途徑，
經過其身邊之人被其感染。
放棄長生，
選擇短暫快樂。
希望在沃土里扎根，
發芽，
長成參天之樹，
結出的果實並非希望果實。
搞錯了，
人們一開始種進了滿懷怨恨的希望，
果實毒死所有人。
希望永存。
無限延伸。
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;:::note
我又要說今天過得好快，就像是沒把水龍頭擰住，時間嘩啦嘩啦都跑了，我還在掙扎，好傻，仔細看看錶，已經接近十一點，在我所喜歡的時間里，也許最有意義，過得最慢的，做的事情最多的便是夜晚了，黑暗就像是興奮劑，加快人的思維和想象，不希望這樣，我希望每時每刻都可以思如泉湧，這樣又有點誇張了，有時候真的沒辦法寫出如意的東西，也不想把不好的東西給別人看，我寫的東西可能有點消極，假若給大家帶來了困擾，對不起。可是我就是想把思想扔進黑暗裡再親手把它拽出來，這樣可能會好受一些，詩也好受，我也是。
今天晚上跟朋友定時跑了2000，朋友拉我半圈，累的很，但是很享受過程，希望大家能多多運動。&lt;br /&gt;
欣賞：&lt;br /&gt;
布朗肖《死刑判決》
|比如我讀一本感興趣的書，就會產生強烈的愉悅感，但這愉悅隔著一塊窗玻璃，我能看見它，欣賞它，卻不能使用它。同樣，如果我遇見心儀的人，那麼所有因她而來的歡樂都在玻璃下面，不可損毀，但也因此距離遙遠，處於永恆的過去。|&lt;br /&gt;
安好
:::&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>眠</title><link>https://sukha.dev/posts/%E7%9C%A0</link><guid isPermaLink="true">https://sukha.dev/posts/%E7%9C%A0</guid><pubDate>Thu, 18 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;text-align: left; white-space: pre-line&quot;&amp;gt;
踡縮著，
又轉了一次身，
睡不著。
睡眠溜走了，
從我的大腦裡，
我的五官裡，
我的七竅裡。
一切都在沉睡，
連世界都在睡眠，
而我獨醒，
我被拋棄了。
我可真是太特別了，
就像是一個單獨的生命體游離在一個蔚藍的透明的一望無垠又一覽無余的大海裡，
真孤獨。
我希望能有人陪我一下，
一下也好。
這不是奢求，
而又越過奢求。
我又擦擦眼睛，
看到人們都睜著眼，
可還是在睡覺，
有的甚至說著話在睡覺。
我拍拍他們，
他們轉過身去繼續說話，
笑得臉部表情都變得扭曲也不想停一下。
很累又睡不著的話，
就尋找一切能使自己匆忙起來的方法，
我又踱著步去找我家種的那幾朵月季，
他們沒有睡著，
可是又不能張開嘴來說話，
我心知肚明，
我很想幫他們，
給他們發明一個說話的機器，
這又使我在腦中想的到了那句話，
自然界里萬物平等，
我在心裡默念，
真希望能有人或物跟我說說話，
真希望一切都能慢慢好起來。
我想在眠裡，
在這美好安靜裡，
等待敲門的人。
我就緩緩地躺到地上，
等待眠使我睡著。
我想對那些整天睡眠無憂的人說，
我真羨慕嫉妒，
能不能借給我一下睡眠，
能不能讓我做一個甜甜的夢？
&amp;lt;/div&amp;gt;&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>解散</title><link>https://sukha.dev/posts/%E8%A7%A3%E6%95%A3</link><guid isPermaLink="true">https://sukha.dev/posts/%E8%A7%A3%E6%95%A3</guid><pubDate>Mon, 27 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;text-align: left; white-space: pre-line;&quot;&amp;gt;
出現在霧裡，
聲音在發燙，
滾燙的跑到耳朵里，
快要沒有知覺了，
在他想要爬出的這充滿聲音的地方，
解散了，
他在解散，
霧也在解散。
好言好語在解散，
你看，
它冒出了烏煙瘴氣，
就像是被鏡子照出了真相。
膩，
溺，
匿。
都準備好，
都準備好要消失在別人看不到的地方，
承受痛苦，
也要是自己的事情，
也要是瞞天過海之後還要承受的無限悲傷，
光明正在前進，
止步，
就是倒退。
沒有準備好，
害怕，
都退回到原點，
都在連接著惡，
在逐漸解散，
飛鳥也，
也無法逃過天空的解散，
他用盡力氣奔跑，
用盡力氣捕捉氧氣，
快樂在解散，
不在乎喜怒哀樂，
只想正常。
滿足，
要滿足自己，
就像滿足別人一樣，
不能完全遮蔽真實面目，
面具無法停留，
總會解散。
親親過去，
抱抱未來，
殺死現在，
做對自己。
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;:::note
話：我總是在慢慢地，做什麼都是，但是我走路很快，我討厭把時間浪費在走路上，我很希望自己能學會像走路一樣做別的事情，我有拖延癥，而且很嚴重，我的時間大部分都浪費在猶豫上，我也不想，但是還是沒有辦法，我想慢慢改善，我希望可以更好地做一個人，而不是行尸走肉一般，很不希望自己要過多的集中注意力於無意義的事，比如說亂思考，思緒很難止住的時候，就會很累，有時候我上兩小時的課會有一個半小時正在想別的事情，亂想，就像在給自己找事情做，不是因為當下的事情沒有意義，而是因為自己有些不堪，對於這些事情，我希望自己應該是勇敢的，積極地，我在這條成長的路上總會遇到這樣那樣的事情，壓力很小就會感到不堪重負，這些東西是不敢說出去的，因為我需要快一點，快一點拯救這樣的自己，將自己推進積極樂觀不會害怕壓力的人，也有很大的原因是因為自己不能完成任務，後悔是無效的，只能彌補，彌補自己所犯的小錯誤積累成的錯誤，糾正自己，就是收穫，收穫未來。
:::&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>靥²</title><link>https://sukha.dev/posts/%E9%9D%A5</link><guid isPermaLink="true">https://sukha.dev/posts/%E9%9D%A5</guid><pubDate>Sun, 02 Jun 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;text-align: left; white-space: pre-line&quot;&amp;gt;
星空是藍色的，
於是倒映在海水上的，
渺小的存在。
也是以藍色作為背景。
撥開夜雲，
月光灑在路邊，
灑在你經過的，
亦平亦嶇，
是一條，
一條條暖色的充滿砂礫的小道。
走在上面心情好了，
就像是有人跟在身邊，
是影子，
在默默保護你。
只有在沒有光亮的，
窄窄的黑暗裡，
他才會悄悄躲起來，
並不是丟掉你了，
是緊緊抱住你了。
不要害怕，
很多很多善良的，
美好的人都在，
不要悲傷，
你看看你，
還是笑起來最好看，
但是還是照自己的來。
月亮從無到有從有到無，
一個月就會過去。
不要把所有難過都貼在日曆上啊，
日曆也想你開心一點，
就算不能開心一點，
也要在沒人陪你說話的日子裡，
學會自己保持積極。
抱抱自己，
理解別人。
在所有的，
你認為的，
不字貼在上面的，
記憶裡，
還是撕掉那個不字，
然後貼上好字，
或者直接丟進垃圾桶。
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;:::note
這個四不像是寫給每一個人的，不管你喜不喜歡，我希望你可以找到生活的出路，走出，再走進，這樣就很好。
寫給你，
在看的你。
:::&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>聚集狀態的胡思亂想</title><link>https://sukha.dev/posts/%E8%81%9A%E9%9B%86%E7%8B%80%E6%85%8B%E7%9A%84%E8%83%A1%E6%80%9D%E4%BA%82%E6%83%B3</link><guid isPermaLink="true">https://sukha.dev/posts/%E8%81%9A%E9%9B%86%E7%8B%80%E6%85%8B%E7%9A%84%E8%83%A1%E6%80%9D%E4%BA%82%E6%83%B3</guid><pubDate>Wed, 20 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;text-align: left; white-space: pre-line&quot;&amp;gt;
更多的情況下，為了更好的進行人與人之間的交流，人們會選擇隱藏自己的內心，表現出一個適合於交流的樣子，其實任何交流可能都存在一定的隱瞞，“我完全相信你”這種話更多是用來建立一種堅定關係的手段，在運用一切手段來建立一種堅定關係的同時，彼此之間出現的隱形的深溝卻是難以修復的，這種猜疑隨時可能會出現，也可能不會出現，更多人恐懼的是離開，更好的做法便是接受任何人的離開，提前做好任何人離開的準備，這樣就不再會因為任何人的離開而產生難過的情緒，這是極其困難的一種妥協，“在任何星星準備掉落之前，我便做好了星星擊中我的心理準備，我不再懼怕任何疼痛，而是在欣賞星星掉落期間產生的美麗，所以我成為了星星掉落的旁觀者，而不是當事人。”&lt;br /&gt;
我經常在想自己的未來是怎樣的，忽略了任何可能影響未來的當前行為，我現在想到的最多是自己未來可能一直孤身一人，這並不可怕，因為我可以給自己提供情緒價值，更具體的表現是我可能不需要跟任何人傾訴任何事情，內化對我來說可能是一種習慣式做法，把所有的負面情緒內化成自己內心的動力用來更加努力地解決當下的問題。這可能是很可怕的，因為這種情況下人的精神可能出現一系列問題，比如過度冷靜，我現在就是這樣一種情況，我最近有一種任何事情都不可能影響到自己的錯誤認知，這可能需要慢慢來轉變，希望變成真正的希望，不是對於任何不應出現事情消失的祈禱，而是對於任何應當出現事物的期盼。我們會說不好的事情永遠都不要發生吧！這本身可能就是一件不好的事情，有壞才會有好，失掉了壞，好便沒有了比較的對象，那它也並不是好了，可能就是一般，也可能好也變成了壞。&lt;br /&gt;
在很多社交途徑上看到文字，看到視頻，看到讓人溫暖的一切，都會流下眼淚。“看到現代詩裡面包裹的經年累月的對生活的失樂，看到書裡面人與人的勾心鬥角，看到自私，看到欺騙，不再相信別人，看到一些積極的句子，記錄一下，用腦子過一遍，想著明天又是美好的一天，想著總有一天會出現轉機的，無論如何自己都不應該在這一天到來之前離開，多餘的情感用來複製自己的動力。”&lt;br /&gt;
不經意地問一句你還好嗎可能沒有任何作用。&lt;br /&gt;
大聲的說出我真的不行，我真的是一個很差勁的人，不需要別人來鼓勵自己，不需要別人來告訴自己自己還是很棒的，不需要誇獎的話，需要的是一個擁抱，擁抱來得比什麼都有用，釋放自己，同時也寬容別人，“你為什麼不安慰我一下，你為什麼不陪我一下，你可以陪陪我嗎，你可以跟我說說話嗎”這些類似的話語是因為一方需求，另一方付出，可能真的沒有任何回報啦，不應該過度的交給別人自己的情緒，不應該過度的共情。對於一些自己沒有任何回報的事情，一些人會反駁，“我覺得做好事不需要回報。”重點不是有沒有回報啦，重點在於在最終一切都結束的時候是否會有一種負罪感，這負罪感是指自己對不起自己，轉而怪罪到別人，這個時候總是還會想為什麼我就受這痛苦呢，其實最好的辦法就是重啟自我，具體做法就是慢慢忘記，在你不知道是否雲淡風輕時就回想看看，真正放下一切的人在這時是沒有任何仇恨的。如一個石頭一樣堅不可摧。&lt;br /&gt;
在多數情況下，人们做不到完全忘记。
偶爾會思考人生為何有許多不盡人意的事情，這些事情會打破你的原本生活軌跡，迫使你去做出一些改變，無數年前的人們可能有一種現代人永遠不會擁有的想法——活著就是為了活著，“我們總是在期待一些好運發生在我們身上，即使不會發生，擁有這樣的想法便支撐著我們活了下去。”用一天的大部分時間來思考，來完成自己的任務，毋寧說在為了自己而活，不如說在為了保護自己。要明白的一點就是真正害怕的是沒有完成任務帶來的可怕後果，這種事情逼迫自己產生了緊張、難過和消極的情緒，應該做的可能不是打破這些情緒帶來的自我約束，而是在得到這些情緒之後的自我接納。
接納自己，無論怎樣的自己，都是自己。
&amp;lt;/div&amp;gt;&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>使用rust完成基于wasm的gis工具的简明教程</title><link>https://sukha.dev/posts/a-wasm-for-gis</link><guid isPermaLink="true">https://sukha.dev/posts/a-wasm-for-gis</guid><description>介绍如何使用Rust和WebAssembly构建GIS工具的过程和经验.&apos;</description><pubDate>Thu, 04 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;rust&lt;/h2&gt;
&lt;p&gt;:::note
rust是一门比较新的系统编程语言，具有内存安全性和高性能的特点。它在构建高效的WebAssembly模块方面表现出色，非常适合用于开发GIS工具。如果你还不熟悉rust，可以访问&lt;a href=&quot;https://course.rs/&quot;&gt;&lt;strong&gt;rust圣经&lt;/strong&gt;&lt;/a&gt;进行学习。
:::&lt;/p&gt;
&lt;h2&gt;wasm&lt;/h2&gt;
&lt;p&gt;:::note
WebAssembly（wasm）是一种二进制指令格式，设计用于在现代Web浏览器中高效运行。它允许开发者使用多种编程语言（如Rust）编写代码，并将其编译为高性能的WebAssembly模块，从而提升Web应用的性能。
:::&lt;/p&gt;
&lt;h2&gt;get started&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;首先我们需要创建一个新的rust项目，由于我们创建的是库项目，所以需要添加&lt;code&gt;--lib&lt;/code&gt;参数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cargo new gis-wasm --lib
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;然后使用&lt;code&gt;vscode&lt;/code&gt;打开项目，并进行依赖的安装，安装依赖不需要手动下载，只需要在&lt;code&gt;Cargo.toml&lt;/code&gt;文件中添加依赖即可，下面是我们需要添加的依赖和其他配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[package]
name = &quot;gis-wasm&quot;
version = &quot;0.1.0&quot;
edition = &quot;2024&quot;

[lib]
crate-type = [&quot;cdylib&quot;, &quot;rlib&quot;]

[package.metadata.wasm-pack.profile.release]
wasm-opt = false

[dependencies]
geo = &quot;0.31.0&quot;
geojson = &quot;0.24.2&quot;
serde = &quot;1.0.228&quot;
serde_json = &quot;1.0.145&quot;
wasm-bindgen = &quot;0.2.93&quot;
wasm-bindgen-futures = &quot;0.4&quot;
web-sys = { version=&quot;0.3.70&quot;, features=[&quot;console&quot;]}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;接下来我们需要实现具体的代码逻辑，我将会实现一个简单的功能，即计算&lt;strong&gt;geojson&lt;/strong&gt;面要素的&lt;strong&gt;bbox&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;首先我们需要导入需要用到的依赖&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;use serde::{ Deserialize, Serialize };
use wasm_bindgen::{JsValue, prelude::wasm_bindgen};
use web_sys::js_sys::{ Array, JsString };
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;然后我们根据需要定义几个struct和enum&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//  这里是为struct加上需要的derive
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PolygonGeoJson&amp;lt;T = Geometry&amp;gt; {
    //由于&quot;type&quot;是rust的关键字，所以我们使用r#type来命名，并使用serde的rename属性来指定序列化和反序列化时的名称
    #[serde(rename = &quot;type&quot;)]
    r#type: String,
    features: Vec&amp;lt;PolygonFeature&amp;lt;T&amp;gt;&amp;gt;,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PolygonFeature&amp;lt;T&amp;gt; {
    #[serde(rename = &quot;type&quot;)]
    r#type: String,
    geometry: T,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
//这里我们使用serde的tag属性来指定枚举的变体类型，即根据哪个字段来区分不同的变体
#[serde(tag = &quot;type&quot;)]
enum Geometry {
    Polygon {
        coordinates: Vec&amp;lt;Vec&amp;lt;[f64; 2]&amp;gt;&amp;gt;,
    },
    MultiPolygon {
        coordinates: Vec&amp;lt;Vec&amp;lt;Vec&amp;lt;[f64; 2]&amp;gt;&amp;gt;&amp;gt;,
    },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;接下来我们实现计算bbox的逻辑&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;//这里需要加上wasm_bindgen的宏，以便将函数标记为wasm-pack可以导出的函数
 #[wasm_bindgen]
 pub fn get_bbox(geojson_str: JsString) -&amp;gt; Array {
     let array = Array::new();
     //将JsString转换为Rust的String类型
     let geojson_string: String = geojson_str.into();
     let mut min_x = f64::INFINITY;
     let mut min_y = f64::INFINITY;
     let mut max_x = f64::NEG_INFINITY;
     let mut max_y = f64::NEG_INFINITY;
     //这里定义一个宏，用于计算最大最小值，以免重复代码
     macro_rules! get_max_min_xy {
         ($f:expr) =&amp;gt; {
             $f.iter().for_each(|cor| {
               let x = cor[0];
               let y = cor[1];
               min_x = min_x.min(x);
               min_y = min_y.min(y);
               max_x = max_x.max(x);
               max_y = max_y.max(y);
           });
         };
     }
     let geojson: PolygonGeoJson = serde_json
         ::from_str(&amp;amp;geojson_string)
         .expect(&quot;serde parse failed&quot;);
     if geojson.features.is_empty() {
         for _ in 0..4 {
             array.push(&amp;amp;JsValue::from_f64(f64::NAN));
         }
     } else {
       //遍历每个feature的geometry，根据不同的geometry类型进行处理
         for feature in &amp;amp;geojson.features {
             match &amp;amp;feature.geometry {
               //最后通过rust的迭代器配合闭包和自定义宏遍历所有坐标点，计算最小和最大值
                 Geometry::MultiPolygon { coordinates } =&amp;gt; {
                     let _ = coordinates.iter().for_each(|f| {
                         f.iter().for_each(|c| {
                             get_max_min_xy!(c);
                         });
                     });
                 }
                 Geometry::Polygon { coordinates } =&amp;gt; {
                     let _ = coordinates.iter().for_each(|f| {
                         get_max_min_xy!(f);
                     });
                 }
             }
         }
         array.push(&amp;amp;JsValue::from_f64(min_x));
         array.push(&amp;amp;JsValue::from_f64(min_y));
         array.push(&amp;amp;JsValue::from_f64(max_x));
         array.push(&amp;amp;JsValue::from_f64(max_y));
     }
     //返回计算得到的bbox坐标数组
     array
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::important
请注意，该示例并未考虑应用到前端的性能问题，该功能只是用来演示如何使用Rust和Wasm实现GIS功能。在实际应用中，可能需要根据具体需求进行优化和调整。
:::&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::note
这样我们就完成了从&lt;strong&gt;rust&lt;/strong&gt;项目的创建，到依赖的安装，再到具体功能代码的实现的全过程。通过这个简明教程，你应该能够理解如何使用&lt;strong&gt;Rust&lt;/strong&gt;和&lt;strong&gt;WebAssembly&lt;/strong&gt;构建&lt;strong&gt;GIS&lt;/strong&gt;工具的基本步骤和方法。希望这能帮助你在&lt;strong&gt;GIS&lt;/strong&gt;开发领域迈出坚实的一步！
:::&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>在cesiumjs中实现真实草地渲染</title><link>https://sukha.dev/posts/cesium-grass-render</link><guid isPermaLink="true">https://sukha.dev/posts/cesium-grass-render</guid><description>本文主要是关于笔者在cesiumjs中实现真实草地渲染的经验分享。</description><pubDate>Fri, 05 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;草地配置&lt;/h2&gt;
&lt;p&gt;首先我们要完成草地的主要参数的配置，这里使用ts的interface来定义草地的相关参数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export interface GrassConfig {
  density: number //草地密度
  windStrength: number //风力强度
  primaryColor: string //草地主色调
  secondaryColor: string //草地次色调
  shadowIntensity: number //阴影强度
  bounds: {
    west: number
    south: number
    east: number
    north: number
  } //草地范围
  polygon?: Cesium.Cartesian3[] //添加多边形边界支持
  enableLOD?: boolean //是否启用LOD优化
  lodDistances?: {
    high: number //高细节距离阈值
    medium: number //中细节距离阈值
    low: number //低细节距离阈值
  } //LOD距离阈值
  //高级控制参数
  bladeWidth?: number //草叶宽度
  bladeHeight?: number //草叶高度
  joints?: number //草叶关节数
  windSpeed?: number //风速
  windIntensity?: number //风强度
  stretchVariation?: number //草叶拉伸变化度
  maxLeanAngle?: number //最大倾斜角度(度数)
  leanVariation?: number //倾斜变化度
  windDirection?: number //风向(度数 0=北 90=东 180=南 270=西)
  windAngle?: number //风倾斜角度
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;草实体配置&lt;/h2&gt;
&lt;p&gt;接下来我们要进行草实例的配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export interface GrassInstance {
  position: Cesium.Cartesian3 //草实例的位置
  scale: number //草实例的缩放比例
  rotation: number //草实例的旋转角度
  width: number //草实例的宽度
  lean: number //倾斜角度
  orientation: Cesium.Quaternion //四元数生长方向
  stretch: number //拉伸系数
  halfRootAngleSin: number //根部角度正弦值
  halfRootAngleCos: number //根部角度余弦值
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;工具函数实现&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;然后我们需要实现一些工具函数来辅助草地的生成和渲染&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;四元数相乘&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;如果有不理解的地方，可以参考我之前写的&lt;a href=&quot;/posts/quaternion-multiplication-math&quot;&gt;四元数乘法的数学原理及其在3D图形中的应用&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;// 四元数乘法函数，在这个草地渲染系统中，四元数用于表示3D空间中的旋转。函数通过标准的四元数乘法公式组合两个旋转，生成一个新的四元数，表示先应用第一个旋转再应用第二个旋转的结果。这对于计算草叶在风中摆动时的旋转非常重要。
/*
在本实现中主要用于
1. 组合多个旋转方向 - 将绕Y轴的旋转与绕X轴的旋转组合
2. 添加细微变化 - 再叠加Z轴的微小旋转，使每片草叶的方向更自然、更随机
*/
function multiplyQuaternions(
  q1: Cesium.Quaternion,
  q2: Cesium.Quaternion,
): Cesium.Quaternion {
  const x = q1.x * q2.w + q1.y * q2.z - q1.z * q2.y + q1.w * q2.x
  const y = -q1.x * q2.z + q1.y * q2.w + q1.z * q2.x + q1.w * q2.y
  const z = q1.x * q2.y - q1.y * q2.x + q1.z * q2.w + q1.w * q2.z
  const w = -q1.x * q2.x - q1.y * q2.y - q1.z * q2.z + q1.w * q2.w
  return new Cesium.Quaternion(x, y, z, w)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Simplex噪声实现&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Simplex噪声是一种用于生成自然现象的程序化噪声函数，常用于计算机图形学中以模拟自然界中的纹理和形态&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;function createNoise2D() {
  return function (x: number, y: number): number {
    const n0 = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453123
    return (n0 - Math.floor(n0)) * 2.0 - 1.0
  }
}

const noise2D = createNoise2D()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;检测点是否在多边形内&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;该函数使用了&lt;a href=&quot;/posts/ray-casting-algorithm&quot;&gt;&lt;strong&gt;射线投射算法&lt;/strong&gt;&lt;/a&gt;，主要用于检测一个点是否在一个多边形内，常用于地图应用中判断某个位置是否在指定区域内&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 检测点是否在多边形内部 - 使用射线投射算法
 */
function isPointInPolygon(
  point: { longitude: number; latitude: number },
  polygon: Cesium.Cartesian3[],
): boolean {
  if (!polygon || polygon.length &amp;lt; 3) return true // 如果没有多边形，默认在内部

  // 将Cartesian3多边形点转换为经纬度
  const polygonPoints: { longitude: number; latitude: number }[] = []

  for (let i = 0; i &amp;lt; polygon.length; i++) {
    const cartographic = Cesium.Cartographic.fromCartesian(polygon[i])
    polygonPoints.push({
      longitude: Cesium.Math.toDegrees(cartographic.longitude),
      latitude: Cesium.Math.toDegrees(cartographic.latitude),
    })
  }

  let inside = false
  const x = point.longitude
  const y = point.latitude

  for (let i = 0, j = polygonPoints.length - 1; i &amp;lt; polygonPoints.length; j = i++) {
    const xi = polygonPoints[i].longitude
    const yi = polygonPoints[i].latitude
    const xj = polygonPoints[j].longitude
    const yj = polygonPoints[j].latitude

    if (yi &amp;gt; y !== yj &amp;gt; y &amp;amp;&amp;amp; x &amp;lt; ((xj - xi) * (y - yi)) / (yj - yi) + xi) {
      inside = !inside
    }
  }

  return inside
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><author>sukha</author></item><item><title>四元数乘法的数学原理</title><link>https://sukha.dev/posts/quaternion-multiplication-math</link><guid isPermaLink="true">https://sukha.dev/posts/quaternion-multiplication-math</guid><description>本文介绍了四元数乘法的数学原理及其在三维计算中的应用。</description><pubDate>Fri, 05 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;四元数乘法（Quaternion Multiplication）是3D图形学中用于组合旋转的核心运算。在&lt;a href=&quot;/posts/cesium-grass-render&quot;&gt;草地渲染系统&lt;/a&gt;中，这个函数用于组合多个旋转变换，生成每片草叶独特的生长方向。&lt;/p&gt;
&lt;h2&gt;四元数基础&lt;/h2&gt;
&lt;p&gt;四元数是四维超复数，可以表示为：&lt;/p&gt;
&lt;p&gt;$$q = w + xi + yj + zk$$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$w$ 是实部（标量部分）&lt;/li&gt;
&lt;li&gt;$(x, y, z)$ 是虚部（向量部分）&lt;/li&gt;
&lt;li&gt;$i, j, k$ 是虚数单位&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;虚数单位的性质&lt;/h3&gt;
&lt;p&gt;虚数单位满足以下基本关系：&lt;/p&gt;
&lt;p&gt;$$i^2 = j^2 = k^2 = ijk = -1$$&lt;/p&gt;
&lt;h3&gt;虚数单位的乘法规则&lt;/h3&gt;
&lt;p&gt;四元数乘法基于虚数单位的乘法规则（右手定则）：&lt;/p&gt;
&lt;p&gt;$$
\begin{align}
i \cdot j &amp;amp;= k, \quad j \cdot k = i, \quad k \cdot i = j \
j \cdot i &amp;amp;= -k, \quad k \cdot j = -i, \quad i \cdot k = -j
\end{align}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;记忆技巧&lt;/strong&gt;：顺时针相乘为正，逆时针相乘为负。&lt;/p&gt;
&lt;h2&gt;四元数乘法公式&lt;/h2&gt;
&lt;p&gt;对于两个四元数 $q_1 = (x_1, y_1, z_1, w_1)$ 和 $q_2 = (x_2, y_2, z_2, w_2)$，它们的乘积 $q_1 \times q_2$ 计算如下：&lt;/p&gt;
&lt;h3&gt;完整展开式&lt;/h3&gt;
&lt;p&gt;$$
\begin{align}
x &amp;amp;= w_1 x_2 + x_1 w_2 + y_1 z_2 - z_1 y_2 \
y &amp;amp;= w_1 y_2 - x_1 z_2 + y_1 w_2 + z_1 x_2 \
z &amp;amp;= w_1 z_2 + x_1 y_2 - y_1 x_2 + z_1 w_2 \
w &amp;amp;= w_1 w_2 - x_1 x_2 - y_1 y_2 - z_1 z_2
\end{align}
$$&lt;/p&gt;
&lt;h3&gt;📚 推导过程详解（初学者友好）&lt;/h3&gt;
&lt;p&gt;让我们一步一步推导这个公式，理解为什么是这样的。&lt;/p&gt;
&lt;h4&gt;第一步：写出四元数的完整形式&lt;/h4&gt;
&lt;p&gt;回忆四元数的定义：&lt;/p&gt;
&lt;p&gt;$$
\begin{align}
q_1 &amp;amp;= w_1 + x_1 i + y_1 j + z_1 k \
q_2 &amp;amp;= w_2 + x_2 i + y_2 j + z_2 k
\end{align}
$$&lt;/p&gt;
&lt;h4&gt;第二步：展开乘法（像多项式乘法）&lt;/h4&gt;
&lt;p&gt;把 $q_1 \times q_2$ 展开，就像 $(a+b)(c+d) = ac + ad + bc + bd$ 一样：&lt;/p&gt;
&lt;p&gt;$$
q_1 \times q_2 = (w_1 + x_1 i + y_1 j + z_1 k)(w_2 + x_2 i + y_2 j + z_2 k)
$$&lt;/p&gt;
&lt;p&gt;展开后得到 &lt;strong&gt;16 项&lt;/strong&gt;（4×4）：&lt;/p&gt;
&lt;p&gt;$$
\begin{align}
q_1 \times q_2 = &amp;amp;;w_1 w_2 + w_1 x_2 i + w_1 y_2 j + w_1 z_2 k \
&amp;amp;+ x_1 i w_2 + x_1 i \cdot x_2 i + x_1 i \cdot y_2 j + x_1 i \cdot z_2 k \
&amp;amp;+ y_1 j w_2 + y_1 j \cdot x_2 i + y_1 j \cdot y_2 j + y_1 j \cdot z_2 k \
&amp;amp;+ z_1 k w_2 + z_1 k \cdot x_2 i + z_1 k \cdot y_2 j + z_1 k \cdot z_2 k
\end{align}
$$&lt;/p&gt;
&lt;h4&gt;第三步：应用虚数单位乘法规则&lt;/h4&gt;
&lt;p&gt;现在关键来了！使用我们前面学的乘法规则：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;规则回顾&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$i^2 = j^2 = k^2 = -1$&lt;/li&gt;
&lt;li&gt;$i \cdot j = k, \quad j \cdot k = i, \quad k \cdot i = j$&lt;/li&gt;
&lt;li&gt;$j \cdot i = -k, \quad k \cdot j = -i, \quad i \cdot k = -j$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;让我们逐项化简：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第1行（$w_1$ 的项）&lt;/strong&gt;：
$$
w_1 w_2 + w_1 x_2 i + w_1 y_2 j + w_1 z_2 k
$$
这些项已经是最简形式了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第2行（$x_1 i$ 的项）&lt;/strong&gt;：
$$
\begin{align}
x_1 w_2 i &amp;amp;\rightarrow x_1 w_2 i \quad \text{(已是最简)} \
x_1 x_2 i^2 &amp;amp;\rightarrow x_1 x_2 \cdot (-1) = -x_1 x_2 \quad \text{(实部)} \
x_1 y_2 (i \cdot j) &amp;amp;\rightarrow x_1 y_2 \cdot k \quad \text{(变成 k 分量)} \
x_1 z_2 (i \cdot k) &amp;amp;\rightarrow x_1 z_2 \cdot (-j) = -x_1 z_2 j \quad \text{(变成负的 j)}
\end{align}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第3行（$y_1 j$ 的项）&lt;/strong&gt;：
$$
\begin{align}
y_1 w_2 j &amp;amp;\rightarrow y_1 w_2 j \quad \text{(已是最简)} \
y_1 x_2 (j \cdot i) &amp;amp;\rightarrow y_1 x_2 \cdot (-k) = -y_1 x_2 k \quad \text{(变成负的 k)} \
y_1 y_2 j^2 &amp;amp;\rightarrow y_1 y_2 \cdot (-1) = -y_1 y_2 \quad \text{(实部)} \
y_1 z_2 (j \cdot k) &amp;amp;\rightarrow y_1 z_2 \cdot i \quad \text{(变成 i 分量)}
\end{align}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第4行（$z_1 k$ 的项）&lt;/strong&gt;：
$$
\begin{align}
z_1 w_2 k &amp;amp;\rightarrow z_1 w_2 k \quad \text{(已是最简)} \
z_1 x_2 (k \cdot i) &amp;amp;\rightarrow z_1 x_2 \cdot j \quad \text{(变成 j 分量)} \
z_1 y_2 (k \cdot j) &amp;amp;\rightarrow z_1 y_2 \cdot (-i) = -z_1 y_2 i \quad \text{(变成负的 i)} \
z_1 z_2 k^2 &amp;amp;\rightarrow z_1 z_2 \cdot (-1) = -z_1 z_2 \quad \text{(实部)}
\end{align}
$$&lt;/p&gt;
&lt;h4&gt;第四步：按分量归类整理&lt;/h4&gt;
&lt;p&gt;现在把所有项按照 $i, j, k, \text{实部}$ 分组：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实部（w 分量）&lt;/strong&gt;：
$$
w = w_1 w_2 - x_1 x_2 - y_1 y_2 - z_1 z_2
$$
来源：直接乘积 + 三个 $i^2, j^2, k^2$ 项&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;i 分量（x 分量）&lt;/strong&gt;：
$$
\begin{align}
x &amp;amp;= w_1 x_2 i + x_1 w_2 i + y_1 z_2 i - z_1 y_2 i \
&amp;amp;= (w_1 x_2 + x_1 w_2 + y_1 z_2 - z_1 y_2) i
\end{align}
$$
所以：$x = w_1 x_2 + x_1 w_2 + y_1 z_2 - z_1 y_2$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;j 分量（y 分量）&lt;/strong&gt;：
$$
\begin{align}
y &amp;amp;= w_1 y_2 j + y_1 w_2 j - x_1 z_2 j + z_1 x_2 j \
&amp;amp;= (w_1 y_2 + y_1 w_2 - x_1 z_2 + z_1 x_2) j
\end{align}
$$
所以：$y = w_1 y_2 - x_1 z_2 + y_1 w_2 + z_1 x_2$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;k 分量（z 分量）&lt;/strong&gt;：
$$
\begin{align}
z &amp;amp;= w_1 z_2 k + z_1 w_2 k + x_1 y_2 k - y_1 x_2 k \
&amp;amp;= (w_1 z_2 + z_1 w_2 + x_1 y_2 - y_1 x_2) k
\end{align}
$$
所以：$z = w_1 z_2 + x_1 y_2 - y_1 x_2 + z_1 w_2$&lt;/p&gt;
&lt;h4&gt;第五步：最终结果&lt;/h4&gt;
&lt;p&gt;这就得到了我们的四元数乘法公式！&lt;/p&gt;
&lt;p&gt;$$
\boxed{
\begin{align}
x &amp;amp;= w_1 x_2 + x_1 w_2 + y_1 z_2 - z_1 y_2 \
y &amp;amp;= w_1 y_2 - x_1 z_2 + y_1 w_2 + z_1 x_2 \
z &amp;amp;= w_1 z_2 + x_1 y_2 - y_1 x_2 + z_1 w_2 \
w &amp;amp;= w_1 w_2 - x_1 x_2 - y_1 y_2 - z_1 z_2
\end{align}
}
$$&lt;/p&gt;
&lt;h4&gt;💡 记忆技巧&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;实部（w）&lt;/strong&gt;：所有平方项都是负的（$-x^2, -y^2, -z^2$）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;虚部（x, y, z）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前两项：对应分量与实部的交叉项（如 $x$ 的 $w_1x_2 + x_1w_2$）&lt;/li&gt;
&lt;li&gt;后两项：其他两个虚部的&quot;循环&quot;交叉（一正一负）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;符号规律&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;正号：顺时针循环（$i \to j \to k \to i$）&lt;/li&gt;
&lt;li&gt;负号：逆时针循环（$i \to k \to j \to i$）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;向量形式&lt;/h3&gt;
&lt;p&gt;也可以用向量和标量的形式表示：&lt;/p&gt;
&lt;p&gt;$$
q_1 \times q_2 = \begin{pmatrix}
w_1 w_2 - \mathbf{v}_1 \cdot \mathbf{v}_2 \
w_1 \mathbf{v}_2 + w_2 \mathbf{v}_1 + \mathbf{v}_1 \times \mathbf{v}_2
\end{pmatrix}
$$&lt;/p&gt;
&lt;p&gt;其中 $\mathbf{v}_1 = (x_1, y_1, z_1)$ 和 $\mathbf{v}_2 = (x_2, y_2, z_2)$ 是虚部向量。&lt;/p&gt;
&lt;h2&gt;代码实现对应关系&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;function multiplyQuaternions(q1: Cesium.Quaternion, q2: Cesium.Quaternion): Cesium.Quaternion {
  // x 分量：w₁x₂ + x₁w₂ + y₁z₂ - z₁y₂
  const x = q1.x * q2.w + q1.w * q2.x + q1.y * q2.z - q1.z * q2.y
  //        ↑ x₁w₂        ↑ w₁x₂        ↑ y₁z₂        ↑ -z₁y₂

  // y 分量：w₁y₂ - x₁z₂ + y₁w₂ + z₁x₂
  const y = -q1.x * q2.z + q1.w * q2.y + q1.y * q2.w + q1.z * q2.x
  //        ↑ -x₁z₂        ↑ w₁y₂        ↑ y₁w₂        ↑ z₁x₂

  // z 分量：w₁z₂ + x₁y₂ - y₁x₂ + z₁w₂
  const z = q1.x * q2.y - q1.y * q2.x + q1.w * q2.z + q1.z * q2.w
  //        ↑ x₁y₂        ↑ -y₁x₂       ↑ w₁z₂        ↑ z₁w₂

  // w 分量：w₁w₂ - x₁x₂ - y₁y₂ - z₁z₂
  const w = -q1.x * q2.x - q1.y * q2.y - q1.z * q2.z + q1.w * q2.w
  //        ↑ -x₁x₂       ↑ -y₁y₂       ↑ -z₁z₂       ↑ w₁w₂

  return new Cesium.Quaternion(x, y, z, w)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;几何意义&lt;/h2&gt;
&lt;h3&gt;旋转组合&lt;/h3&gt;
&lt;p&gt;四元数乘法的主要几何意义是&lt;strong&gt;旋转的组合&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$q_{result} = q_1 \times q_2$$&lt;/p&gt;
&lt;p&gt;表示：&lt;strong&gt;先应用旋转 $q_2$，再应用旋转 $q_1$&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;⚠️ &lt;strong&gt;注意&lt;/strong&gt;：旋转顺序从右到左！&lt;/p&gt;
&lt;h3&gt;非交换性&lt;/h3&gt;
&lt;p&gt;四元数乘法是&lt;strong&gt;非交换的&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$q_1 \times q_2 \neq q_2 \times q_1$$&lt;/p&gt;
&lt;p&gt;这意味着旋转的顺序非常重要。例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先绕X轴转90°，再绕Y轴转90°&lt;/li&gt;
&lt;li&gt;先绕Y轴转90°，再绕X轴转90°&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两种操作的结果完全不同！&lt;/p&gt;
&lt;h3&gt;单位四元数与旋转&lt;/h3&gt;
&lt;p&gt;用于表示旋转的四元数必须是&lt;strong&gt;单位四元数&lt;/strong&gt;，满足：&lt;/p&gt;
&lt;p&gt;$$x^2 + y^2 + z^2 + w^2 = 1$$&lt;/p&gt;
&lt;p&gt;单位四元数与旋转的关系：&lt;/p&gt;
&lt;p&gt;$$q = \begin{pmatrix} \sin(\theta/2) \cdot \mathbf{axis} \ \cos(\theta/2) \end{pmatrix}$$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\theta$ 是旋转角度&lt;/li&gt;
&lt;li&gt;$\mathbf{axis} = (x, y, z)$ 是归一化的旋转轴&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;在草地渲染中的应用&lt;/h2&gt;
&lt;h3&gt;应用场景&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;generateInstancesWithTerrain()&lt;/code&gt; 方法中，使用四元数乘法创建草叶的复杂朝向：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 1️⃣ 绕Y轴旋转（基础方向）- 随机水平朝向
let angle = Math.PI - Math.random() * (2 * Math.PI)  // 0 到 2π
let rotationAxis = new Cesium.Cartesian3(0, 1, 0)    // Y轴
let x = rotationAxis.x * Math.sin(angle / 2.0)
let y = rotationAxis.y * Math.sin(angle / 2.0)
let z = rotationAxis.z * Math.sin(angle / 2.0)
let w = Math.cos(angle / 2.0)
quaternion0 = new Cesium.Quaternion(x, y, z, w)

// 2️⃣ 绕X轴旋转（前后倾斜）
angle = Math.random() * (0.25 - (-0.25)) + (-0.25)  // -0.25 到 0.25 弧度
rotationAxis = new Cesium.Cartesian3(1, 0, 0)       // X轴
x = rotationAxis.x * Math.sin(angle / 2.0)
y = rotationAxis.y * Math.sin(angle / 2.0)
z = rotationAxis.z * Math.sin(angle / 2.0)
w = Math.cos(angle / 2.0)
quaternion1 = new Cesium.Quaternion(x, y, z, w)

// 3️⃣ 组合：先Y轴旋转，再X轴旋转
quaternion0 = multiplyQuaternions(quaternion0, quaternion1)

// 4️⃣ 绕Z轴旋转（细微扭转）
const microVariation = (Math.random() - 0.5) * 0.1  // -0.05 到 0.05
angle = microVariation
rotationAxis = new Cesium.Cartesian3(0, 0, 1)       // Z轴
x = rotationAxis.x * Math.sin(angle / 2.0)
y = rotationAxis.y * Math.sin(angle / 2.0)
z = rotationAxis.z * Math.sin(angle / 2.0)
w = Math.cos(angle / 2.0)
quaternion1 = new Cesium.Quaternion(x, y, z, w)

// 5️⃣ 最终方向：前面的组合 + Z轴扭转
const orientation = multiplyQuaternions(quaternion0, quaternion1)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;视觉效果&lt;/h3&gt;
&lt;p&gt;通过这种三次旋转的组合，每片草叶获得：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;随机水平朝向&lt;/strong&gt;（Y轴旋转）：草叶向各个方向生长&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自然倾斜&lt;/strong&gt;（X轴旋转）：轻微的前后倾斜，模拟重力和生长不均匀&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;细微扭转&lt;/strong&gt;（Z轴旋转）：微小的旋转变化，避免过于规则&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最终结果是每片草都有独特的、自然的三维朝向，极大增强了草地的真实感和自然度。&lt;/p&gt;
&lt;h2&gt;数学性质总结&lt;/h2&gt;
&lt;h3&gt;1. 结合律&lt;/h3&gt;
&lt;p&gt;$$
(q_1 \times q_2) \times q_3 = q_1 \times (q_2 \times q_3)
$$&lt;/p&gt;
&lt;h3&gt;2. 非交换性&lt;/h3&gt;
&lt;p&gt;$$
q_1 \times q_2 \neq q_2 \times q_1
$$&lt;/p&gt;
&lt;h3&gt;3. 单位元&lt;/h3&gt;
&lt;p&gt;$$
q \times q_{identity} = q_{identity} \times q = q
$$
其中 $q_{identity} = (0, 0, 0, 1)$&lt;/p&gt;
&lt;h3&gt;4. 范数保持（单位四元数）&lt;/h3&gt;
&lt;p&gt;如果 $|q_1| = |q_2| = 1$，则 $|q_1 \times q_2| = 1$&lt;/p&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation&quot;&gt;Quaternions and spatial rotation - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.3dgep.com/understanding-quaternions/&quot;&gt;Understanding Quaternions - 3D Game Engine Programming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eater.net/quaternions&quot;&gt;Visualizing Quaternions - eater.net&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>sukha</author></item><item><title>射线投射算法原理解析</title><link>https://sukha.dev/posts/ray-casting-algorithm</link><guid isPermaLink="true">https://sukha.dev/posts/ray-casting-algorithm</guid><description>本文详细解析了射线投射算法的原理及其在判断点是否在多边形内部的应用。</description><pubDate>Fri, 05 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;射线的形成原理&lt;/h2&gt;
&lt;p&gt;射线在这个算法中是&lt;strong&gt;隐式的&lt;/strong&gt;,并不是显式画出来的。理解射线如何形成是关键:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;射线定义:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;起点:&lt;/strong&gt; 要检测的点 &lt;code&gt;(x, y)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;方向:&lt;/strong&gt; 向右 → (正 x 轴方向)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;长度:&lt;/strong&gt; 无限长 (从点的 x 坐标延伸到 +∞)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;数学表达:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;射线上的所有点: (x&apos;, y) 其中 x&apos; ≥ x
即: 从 (x, y) 向右无限延伸,y 坐标始终保持不变
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;可视化:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;射线的形成过程:

第1步: 标记要检测的点
            ●────────&amp;gt; 射线方向(向右)
            (x, y)

第2步: 从点向右射出一条无限长的射线
                     边1
                      │
    ●─────────────────┼──────────&amp;gt; 射线
    (x,y)            │交点1

第3步: 与多边形边界相交
           边2  边1  边3
            │   │    │
    ●───────┼───┼────┼──────────&amp;gt; 射线
    (x,y)  │  交点1 交点2交点3

结果: 射线穿过3次 = 奇数 = 点在内部 ✅
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;算法原理&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;从点向右射出一条射线
    ↓
数一下射线穿过多边形边界的次数
    ↓
奇数次 = 点在多边形内
偶数次 = 点在多边形外
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;直观示例&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;情况1: 点在多边形内
        ╱────╲
       ╱      ╲
      │    ●→→→│→→ 射线穿过1次(奇数) = 在内部 ✅
      │        │
       ╲      ╱
        ╲────╱

情况2: 点在多边形外
   ●→→→│    │
      ╱────╲
     ╱      ╲
    │        │ 射线穿过0次(偶数) = 在外部 ❌
     ╲      ╱
      ╲────╱
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;代码逐行解释&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// 核心逻辑:遍历多边形的每条边
for (let i = 0, j = polygonPoints.length - 1; i &amp;lt; polygonPoints.length; j = i++) {
  // j 是前一个点的索引，i 是当前点的索引
  // 这样可以获取多边形的每条边 (j→i)
  
  const xi = polygonPoints[i].longitude  // 当前点的经度
  const yi = polygonPoints[i].latitude   // 当前点的纬度
  const xj = polygonPoints[j].longitude  // 前一个点的经度
  const yj = polygonPoints[j].latitude   // 前一个点的纬度

  // 关键判断条件
  if (yi &amp;gt; y !== yj &amp;gt; y &amp;amp;&amp;amp; x &amp;lt; ((xj - xi) * (y - yi)) / (yj - yi) + xi) {
    inside = !inside  // 切换点的内外状态
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;条件拆解&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;① &lt;code&gt;yi &amp;gt; y !== yj &amp;gt; y&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;检查边是否&lt;strong&gt;跨越射线的 y 坐标&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yj ────────────  (第 j 个点)
    ╲
     ╲  ← 边跨越了 y 坐标
      ╲
y ─────●─────── (我们要检测的点)
        ╲
         ╲
yi ───────●── (第 i 个点)

yi &amp;gt; y !== yj &amp;gt; y 为 true ✅
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;作用:&lt;/strong&gt; 只检查与射线高度相关的边,跳过不相交的边&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② &lt;code&gt;x &amp;lt; ((xj - xi) * (y - yi)) / (yj - yi) + xi&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;计算边与射线的&lt;strong&gt;交点 x 坐标&lt;/strong&gt;,检查是否在点的右侧:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;数学推导:
边的直线方程: (y - yi) / (yj - yi) = (x - xi) / (xj - xi)

解出 x:
x = xi + (xj - xi) * (y - yi) / (yj - yi)

判断: 点的 x 坐标 &amp;lt; 交点的 x 坐标
即: x &amp;lt; 交点x坐标
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;直观理解:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;从点向右射线:
             交点
              │
              ↓
  ●────────────┼────→ (射线)
  ^            │
  │            │
  点的x        边与射线的交点x

点的 x &amp;lt; 交点x ✅ 说明射线确实穿过了这条边
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;完整流程示例&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;点: (10, 15)
多边形: [(5,10), (15,10), (15,20), (5,20)]

遍历边:
1. 边 (5,20)→(5,10): 不满足 yi &amp;gt; y !== yj &amp;gt; y (都 ≤ y) → 跳过
2. 边 (5,10)→(15,10): 不满足 yi &amp;gt; y !== yj &amp;gt; y (都 &amp;lt; y) → 跳过
3. 边 (15,10)→(15,20): ✅ 满足条件
   - 计算交点x ≈ 15
   - 10 &amp;lt; 15 → inside = true ✅
4. 边 (15,20)→(5,20): 不满足条件 → 跳过

结果: inside = true (点在多边形内)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
</content:encoded><author>sukha</author></item><item><title>react-useReducer</title><link>https://sukha.dev/posts/react-learn-usereducer</link><guid isPermaLink="true">https://sukha.dev/posts/react-learn-usereducer</guid><description>useReducer.</description><pubDate>Fri, 05 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import UseReducerDemo from &apos;~/components/mdx/UseReducerDemo&apos;
import ComponentWithSource from &apos;~/components/mdx/ComponentWithSource&apos;
import UseReducerDemoSource from &apos;~/components/mdx/UseReducerDemo.tsx?raw&apos;&lt;/p&gt;
&lt;h2&gt;react&lt;/h2&gt;
&lt;p&gt;&amp;lt;ComponentWithSource
client:load
sourceCode={UseReducerDemoSource}
language=&quot;tsx&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;UseReducerDemo client:load /&amp;gt;
&amp;lt;/ComponentWithSource&amp;gt;&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>react-useTransition</title><link>https://sukha.dev/posts/react-learn-usetransition</link><guid isPermaLink="true">https://sukha.dev/posts/react-learn-usetransition</guid><description>useTransition.</description><pubDate>Wed, 10 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;首先我们需要开启节流模式&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;然后我们可以使用useTransition来避免同步渲染造成的页面阻塞&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import React, { useEffect, useState, useTransition } from &quot;react&quot;

function App() {
  const [keyword, setKeyword] = useState&amp;lt;string&amp;gt;(&apos;&apos;)
  const [list, setList] = useState&amp;lt;{
    name: string,
    address: string,
    id: number
  }[]&amp;gt;([])
  const fetchData = async (keyword?: string) =&amp;gt; {
    //结合我们的vite-mock-server来实现数据获取
    const res = await (await fetch(`/api/list?keyword=${keyword || &apos;sukha&apos;}`)).json()
    //使用了startTransition不会造成页面卡顿
    startTransition(() =&amp;gt; {
      setList(res.list)
    })
    //不使用useTransition会造成页面卡顿
    setList(res.list)
  }
  const [isPending, startTransition] = useTransition()
  useEffect(() =&amp;gt; {
    fetchData(keyword)
  }, [keyword])
  const findItem = (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
    setKeyword(e.target.value)
  }
  return &amp;lt;&amp;gt;
    &amp;lt;input type=&quot;text&quot; value={keyword} onChange={(e) =&amp;gt; findItem(e)} /&amp;gt;
    {isPending &amp;amp;&amp;amp; &amp;lt;div&amp;gt;加载中...&amp;lt;/div&amp;gt;}
    {
      list.map(item =&amp;gt; &amp;lt;div key={item.id}&amp;gt;
        {`${item.name}-${item.id}-${item.address}`}
      &amp;lt;/div&amp;gt;)
    }
  &amp;lt;/&amp;gt;
}
export default App
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><author>sukha</author></item><item><title>vite-plugin-mockserver</title><link>https://sukha.dev/posts/vite-mock-server</link><guid isPermaLink="true">https://sukha.dev/posts/vite-mock-server</guid><description>写一个vite plugin来完成一个mock数据的server</description><pubDate>Wed, 10 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;安装依赖&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;首先我们创建一个vite项目&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm create vite
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;然后我们要进行依赖的安装&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm add mockjs @types/mockjs
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;随后我们开始编写插件&lt;/p&gt;
&lt;p&gt;:::note
以下配置项可以查看&lt;a href=&quot;https://cn.vite.dev/guide/api-plugin&quot;&gt;&lt;strong&gt;vite官网plugin&lt;/strong&gt;&lt;/a&gt;相关内容
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { defineConfig } from &apos;vite&apos;
import react from &apos;@vitejs/plugin-react&apos;
import { type Plugin } from &apos;vite&apos;
import mockjs from &apos;mockjs&apos;
import url from &apos;node:url&apos;

const viteMockServer = (): Plugin =&amp;gt; {
  return {
    
    name: &apos;vite-mock-server&apos;,
    configureServer(server) {
      //使用中间件来完成mock接口
      server.middlewares.use(&apos;/api/list&apos;, (req, res) =&amp;gt; {
        //通过url.parse来获取参数对象
        const parseUrl = url.parse(req.originalUrl!, true).query
        //保证返回的数据被正常序列化
        res.setHeader(&apos;content-type&apos;, &apos;application/json&apos;)
        const data = mockjs.mock({
          &apos;list|100&apos;: [
            {
              &apos;id|+1&apos;: 1,
              name: parseUrl.keyword,
              &apos;address&apos;: &apos;@county(true)&apos;
            }
          ]
        })
        //将数据返回给前端
        res.end(JSON.stringify(data))
      })
    }
  }
}

export default defineConfig({
  plugins: [react(), viteMockServer()],
})
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最后我们可以通过fetch来获取数据或者通过网页路由直接访问&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;  import { useEffect, useState } from &quot;react&quot;
  function App() {
    const [list, setList] = useState&amp;lt;{
      name: string,
      address: string,
      id: number
    }[]&amp;gt;([])
    useEffect(() =&amp;gt; {
      const fetchData = async () =&amp;gt; {
        const res = await (await fetch(&apos;/api/list?keyword=sukha&apos;)).json()
        setList(res.list)
      }
      fetchData()
    }, [])
    return &amp;lt;&amp;gt;
      {list.map(item =&amp;gt; &amp;lt;div&amp;gt;
        {`${item.name}-${item.id}-${item.address}`}
      &amp;lt;/div&amp;gt;)}
    &amp;lt;/&amp;gt;
  }
  export default App
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><author>sukha</author></item><item><title>cesium map init</title><link>https://sukha.dev/posts/cesium-map-init</link><guid isPermaLink="true">https://sukha.dev/posts/cesium-map-init</guid><description>cesium map init with react and vue</description><pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import InitMapDemo from &apos;~/components/mdx/InitMapDemo&apos;
import InitMapVueDemo from &apos;~/components/mdx/InitMapDemo.vue&apos;
import InitMapDemoSourceCode from &apos;~/components/mdx/InitMapDemo.tsx?raw&apos;
import InitMapVueDemoSourceCode from &apos;~/components/mdx/InitMapDemo.vue?raw&apos;
import ComponentWithSource from &apos;~/components/mdx/ComponentWithSource&apos;
import CesiumLoader from &apos;~/components/common/CesiumLoader.astro&apos;&lt;/p&gt;
&lt;h2&gt;使用react-tsx初始化&lt;/h2&gt;
&lt;p&gt;&amp;lt;CesiumLoader /&amp;gt;
&amp;lt;ComponentWithSource sourceCode={InitMapDemoSourceCode} fullscreenTargetId=&quot;react&quot; client:load&amp;gt;
&amp;lt;InitMapDemo client:load id={&apos;react&apos;}/&amp;gt;
&amp;lt;/ComponentWithSource&amp;gt;&lt;/p&gt;
&lt;h2&gt;使用vue初始化&lt;/h2&gt;
&lt;p&gt;&amp;lt;CesiumLoader /&amp;gt;
&amp;lt;ComponentWithSource sourceCode={InitMapVueDemoSourceCode} fullscreenTargetId=&quot;vue&quot; client:load&amp;gt;
&amp;lt;InitMapVueDemo client:load id={&apos;vue&apos;}/&amp;gt;
&amp;lt;/ComponentWithSource&amp;gt;&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>react-useCallback</title><link>https://sukha.dev/posts/react-learn-usecallback</link><guid isPermaLink="true">https://sukha.dev/posts/react-learn-usecallback</guid><description>useCallback.</description><pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import UseCallbackDemo from &apos;~/components/mdx/UseCallbackDemo&apos;
import UseCallbackDemoSource from &apos;~/components/mdx/UseCallbackDemo.tsx?raw&apos;
import ComponentWithSource from &apos;~/components/mdx/ComponentWithSource&apos;&lt;/p&gt;
&lt;h2&gt;主要作用&lt;/h2&gt;
&lt;p&gt;::::note&lt;/p&gt;
&lt;p&gt;useCallback 用于缓存回调函数的引用，只有依赖项变化时才返回新的函数，避免子组件或副作用因回调引用变化而不必要地重新执行。&lt;/p&gt;
&lt;p&gt;::::&lt;/p&gt;
&lt;h2&gt;核心概念&lt;/h2&gt;
&lt;p&gt;::::note&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;缓存函数引用：依赖数组未变时返回同一函数引用，稳定传递给子组件或注册器。&lt;/li&gt;
&lt;li&gt;避免无意义重渲染：配合 React.memo，防止因回调引用变化导致子组件重复渲染。&lt;/li&gt;
&lt;li&gt;稳定回调给副作用：在 useEffect 等场景确保回调引用不变，避免重复清理/注册。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;::::&lt;/p&gt;
&lt;h2&gt;注意事项&lt;/h2&gt;
&lt;p&gt;::::note&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不要滥用：函数创建本身很廉价，过度使用会让依赖管理变复杂。&lt;/li&gt;
&lt;li&gt;依赖项必须完整：闭包中用到的 state/props 都要放进依赖数组；否则可能读到旧值。&lt;/li&gt;
&lt;li&gt;与 useMemo 区分：useMemo 缓存值，useCallback 缓存函数引用；若只需函数体执行结果用 useMemo。&lt;/li&gt;
&lt;li&gt;配合 React.memo 才有意义：只有当子组件做浅比较时，稳定引用才能减少渲染。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;::::&lt;/p&gt;
&lt;h2&gt;示例代码&lt;/h2&gt;
&lt;p&gt;&amp;lt;ComponentWithSource
client:load
sourceCode={UseCallbackDemoSource}
language=&quot;tsx&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;UseCallbackDemo client:load /&amp;gt;
&amp;lt;/ComponentWithSource&amp;gt;&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>react-useContext</title><link>https://sukha.dev/posts/react-learn-usecontext</link><guid isPermaLink="true">https://sukha.dev/posts/react-learn-usecontext</guid><description>useContext.</description><pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import UseContextDemo from &apos;~/components/mdx/UseContextDemo&apos;
import ComponentWithSource from &apos;~/components/mdx/ComponentWithSource&apos;
import useContextDemoSource from &apos;~/components/mdx/UseContextDemo.tsx?raw&apos;&lt;/p&gt;
&lt;h2&gt;主要作用&lt;/h2&gt;
&lt;p&gt;:::note
用于在组件之间共享数据，避免通过 props 逐层传递。
:::&lt;/p&gt;
&lt;h2&gt;核心特点&lt;/h2&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;跨组件共享数据：无需通过 props 逐层传递&lt;/li&gt;
&lt;li&gt;简化组件树：减少中间组件的 props 传递&lt;/li&gt;
&lt;li&gt;配合 createContext 和 Provider 使用：需要先创建 Context，然后用 Provider 包裹组件树&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;使用场景&lt;/h2&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;主题切换&lt;/li&gt;
&lt;li&gt;用户认证信息&lt;/li&gt;
&lt;li&gt;语言/国际化&lt;/li&gt;
&lt;li&gt;全局配置&lt;/li&gt;
&lt;li&gt;避免深层 props 传递&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;注意事项&lt;/h2&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Context&lt;/code&gt; 值变化会触发所有使用该 Context 的组件重新渲染&lt;/li&gt;
&lt;li&gt;不要过度使用：简单场景优先用 props&lt;/li&gt;
&lt;li&gt;可以拆分多个 Context：避免单个 Context 包含过多数据&lt;/li&gt;
&lt;li&gt;提供默认值：createContext(defaultValue) 可以在没有 Provider 时使用&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;:::note
&lt;code&gt;useContext&lt;/code&gt; 用于在组件树中共享数据，避免 props 逐层传递，适合全局或跨层级的状态共享场景。
:::&lt;/p&gt;
&lt;h2&gt;使用useContext来在组件树中共享数据&lt;/h2&gt;
&lt;p&gt;:::note
这里使用 useContext 在组件树中共享数据：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建 Context：createContext(defaultValue)&lt;/li&gt;
&lt;li&gt;用 Provider 包裹组件树：&lt;code&gt;&amp;lt;Context.Provider value={value}&amp;gt;&amp;lt;/Context.Provider&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在组件中使用 Context：const value = useContext(Context)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;&amp;lt;ComponentWithSource
client:load
sourceCode={useContextDemoSource}
language=&quot;tsx&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;UseContextDemo client:load /&amp;gt;
&amp;lt;/ComponentWithSource&amp;gt;&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>react-useDefferredValue</title><link>https://sukha.dev/posts/react-learn-usedefferredvalue</link><guid isPermaLink="true">https://sukha.dev/posts/react-learn-usedefferredvalue</guid><description>useDefferredValue.</description><pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;主要作用&lt;/h2&gt;
&lt;p&gt;:::note
用于延迟更新某个值，让 React 优先处理更紧急的更新（如用户输入），从而避免阻塞 UI。
:::&lt;/p&gt;
&lt;h2&gt;我们可以使用useDefferredValue来避免同步渲染造成的页面阻塞&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import { useDeferredValue, useState } from &apos;react&apos;
import { Flex, Input, List } from &apos;antd&apos;
import mockjs from &apos;mockjs&apos;

interface Item {
  name: string
  id: number
  address: string
}
function App() {
  const [inputValue, setInputValue] = useState(&apos;&apos;)
  const [list] = useState&amp;lt;Item[]&amp;gt;(() =&amp;gt; {
    return mockjs.mock({
      &apos;list|1000&apos;: [
        {
          &apos;id|+1&apos;: 1,
          name: &apos;@natural&apos;,
          address: &apos;@county(true)&apos;,
        },
      ],
    }).list
  })
  const deferredQuery = useDeferredValue(inputValue)
  const isStale = deferredQuery !== inputValue
  const findItem = () =&amp;gt; {
    console.log(deferredQuery, &apos;----&apos;, inputValue)
    return list.filter((item) =&amp;gt; item.name.toString().includes(deferredQuery))
  }
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Flex&amp;gt;
        &amp;lt;Input
          value={inputValue}
          onChange={(e) =&amp;gt; setInputValue(e.target.value)}
          placeholder=&quot;请输入姓名&quot;
        /&amp;gt;
      &amp;lt;/Flex&amp;gt;
      &amp;lt;List
        style={{ opacity: isStale ? 0.5 : 1.0, transition: &apos;opacity 0.8s ease-in-out&apos; }}
        renderItem={(item) =&amp;gt; (
          &amp;lt;List.Item&amp;gt;
            &amp;lt;List.Item.Meta title={item.name} description={item.address} /&amp;gt;
          &amp;lt;/List.Item&amp;gt;
        )}
        dataSource={findItem()}
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default App
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><author>sukha</author></item><item><title>react-useImperativeHandle</title><link>https://sukha.dev/posts/react-learn-useimperativehandle</link><guid isPermaLink="true">https://sukha.dev/posts/react-learn-useimperativehandle</guid><description>useImperativeHandle.</description><pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import ComponentWithSource from &apos;~/components/mdx/ComponentWithSource&apos;
import UseImperativeHandleDemo from &apos;~/components/mdx/UseImperativeHandleDemo&apos;
import UseImperativeHandleDemoSource from &apos;~/components/mdx/UseImperativeHandleDemo.tsx?raw&apos;&lt;/p&gt;
&lt;h2&gt;主要作用&lt;/h2&gt;
&lt;p&gt;:::note
用于自定义通过 ref 暴露给父组件的实例值和方法，让父组件可以访问子组件的特定属性和方法。
:::&lt;/p&gt;
&lt;h2&gt;核心特点&lt;/h2&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;自定义 ref 暴露：控制父组件通过 ref 能访问到的内容&lt;/li&gt;
&lt;li&gt;封装子组件：只暴露需要的方法和属性，隐藏内部实现&lt;/li&gt;
&lt;li&gt;配合 forwardRef 使用：通常与 React.forwardRef 一起使用&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;注意事项&lt;/h2&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不要过度使用：优先使用 props 和状态提升&lt;/li&gt;
&lt;li&gt;只在必要时使用：会破坏 React 的数据流&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;:::note
useImperativeHandle 用于自定义通过 ref 暴露给父组件的实例值和方法，让父组件可以访问子组件的特定属性和方法。
:::&lt;/p&gt;
&lt;h2&gt;使用useImperativeHandle&lt;/h2&gt;
&lt;p&gt;:::note
这里使用 useImperativeHandle 自定义通过 ref 暴露给父组件的实例值和方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;自定义 ref 暴露：控制父组件通过 ref 能访问到的内容&lt;/li&gt;
&lt;li&gt;封装子组件：只暴露需要的方法和属性，隐藏内部实现&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;&amp;lt;ComponentWithSource
client:load
sourceCode={UseImperativeHandleDemoSource}
language=&quot;tsx&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;UseImperativeHandleDemo client:load /&amp;gt;
&amp;lt;/ComponentWithSource&amp;gt;&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>react-useLayoutEffect</title><link>https://sukha.dev/posts/react-learn-uselayouteffect</link><guid isPermaLink="true">https://sukha.dev/posts/react-learn-uselayouteffect</guid><description>useLayoutEffect.</description><pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;主要作用&lt;/h2&gt;
&lt;p&gt;:::note
在浏览器绘制之前同步执行，用于在 DOM 更新后、浏览器绘制前进行 DOM 测量或同步修改，避免视觉闪烁。
:::&lt;/p&gt;
&lt;h2&gt;核心特点&lt;/h2&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;同步执行：在浏览器绘制之前同步执行，确保 DOM 操作在渲染之前完成。&lt;/li&gt;
&lt;li&gt;避免视觉闪烁：由于同步执行，可以确保 DOM 操作在渲染之前完成，避免视觉闪烁。&lt;/li&gt;
&lt;li&gt;优先级高于 useEffect：在 useEffect 之前执行，确保 DOM 操作在渲染之前完成。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;注意事项&lt;/h2&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;会阻塞浏览器绘制，避免执行耗时操作&lt;/li&gt;
&lt;li&gt;优先使用 useEffect，仅在需要避免视觉闪烁时使用 useLayoutEffect&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;:::note
useLayoutEffect 用于在浏览器绘制前同步执行 DOM 操作，避免闪烁；useEffect 在绘制后异步执行，不阻塞渲染。
:::&lt;/p&gt;
&lt;h2&gt;我们可以使用useLayoutEffect来处理DOM操作&lt;/h2&gt;
&lt;p&gt;:::note
这里使用 useLayoutEffect 恢复滚动位置：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在浏览器绘制前设置 scrollTop&lt;/li&gt;
&lt;li&gt;避免先显示顶部再跳转的闪烁&lt;/li&gt;
&lt;li&gt;如果用 useEffect，可能先看到顶部，再跳转，造成闪烁&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { useLayoutEffect, useEffect, useRef } from &apos;react&apos;

export default function App() {
  const scrollHandler = (e: React.UIEvent&amp;lt;HTMLDivElement&amp;gt;) =&amp;gt; {
    const scrollTop = e.currentTarget.scrollTop
    window.history.replaceState({}, &apos;&apos;, `?top=${scrollTop}`)
  }
  const container = useRef&amp;lt;HTMLDivElement&amp;gt;(null)
  useLayoutEffect(() =&amp;gt; {
    if (container.current) {
      const top = window.location.search.split(&apos;=&apos;)[1]
      container.current.scrollTop = Number(top) || 0
    }
  })
  return (
    &amp;lt;div
      onScroll={scrollHandler}
      ref={container}
      id=&quot;container&quot;
      className=&quot;h-[500px] overflow-auto&quot;
    &amp;gt;
      {Array.from({ length: 500 }).map((_, index) =&amp;gt; {
        return &amp;lt;div key={index}&amp;gt;{index + 1}&amp;lt;/div&amp;gt;
      })}
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><author>sukha</author></item><item><title>react-useMemo</title><link>https://sukha.dev/posts/react-learn-usememo</link><guid isPermaLink="true">https://sukha.dev/posts/react-learn-usememo</guid><description>useMemo.</description><pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import ComponentWithSource from &apos;~/components/mdx/ComponentWithSource&apos;
import UseMemoDemo1 from &apos;~/components/mdx/UseMemoDemo1&apos;
import UseMemoDemoSource1 from &apos;~/components/mdx/UseMemoDemo1.tsx?raw&apos;
import UseMemoDemo2 from &apos;~/components/mdx/UseMemoDemo2&apos;
import UseMemoDemoSource2 from &apos;~/components/mdx/UseMemoDemo2.tsx?raw&apos;&lt;/p&gt;
&lt;h2&gt;主要作用&lt;/h2&gt;
&lt;p&gt;:::note
&lt;code&gt;useMemo&lt;/code&gt; 用于缓存计算结果，避免在每次渲染时重复执行昂贵的计算。
:::&lt;/p&gt;
&lt;h2&gt;核心概念&lt;/h2&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;缓存计算结果：只有当依赖项数组中的值发生变化时，才会重新计算&lt;/li&gt;
&lt;li&gt;返回记忆化的值：依赖未变化时，直接返回上次计算的结果&lt;/li&gt;
&lt;li&gt;避免不必要的计算：减少重复的复杂计算，提升性能
:::&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;注意事项&lt;/h2&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不要滥用 useMemo：只在必要时使用，避免过度优化&lt;/li&gt;
&lt;li&gt;不要忘记依赖项数组：确保依赖项数组正确，避免不必要的重新计算&lt;/li&gt;
&lt;li&gt;不要忘记返回值：确保返回值正确，避免不必要的重新计算
:::&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;示例代码&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;组件重新渲染的例子&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;ComponentWithSource
client:load
sourceCode={UseMemoDemoSource1}
language=&quot;tsx&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;UseMemoDemo1 client:load /&amp;gt;
&amp;lt;/ComponentWithSource&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;useMemo 缓存计算结果的例子&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;ComponentWithSource
client:load
sourceCode={UseMemoDemoSource2}
language=&quot;tsx&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;UseMemoDemo2 client:load /&amp;gt;
&amp;lt;/ComponentWithSource&amp;gt;&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>解决 Astro + Vite + Cesium 打包问题</title><link>https://sukha.dev/posts/cesium-build-issue</link><guid isPermaLink="true">https://sukha.dev/posts/cesium-build-issue</guid><description>记录在 Astro 项目中使用 vite-plugin-cesium 时遇到的打包问题及解决方案</description><pubDate>Fri, 12 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;问题描述&lt;/h2&gt;
&lt;p&gt;在使用 Astro + Vite + Cesium 构建博客项目时，发现了一个奇怪的问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;生产构建时&lt;/strong&gt;：Cesium 没有正确打包进 &lt;code&gt;dist&lt;/code&gt; 文件夹&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开发运行时&lt;/strong&gt;：一切正常，没有任何问题&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这导致生产环境可能无法正常运行，但开发环境却完全正常。&lt;/p&gt;
&lt;h2&gt;问题分析&lt;/h2&gt;
&lt;h3&gt;1. vite-plugin-cesium 的构建行为&lt;/h3&gt;
&lt;p&gt;通过查看 &lt;code&gt;vite-plugin-cesium&lt;/code&gt; 的源码，发现了问题的根源：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// vite-plugin-cesium 在构建时的行为
if (rebuildCesium) {
  // 重新构建 Cesium，打包进 bundle
  userConfig.build = {
    assetsInlineLimit: 0,
    chunkSizeWarningLimit: 5000,
    rollupOptions: {
      output: {
        intro: `window.CESIUM_BASE_URL = ${JSON.stringify(CESIUM_BASE_URL)};`
      }
    }
  }
} else {
  // 默认行为：将 Cesium 设为外部依赖
  userConfig.build = {
    rollupOptions: {
      external: [&quot;cesium&quot;],
      plugins: [externalGlobals({ cesium: &quot;Cesium&quot; })]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;问题所在&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;默认情况下（&lt;code&gt;rebuildCesium: false&lt;/code&gt;），Cesium 被配置为外部依赖&lt;/li&gt;
&lt;li&gt;这意味着 Cesium 不会被打包，而是期望从外部加载（如 CDN 或全局 script）&lt;/li&gt;
&lt;li&gt;但是 HTML 中没有引入 Cesium.js 的 script 标签&lt;/li&gt;
&lt;li&gt;静态资源（Assets、Workers、Widgets 等）也没有被复制到 dist 目录&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. Astro 构建流程的差异&lt;/h3&gt;
&lt;p&gt;Astro 的构建流程与纯 Vite 项目不同：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;vite-plugin-cesium&lt;/code&gt; 的 &lt;code&gt;transformIndexHtml&lt;/code&gt; hook 可能没有被正确执行&lt;/li&gt;
&lt;li&gt;&lt;code&gt;closeBundle&lt;/code&gt; hook 中的资源复制逻辑可能没有运行&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 开发模式为什么正常？&lt;/h3&gt;
&lt;p&gt;开发模式下正常是因为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vite 的 dev server 会从 &lt;code&gt;node_modules&lt;/code&gt; 直接加载 Cesium&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vite-plugin-cesium&lt;/code&gt; 的 &lt;code&gt;configureServer&lt;/code&gt; hook 会设置中间件来服务 Cesium 资源&lt;/li&gt;
&lt;li&gt;所以开发时一切正常，但构建后缺少这些资源&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;解决方案&lt;/h2&gt;
&lt;h3&gt;步骤一：创建自定义 Astro 集成&lt;/h3&gt;
&lt;p&gt;创建一个自定义的 Astro 集成来处理 Cesium 资源的复制和配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// src/integrations/cesium.ts
import type { AstroIntegration } from &apos;astro&apos;
import fs from &apos;fs/promises&apos;
import path from &apos;path&apos;

export function cesiumIntegration(): AstroIntegration {
  return {
    name: &apos;cesium-integration&apos;,
    hooks: {
      &apos;astro:build:done&apos;: async ({ dir }) =&amp;gt; {
        const cesiumBuildPath = path.join(
          process.cwd(),
          &apos;node_modules/cesium/Build/Cesium&apos;
        )
        const cesiumDistPath = path.join(dir.pathname, &apos;cesium&apos;)

        // 确保目标目录存在
        await fs.mkdir(cesiumDistPath, { recursive: true })

        // 复制 Cesium 的静态资源
        const resources = [&apos;Assets&apos;, &apos;ThirdParty&apos;, &apos;Workers&apos;, &apos;Widgets&apos;, &apos;Cesium.js&apos;]

        const copyRecursive = async (src: string, dest: string) =&amp;gt; {
          const stat = await fs.stat(src)
          if (stat.isDirectory()) {
            await fs.mkdir(dest, { recursive: true })
            const entries = await fs.readdir(src)
            for (const entry of entries) {
              await copyRecursive(path.join(src, entry), path.join(dest, entry))
            }
          } else {
            await fs.copyFile(src, dest)
          }
        }

        for (const resource of resources) {
          const src = path.join(cesiumBuildPath, resource)
          const dest = path.join(cesiumDistPath, resource)

          try {
            await fs.access(src)
            await copyRecursive(src, dest)
            console.log(`✓ Copied Cesium ${resource} to ${dest}`)
          } catch (err) {
            console.warn(
              `⚠ Skipped Cesium ${resource}: ${err instanceof Error ? err.message : &apos;unknown error&apos;}`
            )
          }
        }
      },
      &apos;astro:config:setup&apos;: ({ injectScript }) =&amp;gt; {
        // 注入 Cesium 的全局配置脚本
        injectScript(&apos;head-inline&apos;, `
          window.CESIUM_BASE_URL = &apos;/cesium/&apos;;
        `)
      },
    },
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;步骤二：创建 CesiumLoader 组件&lt;/h3&gt;
&lt;p&gt;创建一个组件来动态加载 Cesium.js：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
// src/components/CesiumLoader.astro
// CesiumLoader 组件：在需要 Cesium 的页面加载 Cesium.js
// 只在客户端加载，避免 SSR 问题
---

&amp;lt;script&amp;gt;
  // 检查是否已经加载了 Cesium
  if (typeof window !== &apos;undefined&apos; &amp;amp;&amp;amp; !window.Cesium) {
    // 动态加载 Cesium.js
    const script = document.createElement(&apos;script&apos;)
    script.src = &apos;/cesium/Cesium.js&apos;
    script.async = true
    script.onload = () =&amp;gt; {
      console.log(&apos;Cesium loaded successfully&apos;)
    }
    script.onerror = () =&amp;gt; {
      console.error(&apos;Failed to load Cesium.js&apos;)
    }
    document.head.appendChild(script)
  }
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;步骤三：更新配置文件&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;astro.config.mjs&lt;/code&gt; 中添加集成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { cesiumIntegration } from &apos;./src/integrations/cesium&apos;

export default defineConfig({
  // ... 其他配置
  integrations: [
    // ... 其他集成
    cesiumIntegration(),
  ],
  vite: {
    plugins: [tailwindcss(), cesium()],
    // ... 其他配置
  },
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在需要 Cesium 的页面中使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
title: &apos;init cesium with react&apos;
---
import CesiumLoader from &apos;~/components/CesiumLoader.astro&apos;
import InitMapDemo from &apos;~/components/mdx/InitMapDemo&apos;

&amp;lt;CesiumLoader /&amp;gt;
&amp;lt;InitMapDemo client:load id={&apos;fs&apos;}/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;开发模式问题修复&lt;/h2&gt;
&lt;p&gt;在解决打包问题后，还遇到了开发模式下的问题：&lt;/p&gt;
&lt;h3&gt;问题&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Vite 依赖优化缓存过期（504 Outdated Optimize Dep）&lt;/li&gt;
&lt;li&gt;React 运行在生产模式但死代码消除未应用&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;更新 &lt;code&gt;optimizeDeps&lt;/code&gt; 配置&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;vite: {
  optimizeDeps: {
    include: [&apos;cesium&apos;, &apos;clsx&apos;, &apos;react-syntax-highlighter&apos;],
    exclude: []
  },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;添加清理缓存的脚本&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;dev:clean&quot;: &quot;rm -rf node_modules/.vite &amp;amp;&amp;amp; astro dev --host&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;使用清理后的开发命令&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;npm run dev:clean
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;最终效果&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;✅ Cesium 资源正确复制到 &lt;code&gt;dist/cesium/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;✅ HTML 中正确注入 CesiumLoader 的 script 标签&lt;/li&gt;
&lt;li&gt;✅ &lt;code&gt;CESIUM_BASE_URL&lt;/code&gt; 正确设置为 &lt;code&gt;/cesium/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;✅ 开发模式依赖优化正常工作&lt;/li&gt;
&lt;li&gt;✅ 生产构建和开发运行都正常&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;这个问题的核心在于：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;插件适配问题&lt;/strong&gt;：&lt;code&gt;vite-plugin-cesium&lt;/code&gt; 主要针对纯 Vite 项目设计，在 Astro 中需要额外处理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;构建流程差异&lt;/strong&gt;：Astro 的构建流程与 Vite 不同，某些 hook 可能不会按预期执行&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开发/生产差异&lt;/strong&gt;：开发模式和生产模式的资源加载方式不同&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过创建自定义集成和组件，我们成功解决了这些问题，确保了 Cesium 在 Astro 项目中能够正常工作。&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>按年月归档但保持 `/posts/&lt;slug&gt;` 路由的实现笔记</title><link>https://sukha.dev/posts/posts-archive-year-month</link><guid isPermaLink="true">https://sukha.dev/posts/posts-archive-year-month</guid><description>记录如何让内容按年/月目录存放，同时前端路由仍为 /posts/&lt;slug&gt;。</description><pubDate>Fri, 12 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;背景与概念&lt;/h2&gt;
&lt;p&gt;目标：内容按 &lt;code&gt;src/content/posts/&amp;lt;年&amp;gt;/&amp;lt;月&amp;gt;/...&lt;/code&gt; 存放，但线上路由固定 &lt;code&gt;/posts/&amp;lt;slug&amp;gt;&lt;/code&gt;，不夹带目录。&lt;/p&gt;
&lt;p&gt;什么是 slug？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;URL 中简短、可读、URL 安全的标识（常用小写、连字符），如 &lt;code&gt;my-first-post&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;作用：让链接稳定可读，脱离目录耦合，便于 SSG/RSS/OG，且可控唯一性（文件名或 frontmatter）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;关键改动&lt;/h2&gt;
&lt;h3&gt;1）内容层统一 slug 生成（入口）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;位置：&lt;code&gt;src/content.config.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;规则：前置 frontmatter &lt;code&gt;slug&lt;/code&gt;；否则取路径末段，若是 &lt;code&gt;index&lt;/code&gt; 则回退上一段；最后 slug 化。&lt;/li&gt;
&lt;li&gt;结果：文件可放多级目录，但生成的 slug 始终是单段。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// src/content.config.ts（节选）
slug: ({ defaultSlug, data }) =&amp;gt; {
  const frontmatterSlug = (data as any).slug
  if (typeof frontmatterSlug === &apos;string&apos; &amp;amp;&amp;amp; frontmatterSlug.trim()) {
    return slugify(frontmatterSlug.trim())
  }
  const segments = defaultSlug.split(&apos;/&apos;).filter(Boolean)
  let candidate = segments.pop() || defaultSlug || &apos;post&apos;
  if (candidate.toLowerCase() === &apos;index&apos; &amp;amp;&amp;amp; segments.length &amp;gt; 0) {
    candidate = segments.pop()!
  }
  return slugify(candidate || &apos;post&apos;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2）工具函数统一出口（运行时兜底）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;位置：&lt;code&gt;src/utils.ts#getPostSlug&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;逻辑：如果 &lt;code&gt;post.slug&lt;/code&gt; 已是单段直接用；若包含 &lt;code&gt;/&lt;/code&gt; 或为空，则取 &lt;code&gt;post.id&lt;/code&gt; 末段（跳过 &lt;code&gt;index&lt;/code&gt;）再 slug 化。&lt;/li&gt;
&lt;li&gt;结果：任何入口都能拿到单段 slug，彻底避免 &lt;code&gt;/posts/2025/12/...&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// src/utils.ts（节选）
export function getPostSlug(post: CollectionEntry&amp;lt;&apos;posts&apos;&amp;gt;): string {
  const rawSlug = post.slug
  const hasPath = rawSlug?.includes(&apos;/&apos;)
  if (rawSlug &amp;amp;&amp;amp; !hasPath) return rawSlug

  const parts = post.id.split(&apos;/&apos;).filter(Boolean)
  let candidate = parts.pop() || post.id
  if (candidate.toLowerCase() === &apos;index&apos; &amp;amp;&amp;amp; parts.length &amp;gt; 0) {
    candidate = parts.pop()!
  }
  return slug(candidate)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3）路由/链接全面调用 &lt;code&gt;getPostSlug&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;详情页路径与上下篇：&lt;code&gt;src/pages/posts/[slug].astro&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;列表与时间线：&lt;code&gt;src/components/PostPreview.astro&lt;/code&gt;、&lt;code&gt;src/components/TimelineSection.astro&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;RSS / 社交卡片：&lt;code&gt;src/pages/rss.xml.ts&lt;/code&gt;、&lt;code&gt;src/pages/social-cards/[slug].png.ts&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- src/pages/posts/[slug].astro（节选） --&amp;gt;
export const getStaticPaths = (async () =&amp;gt; {
  const posts = await getSortedPosts()
  return posts.map((post) =&amp;gt; {
    const slug = getPostSlug(post)
    const { prev, next } = getPostSequenceContext(post, posts)
    return { params: { slug }, props: { post, prev, next, slug } }
  })
}) satisfies GetStaticPaths
...
&amp;lt;h1 id={slug} class=&quot;mb-4 text-[1.75rem] text-heading1 font-semibold&quot;&amp;gt;
  # {postData.title}
&amp;lt;/h1&amp;gt;
...
{nextPostInSeries &amp;amp;&amp;amp; (
  &amp;lt;a href={`/posts/${getPostSlug(nextPostInSeries)}`} class=&quot;button ...&quot;&amp;gt;
    &amp;lt;span&amp;gt;Next: {nextPostInSeries.data.title}&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
)}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- src/components/PostPreview.astro（节选） --&amp;gt;
const slug = getPostSlug(post)
const samePage = Astro.url.pathname === `/posts/${slug}`
const articleLink = samePage ? `#${slug}` : `/posts/${slug}`
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- src/components/TimelineSection.astro（节选） --&amp;gt;
&amp;lt;a
  href={`/posts/${getPostSlug(post)}`}
  class=&quot;block text-foreground/90 hover:text-accent transition-colors group&quot;
&amp;gt;
  ...
&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// src/pages/rss.xml.ts（节选）
items: posts.map((post) =&amp;gt; ({
  title: post.data.title,
  pubDate: post.data.published,
  link: `/posts/${getPostSlug(post)}`,
}))
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// src/pages/social-cards/[slug].png.ts（节选）
return posts.map((post) =&amp;gt; ({
  params: { slug: getPostSlug(post) },
  props: {
    pubDate: post.data.published ? dateString(post.data.published) : undefined,
    title: post.data.title,
    author: post.data.author || siteConfig.author,
  },
}))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实现原理（代码路径）&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;内容层 slug 压平&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;src/content.config.ts&lt;/code&gt;：为 posts 集合定义 slug 规则，frontmatter 优先；否则取路径末段（跳过 &lt;code&gt;index&lt;/code&gt;）并 slug 化，确保得到单段 slug。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;统一 slug 出口&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;src/utils.ts#getPostSlug&lt;/code&gt;：如果 &lt;code&gt;post.slug&lt;/code&gt; 自带多级路径或为空，则用 &lt;code&gt;post.id&lt;/code&gt; 的末段（跳过 &lt;code&gt;index&lt;/code&gt;）再 slug 化，杜绝 &lt;code&gt;/posts/2025/12/...&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;所有路由与链接只用单段 slug&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;详情页静态路径/上下篇：&lt;code&gt;src/pages/posts/[slug].astro&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;列表与时间线：&lt;code&gt;src/components/PostPreview.astro&lt;/code&gt;、&lt;code&gt;src/components/TimelineSection.astro&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;RSS / 社交卡片：&lt;code&gt;src/pages/rss.xml.ts&lt;/code&gt;、&lt;code&gt;src/pages/social-cards/[slug].png.ts&lt;/code&gt;&lt;br /&gt;
以上都调用 &lt;code&gt;getPostSlug&lt;/code&gt;，保证跳转与生成资源时路由为 &lt;code&gt;/posts/&amp;lt;slug&amp;gt;&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cesium 插件简化&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;astro.config.mjs&lt;/code&gt; 移除多余的 &lt;code&gt;vite-plugin-cesium&lt;/code&gt;，仅保留自定义集成，避免构建期路径被拼成重复盘符。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;怎么用&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;放文档：&lt;code&gt;src/content/posts/&amp;lt;年&amp;gt;/&amp;lt;月&amp;gt;/&lt;/code&gt;，文件名默认决定 slug；若文件名为 &lt;code&gt;index.md(x)&lt;/code&gt;，会回退用父目录名。&lt;/li&gt;
&lt;li&gt;想自定义：frontmatter 写 &lt;code&gt;slug: your-slug&lt;/code&gt;，访问 &lt;code&gt;/posts/your-slug&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;验证：&lt;code&gt;npm run dev&lt;/code&gt; 或 &lt;code&gt;npm run build&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;注意&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;确保 slug 唯一（不同目录同名会冲突）。&lt;/li&gt;
&lt;li&gt;若路由带目录层级，检查自定义 &lt;code&gt;slug&lt;/code&gt; 是否含 &lt;code&gt;/&lt;/code&gt;，或文件名是否重复用 &lt;code&gt;index&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;RSS、社交卡片、时间线、预览都走 &lt;code&gt;getPostSlug&lt;/code&gt;，无需额外配置。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>sukha</author></item><item><title>react-component</title><link>https://sukha.dev/posts/react-learn-component</link><guid isPermaLink="true">https://sukha.dev/posts/react-learn-component</guid><description>component.</description><pubDate>Wed, 17 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import ComponentWithSource from &apos;~/components/mdx/ComponentWithSource&apos;
import ComponentDemo from &apos;~/components/mdx/component/ComponentDemo&apos;
import ComponentDemoSource from &apos;~/components/mdx/component/ComponentDemo.tsx?raw&apos;
import ComponentConnect from &apos;~/components/mdx/component/ComponentConnect&apos;
import ComponentConnectSource from &apos;~/components/mdx/component/ComponentConnect.tsx?raw&apos;&lt;/p&gt;
&lt;h2&gt;react组件的定义&lt;/h2&gt;
&lt;p&gt;:::note
React 组件是 JavaScript 函数的封装，接收输入的数据然后渲染输出，本质上是一个纯函数。
下面我们来实现一个信息框
:::&lt;/p&gt;
&lt;p&gt;&amp;lt;ComponentWithSource
client:load
sourceCode={ComponentDemoSource}
language=&quot;tsx&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;ComponentDemo client:load /&amp;gt;
&amp;lt;/ComponentWithSource&amp;gt;&lt;/p&gt;
&lt;h2&gt;组件通信&lt;/h2&gt;
&lt;p&gt;:::note
组件通信分父子通信和兄弟通信
:::&lt;/p&gt;
&lt;p&gt;&amp;lt;ComponentWithSource
client:load
sourceCode={ComponentConnectSource}
language=&quot;tsx&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;ComponentConnect client:load /&amp;gt;
&amp;lt;/ComponentWithSource&amp;gt;&lt;/p&gt;
&lt;h2&gt;受控组件与非受控组件&lt;/h2&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;:::important&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;组件的值由父组件控制，称为受控组件&lt;/li&gt;
&lt;li&gt;组件的值不由父组件控制，称为非受控组件
:::&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;使用场景&lt;/h3&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;受控组件适合需要实时响应用户输入的场景&lt;/li&gt;
&lt;li&gt;非受控组件适合需要手动控制组件值的场景
:::&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><author>sukha</author></item><item><title>react-async-component</title><link>https://sukha.dev/posts/react-learn-async-component</link><guid isPermaLink="true">https://sukha.dev/posts/react-learn-async-component</guid><description>react-async-component.</description><pubDate>Fri, 19 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import ComponentWithSource from &apos;~/components/mdx/ComponentWithSource&apos;
import AsyncCard from &apos;~/components/mdx/component/Async/Card&apos;
import AsyncCardCode from &apos;~/components/mdx/component/Async/Card/index.tsx?raw&apos;&lt;/p&gt;
&lt;h2&gt;异步组件&lt;/h2&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;:::important
异步组件通过 &lt;code&gt;React.lazy()&lt;/code&gt; 和 &lt;code&gt;Suspense&lt;/code&gt; 实现代码分割和按需加载，用于延迟加载组件代码，减少初始包大小。
:::&lt;/p&gt;
&lt;h3&gt;使用场景&lt;/h3&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;代码分割&lt;/strong&gt;：减少初始包大小，提升首屏加载速度&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;路由懒加载&lt;/strong&gt;：按需加载页面组件，优化路由性能&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大型库延迟加载&lt;/strong&gt;：延迟加载体积较大的第三方库（如编辑器、图表库、3D库）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;条件渲染组件&lt;/strong&gt;：只在需要时才加载的组件（如管理员面板、复杂模态框）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提升首屏性能&lt;/strong&gt;：将非关键组件延迟加载，优先渲染核心内容
:::&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;必须配合 &lt;code&gt;Suspense&lt;/code&gt; 使用：异步组件必须包裹在 &lt;code&gt;Suspense&lt;/code&gt; 组件中&lt;/li&gt;
&lt;li&gt;提供合适的 fallback：需要为 &lt;code&gt;Suspense&lt;/code&gt; 提供加载状态的 fallback&lt;/li&gt;
&lt;li&gt;仅支持默认导出：&lt;code&gt;React.lazy()&lt;/code&gt; 只能加载使用默认导出的组件&lt;/li&gt;
&lt;li&gt;SSR 需要额外处理：服务端渲染时需要使用 &lt;code&gt;React.lazy()&lt;/code&gt; 的替代方案&lt;/li&gt;
&lt;li&gt;错误边界：建议配合错误边界（Error Boundary）处理加载失败的情况
:::&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;ComponentWithSource
client:load
sourceCode={AsyncCardCode}
language=&quot;tsx&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;AsyncCard client:only=&quot;react&quot; /&amp;gt;
&amp;lt;/ComponentWithSource&amp;gt;&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>react-learn-hoc</title><link>https://sukha.dev/posts/react-learn-hoc</link><guid isPermaLink="true">https://sukha.dev/posts/react-learn-hoc</guid><description>learn react hoc</description><pubDate>Fri, 19 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import ComponentWithSource from &apos;~/components/mdx/ComponentWithSource&apos;
import AuthCom from &apos;~/components/mdx/component/HOC/AuthCom&apos;
import AuthComCode from &apos;~/components/mdx/component/HOC/AuthCom.tsx?raw&apos;
import Tracker from &apos;~/components/mdx/component/HOC/Tracker&apos;
import TrackerCode from &apos;~/components/mdx/component/HOC/Tracker.tsx?raw&apos;&lt;/p&gt;
&lt;h1&gt;React 高阶组件（HOC）详解&lt;/h1&gt;
&lt;h2&gt;什么是高阶组件？&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;高阶组件（Higher-Order Component，HOC）&lt;/strong&gt; 是 React 中用于复用组件逻辑的一种高级技巧。HOC 本质上是一个函数，它接收一个组件作为参数，并返回一个新的增强组件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const EnhancedComponent = higherOrderComponent(WrappedComponent)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;基础示例&lt;/h2&gt;
&lt;h3&gt;1. 简单的 HOC - 添加日志功能&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// withLogger.js - 为组件添加日志功能
function withLogger(WrappedComponent) {
  return function LoggerComponent(props) {
    console.log(&apos;组件渲染了，props:&apos;, props)

    return &amp;lt;WrappedComponent {...props} /&amp;gt;
  }
}

// 使用
function MyButton({ onClick, label }) {
  return &amp;lt;button onClick={onClick}&amp;gt;{label}&amp;lt;/button&amp;gt;
}

const MyButtonWithLogger = withLogger(MyButton)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 实用的 HOC - 权限控制&lt;/h3&gt;
&lt;p&gt;&amp;lt;ComponentWithSource client:load sourceCode={AuthComCode} language=&quot;tsx&quot;&amp;gt;
&amp;lt;AuthCom client:load /&amp;gt;
&amp;lt;/ComponentWithSource&amp;gt;&lt;/p&gt;
&lt;h3&gt;3. 注入 Props 的 HOC&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// withUserData.js
function withUserData(WrappedComponent) {
  return function UserDataComponent(props) {
    const [userData, setUserData] = React.useState(null)
    const [loading, setLoading] = React.useState(true)

    React.useEffect(() =&amp;gt; {
      fetchUserData().then((data) =&amp;gt; {
        setUserData(data)
        setLoading(false)
      })
    }, [])

    // 注入额外的 props
    return &amp;lt;WrappedComponent {...props} userData={userData} isLoading={loading} /&amp;gt;
  }
}

// 使用
function UserProfile({ userData, isLoading, customProp }) {
  if (isLoading) return &amp;lt;div&amp;gt;加载中...&amp;lt;/div&amp;gt;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{userData.name}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;{customProp}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

const EnhancedUserProfile = withUserData(UserProfile)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;常见应用场景&lt;/h2&gt;
&lt;h3&gt;1. 条件渲染&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function withConditionalRender(WrappedComponent, condition) {
  return function ConditionalComponent(props) {
    if (!condition(props)) {
      return null // 或者返回占位符
    }
    return &amp;lt;WrappedComponent {...props} /&amp;gt;
  }
}

// 使用
const VisibleOnlyIfAdmin = withConditionalRender(
  AdminPanel,
  (props) =&amp;gt; props.user.role === &apos;admin&apos;,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 数据获取和订阅&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function withSubscription(WrappedComponent, selectData) {
  return class extends React.Component {
    constructor(props) {
      super(props)
      this.state = {
        data: selectData(DataSource, props),
      }
    }

    componentDidMount() {
      DataSource.addChangeListener(this.handleChange)
    }

    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange)
    }

    handleChange = () =&amp;gt; {
      this.setState({
        data: selectData(DataSource, this.props),
      })
    }

    render() {
      return &amp;lt;WrappedComponent data={this.state.data} {...this.props} /&amp;gt;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 组合多个 HOC&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 多个 HOC 组合使用
const enhance = compose(withAuth, withLogger, withUserData)

const EnhancedComponent = enhance(MyComponent)

// 或者链式调用
const EnhancedComponent = withAuth(withLogger(withUserData(MyComponent)))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 追踪事件&lt;/h3&gt;
&lt;p&gt;&amp;lt;ComponentWithSource client:load sourceCode={TrackerCode} language=&quot;tsx&quot;&amp;gt;
&amp;lt;Tracker client:load /&amp;gt;
&amp;lt;/ComponentWithSource&amp;gt;&lt;/p&gt;
&lt;h2&gt;最佳实践和注意事项&lt;/h2&gt;
&lt;h3&gt;✅ 该做的：&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;透传不相关的 Props&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;function withSomething(WrappedComponent) {
  return function (props) {
    const extraProp = &apos;value&apos;
    // 将所有 props 传递给被包装组件
    return &amp;lt;WrappedComponent extraProp={extraProp} {...props} /&amp;gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;最大化组合性 - 使用 displayName&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;function withSomething(WrappedComponent) {
  function WithSomething(props) {
    return &amp;lt;WrappedComponent {...props} /&amp;gt;
  }

  // 设置 displayName 便于调试
  WithSomething.displayName = `WithSomething(${getDisplayName(WrappedComponent)})`

  return WithSomething
}

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || &apos;Component&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;❌ 不要做的：&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;不要在 render 方法中使用 HOC&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// ❌ 错误
render() {
  // 每次渲染都会创建新组件，导致性能问题
  const EnhancedComponent = withSomething(MyComponent);
  return &amp;lt;EnhancedComponent /&amp;gt;;
}

// ✅ 正确
const EnhancedComponent = withSomething(MyComponent);
class App extends React.Component {
  render() {
    return &amp;lt;EnhancedComponent /&amp;gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;不要修改原始组件&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// ❌ 错误
function withMutation(WrappedComponent) {
  WrappedComponent.prototype.componentDidUpdate = function () {
    // 修改了原始组件
  }
  return WrappedComponent
}

// ✅ 正确 - 使用组合
function withEnhancement(WrappedComponent) {
  return class extends React.Component {
    componentDidUpdate() {
      // 新增行为
    }
    render() {
      return &amp;lt;WrappedComponent {...this.props} /&amp;gt;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;静态方法需要手动复制&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;import hoistNonReactStatics from &apos;hoist-non-react-statics&apos;

function withSomething(WrappedComponent) {
  class WithSomething extends React.Component {
    /* ... */
  }

  // 复制静态方法
  hoistNonReactStatics(WithSomething, WrappedComponent)

  return WithSomething
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;HOC vs Hooks&lt;/h2&gt;
&lt;p&gt;现代 React 开发中，Hooks（特别是自定义 Hooks）在很多场景下可以替代 HOC：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// HOC 方式
const EnhancedComponent = withUserData(MyComponent)

// Hooks 方式 (更推荐)
function MyComponent() {
  const userData = useUserData() // 自定义 Hook
  return &amp;lt;div&amp;gt;{userData.name}&amp;lt;/div&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Hooks 的优势：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;更简洁的代码&lt;/li&gt;
&lt;li&gt;避免&quot;包装地狱&quot;&lt;/li&gt;
&lt;li&gt;更好的 TypeScript 支持&lt;/li&gt;
&lt;li&gt;逻辑更容易追踪&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>sukha</author></item><item><title>styled-components</title><link>https://sukha.dev/posts/styled-components</link><guid isPermaLink="true">https://sukha.dev/posts/styled-components</guid><description>styled-components.</description><pubDate>Sat, 20 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import ComponentWithSource from &apos;~/components/mdx/ComponentWithSource&apos;
import StyledComponents from &apos;~/components/mdx/component/StyledComponents&apos;
import StyledComponentsSource from &apos;~/components/mdx/component/StyledComponents/index.tsx?raw&apos;&lt;/p&gt;
&lt;h2&gt;styled-components&lt;/h2&gt;
&lt;p&gt;:::note
styled-components 是一个用于 React 的样式组件库，它允许你使用 CSS-in-JS 的方式来编写样式。&lt;/p&gt;
&lt;p&gt;它有以下特点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 CSS-in-JS 的方式来编写样式&lt;/li&gt;
&lt;li&gt;支持组件化&lt;/li&gt;
&lt;li&gt;支持主题化&lt;/li&gt;
&lt;li&gt;支持响应式&lt;/li&gt;
&lt;li&gt;支持动画&lt;/li&gt;
&lt;li&gt;与tailwindcss兼容
:::&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;ComponentWithSource
client:load
sourceCode={StyledComponentsSource}
language=&quot;tsx&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;StyledComponents client:load /&amp;gt;
&amp;lt;/ComponentWithSource&amp;gt;&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>react-router-action</title><link>https://sukha.dev/posts/learn-react-router-action</link><guid isPermaLink="true">https://sukha.dev/posts/learn-react-router-action</guid><description>learn react router action</description><pubDate>Sun, 21 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note
&lt;em&gt;&lt;strong&gt;React Router v7: &lt;code&gt;action&lt;/code&gt; 使用指南&lt;/strong&gt;&lt;/em&gt;&lt;br /&gt;
&lt;code&gt;action&lt;/code&gt; 是 React Router v7（及 Remix）中用于在组件中处理表单提交和数据提交的核心 Hook。它与 &lt;code&gt;loader&lt;/code&gt; (或 &lt;code&gt;clientLoader&lt;/code&gt;) 函数配合使用，实现了“数据加载与 UI 渲染分离”的模式。
:::&lt;/p&gt;
&lt;h2&gt;核心概念&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Action&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;路由级别的异步函数，用于处理数据提交。&lt;/li&gt;
&lt;li&gt;在组件提交表单（Form）后执行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动重新验证 (Revalidation)&lt;/strong&gt;: Action 执行成功后，React Router 会&lt;strong&gt;自动重新运行页面上所有的 Loader&lt;/strong&gt;，确保 UI 显示最新的数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;命名规则 (重要)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SPA 模式 (&lt;code&gt;ssr: false&lt;/code&gt;)&lt;/strong&gt;: 必须导出为 &lt;code&gt;clientAction&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSR 模式 (&lt;code&gt;ssr: true&lt;/code&gt;)&lt;/strong&gt;: 服务端处理导出 &lt;code&gt;action&lt;/code&gt;，客户端处理导出 &lt;code&gt;clientAction&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;基本用法&lt;/h2&gt;
&lt;h3&gt;1. 定义 Action&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import type { ActionFunctionArgs } from &quot;react-router&quot;;
import { redirect } from &quot;react-router&quot;;

// SPA 模式下使用 clientAction
export async function clientAction({ request, params }: ActionFunctionArgs) {
  // 1. 获取表单数据
  const formData = await request.formData();
  const title = formData.get(&quot;title&quot;);
  
  // 2. 数据校验
  if (typeof title !== &quot;string&quot; || title.length === 0) {
    return { error: &quot;标题不能为空&quot; };
  }

  // 3. 执行副作用 (API 调用)
  await createPost({ title });

  // 4. 返回结果或重定向
  return redirect(&quot;/posts&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 触发 Action&lt;/h3&gt;
&lt;h4&gt;方式 A: &lt;code&gt;&amp;lt;Form&amp;gt;&lt;/code&gt; 组件 (全页跳转)&lt;/h4&gt;
&lt;p&gt;适用于普通的页面级表单提交。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Form, useActionData } from &quot;react-router&quot;;

export default function NewPost() {
  const actionData = useActionData&amp;lt;typeof clientAction&amp;gt;();

  return (
    &amp;lt;Form method=&quot;post&quot;&amp;gt;
      &amp;lt;input name=&quot;title&quot; /&amp;gt;
      {actionData?.error &amp;amp;&amp;amp; &amp;lt;p&amp;gt;{actionData.error}&amp;lt;/p&amp;gt;}
      &amp;lt;button type=&quot;submit&quot;&amp;gt;提交&amp;lt;/button&amp;gt;
    &amp;lt;/Form&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方式 B: &lt;code&gt;useFetcher&lt;/code&gt; (局部更新)&lt;/h4&gt;
&lt;p&gt;适用于不进行页面跳转的操作（如点赞、加入购物车、弹窗表单）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useFetcher } from &quot;react-router&quot;;

export default function LikeButton() {
  const fetcher = useFetcher&amp;lt;typeof clientAction&amp;gt;();
  const isSubmitting = fetcher.state === &quot;submitting&quot;;

  return (
    &amp;lt;fetcher.Form method=&quot;post&quot; action=&quot;/like&quot;&amp;gt;
      &amp;lt;button type=&quot;submit&quot; disabled={isSubmitting}&amp;gt;
        {isSubmitting ? &quot;点赞中...&quot; : &quot;点赞&quot;}
      &amp;lt;/button&amp;gt;
    &amp;lt;/fetcher.Form&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方式 C: 编程式提交 (Antd 等第三方库集成)&lt;/h4&gt;
&lt;p&gt;当使用 Antd &lt;code&gt;Form&lt;/code&gt; 的 &lt;code&gt;onFinish&lt;/code&gt; 时，手动调用 &lt;code&gt;fetcher.submit&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const fetcher = useFetcher();

const onFinish = (values) =&amp;gt; {
  // 提交数据到 Action
  fetcher.submit(values, { method: &quot;post&quot; });
};

// 获取 Action 返回值
const result = fetcher.data;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;常见坑点 (Pitfalls) 🚨&lt;/h2&gt;
&lt;h3&gt;1. &lt;strong&gt;手动调用 Action 函数&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;错误&lt;/strong&gt;: &lt;code&gt;const res = await clientAction(...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;后果&lt;/strong&gt;: 失去了 React Router 的核心优势（加载状态管理、自动重新验证 Loader）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;正确&lt;/strong&gt;: 必须通过 &lt;code&gt;&amp;lt;Form&amp;gt;&lt;/code&gt; 或 &lt;code&gt;fetcher.submit&lt;/code&gt; 触发。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. &lt;strong&gt;SPA 模式下的命名&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;错误&lt;/strong&gt;: 在 &lt;code&gt;ssr: false&lt;/code&gt; 项目中导出 &lt;code&gt;export async function action(...)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;后果&lt;/strong&gt;: 报错 &lt;code&gt;invalid route export&lt;/code&gt; 或 Action 不生效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;正确&lt;/strong&gt;: 必须命名为 &lt;code&gt;clientAction&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. &lt;strong&gt;FormData 与 JSON 的混淆&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;现象&lt;/strong&gt;: 在 &lt;code&gt;clientAction&lt;/code&gt; 中 &lt;code&gt;await request.json()&lt;/code&gt; 报错。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原因&lt;/strong&gt;: 默认 &lt;code&gt;&amp;lt;Form&amp;gt;&lt;/code&gt; 和 &lt;code&gt;fetcher.submit&lt;/code&gt; 提交的是 &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt; 或 &lt;code&gt;multipart/form-data&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解决&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;标准做法是使用 &lt;code&gt;request.formData()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果必须发 JSON，需在 submit 时指定 &lt;code&gt;encType&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;fetcher.submit(data, { method: &quot;post&quot;, encType: &quot;application/json&quot; });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在 Action 中使用 &lt;code&gt;request.json()&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;4. &lt;strong&gt;第三方 UI 库 (Antd) 的嵌套对象&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;现象&lt;/strong&gt;: Antd Form 返回 &lt;code&gt;{ user: { name: &quot;xxx&quot; } }&lt;/code&gt;，直接传给 &lt;code&gt;fetcher.submit&lt;/code&gt; 后，在 Action 中 &lt;code&gt;formData.get(&apos;user.name&apos;)&lt;/code&gt; 取不到值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原因&lt;/strong&gt;: &lt;code&gt;fetcher.submit&lt;/code&gt; 默认会将对象扁平化为 FormData。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解决&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;方案一：手动扁平化键名 &lt;code&gt;{ &quot;user.name&quot;: values.user.name }&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;方案二：使用 &lt;code&gt;encType: &quot;application/json&quot;&lt;/code&gt; 提交 JSON 格式。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. &lt;strong&gt;Action 不会重新渲染当前组件的 &lt;code&gt;Loader&lt;/code&gt; 数据？&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;机制&lt;/strong&gt;: 默认情况下，Action 成功后会重新验证&lt;strong&gt;所有&lt;/strong&gt;活跃路由的 Loader。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;例外&lt;/strong&gt;: 如果你在 Action 中返回了非 2xx/3xx 状态码（比如 400校验错误），Loader &lt;strong&gt;不会&lt;/strong&gt;重新运行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑点&lt;/strong&gt;: 如果你想在校验错误时也刷新某些数据，需要注意这一点。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6. &lt;strong&gt;&lt;code&gt;fetcher.data&lt;/code&gt; vs &lt;code&gt;useActionData&lt;/code&gt;&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;useActionData&lt;/code&gt;&lt;/strong&gt;: 获取&lt;strong&gt;当前页面&lt;/strong&gt; URL 对应的 Action 返回值（通常用于 &lt;code&gt;&amp;lt;Form&amp;gt;&lt;/code&gt; 提交）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;fetcher.data&lt;/code&gt;&lt;/strong&gt;: 获取&lt;strong&gt;特定 fetcher&lt;/strong&gt; 实例对应的 Action 返回值（用于局部交互）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;混用后果&lt;/strong&gt;: 在 &lt;code&gt;useFetcher&lt;/code&gt; 提交后去读 &lt;code&gt;useActionData&lt;/code&gt; 是拿不到结果的。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>sukha</author></item><item><title>useLoaderData</title><link>https://sukha.dev/posts/learn-react-router-useloaderdata</link><guid isPermaLink="true">https://sukha.dev/posts/learn-react-router-useloaderdata</guid><description>learn react router useLoaderData</description><pubDate>Sun, 21 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note
&lt;em&gt;&lt;strong&gt;React Router v7: &lt;code&gt;useLoaderData&lt;/code&gt; 使用指南&lt;/strong&gt;&lt;/em&gt;&lt;br /&gt;
&lt;code&gt;useLoaderData&lt;/code&gt; 是 React Router v7（及 Remix）中用于在组件中获取路由数据的核心 Hook。它与 &lt;code&gt;loader&lt;/code&gt; (或 &lt;code&gt;clientLoader&lt;/code&gt;) 函数配合使用，实现了“数据加载与 UI 渲染分离”的模式。
:::&lt;/p&gt;
&lt;h2&gt;核心概念&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Loader (数据加载器)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在路由级别定义的&lt;strong&gt;异步函数&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;在组件渲染&lt;strong&gt;之前&lt;/strong&gt;执行。&lt;/li&gt;
&lt;li&gt;用于获取组件所需的数据（API 请求、数据库查询等）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;命名规则&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;SSR 模式或服务端运行: 使用 &lt;code&gt;loader&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;SPA 模式 (ssr: false) 或仅客户端运行: 使用 &lt;code&gt;clientLoader&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;useLoaderData (数据获取 Hook)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在组件内部使用的 Hook。&lt;/li&gt;
&lt;li&gt;用于获取对应 Loader 函数返回的数据。&lt;/li&gt;
&lt;li&gt;提供完整的类型推断支持。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;基本用法&lt;/h2&gt;
&lt;h3&gt;1. 定义 Loader 函数&lt;/h3&gt;
&lt;p&gt;在你的路由组件文件中（通常是页面级组件），导出名为 &lt;code&gt;loader&lt;/code&gt; (SSR) 或 &lt;code&gt;clientLoader&lt;/code&gt; (SPA) 的异步函数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SPA 模式 (ssr: false) 示例:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// app/routes/blog-post.tsx
import type { LoaderFunctionArgs } from &quot;react-router&quot;;

// 注意：在 SPA 模式下必须命名为 clientLoader
export async function clientLoader({ params, request }: LoaderFunctionArgs) {
  // 1. 获取 URL 参数
  const postId = params.postId;

  // 2. 发起数据请求 (可以是 fetch, 数据库查询等)
  const response = await fetch(`https://api.example.com/posts/${postId}`);
  
  if (!response.ok) {
    throw new Response(&quot;Not Found&quot;, { status: 404 });
  }

  const post = await response.json();

  // 3. 返回数据
  return { post };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 在组件中使用数据&lt;/h3&gt;
&lt;p&gt;在同一个文件中，使用 &lt;code&gt;useLoaderData&lt;/code&gt; 获取数据。为了获得类型提示，可以使用 &lt;code&gt;typeof clientLoader&lt;/code&gt; 泛型。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useLoaderData } from &quot;react-router&quot;;
// 引入上面定义的 clientLoader 类型用于推断 (SPA 模式)
// 如果是 SSR 模式，则使用 typeof loader
import type { clientLoader } from &quot;./blog-post&quot;; 

export default function BlogPost() {
  // data 将自动推断为 { post: ... } 类型
  const data = useLoaderData&amp;lt;typeof clientLoader&amp;gt;();

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{data.post.title}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;{data.post.content}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;进阶场景&lt;/h2&gt;
&lt;h3&gt;1. 获取 URL 参数和查询参数 (Query Params)&lt;/h3&gt;
&lt;p&gt;Loader 函数接收一个参数对象 &lt;code&gt;args&lt;/code&gt;，包含 &lt;code&gt;params&lt;/code&gt; 和 &lt;code&gt;request&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export async function clientLoader({ request, params }: LoaderFunctionArgs) {
  // 获取动态路由参数，例如 /users/:userId
  const userId = params.userId;

  // 获取 URL 查询参数，例如 /search?q=react
  const url = new URL(request.url);
  const searchTerm = url.searchParams.get(&quot;q&quot;);

  return { userId, searchTerm };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 处理重定向&lt;/h3&gt;
&lt;p&gt;如果你需要根据逻辑重定向用户（例如未登录用户跳转到登录页），可以在 Loader 中直接 &lt;code&gt;throw&lt;/code&gt; 或 &lt;code&gt;return&lt;/code&gt; 一个 &lt;code&gt;redirect&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { redirect } from &quot;react-router&quot;;

export async function clientLoader() {
  const user = await getUser();
  
  if (!user) {
    // 中断加载并跳转
    throw redirect(&quot;/login&quot;);
  }
  
  return { user };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 错误处理&lt;/h3&gt;
&lt;p&gt;如果 Loader 执行过程中抛出错误（&lt;code&gt;throw new Error(...)&lt;/code&gt; 或 &lt;code&gt;throw new Response(...)&lt;/code&gt;），React Router 会捕获它并渲染最近的 &lt;code&gt;ErrorBoundary&lt;/code&gt; 组件，而不是正常的页面组件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export async function clientLoader() {
  const res = await fetch(&quot;/api/data&quot;);
  if (res.status === 404) {
    throw new Response(&quot;Not Found&quot;, { status: 404 });
  }
  return res.json();
}
// 对应的错误边界组件
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
  return &amp;lt;div&amp;gt;出错了！&amp;lt;/div&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;重要提示&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;阻塞渲染&lt;/strong&gt;: 默认情况下，React Router 会等待 Loader 执行完毕后才渲染组件。如果 Loader 很慢，页面切换会有明显的延迟。建议在父级布局中使用 &lt;code&gt;useNavigation&lt;/code&gt; 显示全局 Loading 状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSR vs SPA&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SPA 模式 (&lt;code&gt;ssr: false&lt;/code&gt;)&lt;/strong&gt;: 必须使用 &lt;code&gt;export function clientLoader&lt;/code&gt;。它始终在浏览器中运行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSR 模式 (&lt;code&gt;ssr: true&lt;/code&gt;)&lt;/strong&gt;: 使用 &lt;code&gt;export function loader&lt;/code&gt; (服务端运行) 和可选的 &lt;code&gt;export function clientLoader&lt;/code&gt; (客户端运行)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;序列化&lt;/strong&gt;: 返回的数据必须是可序列化的（JSON 兼容），不能传递函数或类实例（除非使用特定的库如 &lt;code&gt;superjson&lt;/code&gt;，但这通常不建议）。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;useLoaderData&lt;/code&gt; 模式消除了传统的 &lt;code&gt;useEffect&lt;/code&gt; + &lt;code&gt;useState&lt;/code&gt; + &lt;code&gt;fetch&lt;/code&gt; 的样板代码，解决了“渲染后获取”导致的布局抖动（Waterfall）问题，是现代 React Router 开发的核心实践。&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>react-router-v7-params</title><link>https://sukha.dev/posts/learn-react-router-v7-params</link><guid isPermaLink="true">https://sukha.dev/posts/learn-react-router-v7-params</guid><description>learn react router v7 params</description><pubDate>Sun, 21 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note
本文档总结了 React Router 中常用的路由传参方式，基于 React Router v7。
:::&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 路径参数（Path Params）&lt;/h2&gt;
&lt;h3&gt;1.1 定义方式&lt;/h3&gt;
&lt;p&gt;在路由配置中使用 &lt;code&gt;:参数名&lt;/code&gt; 的形式定义路径参数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// app/routes.ts
{
  path: &apos;test/:id&apos;,
  file: &apos;./pages/Test/index.tsx&apos;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 传递参数&lt;/h3&gt;
&lt;h4&gt;方式一：直接拼接 URL&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 使用 navigate
navigate(&apos;/index/test/123&apos;)

// 使用 Link
&amp;lt;Link to=&quot;/index/test/123&quot;&amp;gt;跳转&amp;lt;/Link&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方式二：使用对象形式（推荐）&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 使用 navigate
navigate({
  pathname: &apos;/index/test&apos;,
  params: { id: &apos;123&apos; }
})

// 使用 Link
&amp;lt;Link to={{ pathname: &apos;/index/test&apos;, params: { id: &apos;123&apos; } }}&amp;gt;跳转&amp;lt;/Link&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3 接收参数&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;useParams()&lt;/code&gt; Hook 获取路径参数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useParams } from &apos;react-router&apos;

export default function Test() {
  const { id } = useParams()
  return &amp;lt;div&amp;gt;Test Params: {id}&amp;lt;/div&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.4 特点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;✅ 参数直接显示在 URL 路径中&lt;/li&gt;
&lt;li&gt;✅ 刷新页面参数不会丢失&lt;/li&gt;
&lt;li&gt;✅ 可以分享带参数的 URL&lt;/li&gt;
&lt;li&gt;✅ 适合必填参数&lt;/li&gt;
&lt;li&gt;⚠️ URL 路径会变长&lt;/li&gt;
&lt;li&gt;⚠️ 参数类型只能是字符串&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.5 项目示例&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;路由配置&lt;/strong&gt; (&lt;code&gt;app/routes.ts&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  path: &apos;test/:id&apos;,
  file: &apos;./pages/Test/index.tsx&apos;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;导航&lt;/strong&gt; (&lt;code&gt;app/layout/Menu/index.tsx&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;navigate(&apos;/index/test/123&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;接收&lt;/strong&gt; (&lt;code&gt;app/pages/Test/index.tsx&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const { id } = useParams()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 查询参数（Search Params / Query Params）&lt;/h2&gt;
&lt;h3&gt;2.1 定义方式&lt;/h3&gt;
&lt;p&gt;查询参数不需要在路由配置中定义，直接在 URL 后添加 &lt;code&gt;?key=value&lt;/code&gt; 即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// app/routes.ts
{
  path: &apos;search&apos;,
  file: &apos;./pages/Search/index.tsx&apos;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 传递参数&lt;/h3&gt;
&lt;h4&gt;2.2.1：直接拼接 URL&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 使用 navigate
navigate(&apos;/index/search?id=123&amp;amp;name=test&apos;)

// 使用 Link
&amp;lt;Link to=&quot;/index/search?id=123&amp;amp;name=test&quot;&amp;gt;跳转&amp;lt;/Link&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.2.2：使用对象形式（推荐）&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 使用 navigate
navigate({
  pathname: &apos;/index/search&apos;,
  search: &apos;?id=123&amp;amp;name=test&apos;
})

// 或使用 URLSearchParams
const params = new URLSearchParams({ id: &apos;123&apos;, name: &apos;test&apos; })
navigate({
  pathname: &apos;/index/search&apos;,
  search: params.toString()
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 接收参数&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;useSearchParams()&lt;/code&gt; Hook 获取查询参数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useSearchParams } from &apos;react-router&apos;

export default function Search() {
  const [searchParams, setSearchParams] = useSearchParams()
  
  // 获取单个参数
  const id = searchParams.get(&apos;id&apos;)
  const name = searchParams.get(&apos;name&apos;)
  
  // 获取所有参数
  const allParams = Object.fromEntries(searchParams)
  
  // 更新参数
  const updateParams = () =&amp;gt; {
    setSearchParams({ id: &apos;456&apos;, name: &apos;new&apos; })
  }
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div&amp;gt;ID: {id}&amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;Name: {name}&amp;lt;/div&amp;gt;
      &amp;lt;button onClick={updateParams}&amp;gt;更新参数&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.4 特点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;✅ 参数不改变 URL 路径结构&lt;/li&gt;
&lt;li&gt;✅ 可以传递多个参数&lt;/li&gt;
&lt;li&gt;✅ 刷新页面参数不会丢失&lt;/li&gt;
&lt;li&gt;✅ 可以分享带参数的 URL&lt;/li&gt;
&lt;li&gt;✅ 适合可选参数或筛选条件&lt;/li&gt;
&lt;li&gt;⚠️ URL 会变长&lt;/li&gt;
&lt;li&gt;⚠️ 参数类型只能是字符串&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.5 项目示例&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;路由配置&lt;/strong&gt; (&lt;code&gt;app/routes.ts&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  path: &apos;search&apos;,
  file: &apos;./pages/Search/index.tsx&apos;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;导航&lt;/strong&gt; (&lt;code&gt;app/layout/Menu/index.tsx&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;navigate(&apos;/index/search?id=123&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;接收&lt;/strong&gt; (&lt;code&gt;app/pages/Search/index.tsx&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const [searchParams] = useSearchParams()
const id = searchParams.get(&apos;id&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 状态传参（State）&lt;/h2&gt;
&lt;h3&gt;3.1 定义方式&lt;/h3&gt;
&lt;p&gt;状态参数不需要在路由配置中定义，通过 &lt;code&gt;navigate&lt;/code&gt; 或 &lt;code&gt;Link&lt;/code&gt; 的 &lt;code&gt;state&lt;/code&gt; 属性传递。&lt;/p&gt;
&lt;h3&gt;3.2 传递参数&lt;/h3&gt;
&lt;h4&gt;使用 navigate&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import { useNavigate } from &apos;react-router&apos;

function Component() {
  const navigate = useNavigate()
  
  const handleNavigate = () =&amp;gt; {
    navigate(&apos;/index/about&apos;, {
      state: {
        id: 123,
        name: &apos;test&apos;,
        data: { foo: &apos;bar&apos; }
      }
    })
  }
  
  return &amp;lt;button onClick={handleNavigate}&amp;gt;跳转&amp;lt;/button&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;使用 Link&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import { Link } from &apos;react-router&apos;

function Component() {
  return (
    &amp;lt;Link 
      to=&quot;/index/about&quot; 
      state={{ id: 123, name: &apos;test&apos; }}
    &amp;gt;
      跳转
    &amp;lt;/Link&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 接收参数&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;useLocation()&lt;/code&gt; Hook 获取状态参数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useLocation } from &apos;react-router&apos;

export default function About() {
  const location = useLocation()
  const state = location.state as { id: number; name: string } | null
  
  return (
    &amp;lt;div&amp;gt;
      {state &amp;amp;&amp;amp; (
        &amp;lt;&amp;gt;
          &amp;lt;div&amp;gt;ID: {state.id}&amp;lt;/div&amp;gt;
          &amp;lt;div&amp;gt;Name: {state.name}&amp;lt;/div&amp;gt;
        &amp;lt;/&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4 特点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;✅ 可以传递复杂对象（不仅仅是字符串）&lt;/li&gt;
&lt;li&gt;✅ 参数不会显示在 URL 中（更安全）&lt;/li&gt;
&lt;li&gt;✅ 适合传递敏感数据或临时数据&lt;/li&gt;
&lt;li&gt;❌ 刷新页面参数会丢失&lt;/li&gt;
&lt;li&gt;❌ 不能通过 URL 分享参数&lt;/li&gt;
&lt;li&gt;❌ 浏览器前进/后退可能丢失状态&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.5 使用场景&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;传递表单数据到详情页&lt;/li&gt;
&lt;li&gt;传递临时状态信息&lt;/li&gt;
&lt;li&gt;传递不需要在 URL 中显示的数据&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 对比总结&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;路径参数&lt;/th&gt;
&lt;th&gt;查询参数&lt;/th&gt;
&lt;th&gt;状态传参&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;URL 显示&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ 显示在路径中&lt;/td&gt;
&lt;td&gt;✅ 显示在查询字符串&lt;/td&gt;
&lt;td&gt;❌ 不显示&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;刷新保留&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ 保留&lt;/td&gt;
&lt;td&gt;✅ 保留&lt;/td&gt;
&lt;td&gt;❌ 丢失&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;可分享&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ 可以&lt;/td&gt;
&lt;td&gt;✅ 可以&lt;/td&gt;
&lt;td&gt;❌ 不可以&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;参数类型&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;字符串&lt;/td&gt;
&lt;td&gt;字符串&lt;/td&gt;
&lt;td&gt;任意类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;必填性&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;适合必填&lt;/td&gt;
&lt;td&gt;适合可选&lt;/td&gt;
&lt;td&gt;任意&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;URL 长度&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;中等&lt;/td&gt;
&lt;td&gt;较长&lt;/td&gt;
&lt;td&gt;不变&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;安全性&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;中等&lt;/td&gt;
&lt;td&gt;中等&lt;/td&gt;
&lt;td&gt;较高&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;使用场景&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;资源 ID、必填参数&lt;/td&gt;
&lt;td&gt;筛选条件、可选参数&lt;/td&gt;
&lt;td&gt;临时数据、复杂对象&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 最佳实践&lt;/h2&gt;
&lt;h3&gt;5.1 选择建议&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;路径参数&lt;/strong&gt;：用于标识资源的唯一 ID（如用户 ID、文章 ID）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查询参数&lt;/strong&gt;：用于筛选、排序、分页等可选参数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态传参&lt;/strong&gt;：用于临时数据、复杂对象、敏感信息&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.2 组合使用&lt;/h3&gt;
&lt;p&gt;可以同时使用多种传参方式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 路径参数 + 查询参数
navigate(&apos;/index/user/123?tab=profile&amp;amp;sort=asc&apos;)

// 路径参数 + 状态传参
navigate(&apos;/index/user/123&apos;, {
  state: { from: &apos;dashboard&apos; }
})

// 查询参数 + 状态传参
navigate(&apos;/index/search?q=keyword&apos;, {
  state: { filters: { category: &apos;tech&apos; } }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.3 类型安全&lt;/h3&gt;
&lt;p&gt;建议为参数定义类型：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 路径参数类型
type PathParams = {
  id: string
}

// 查询参数类型
type SearchParams = {
  id?: string
  name?: string
  page?: string
}

// 状态参数类型
type LocationState = {
  id: number
  name: string
  data?: Record&amp;lt;string, unknown&amp;gt;
}

// 使用
const { id } = useParams&amp;lt;PathParams&amp;gt;()
const [searchParams] = useSearchParams()
const location = useLocation()
const state = location.state as LocationState | null
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 相关 API 参考&lt;/h2&gt;
&lt;h3&gt;6.1 Hooks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;useParams()&lt;/code&gt; - 获取路径参数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useSearchParams()&lt;/code&gt; - 获取和设置查询参数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useLocation()&lt;/code&gt; - 获取 location 对象（包含 state）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useNavigate()&lt;/code&gt; - 编程式导航&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6.2 组件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt; - 声明式导航组件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;NavLink&amp;gt;&lt;/code&gt; - 带激活状态的导航组件&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6.3 方法&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;navigate(path, options?)&lt;/code&gt; - 编程式导航
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;path&lt;/code&gt;: 路径字符串或对象&lt;/li&gt;
&lt;li&gt;&lt;code&gt;options.state&lt;/code&gt;: 状态数据&lt;/li&gt;
&lt;li&gt;&lt;code&gt;options.replace&lt;/code&gt;: 是否替换历史记录&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://reactrouter.com/&quot;&gt;React Router v7 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://reactrouter.com/en/main/hooks/use-params&quot;&gt;useParams API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://reactrouter.com/en/main/hooks/use-search-params&quot;&gt;useSearchParams API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://reactrouter.com/en/main/hooks/use-location&quot;&gt;useLocation API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://reactrouter.com/en/main/hooks/use-navigate&quot;&gt;useNavigate API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>sukha</author></item><item><title>react-router-v7-spa-mode-tailwindcss</title><link>https://sukha.dev/posts/learn-react-router-v7-spa-tailwindcss</link><guid isPermaLink="true">https://sukha.dev/posts/learn-react-router-v7-spa-tailwindcss</guid><description>react router v7 在 SPA 模式下使用 Tailwind CSS 的配置与原理</description><pubDate>Sun, 21 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;React Router v7 SPA 模式下的 Tailwind CSS 配置与原理&lt;/h2&gt;
&lt;p&gt;在 React Router v7 的 SPA 模式（&lt;code&gt;ssr: false&lt;/code&gt;）下，如果直接使用 &lt;code&gt;import &apos;./app.css&apos;&lt;/code&gt; 引入 Tailwind CSS，可能会在生产环境打包（Production Build）中遇到样式丢失的问题。本文记录了该问题的解决方案及其背后的原理。&lt;/p&gt;
&lt;h2&gt;1. 问题现象&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;开发环境&lt;/strong&gt;：样式正常。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生产环境&lt;/strong&gt;：运行 &lt;code&gt;npm run build&lt;/code&gt; 后，生成的 &lt;code&gt;index.html&lt;/code&gt; 中缺少 CSS &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; 标签，导致页面无样式。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原因&lt;/strong&gt;：在 SPA 模式下，React Router 需要通过 &lt;code&gt;HydrateFallback&lt;/code&gt; 组件来生成初始的 HTML 骨架。如果没有正确配置 &lt;code&gt;links&lt;/code&gt; 导出或 &lt;code&gt;HydrateFallback&lt;/code&gt;，框架无法将样式注入到入口 HTML 中。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 解决方案&lt;/h2&gt;
&lt;h3&gt;步骤一：使用 &lt;code&gt;links&lt;/code&gt; 导出 CSS&lt;/h3&gt;
&lt;p&gt;将全局样式从直接 &lt;code&gt;import&lt;/code&gt; 改为通过 &lt;code&gt;links&lt;/code&gt; 函数导出。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;修改 &lt;code&gt;app/root.tsx&lt;/code&gt;：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import {
  // ... 其他导入
  type LinksFunction,
} from &apos;react-router&apos;;

// 1. 使用 ?url 后缀导入 CSS 文件路径
import appStyles from &apos;./app.css?url&apos;;

// 2. 导出 links 函数
export const links: LinksFunction = () =&amp;gt; [
  { rel: &quot;stylesheet&quot;, href: appStyles },
];
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;步骤二：配置 &lt;code&gt;HydrateFallback&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;root.tsx&lt;/code&gt; 中导出 &lt;code&gt;HydrateFallback&lt;/code&gt; 组件。这是 SPA 模式的关键，React Router 会使用此组件渲染 &lt;code&gt;index.html&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;修改 &lt;code&gt;app/root.tsx&lt;/code&gt;：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Links, Meta, Scripts } from &apos;react-router&apos;;

// ... links 导出 ...

// 3. 导出 HydrateFallback 组件
export function HydrateFallback() {
  return (
    &amp;lt;html lang=&quot;en&quot;&amp;gt;
      &amp;lt;head&amp;gt;
        &amp;lt;meta charSet=&quot;utf-8&quot; /&amp;gt;
        &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&amp;gt;
        &amp;lt;Meta /&amp;gt;
        {/* 关键：确保 &amp;lt;Links /&amp;gt; 被渲染，这样 CSS 链接才会被注入 */}
        &amp;lt;Links /&amp;gt;
      &amp;lt;/head&amp;gt;
      &amp;lt;body&amp;gt;
        {/* 注入脚本 */}
        &amp;lt;Scripts /&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
}

// ... Root 组件 ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;步骤三：清理构建脚本&lt;/h3&gt;
&lt;p&gt;如果之前使用了额外的脚本（如 &lt;code&gt;inject-css.js&lt;/code&gt;）来手动注入 CSS，现在可以安全地移除它，并恢复标准的构建命令。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;修改 &lt;code&gt;package.json&lt;/code&gt;：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {
  &quot;build&quot;: &quot;react-router build&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 原理分析：为什么是 &lt;code&gt;export const links&lt;/code&gt;？&lt;/h2&gt;
&lt;p&gt;这是 React Router 的 &lt;strong&gt;约定式路由 API (Route Module API)&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;自动识别机制&lt;/h3&gt;
&lt;p&gt;框架会自动扫描路由文件（如 &lt;code&gt;root.tsx&lt;/code&gt;）中的特定命名导出（Named Exports）。只要按照约定命名，框架就会自动提取并使用它们。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;links&lt;/code&gt;&lt;/strong&gt;: 用于定义 &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; 标签（CSS、Favicon 等）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;meta&lt;/code&gt;&lt;/strong&gt;: 用于定义 &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; 标签（SEO 标题、描述）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;loader&lt;/code&gt;&lt;/strong&gt;: 数据加载函数。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;code&gt;&amp;lt;Links /&amp;gt;&lt;/code&gt; 组件的作用&lt;/h3&gt;
&lt;p&gt;在组件树中渲染 &lt;code&gt;&amp;lt;Links /&amp;gt;&lt;/code&gt; 组件时，React Router 会：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;遍历当前激活的所有路由层级（例如 Root -&amp;gt; Layout -&amp;gt; Page）。&lt;/li&gt;
&lt;li&gt;收集每个路由模块导出的 &lt;code&gt;links&lt;/code&gt; 函数返回值。&lt;/li&gt;
&lt;li&gt;将它们合并并渲染为 HTML 的 &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; 标签。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;相比直接 &lt;code&gt;import&lt;/code&gt; 的优势&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;按需加载&lt;/strong&gt;：子路由导出的 &lt;code&gt;links&lt;/code&gt; 只有在访问该路由时才会加载，减少首屏资源大小。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;层级控制&lt;/strong&gt;：框架确保父路由样式先加载，子路由样式后加载，便于样式覆盖。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;声明式管理&lt;/strong&gt;：更符合 React Router 的数据流和生命周期管理。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><author>sukha</author></item><item><title>react-router-navigation-summary</title><link>https://sukha.dev/posts/react-router-navigation-summary</link><guid isPermaLink="true">https://sukha.dev/posts/react-router-navigation-summary</guid><description>总结 React Router 中的导航方式及常见的坑</description><pubDate>Sun, 21 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note
&lt;em&gt;&lt;strong&gt;React Router 导航指南&lt;/strong&gt;&lt;/em&gt;
本文总结了 React Router (v6+) 中常见的导航方式，包括声明式导航、编程式导航，并重点梳理了开发中容易遇到的“坑”和最佳实践。
:::&lt;/p&gt;
&lt;h2&gt;核心导航方式&lt;/h2&gt;
&lt;p&gt;React Router 提供了两种主要的导航方式：&lt;strong&gt;声明式 (Declarative)&lt;/strong&gt; 和 &lt;strong&gt;编程式 (Imperative)&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;1. 声明式导航 (&lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;NavLink&amp;gt;&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;最常用的方式，用于创建应用内的链接。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt;&lt;/strong&gt;: 基础链接组件，相当于 HTML 的 &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; 标签，但不会触发页面完全刷新。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;&amp;lt;NavLink&amp;gt;&lt;/code&gt;&lt;/strong&gt;: 特殊的 &lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt;，知道自己是否处于“激活”状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import { Link, NavLink } from &quot;react-router&quot;;

// 基础用法
&amp;lt;Link to=&quot;/home&quot;&amp;gt;Go Home&amp;lt;/Link&amp;gt;

// NavLink 激活状态样式
&amp;lt;NavLink
  to=&quot;/messages&quot;
  className={({ isActive, isPending }) =&amp;gt;
    isPending ? &quot;pending&quot; : isActive ? &quot;active&quot; : &quot;&quot;
  }
&amp;gt;
  Messages
&amp;lt;/NavLink&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 编程式导航 (&lt;code&gt;useNavigate&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;当需要在执行某些逻辑（如提交表单、登录成功）后跳转时使用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useNavigate } from &quot;react-router&quot;;

function LoginForm() {
  const navigate = useNavigate();

  async function handleSubmit(event) {
    event.preventDefault();
    await login();
    // 跳转到主页，replace: true 表示替换当前历史记录，用户无法回退到登录页
    navigate(&quot;/dashboard&quot;, { replace: true });
    // 或者后退一步
    // navigate(-1); 
  }

  return &amp;lt;form onSubmit={handleSubmit}&amp;gt;...&amp;lt;/form&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 组件式重定向 (&lt;code&gt;&amp;lt;Navigate&amp;gt;&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;用于在渲染时立即重定向。常见于保护路由（Protected Routes）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Navigate } from &quot;react-router&quot;;

const ProtectedRoute = ({ user, children }) =&amp;gt; {
  if (!user) {
    // 没登录直接跳去登录页，replace 同样是为了防止回退死循环
    return &amp;lt;Navigate to=&quot;/login&quot; replace /&amp;gt;;
  }
  return children;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Loader/Action 中的重定向 (&lt;code&gt;redirect&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;在 v6.4+ (Data Router) 中，推荐在 &lt;code&gt;loader&lt;/code&gt; 或 &lt;code&gt;action&lt;/code&gt; 中处理重定向，而不是在组件渲染时。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { redirect } from &quot;react-router&quot;;

export const loader = async () =&amp;gt; {
  const user = await getUser();
  if (!user) {
    throw redirect(&quot;/login&quot;);
  }
  return { user };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;避坑指南 (Common Pitfalls)&lt;/h2&gt;
&lt;h3&gt;1. 相对路径 vs 绝对路径&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;绝对路径&lt;/strong&gt;: 以 &lt;code&gt;/&lt;/code&gt; 开头（如 &lt;code&gt;/about&lt;/code&gt;），总是从根路径开始匹配。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;相对路径&lt;/strong&gt;: 不以 &lt;code&gt;/&lt;/code&gt; 开头（如 &lt;code&gt;details&lt;/code&gt;），基于&lt;strong&gt;当前路由路径&lt;/strong&gt;进行拼接。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;坑点&lt;/strong&gt;: 如果你在 &lt;code&gt;/users/123&lt;/code&gt; 页面，点击 &lt;code&gt;&amp;lt;Link to=&quot;edit&quot;&amp;gt;&lt;/code&gt;，会跳转到 &lt;code&gt;/users/123/edit&lt;/code&gt;。但如果你写成了 &lt;code&gt;&amp;lt;Link to=&quot;/edit&quot;&amp;gt;&lt;/code&gt;，则会跳到根目录下的 &lt;code&gt;/edit&lt;/code&gt;。
另外，&lt;code&gt;..&lt;/code&gt; 在相对路径中的行为是指向&lt;strong&gt;路由层级&lt;/strong&gt;的父级，而不是 URL 路径的父级（虽然通常一致，但在嵌套路由中可能有区别）。&lt;/p&gt;
&lt;h3&gt;2. &lt;code&gt;replace: true&lt;/code&gt; 的重要性&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;: 登录页 -&amp;gt; 仪表盘。
如果不加 &lt;code&gt;replace: true&lt;/code&gt;，用户点击浏览器“后退”按钮会回到登录页（此时用户已登录，可能又被重定向回仪表盘，体验很差）。
&lt;strong&gt;解决&lt;/strong&gt;: 状态改变导致的跳转（Login -&amp;gt; Home, Form Success -&amp;gt; List），通常都应该使用 &lt;code&gt;{ replace: true }&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;3. &lt;code&gt;useEffect&lt;/code&gt; 中使用 &lt;code&gt;useNavigate&lt;/code&gt; 的竞态/双重调用&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;坑点&lt;/strong&gt;: 在 &lt;code&gt;useEffect&lt;/code&gt; 中调用 &lt;code&gt;navigate&lt;/code&gt; 可能会在 React Strict Mode (开发环境) 下执行两次，或者与其他状态更新产生冲突。
&lt;strong&gt;建议&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优先使用 &lt;code&gt;loader&lt;/code&gt; 中的 &lt;code&gt;redirect&lt;/code&gt; 处理前置跳转逻辑。&lt;/li&gt;
&lt;li&gt;如果是交互后的跳转，放在事件处理函数中，而不是 &lt;code&gt;useEffect&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果必须在 &lt;code&gt;useEffect&lt;/code&gt; 中跳转，确保依赖项正确，并且该逻辑是幂等的。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 路由状态 (&lt;code&gt;state&lt;/code&gt;) 的易失性&lt;/h3&gt;
&lt;p&gt;你可以通过 &lt;code&gt;state&lt;/code&gt; 传递数据：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;navigate(&quot;/target&quot;, { state: { from: &quot;home&quot;, id: 123 } });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在目标页面获取：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const location = useLocation();
const { from, id } = location.state || {}; // 必须处理 null/undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;坑点&lt;/strong&gt;: 用户&lt;strong&gt;刷新页面&lt;/strong&gt;或者&lt;strong&gt;直接复制链接&lt;/strong&gt;在新标签页打开时，&lt;code&gt;state&lt;/code&gt; 会丢失（变为 &lt;code&gt;null&lt;/code&gt;）。
&lt;strong&gt;解决&lt;/strong&gt;: &lt;code&gt;state&lt;/code&gt; 仅用于非关键的 UI 增强（如“返回上一页”的路径记录、Toast 消息）。核心业务数据（如 ID）必须放在 URL 参数 (&lt;code&gt;params&lt;/code&gt; 或 &lt;code&gt;searchParams&lt;/code&gt;) 中，以便刷新后能重新获取。&lt;/p&gt;
&lt;h3&gt;5. 阻塞导航 (&lt;code&gt;useBlocker&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;在旧版本中常用的 &lt;code&gt;&amp;lt;Prompt&amp;gt;&lt;/code&gt; 已被移除。v6 使用 &lt;code&gt;useBlocker&lt;/code&gt;（需要 Data Router 支持）。
&lt;strong&gt;坑点&lt;/strong&gt;: 浏览器对于 &lt;code&gt;beforeunload&lt;/code&gt; 事件的限制越来越严，自定义 UI 的阻塞导航只能在应用内路由跳转时生效，无法完全阻止用户关闭浏览器标签页或刷新（只能弹默认的浏览器原生对话框）。&lt;/p&gt;
&lt;h3&gt;6. &lt;code&gt;&amp;lt;a href=&quot;...&quot;&amp;gt;&lt;/code&gt; vs &lt;code&gt;&amp;lt;Link to=&quot;...&quot;&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;新手误区&lt;/strong&gt;: 使用 &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; 标签跳转内部页面。
&lt;strong&gt;后果&lt;/strong&gt;: 浏览器会发起全新的页面请求，React 应用会被卸载并重新加载，导致：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;状态丢失（Redux/Context 重置）。&lt;/li&gt;
&lt;li&gt;性能下降（需要重新下载/解析 JS）。
&lt;strong&gt;原则&lt;/strong&gt;: 内部跳转永远使用 &lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt;，外部链接才使用 &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;优先声明式&lt;/strong&gt;: 能用 &lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt; 解决的，不要写 &lt;code&gt;onClick={() =&amp;gt; navigate(...)}&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拥抱 Data Router&lt;/strong&gt;: v6.4+ 的 &lt;code&gt;loader/action&lt;/code&gt; 模式让重定向逻辑更清晰，避免了组件渲染后的“闪烁”跳转。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;URL 是单一事实来源&lt;/strong&gt;: 不要过度依赖 &lt;code&gt;state&lt;/code&gt; 传参，尽量将状态同步到 URL 上，增强页面的可分享性和健壮性。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><author>sukha</author></item><item><title>react-zustand-immer</title><link>https://sukha.dev/posts/learn-react-immer</link><guid isPermaLink="true">https://sukha.dev/posts/learn-react-immer</guid><description>learn react zustand immer</description><pubDate>Mon, 22 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note
&lt;strong&gt;Immer 基本实现原理&lt;/strong&gt;&lt;br /&gt;
Immer 是一个用于处理不可变数据的 JavaScript 库，它允许你以可变的方式编写代码，同时保持数据的不可变性。Immer 的核心思想是&quot;写时复制&quot;（Copy-on-Write），通过 Proxy 来拦截对对象的修改操作。
:::&lt;/p&gt;
&lt;h2&gt;核心概念&lt;/h2&gt;
&lt;h3&gt;1. 三个关键角色&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Base State（基础状态）&lt;/strong&gt;：原始的不可变数据&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Draft State（草稿状态）&lt;/strong&gt;：可以被修改的代理对象&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Next State（下一个状态）&lt;/strong&gt;：基于修改生成的新的不可变数据&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 基本使用方式&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const newState = produce(baseState, (draft) =&amp;gt; {
  // 在这里可以直接修改 draft
  draft.user.name = &apos;新名字&apos;
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实现原理&lt;/h2&gt;
&lt;h3&gt;简化版实现&lt;/h3&gt;
&lt;p&gt;基于项目中的示例代码，我们可以看到 Immer 的核心实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const produce = (base, fn) =&amp;gt; {
  // 1. 用于存储所有修改的对象
  const modified = {}
  
  // 2. 创建 Proxy 处理器
  const handler = {
    // 拦截读取操作
    get(target, prop, receiver) {
      // 如果属性已被修改，返回修改后的值
      if (prop in modified) {
        return modified[prop]
      }
      // 如果属性是对象，递归创建 Proxy
      if (typeof target[prop] === &apos;object&apos; &amp;amp;&amp;amp; target[prop] !== null) {
        return new Proxy(target[prop], handler)
      }
      // 否则返回原始值
      return Reflect.get(target, prop, receiver)
    },
    
    // 拦截写入操作
    set(_target, prop, value) {
      // 将修改记录到 modified 对象中
      return Reflect.set(modified, prop, value)
    },
  }
  
  // 3. 创建基础对象的代理
  const proxy = new Proxy(base, handler)
  
  // 4. 执行用户的修改函数
  fn(proxy)
  
  // 5. 如果没有修改，直接返回原对象
  if (Object.keys(modified).length === 0) {
    return base
  }
  
  // 6. 返回合并后的新对象
  return JSON.parse(JSON.stringify(proxy))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;工作流程详解&lt;/h2&gt;
&lt;h3&gt;步骤 1：创建 Proxy&lt;/h3&gt;
&lt;p&gt;当调用 &lt;code&gt;produce(obj, fn)&lt;/code&gt; 时，Immer 会为基础对象创建一个 Proxy 代理。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const obj = {
  user: {
    name: &apos;张三&apos;,
    age: 18,
  },
}

const newObj = produce(obj, (draft) =&amp;gt; {
  // draft 是 obj 的 Proxy 代理
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;步骤 2：拦截读取操作（get）&lt;/h3&gt;
&lt;p&gt;当访问 &lt;code&gt;draft.user.name&lt;/code&gt; 时：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先检查 &lt;code&gt;modified&lt;/code&gt; 对象中是否有 &lt;code&gt;user&lt;/code&gt; 属性&lt;/li&gt;
&lt;li&gt;如果没有，检查 &lt;code&gt;obj.user&lt;/code&gt; 是否是对象&lt;/li&gt;
&lt;li&gt;如果是对象，为 &lt;code&gt;obj.user&lt;/code&gt; 创建一个新的 Proxy&lt;/li&gt;
&lt;li&gt;这样就实现了深层嵌套对象的代理&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;draft.user.name
// ↓
get(obj, &apos;user&apos;, receiver)
// → obj.user 是对象，返回 new Proxy(obj.user, handler)
// ↓
get(obj.user, &apos;name&apos;, receiver)
// → 返回 &apos;张三&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;步骤 3：拦截写入操作（set）&lt;/h3&gt;
&lt;p&gt;当执行 &lt;code&gt;draft.user.name = &apos;李四&apos;&lt;/code&gt; 时：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Proxy 的 &lt;code&gt;set&lt;/code&gt; 方法被触发&lt;/li&gt;
&lt;li&gt;修改被记录到 &lt;code&gt;modified&lt;/code&gt; 对象中&lt;/li&gt;
&lt;li&gt;原始的 &lt;code&gt;obj&lt;/code&gt; 不会被修改&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;draft.user.name = &apos;李四&apos;
// ↓
set(obj.user, &apos;name&apos;, &apos;李四&apos;)
// → modified.name = &apos;李四&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;步骤 4：生成新状态&lt;/h3&gt;
&lt;p&gt;在用户的修改函数执行完毕后：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;检查 &lt;code&gt;modified&lt;/code&gt; 对象是否为空&lt;/li&gt;
&lt;li&gt;如果为空，说明没有任何修改，直接返回原对象（结构共享）&lt;/li&gt;
&lt;li&gt;如果有修改，将 &lt;code&gt;proxy&lt;/code&gt; 序列化后反序列化，生成一个新对象&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;关键特性&lt;/h2&gt;
&lt;h3&gt;1. 结构共享（Structural Sharing）&lt;/h3&gt;
&lt;p&gt;如果没有进行任何修改，Immer 会返回原始对象，而不是创建新对象：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const obj1 = { name: &apos;张三&apos; }
const obj2 = produce(obj1, (draft) =&amp;gt; {
  // 没有修改
})

console.log(obj1 === obj2) // true
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 深层嵌套支持&lt;/h3&gt;
&lt;p&gt;通过递归创建 Proxy，Immer 可以处理任意深度的嵌套对象：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;produce(state, (draft) =&amp;gt; {
  draft.level1.level2.level3.value = &apos;new value&apos;
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 写时复制（Copy-on-Write）&lt;/h3&gt;
&lt;p&gt;只有在真正修改时才会创建新对象，未修改的部分保持原引用。&lt;/p&gt;
&lt;h2&gt;实际应用场景&lt;/h2&gt;
&lt;h3&gt;在 Zustand 中使用 Immer&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import { create } from &apos;zustand/react&apos;
import { immer } from &apos;zustand/middleware/immer&apos;

interface Store {
  user: { name: string; age: number }
  updateName: (name: string) =&amp;gt; void
}

const useStore = create&amp;lt;Store&amp;gt;()(
  immer((set) =&amp;gt; ({
    user: { name: &apos;张三&apos;, age: 18 },
    updateName: (name) =&amp;gt;
      set((state) =&amp;gt; {
        // 可以直接修改 state，Immer 会处理不可变性
        state.user.name = name
      }),
  }))
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;在 React 状态管理中&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const [state, setState] = useState({ user: { name: &apos;张三&apos; } })

// 使用 Immer 更新深层状态
setState(produce((draft) =&amp;gt; {
  draft.user.name = &apos;李四&apos;
}))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实际 Immer 库的优化&lt;/h2&gt;
&lt;p&gt;上面的简化实现只是为了演示原理，实际的 Immer 库还包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;更高效的对象合并&lt;/strong&gt;：不使用 JSON 序列化，而是智能地合并对象&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;支持数组操作&lt;/strong&gt;：可以使用 &lt;code&gt;push&lt;/code&gt;、&lt;code&gt;splice&lt;/code&gt; 等数组方法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;支持 Map 和 Set&lt;/strong&gt;：扩展了对 ES6 集合类型的支持&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动冻结&lt;/strong&gt;：在开发环境中冻结生成的对象，防止意外修改&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能优化&lt;/strong&gt;：使用多种技术优化性能，包括对象池、懒初始化等&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;优势与限制&lt;/h2&gt;
&lt;h3&gt;优势&lt;/h3&gt;
&lt;p&gt;✅ &lt;strong&gt;更直观的代码&lt;/strong&gt;：可以像修改可变数据一样编写代码&lt;br /&gt;
✅ &lt;strong&gt;自动不可变性&lt;/strong&gt;：无需手动使用扩展运算符或 &lt;code&gt;Object.assign&lt;/code&gt;&lt;br /&gt;
✅ &lt;strong&gt;减少样板代码&lt;/strong&gt;：特别是在处理深层嵌套数据时&lt;br /&gt;
✅ &lt;strong&gt;结构共享&lt;/strong&gt;：未修改的部分保持引用相等&lt;/p&gt;
&lt;h3&gt;限制&lt;/h3&gt;
&lt;p&gt;⚠️ &lt;strong&gt;必须在 produce 函数内修改&lt;/strong&gt;：在外部修改无效&lt;br /&gt;
⚠️ &lt;strong&gt;不支持返回值&lt;/strong&gt;：不能在 draft 函数中 return 新值（需要特殊处理）&lt;br /&gt;
⚠️ &lt;strong&gt;性能开销&lt;/strong&gt;：Proxy 有一定的性能开销，但通常可以忽略不计&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;Immer 通过巧妙地使用 JavaScript Proxy 特性，实现了以可变方式编写代码、生成不可变数据的目标。其核心原理是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 Proxy 拦截对象的读写操作&lt;/li&gt;
&lt;li&gt;记录所有的修改操作&lt;/li&gt;
&lt;li&gt;在修改完成后，生成一个新的不可变对象&lt;/li&gt;
&lt;li&gt;通过结构共享优化性能&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这使得在 React、Redux、Zustand 等需要不可变数据的场景中，可以更方便地管理状态。&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>React Zustand 状态管理实践指南</title><link>https://sukha.dev/posts/learn-react-zustand</link><guid isPermaLink="true">https://sukha.dev/posts/learn-react-zustand</guid><description>结合项目实例，深入浅出地介绍 Zustand 在 React 中的应用，从基础的 Store 创建到 Immer、Persist 中间件的高级用法，以及性能优化技巧。</description><pubDate>Mon, 22 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Zustand 是一个轻量级、快速且可扩展的 React 状态管理库。它以其简洁的 API 和基于 Hook 的设计理念受到广泛欢迎。本文将结合实际项目代码，总结 Zustand 的基础用法以及 Immer、Persist 等高级中间件的实战应用。&lt;/p&gt;
&lt;h2&gt;1. 基础篇：快速上手&lt;/h2&gt;
&lt;h3&gt;1.1 创建 Store&lt;/h3&gt;
&lt;p&gt;最基础的 Zustand 用法是通过 &lt;code&gt;create&lt;/code&gt; 方法创建一个 Hook。Store 中包含了状态（State）和更新状态的方法（Actions）。&lt;/p&gt;
&lt;p&gt;参考项目中的 &lt;code&gt;app/store/price.ts&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { create } from &apos;zustand&apos;

interface PriceStore {
  price: number
  state: number
  increment: () =&amp;gt; void
  decrement: () =&amp;gt; void
  resetPrice: () =&amp;gt; void
  getPrice: () =&amp;gt; number
  setPrice: (price: number) =&amp;gt; void
}

const usePriceStore = create&amp;lt;PriceStore&amp;gt;((set, get) =&amp;gt; ({
  price: 0,
  state: 123,
  increment: () =&amp;gt; set((state) =&amp;gt; ({ price: state.price + 1 })),
  decrement: () =&amp;gt; set((state) =&amp;gt; ({ price: state.price - 1 })),
  resetPrice: () =&amp;gt; set(() =&amp;gt; ({ price: 0 })),
  getPrice: () =&amp;gt; get().price,
  setPrice: (price: number) =&amp;gt; set(() =&amp;gt; ({ price: price })),
}))

export default usePriceStore
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 在组件中使用&lt;/h3&gt;
&lt;p&gt;创建好 Store 后，可以直接在组件中调用这个 Hook 来获取状态和方法。&lt;/p&gt;
&lt;p&gt;参考 &lt;code&gt;app/pages/Zustand/index.tsx&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import usePriceStore from &apos;~/store/price&apos;

export default function Zustand() {
  const priceStore = usePriceStore()
  return (
    &amp;lt;div className=&quot;w-120 h-120 bg-[#242424] box-border&quot;&amp;gt;
      &amp;lt;div className=&apos;text-white text-4xl bg-amber-300 w-30 h-40 mt-4 relative box-border&apos;&amp;gt;{priceStore.price}&amp;lt;/div&amp;gt;
      &amp;lt;div className=&quot;h-40 w-full flex items-center justify-start p-4 gap-4&quot;&amp;gt;
        &amp;lt;div
          className=&quot;bg-blue-300 text-white w-40 h-40 flex items-center justify-center cursor-pointer&quot;
          onClick={() =&amp;gt; priceStore.increment()}
        &amp;gt;
          +
        &amp;lt;/div&amp;gt;
        &amp;lt;div
          className=&quot;bg-red-300 text-white w-40 h-40 flex items-center justify-center  cursor-pointer&quot;
          onClick={() =&amp;gt; priceStore.decrement()}
        &amp;gt;
          —
        &amp;lt;/div&amp;gt;
        &amp;lt;div
          className=&quot;bg-green-300 text-white w-40 h-40 flex items-center justify-center  cursor-pointer&quot;
          onClick={() =&amp;gt; priceStore.resetPrice()}
        &amp;gt;
          重置
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 进阶篇：中间件与复杂状态&lt;/h2&gt;
&lt;p&gt;对于更复杂的场景，Zustand 提供了丰富的中间件支持。在 &lt;code&gt;app/store/hlw.ts&lt;/code&gt; 中，我们结合了 &lt;code&gt;immer&lt;/code&gt;、&lt;code&gt;persist&lt;/code&gt; 和 &lt;code&gt;subscribeWithSelector&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;2.1 组合使用中间件&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;app/store/hlw.ts&lt;/code&gt; 中，展示了如何使用 &lt;code&gt;createStore&lt;/code&gt;（Vanilla Store）并组合多个中间件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { createStore } from &apos;zustand/vanilla&apos;
import { immer } from &apos;zustand/middleware/immer&apos;
import { persist, createJSONStorage } from &apos;zustand/middleware&apos;
import { subscribeWithSelector } from &apos;zustand/middleware&apos;

export interface HLW {
  name: &apos;大娃&apos; | &apos;二娃&apos; | &apos;三娃&apos; | &apos;四娃&apos; | &apos;五娃&apos; | &apos;六娃&apos; | &apos;七娃&apos; | undefined
  level: &apos;初始&apos; | &apos;超进化&apos;
}

const data = [
  { name: &apos;大娃&apos;, level: &apos;初始&apos; },
  { name: &apos;二娃&apos;, level: &apos;初始&apos; },
  { name: &apos;三娃&apos;, level: &apos;初始&apos; },
  { name: &apos;四娃&apos;, level: &apos;初始&apos; },
  { name: &apos;五娃&apos;, level: &apos;初始&apos; },
  { name: &apos;六娃&apos;, level: &apos;初始&apos; },
  { name: &apos;七娃&apos;, level: &apos;初始&apos; },
]

const kunkun = {
  name: &apos;房房&apos;,
  age: 18,
  gender: &apos;男&apos;,
  hobbies: {
    sing: &apos;唱歌&apos;,
    dance: &apos;跳舞&apos;,
    rap: &apos;rap&apos;,
    basketball: &apos;篮球&apos;,
  },
}

interface HLWStore {
  hlws: HLW[]
  kunkun: typeof kunkun
  updateName: (name: string) =&amp;gt; void
  updateRap: (rap: string) =&amp;gt; void
  updateBasketball: (basketball: string) =&amp;gt; void
  upgrade: (name: HLW[&apos;name&apos;]) =&amp;gt; void
  setAge: () =&amp;gt; void
  reset: () =&amp;gt; void
}

const useHLWStore = createStore&amp;lt;HLWStore&amp;gt;()(
  immer(
    subscribeWithSelector(
      persist(
        (set) =&amp;gt; ({
          hlws: data as HLW[],
          kunkun,
          updateName: (name: string) =&amp;gt; {
            set((state) =&amp;gt; {
              state.kunkun.name = name
            })
          },
          updateRap: (rap: string) =&amp;gt; {
            set((state) =&amp;gt; {
              state.kunkun.hobbies.rap = rap
            })
          },
          updateBasketball: (basketball: string) =&amp;gt; {
            set((state) =&amp;gt; {
              state.kunkun.hobbies.basketball = basketball
            })
          },
          upgrade: (name: HLW[&apos;name&apos;]) =&amp;gt;
            set((state) =&amp;gt; {
              let hlw = state.hlws.find((hlw) =&amp;gt; hlw.name === name)
              hlw &amp;amp;&amp;amp; (hlw.level = &apos;超进化&apos;)
            }),
          reset: () =&amp;gt;
            set((state) =&amp;gt; {
              state.hlws = data as HLW[]
              state.kunkun = kunkun
            }),
          setAge: () =&amp;gt; {
            set((state) =&amp;gt; {
              state.kunkun.age++
            })
          },
        }),
        {
          name: &apos;hlw&apos;,
          storage: createJSONStorage(() =&amp;gt; localStorage),
        }
      )
    )
  )
)

export default useHLWStore
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;immer&lt;/code&gt; 让我们能够直接修改嵌套状态（如 &lt;code&gt;state.kunkun.hobbies.rap = rap&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;persist&lt;/code&gt; 将状态持久化到 &lt;code&gt;localStorage&lt;/code&gt; 中，key 为 &lt;code&gt;hlw&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;subscribeWithSelector&lt;/code&gt; 允许我们精确订阅 store 的一部分。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;createStore&lt;/code&gt; 创建的是一个 Vanilla Store，在 React 组件中使用时需要配合 &lt;code&gt;useStore&lt;/code&gt; hook。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 订阅状态变化与 useShallow 优化&lt;/h3&gt;
&lt;p&gt;在组件 &lt;code&gt;app/pages/HLW/B.tsx&lt;/code&gt; 中，我们展示了如何使用 &lt;code&gt;useStore&lt;/code&gt; 结合 &lt;code&gt;useShallow&lt;/code&gt; 进行性能优化，以及如何使用 &lt;code&gt;subscribe&lt;/code&gt; 监听状态变化。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useEffect, useState } from &apos;react&apos;
import { useStore } from &apos;zustand&apos;
import { useShallow } from &apos;zustand/react/shallow&apos;
import useHLWStore from &apos;~/store/hlw&apos;

export default function B() {
  // 使用 useShallow 进行浅比较，避免不必要的重渲染
  const { rap, basketball, age } = useStore(
    useHLWStore,
    useShallow((state) =&amp;gt; ({
      rap: state.kunkun.hobbies.rap,
      age: state.kunkun.age,
      basketball: state.kunkun.hobbies.basketball,
    }))
  )
  
  const [status, setStatus] = useState&amp;lt;&apos;单身&apos; | &apos;结婚&apos;&amp;gt;(&apos;单身&apos;)

  // 使用 subscribe 监听 age 的变化
  useEffect(() =&amp;gt; {
    useHLWStore.subscribe(
      (state) =&amp;gt; state.kunkun.age,
      (age) =&amp;gt; {
        console.log(&apos;执行了useEffect&apos;)
        if (age &amp;gt;= 26) {
          setStatus(&apos;结婚&apos;)
        } else {
          setStatus(&apos;单身&apos;)
        }
      }
    )
  }, [])

  return (
    &amp;lt;div className=&quot;w-100 h-50 border border-amber-700 rounded-sm p-4&quot;&amp;gt;
      &amp;lt;div&amp;gt;
        爱好：
        &amp;lt;span&amp;gt;
          {rap}-{basketball}
        &amp;lt;/span&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;
        年龄：&amp;lt;span&amp;gt;{age}&amp;lt;/span&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;
        状态：&amp;lt;span&amp;gt;{status}&amp;lt;/span&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 操作复杂状态&lt;/h3&gt;
&lt;p&gt;组件 &lt;code&gt;app/pages/HLW/A.tsx&lt;/code&gt; 展示了如何调用 store 中的 action 来修改复杂状态：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Button, Input, Select } from &apos;antd&apos;
import { useState } from &apos;react&apos;
import useHLWStore, { type HLW } from &apos;~/store/hlw&apos;
import { useStore } from &apos;zustand&apos;

export default function A() {
  const hlwStore = useStore(useHLWStore)
  const [name, setName] = useState&amp;lt;HLW[&apos;name&apos;] | undefined&amp;gt;()
  
  function superAd() {
    hlwStore.upgrade(name)
  }
  function reset() {
    hlwStore.reset()
  }
  function updateName() {
    hlwStore.updateName(&apos;房房222&apos;)
  }
  function updateRap() {
    hlwStore.updateRap(&apos;rap222&apos;)
  }
  function updateBasketball() {
    hlwStore.updateBasketball(&apos;篮球222&apos;)
  }
  function setAge() {
    hlwStore.setAge()
  }
  
  return (
    &amp;lt;div className=&quot;w-100 h-50 border border-amber-700 rounded-sm p-4&quot;&amp;gt;
      &amp;lt;Button className=&apos;mr-2&apos; onClick={superAd}&amp;gt;超进化&amp;lt;/Button&amp;gt;
      &amp;lt;Button className=&apos;mr-2&apos; onClick={reset}&amp;gt;重置&amp;lt;/Button&amp;gt;
      &amp;lt;Select
        className=&quot;w-30&quot;
        options={hlwStore.hlws.map((hlw) =&amp;gt; ({ label: hlw.name, value: hlw.name }))}
        onChange={(value) =&amp;gt; {
          setName(value)
        }}
      /&amp;gt;
      &amp;lt;Button className=&apos;mr-2&apos; onClick={()=&amp;gt;updateName()}&amp;gt;修改姓名&amp;lt;/Button&amp;gt;
      &amp;lt;Button className=&apos;mr-2&apos; onClick={()=&amp;gt;updateRap()}&amp;gt;修改rap&amp;lt;/Button&amp;gt;
      &amp;lt;Button className=&apos;mr-2&apos; onClick={()=&amp;gt;updateBasketball()}&amp;gt;修改篮球&amp;lt;/Button&amp;gt;
      &amp;lt;Button className=&apos;mr-2&apos; onClick={()=&amp;gt;setAge()}&amp;gt;增加年龄&amp;lt;/Button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;Zustand 提供了极其灵活的状态管理方案。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;简单场景&lt;/strong&gt;：直接使用 &lt;code&gt;create&lt;/code&gt; 和基础 Actions。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复杂场景&lt;/strong&gt;：组合使用 &lt;code&gt;immer&lt;/code&gt; 处理深层更新，&lt;code&gt;persist&lt;/code&gt; 处理持久化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能敏感&lt;/strong&gt;：利用 Selector 和 &lt;code&gt;useShallow&lt;/code&gt; 精确控制渲染。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逻辑解耦&lt;/strong&gt;：利用 &lt;code&gt;subscribe&lt;/code&gt; 在组件外监听状态变化。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过合理组合这些特性，我们可以构建出既高效又易于维护的 React 应用状态管理层。&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>Git 提交信息规范指南：常用类型与最佳实践</title><link>https://sukha.dev/posts/git-commit-message-conventions</link><guid isPermaLink="true">https://sukha.dev/posts/git-commit-message-conventions</guid><description>详解 Git 提交信息的标准格式，包括 feat、fix、docs 等常用类型，以及如何编写清晰规范的提交信息</description><pubDate>Thu, 25 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note
&lt;em&gt;&lt;strong&gt;Git Commit Message 规范完全指南&lt;/strong&gt;&lt;/em&gt;
本文系统介绍了 Git 提交信息的标准规范（基于 Conventional Commits），详细说明了 feat、fix、chore 等常用提交类型的使用场景，并提供实用的最佳实践建议，帮助团队编写更清晰、更易维护的提交历史。
:::&lt;/p&gt;
&lt;h2&gt;为什么需要规范的提交信息&lt;/h2&gt;
&lt;p&gt;在团队协作中，规范的 Git 提交信息能够：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;快速理解变更内容&lt;/strong&gt;：无需深入代码就能了解每次提交做了什么&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动生成 Changelog&lt;/strong&gt;：基于提交类型自动生成版本更新日志&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;便于版本管理&lt;/strong&gt;：明确区分功能更新、Bug 修复、破坏性变更等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提高代码审查效率&lt;/strong&gt;：审查者能快速定位关注重点&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;支持自动化工具&lt;/strong&gt;：语义化版本发布、CI/CD 流程等&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;提交信息的基本格式&lt;/h2&gt;
&lt;p&gt;标准的提交信息遵循以下结构：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;type&amp;gt;(&amp;lt;scope&amp;gt;): &amp;lt;subject&amp;gt;

&amp;lt;body&amp;gt;

&amp;lt;footer&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;格式说明&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;type&lt;/strong&gt;（必需）：提交的类型，如 &lt;code&gt;feat&lt;/code&gt;、&lt;code&gt;fix&lt;/code&gt; 等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;scope&lt;/strong&gt;（可选）：影响的范围，如 &lt;code&gt;auth&lt;/code&gt;、&lt;code&gt;api&lt;/code&gt;、&lt;code&gt;ui&lt;/code&gt; 等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;subject&lt;/strong&gt;（必需）：简短描述，不超过 50 字符&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;body&lt;/strong&gt;（可选）：详细描述变更的动机、实现方式等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;footer&lt;/strong&gt;（可选）：关联的 Issue、破坏性变更说明等&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;基本示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;feat(auth): 添加用户登录功能

实现了基于 JWT 的用户认证系统，包括：
- 登录接口
- Token 验证中间件
- 刷新 Token 机制

Closes #123
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;常用提交类型（Type）详解&lt;/h2&gt;
&lt;h3&gt;1. &lt;code&gt;feat&lt;/code&gt; - 新功能&lt;/h3&gt;
&lt;p&gt;用于添加新功能或特性。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 简单功能
feat: 添加暗黑模式切换按钮

# 带作用域
feat(payment): 集成支付宝支付接口

# 带详细描述
feat(search): 实现全文搜索功能

新增 Elasticsearch 集成，支持：
- 多字段模糊查询
- 高亮显示匹配结果
- 分页与排序

Closes #456
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;添加新的用户功能&lt;/li&gt;
&lt;li&gt;新增 API 接口&lt;/li&gt;
&lt;li&gt;引入新的组件或模块&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. &lt;code&gt;fix&lt;/code&gt; - 修复 Bug&lt;/h3&gt;
&lt;p&gt;用于修复缺陷或问题。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 简单修复
fix: 修复登录页面重复提交问题

# 带作用域
fix(cart): 修复购物车数量计算错误

# 详细说明
fix(api): 修复并发请求导致的数据不一致

问题原因：未对共享资源加锁
解决方案：使用 Redis 分布式锁

Fixes #789
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;修复用户报告的 Bug&lt;/li&gt;
&lt;li&gt;解决代码逻辑错误&lt;/li&gt;
&lt;li&gt;修正样式或布局问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. &lt;code&gt;docs&lt;/code&gt; - 文档更新&lt;/h3&gt;
&lt;p&gt;仅涉及文档的变更。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 更新 README
docs: 更新安装说明文档

# API 文档
docs(api): 补充用户接口的参数说明

# 注释优化
docs: 为核心模块添加详细注释
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;修改 README.md&lt;/li&gt;
&lt;li&gt;更新 API 文档&lt;/li&gt;
&lt;li&gt;完善代码注释&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. &lt;code&gt;style&lt;/code&gt; - 代码格式&lt;/h3&gt;
&lt;p&gt;不影响代码逻辑的格式调整（注意：不是 CSS 样式）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 格式化代码
style: 格式化代码符合 ESLint 规则

# 移除空白
style: 移除多余的空行和尾随空格

# 统一风格
style(components): 统一使用单引号
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代码格式化（Prettier/ESLint）&lt;/li&gt;
&lt;li&gt;缩进、空格、换行调整&lt;/li&gt;
&lt;li&gt;不改变代码语义的修改&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. &lt;code&gt;refactor&lt;/code&gt; - 重构&lt;/h3&gt;
&lt;p&gt;既不修复 Bug 也不添加功能的代码重构。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 重构函数
refactor: 简化用户验证逻辑

# 提取公共代码
refactor(utils): 将日期处理函数抽取为工具类

# 重构架构
refactor: 将 Redux 迁移至 Zustand

重构状态管理方案，优势：
- 减少样板代码
- 更好的 TypeScript 支持
- 降低包体积 50%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优化代码结构&lt;/li&gt;
&lt;li&gt;提取公共逻辑&lt;/li&gt;
&lt;li&gt;重命名变量/函数以提高可读性&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6. &lt;code&gt;perf&lt;/code&gt; - 性能优化&lt;/h3&gt;
&lt;p&gt;提升性能的代码变更。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 优化查询
perf(database): 为用户表添加索引

# 缓存优化
perf: 使用 React.memo 优化组件渲染

# 资源优化
perf(images): 启用 WebP 格式压缩图片

优化结果：首屏加载时间从 3.2s 降至 1.1s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据库查询优化&lt;/li&gt;
&lt;li&gt;前端渲染优化&lt;/li&gt;
&lt;li&gt;资源加载优化&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;7. &lt;code&gt;test&lt;/code&gt; - 测试相关&lt;/h3&gt;
&lt;p&gt;添加或修改测试代码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 添加单元测试
test(auth): 为登录功能添加单元测试

# 修复测试
test: 修复因 API 变更导致的测试失败

# E2E 测试
test(e2e): 添加用户注册流程的端到端测试
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新增单元测试&lt;/li&gt;
&lt;li&gt;补充集成测试&lt;/li&gt;
&lt;li&gt;修复失败的测试用例&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8. &lt;code&gt;build&lt;/code&gt; - 构建系统&lt;/h3&gt;
&lt;p&gt;影响构建系统或外部依赖的变更。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 依赖更新
build: 升级 React 至 v19.0.0

# 构建配置
build(webpack): 优化生产环境打包配置

# 依赖管理
build: 移除未使用的依赖包

删除以下依赖：
- lodash (使用原生 ES6 替代)
- moment (迁移至 date-fns)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;升级/降级依赖版本&lt;/li&gt;
&lt;li&gt;修改 Webpack/Vite 配置&lt;/li&gt;
&lt;li&gt;调整打包策略&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;9. &lt;code&gt;ci&lt;/code&gt; - 持续集成&lt;/h3&gt;
&lt;p&gt;修改 CI/CD 配置文件或脚本。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# GitHub Actions
ci: 添加自动部署到生产环境的 workflow

# 测试覆盖率
ci: 集成 Codecov 代码覆盖率检查

# Docker 配置
ci(docker): 优化镜像构建缓存策略
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;修改 GitHub Actions/GitLab CI 配置&lt;/li&gt;
&lt;li&gt;调整自动化测试流程&lt;/li&gt;
&lt;li&gt;优化 Docker 构建&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;10. &lt;code&gt;chore&lt;/code&gt; - 其他杂项&lt;/h3&gt;
&lt;p&gt;不属于以上类型的其他变更。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 配置文件
chore: 更新 .gitignore 忽略规则

# 脚本工具
chore: 添加数据库迁移脚本

# 依赖维护
chore: 更新开发依赖至最新版本
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;修改配置文件&lt;/li&gt;
&lt;li&gt;更新开发工具&lt;/li&gt;
&lt;li&gt;不影响源代码的变更&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;11. &lt;code&gt;revert&lt;/code&gt; - 回滚&lt;/h3&gt;
&lt;p&gt;撤销之前的提交。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 回滚提交
revert: 回滚 &quot;feat: 添加实验性功能&quot;

This reverts commit a1b2c3d4.

原因：该功能在生产环境导致性能问题
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;撤销有问题的提交&lt;/li&gt;
&lt;li&gt;回退不稳定的功能&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;作用域（Scope）的使用&lt;/h2&gt;
&lt;p&gt;作用域用于标识变更影响的模块或范围，常见示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;feat(auth): 添加 OAuth2 登录
fix(ui): 修复按钮样式错误
perf(api): 优化用户列表查询接口
docs(readme): 更新快速开始指南
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;常见作用域示例&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;前端项目&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;components&lt;/code&gt; - 组件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pages&lt;/code&gt; - 页面&lt;/li&gt;
&lt;li&gt;&lt;code&gt;store&lt;/code&gt; - 状态管理&lt;/li&gt;
&lt;li&gt;&lt;code&gt;router&lt;/code&gt; - 路由&lt;/li&gt;
&lt;li&gt;&lt;code&gt;utils&lt;/code&gt; - 工具函数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;后端项目&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;api&lt;/code&gt; - API 接口&lt;/li&gt;
&lt;li&gt;&lt;code&gt;database&lt;/code&gt; - 数据库&lt;/li&gt;
&lt;li&gt;&lt;code&gt;middleware&lt;/code&gt; - 中间件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;auth&lt;/code&gt; - 认证/授权&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config&lt;/code&gt; - 配置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;全栈通用&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;deps&lt;/code&gt; - 依赖&lt;/li&gt;
&lt;li&gt;&lt;code&gt;security&lt;/code&gt; - 安全&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i18n&lt;/code&gt; - 国际化&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;破坏性变更（Breaking Changes）&lt;/h2&gt;
&lt;p&gt;当提交包含不兼容的 API 变更时，需要特殊标注。&lt;/p&gt;
&lt;h3&gt;方式 1：使用感叹号&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;feat!: 移除对 IE11 的支持

BREAKING CHANGE: 不再支持 Internet Explorer 11
建议用户升级至现代浏览器（Chrome/Firefox/Edge）
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;方式 2：在 Footer 中说明&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;refactor(api): 重构用户接口返回格式

BREAKING CHANGE: 用户接口返回结构变更
旧格式：{ data: { user: {...} } }
新格式：{ user: {...} }

迁移指南：https://docs.example.com/migration-v2
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;关联 Issue&lt;/h2&gt;
&lt;p&gt;在 Footer 中关联相关的 Issue 或 PR：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fix(api): 修复用户注册邮箱验证失败

修复了正则表达式错误，现在支持所有合法邮箱格式

Closes #123
Fixes #456, #789
Related to #101
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;常用关键词&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Closes&lt;/code&gt; / &lt;code&gt;Fixes&lt;/code&gt; - 关闭 Issue&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Resolves&lt;/code&gt; - 解决 Issue&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Related to&lt;/code&gt; - 相关 Issue&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Refs&lt;/code&gt; - 引用 Issue&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;实践建议&lt;/h2&gt;
&lt;h3&gt;✅ 推荐做法&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 使用祈使句，现在时&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;✅ feat: 添加用户导出功能
✅ fix: 修复内存泄漏问题

❌ feat: 已添加用户导出功能
❌ fix: 修复了内存泄漏问题
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 首字母小写（英文）或无需大写（中文）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;✅ feat: add user export feature
✅ feat: 添加用户导出功能

❌ feat: Add user export feature
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 结尾不加句号&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;✅ docs: 更新 API 文档
❌ docs: 更新 API 文档。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. 主题简明扼要（&amp;lt; 50 字符）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;✅ fix: 修复登录状态丢失
❌ fix: 修复了当用户在登录后刷新页面时会导致登录状态丢失的问题
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;5. 一次提交只做一件事&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;✅ 
  feat: 添加暗黑模式
  fix: 修复按钮点击无响应

❌ 
  feat: 添加暗黑模式并修复按钮点击无响应
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;⚠️ 常见错误&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 类型错误&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❌ feature: 添加新功能  # 应该用 feat
❌ bugfix: 修复问题     # 应该用 fix
❌ update: 更新文档     # 应该用 docs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 混淆 style 和 feat&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❌ style: 添加暗黑模式样式  # style 是格式化，这应该是 feat(ui)
✅ feat(ui): 添加暗黑模式样式
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 提交粒度过大&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❌ feat: 实现整个用户管理模块

# 应该拆分成多个提交
✅ feat(user): 添加用户列表页面
✅ feat(user): 添加用户创建功能
✅ feat(user): 添加用户编辑功能
✅ feat(user): 添加用户删除功能
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;工具推荐&lt;/h2&gt;
&lt;h3&gt;1. Commitizen - 交互式提交&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 安装
npm install -g commitizen
cz-conventional-changelog

# 使用
git cz  # 代替 git commit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行后会有交互式提示，引导你填写规范的提交信息。&lt;/p&gt;
&lt;h3&gt;2. Commitlint - 提交信息校验&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 安装
npm install --save-dev @commitlint/cli @commitlint/config-conventional

# 配置 commitlint.config.js
module.exports = {
  extends: [&apos;@commitlint/config-conventional&apos;]
}

# 结合 Husky 使用
npx husky add .husky/commit-msg &apos;npx --no -- commitlint --edit &quot;$1&quot;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Standard Version - 自动生成 Changelog&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 安装
npm install --save-dev standard-version

# 使用
npm run release

# 自动：
# 1. 根据提交历史确定版本号
# 2. 生成 CHANGELOG.md
# 3. 创建 Git Tag
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实际案例对比&lt;/h2&gt;
&lt;h3&gt;案例 1：功能开发&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;❌ 不规范&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git commit -m &quot;update&quot;
git commit -m &quot;add some files&quot;
git commit -m &quot;fix bug&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;✅ 规范&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git commit -m &quot;feat(cart): 添加购物车商品数量修改功能&quot;
git commit -m &quot;style(cart): 格式化购物车组件代码&quot;
git commit -m &quot;fix(cart): 修复删除商品后总价未更新的问题&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;案例 2：依赖升级&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;❌ 不规范&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git commit -m &quot;update dependencies&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;✅ 规范&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;build(deps): 升级核心依赖至最新稳定版

- React: 18.2.0 → 19.0.0
- TypeScript: 5.2.2 → 5.3.3
- Vite: 5.0.0 → 5.1.0

测试通过，无破坏性变更
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;案例 3：紧急修复&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;❌ 不规范&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git commit -m &quot;hotfix&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;✅ 规范&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fix(auth): 修复生产环境 Token 过期导致的登出问题

问题描述：服务器时区设置错误导致 Token 提前过期
解决方案：统一使用 UTC 时间戳进行验证

Fixes #1234
Priority: Critical
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;规范的 Git 提交信息是团队协作的重要实践：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;核心类型要记牢&lt;/strong&gt;：&lt;code&gt;feat&lt;/code&gt;（新功能）、&lt;code&gt;fix&lt;/code&gt;（修复）、&lt;code&gt;docs&lt;/code&gt;（文档）、&lt;code&gt;style&lt;/code&gt;（格式）、&lt;code&gt;refactor&lt;/code&gt;（重构）、&lt;code&gt;perf&lt;/code&gt;（性能）、&lt;code&gt;test&lt;/code&gt;（测试）、&lt;code&gt;build&lt;/code&gt;（构建）、&lt;code&gt;ci&lt;/code&gt;（持续集成）、&lt;code&gt;chore&lt;/code&gt;（杂项）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;格式统一很重要&lt;/strong&gt;：&lt;code&gt;&amp;lt;type&amp;gt;(&amp;lt;scope&amp;gt;): &amp;lt;subject&amp;gt;&lt;/code&gt; 是基础，复杂变更补充 body 和 footer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一次提交一件事&lt;/strong&gt;：小步快跑，便于回滚和追溯&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;善用工具提效率&lt;/strong&gt;：Commitizen、Commitlint、Standard Version 让规范落地更简单&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;团队共识是关键&lt;/strong&gt;：制定符合自己团队的规范，并坚持执行&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;记住：清晰的提交历史就像是代码的时光机，让未来的你和团队成员能够快速理解每一次变更的来龙去脉。&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>nest-learn-day1</title><link>https://sukha.dev/posts/nest-learn-d1</link><guid isPermaLink="true">https://sukha.dev/posts/nest-learn-d1</guid><description>学习nestjs第一天</description><pubDate>Fri, 26 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note
&lt;em&gt;&lt;strong&gt;NestJS 核心概念与实战总结&lt;/strong&gt;&lt;/em&gt;
本文档基于当前项目 (&lt;code&gt;nest.js/demo&lt;/code&gt;) 的代码结构与实践，总结 NestJS 的核心原理及常用开发方式。
:::&lt;/p&gt;
&lt;h2&gt;1. IoC (控制反转) 与装饰器原理&lt;/h2&gt;
&lt;h3&gt;1.1 IoC (Inversion of Control) 控制反转 / DI (Dependency Injection) 依赖注入&lt;/h3&gt;
&lt;p&gt;NestJS 的核心设计基于 IoC 容器。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;原理&lt;/strong&gt;：在传统的开发中，如果 &lt;code&gt;UserController&lt;/code&gt; 需要使用 &lt;code&gt;UserService&lt;/code&gt;，通常需要在 Controller 内部 &lt;code&gt;new UserService()&lt;/code&gt;。而在 NestJS 中，控制权被反转了。对象的创建和依赖关系的组装由 &lt;strong&gt;Nest IoC 容器&lt;/strong&gt;（Runtime System）负责，而不是由开发者手动管理。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;实现&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Provider 定义&lt;/strong&gt;：通过 &lt;code&gt;@Injectable()&lt;/code&gt; 装饰器将 &lt;code&gt;UserService&lt;/code&gt; 标记为可被注入的服务。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Module 注册&lt;/strong&gt;：在 &lt;code&gt;UserModule&lt;/code&gt; 或 &lt;code&gt;AppModule&lt;/code&gt; 的 &lt;code&gt;providers&lt;/code&gt; 数组中注册该服务。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;构造器注入&lt;/strong&gt;：在 &lt;code&gt;UserController&lt;/code&gt; 的构造函数中声明依赖类型，Nest 运行时会自动查找并注入实例。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// src/user/user.controller.ts
export class UserController {
  // 这里的 private readonly userService 实际上就是一种简写的依赖注入声明
  constructor(private readonly userService: UserService) {}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 装饰器 (Decorators) 原理&lt;/h3&gt;
&lt;p&gt;装饰器是 NestJS 的语法糖核心，本质上是 &lt;strong&gt;高阶函数&lt;/strong&gt;，用于在设计阶段为类、方法或属性添加 &lt;strong&gt;元数据 (Metadata)&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;技术基础&lt;/strong&gt;：依赖于 TS 的 &lt;code&gt;experimentalDecorators&lt;/code&gt; 特性和 &lt;code&gt;reflect-metadata&lt;/code&gt; 库。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工作流程&lt;/strong&gt;：
&lt;ol&gt;
&lt;li&gt;当代码运行时，装饰器函数（如 &lt;code&gt;@Controller&lt;/code&gt;, &lt;code&gt;@Get&lt;/code&gt;）被执行。&lt;/li&gt;
&lt;li&gt;装饰器通过 &lt;code&gt;Reflect.defineMetadata&lt;/code&gt; 将配置信息（如路径路由、HTTP 方法、请求参数位置等）存储在目标对象的原型链上。&lt;/li&gt;
&lt;li&gt;NestFactory 在启动时扫描所有注册的 Module，利用反射机制读取这些元数据，从而构建路由映射表和依赖关系图。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. Controller (控制器) 的使用方式&lt;/h2&gt;
&lt;p&gt;控制器负责处理传入的 &lt;strong&gt;Request&lt;/strong&gt; 并向客户端返回 &lt;strong&gt;Response&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;2.1 基础定义&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;@Controller&lt;/code&gt; 装饰器定义，可以指定路由前缀和版本。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// src/user/user.controller.ts
@Controller({
  path: &apos;user&apos;, // 路由前缀，访问路径为 /user
  version: &apos;1&apos;, // 版本控制，若启用URI版本，访问路径可能为 /v1/user
})
export class UserController { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 响应处理&lt;/h3&gt;
&lt;p&gt;NestJS 默认使用标准模式：当请求处理函数返回一个 JavaScript 对象或数组时，它会自动序列化为 JSON。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;状态码&lt;/strong&gt;：默认 200 (POST 为 201)。可以使用 &lt;code&gt;@HttpCode(500)&lt;/code&gt; 修改。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Headers&lt;/strong&gt;：可以使用 &lt;code&gt;@Header(&apos;Cache-Control&apos;, &apos;none&apos;)&lt;/code&gt; 添加响应头。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. GET / POST 写法与传参&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;UserController&lt;/code&gt; 中可以看到多种传参方式的实战应用。&lt;/p&gt;
&lt;h3&gt;3.1 GET 请求与传参&lt;/h3&gt;
&lt;p&gt;GET 请求通常用于获取数据，参数通过 &lt;strong&gt;URL 路径 (Params)&lt;/strong&gt; 或 &lt;strong&gt;查询字符串 (Query)&lt;/strong&gt; 传递。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;查询字符串 (&lt;code&gt;@Query&lt;/code&gt; 或 &lt;code&gt;req.query&lt;/code&gt;)&lt;/strong&gt;：
用于处理如 &lt;code&gt;/user?name=test&lt;/code&gt; 的请求。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 方式一：使用 @Request() 获取底层 Express 对象 (不推荐，耦合了 Express)
@Get()
findAll(@Request() req: ExpressRequest) {
  return { message: req.query.name };
}

// 方式二：使用 @Query() 装饰器 (推荐，Nest 风格)
@Get(&apos;find&apos;)
findByQuery(@Query(&apos;name&apos;) name: string) {
   return { message: name };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;路径参数 (&lt;code&gt;@Param&lt;/code&gt;)&lt;/strong&gt;：
用于处理如 &lt;code&gt;/user/123&lt;/code&gt; 的请求。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Get(&apos;:id&apos;) // 定义动态路径参数 :id
findId(@Param(&apos;id&apos;) id: string) {
  console.log(id);
  return { message: id };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.2 POST 请求与传参&lt;/h3&gt;
&lt;p&gt;POST 请求通常用于提交数据，参数通过 &lt;strong&gt;请求体 (Body)&lt;/strong&gt; 传递。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;请求体 (&lt;code&gt;@Body&lt;/code&gt;)&lt;/strong&gt;：
用于接收 JSON 表单数据。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 读取 Body 中的特定字段 &apos;age&apos;
@Post()
create(@Body(&apos;age&apos;) body: number) {
  console.log(body);
  return { code: 200, message: body };
}

// 读取完整 Body 并使用 DTO (推荐)
// create(@Body() createUserDto: CreateUserDto) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DTO (Data Transfer Object)&lt;/strong&gt;：
虽然示例代码中部分使用了简单类型，但最佳实践是定义 DTO 类（如 &lt;code&gt;src/user/dto/create-user.dto.ts&lt;/code&gt;），结合 &lt;code&gt;ValidationPipe&lt;/code&gt; 进行参数验证。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. Nest CLI 脚手架的使用方法&lt;/h2&gt;
&lt;p&gt;Nest CLI 是极其强大的生产力工具，遵循 &quot;Convention over Configuration&quot; (约定优于配置) 原则。&lt;/p&gt;
&lt;h3&gt;4.1 安装与初始化&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm i -g @nestjs/cli      # 全局安装
nest new project-name     # 创建新项目
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 常用生成命令 (Generator)&lt;/h3&gt;
&lt;p&gt;在项目根目录下运行，会自动创建文件并更新 &lt;code&gt;app.module.ts&lt;/code&gt; 等文件。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;简写&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nest generate module [name]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nest g mo [name]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成模块&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nest g mo user&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nest generate controller [name]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nest g co [name]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成控制器&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nest g co user&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nest generate service [name]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nest g s [name]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成服务&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nest g s user&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nest generate resource [name]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nest g res [name]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;生成完整的 CRUD 资源&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nest g res order&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示&lt;/strong&gt;：使用 &lt;code&gt;nest g res [name]&lt;/code&gt; 是最快的方式，它会一次性生成 Module, Controller, Service, DTO, Entity 并且配置好基本的 CRUD 代码结构。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;4.3 运行项目&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm run start        # 启动
npm run start:dev    # 启动并监听文件变化 (开发模式)
npm run start:debug  # 调试模式
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><author>sukha</author></item><item><title>前后端联动实现指南</title><link>https://sukha.dev/posts/%E5%89%8D%E5%90%8E%E7%AB%AF%E8%81%94%E5%8A%A8%E5%AE%9E%E7%8E%B0%E6%8C%87%E5%8D%97</link><guid isPermaLink="true">https://sukha.dev/posts/%E5%89%8D%E5%90%8E%E7%AB%AF%E8%81%94%E5%8A%A8%E5%AE%9E%E7%8E%B0%E6%8C%87%E5%8D%97</guid><description>这是本人在凌晨使用ai写的一篇前后端联动实现指南，以供日后参考。</description><pubDate>Mon, 29 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;项目概览&lt;/h2&gt;
&lt;p&gt;本项目实现了一个前后端分离的用户管理系统，演示了如何使用 NestJS 和 React 进行前后端联动开发。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;功能特性：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户 CRUD 操作（创建、查询、更新、删除）&lt;/li&gt;
&lt;li&gt;前后端类型安全&lt;/li&gt;
&lt;li&gt;Vite 开发代理配置&lt;/li&gt;
&lt;li&gt;Prisma ORM 数据库操作&lt;/li&gt;
&lt;li&gt;RESTful API 设计&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;技术栈&lt;/h2&gt;
&lt;h3&gt;后端 (db-connection)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;框架&lt;/strong&gt;: NestJS 11.x&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ORM&lt;/strong&gt;: Prisma 5.x&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据库&lt;/strong&gt;: MySQL&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;语言&lt;/strong&gt;: TypeScript 5.x&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运行时&lt;/strong&gt;: Node.js&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;前端 (db-connect-front)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;框架&lt;/strong&gt;: React 19.x&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;构建工具&lt;/strong&gt;: Vite 7.x&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;语言&lt;/strong&gt;: TypeScript 5.x&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;包管理器&lt;/strong&gt;: pnpm&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;项目结构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;front-back-end/
├── db-connection/              # 后端项目
│   ├── src/
│   │   ├── main.ts            # 应用入口
│   │   ├── app.module.ts      # 根模块
│   │   ├── prisma/            # Prisma 配置
│   │   │   ├── prisma.service.ts
│   │   │   └── prisma.module.ts
│   │   └── user/              # 用户模块
│   │       ├── user.controller.ts
│   │       ├── user.service.ts
│   │       ├── user.module.ts
│   │       └── dto/
│   │           ├── create-user.dto.ts
│   │           └── update-user.dto.ts
│   ├── prisma/
│   │   └── schema.prisma      # 数据库模型定义
│   └── package.json
│
└── db-connect-front/          # 前端项目
    ├── src/
    │   ├── App.tsx            # 主应用组件
    │   └── main.tsx           # 应用入口
    ├── vite.config.ts         # Vite 配置（代理设置）
    └── package.json
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;后端实现&lt;/h2&gt;
&lt;h3&gt;1. 初始化 NestJS 项目&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 创建 NestJS 项目
npm i -g @nestjs/cli
nest new db-connection

# 进入项目目录
cd db-connection

# 安装 Prisma
pnpm add @prisma/client
pnpm add -D prisma

# 初始化 Prisma
npx prisma init
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 配置数据库连接&lt;/h3&gt;
&lt;p&gt;创建或编辑 &lt;code&gt;.env&lt;/code&gt; 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DATABASE_URL=&quot;mysql://username:password@localhost:3306/database_name&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 定义数据库模型&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;prisma/schema.prisma&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;generator client {
  provider = &quot;prisma-client-js&quot;
}

datasource db {
  provider = &quot;mysql&quot;
  url      = env(&quot;DATABASE_URL&quot;)
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 创建 Prisma Service&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;src/prisma/prisma.service.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Injectable, OnModuleDestroy, OnModuleInit } from &apos;@nestjs/common&apos;;
import { PrismaClient } from &apos;@prisma/client&apos;;

@Injectable()
export class PrismaService
  extends PrismaClient
  implements OnModuleInit, OnModuleDestroy
{
  async onModuleInit(): Promise&amp;lt;void&amp;gt; {
    await this.$connect();
  }

  async onModuleDestroy(): Promise&amp;lt;void&amp;gt; {
    await this.$disconnect();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;src/prisma/prisma.module.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Global, Module } from &apos;@nestjs/common&apos;;
import { PrismaService } from &apos;./prisma.service&apos;;

@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 生成 User 模块&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 使用 NestJS CLI 生成用户模块
nest g resource user
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 定义 DTO（数据传输对象）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;src/user/dto/create-user.dto.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export class CreateUserDto {
  email: string;
  name?: string;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;src/user/dto/update-user.dto.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { PartialType } from &apos;@nestjs/mapped-types&apos;;
import { CreateUserDto } from &apos;./create-user.dto&apos;;

export class UpdateUserDto extends PartialType(CreateUserDto) {}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7. 实现 User Service&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;src/user/user.service.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Injectable, NotFoundException } from &apos;@nestjs/common&apos;;
import { PrismaService } from &apos;../prisma/prisma.service&apos;;
import { CreateUserDto } from &apos;./dto/create-user.dto&apos;;
import { UpdateUserDto } from &apos;./dto/update-user.dto&apos;;

@Injectable()
export class UserService {
  constructor(private readonly prisma: PrismaService) {}

  create(createUserDto: CreateUserDto) {
    return this.prisma.user.create({ data: createUserDto });
  }

  findAll() {
    return this.prisma.user.findMany({ orderBy: { id: &apos;asc&apos; } });
  }

  async findOne(id: number) {
    const user = await this.prisma.user.findUnique({ where: { id } });
    if (!user) {
      throw new NotFoundException(`User ${id} not found`);
    }
    return user;
  }

  async update(id: number, updateUserDto: UpdateUserDto) {
    await this.findOne(id);
    return this.prisma.user.update({
      where: { id },
      data: updateUserDto,
    });
  }

  async remove(id: number) {
    await this.findOne(id);
    await this.prisma.user.delete({ where: { id } });
    return { success: true };
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8. 实现 User Controller&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;src/user/user.controller.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
} from &apos;@nestjs/common&apos;;
import { UserService } from &apos;./user.service&apos;;
import { CreateUserDto } from &apos;./dto/create-user.dto&apos;;
import { UpdateUserDto } from &apos;./dto/update-user.dto&apos;;

@Controller(&apos;users&apos;)
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.userService.create(createUserDto);
  }

  @Get()
  findAll() {
    return this.userService.findAll();
  }

  @Get(&apos;:id&apos;)
  findOne(@Param(&apos;id&apos;) id: string) {
    return this.userService.findOne(+id);
  }

  @Patch(&apos;:id&apos;)
  update(@Param(&apos;id&apos;) id: string, @Body() updateUserDto: UpdateUserDto) {
    return this.userService.update(+id, updateUserDto);
  }

  @Delete(&apos;:id&apos;)
  remove(@Param(&apos;id&apos;) id: string) {
    return this.userService.remove(+id);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;9. 注册模块&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;src/user/user.module.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Module } from &apos;@nestjs/common&apos;;
import { UserService } from &apos;./user.service&apos;;
import { UserController } from &apos;./user.controller&apos;;

@Module({
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;src/app.module.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Module } from &apos;@nestjs/common&apos;;
import { AppController } from &apos;./app.controller&apos;;
import { AppService } from &apos;./app.service&apos;;
import { PrismaModule } from &apos;./prisma/prisma.module&apos;;
import { UserModule } from &apos;./user/user.module&apos;;

@Module({
  imports: [PrismaModule, UserModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;10. 配置应用入口&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;src/main.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { NestFactory } from &apos;@nestjs/core&apos;;
import { AppModule } from &apos;./app.module&apos;;

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;11. 同步数据库&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 生成 Prisma Client
npx prisma generate

# 推送数据库模式（开发环境）
npx prisma db push

# 或者使用迁移（生产环境推荐）
npx prisma migrate dev --name init

# 打开 Prisma Studio 查看数据
npx prisma studio
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;前端实现&lt;/h2&gt;
&lt;h3&gt;1. 初始化 React 项目&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 创建 Vite + React + TypeScript 项目
pnpm create vite db-connect-front --template react-ts

# 进入项目目录
cd db-connect-front

# 安装依赖
pnpm install
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 配置 Vite 代理&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;vite.config.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { defineConfig } from &apos;vite&apos;
import react from &apos;@vitejs/plugin-react&apos;

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      &apos;/api&apos;: {
        target: &apos;http://localhost:3000&apos;,  // 后端服务地址
        changeOrigin: true,
        rewrite: (path) =&amp;gt; path.replace(/^\/api/, &apos;&apos;),
      },
    },
  },
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;代理工作原理：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前端请求 &lt;code&gt;/api/users&lt;/code&gt; 会被代理到 &lt;code&gt;http://localhost:3000/users&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rewrite&lt;/code&gt; 函数移除了 &lt;code&gt;/api&lt;/code&gt; 前缀&lt;/li&gt;
&lt;li&gt;&lt;code&gt;changeOrigin: true&lt;/code&gt; 修改请求头的 origin&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 创建用户表单组件&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;src/App.tsx&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useState } from &apos;react&apos;
import type { FormEvent } from &apos;react&apos;

interface CreateUserDto {
  email: string
  name?: string
}

export default function App() {
  const [email, setEmail] = useState(&apos;&apos;)
  const [name, setName] = useState(&apos;&apos;)
  const [loading, setLoading] = useState(false)
  const [message, setMessage] = useState&amp;lt;{ type: &apos;success&apos; | &apos;error&apos;; text: string } | null&amp;gt;(null)

  const handleSubmit = async (e: FormEvent) =&amp;gt; {
    e.preventDefault()
    setLoading(true)
    setMessage(null)

    try {
      const userData: CreateUserDto = {
        email,
        ...(name &amp;amp;&amp;amp; { name })
      }

      const response = await fetch(&apos;/api/users&apos;, {
        method: &apos;POST&apos;,
        headers: {
          &apos;Content-Type&apos;: &apos;application/json&apos;,
        },
        body: JSON.stringify(userData),
      })

      if (!response.ok) {
        throw new Error(&apos;创建用户失败&apos;)
      }

      const result = await response.json()
      setMessage({ type: &apos;success&apos;, text: &apos;用户创建成功！&apos; })
      setEmail(&apos;&apos;)
      setName(&apos;&apos;)
      console.log(&apos;创建的用户:&apos;, result)
    } catch (error) {
      setMessage({
        type: &apos;error&apos;,
        text: error instanceof Error ? error.message : &apos;创建用户时发生错误&apos;
      })
    } finally {
      setLoading(false)
    }
  }

  return (
    &amp;lt;div style={{ maxWidth: &apos;500px&apos;, margin: &apos;50px auto&apos;, padding: &apos;20px&apos; }}&amp;gt;
      &amp;lt;h1&amp;gt;创建用户&amp;lt;/h1&amp;gt;

      &amp;lt;form onSubmit={handleSubmit} style={{ display: &apos;flex&apos;, flexDirection: &apos;column&apos;, gap: &apos;15px&apos; }}&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;label htmlFor=&quot;email&quot; style={{ display: &apos;block&apos;, marginBottom: &apos;5px&apos; }}&amp;gt;
            邮箱 &amp;lt;span style={{ color: &apos;red&apos; }}&amp;gt;*&amp;lt;/span&amp;gt;
          &amp;lt;/label&amp;gt;
          &amp;lt;input
            id=&quot;email&quot;
            type=&quot;email&quot;
            value={email}
            onChange={(e) =&amp;gt; setEmail(e.target.value)}
            required
            style={{
              width: &apos;100%&apos;,
              padding: &apos;8px&apos;,
              fontSize: &apos;14px&apos;,
              border: &apos;1px solid #ccc&apos;,
              borderRadius: &apos;4px&apos;
            }}
            placeholder=&quot;请输入邮箱&quot;
          /&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div&amp;gt;
          &amp;lt;label htmlFor=&quot;name&quot; style={{ display: &apos;block&apos;, marginBottom: &apos;5px&apos; }}&amp;gt;
            姓名
          &amp;lt;/label&amp;gt;
          &amp;lt;input
            id=&quot;name&quot;
            type=&quot;text&quot;
            value={name}
            onChange={(e) =&amp;gt; setName(e.target.value)}
            style={{
              width: &apos;100%&apos;,
              padding: &apos;8px&apos;,
              fontSize: &apos;14px&apos;,
              border: &apos;1px solid #ccc&apos;,
              borderRadius: &apos;4px&apos;
            }}
            placeholder=&quot;请输入姓名（可选）&quot;
          /&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;button
          type=&quot;submit&quot;
          disabled={loading}
          style={{
            padding: &apos;10px&apos;,
            fontSize: &apos;16px&apos;,
            backgroundColor: loading ? &apos;#ccc&apos; : &apos;#007bff&apos;,
            color: &apos;white&apos;,
            border: &apos;none&apos;,
            borderRadius: &apos;4px&apos;,
            cursor: loading ? &apos;not-allowed&apos; : &apos;pointer&apos;
          }}
        &amp;gt;
          {loading ? &apos;创建中...&apos; : &apos;创建用户&apos;}
        &amp;lt;/button&amp;gt;
      &amp;lt;/form&amp;gt;

      {message &amp;amp;&amp;amp; (
        &amp;lt;div
          style={{
            marginTop: &apos;20px&apos;,
            padding: &apos;10px&apos;,
            borderRadius: &apos;4px&apos;,
            backgroundColor: message.type === &apos;success&apos; ? &apos;#d4edda&apos; : &apos;#f8d7da&apos;,
            color: message.type === &apos;success&apos; ? &apos;#155724&apos; : &apos;#721c24&apos;,
            border: `1px solid ${message.type === &apos;success&apos; ? &apos;#c3e6cb&apos; : &apos;#f5c6cb&apos;}`
          }}
        &amp;gt;
          {message.text}
        &amp;lt;/div&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;前后端联动配置&lt;/h2&gt;
&lt;h3&gt;关键配置点&lt;/h3&gt;
&lt;h4&gt;1. 后端端口配置&lt;/h4&gt;
&lt;p&gt;后端默认运行在 &lt;code&gt;http://localhost:3000&lt;/code&gt;（src/main.ts:6）&lt;/p&gt;
&lt;h4&gt;2. 前端代理配置&lt;/h4&gt;
&lt;p&gt;Vite 配置文件中设置代理规则（vite.config.ts:8-13）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;proxy: {
  &apos;/api&apos;: {
    target: &apos;http://localhost:3000&apos;,
    changeOrigin: true,
    rewrite: (path) =&amp;gt; path.replace(/^\/api/, &apos;&apos;),
  },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. 前端 API 调用&lt;/h4&gt;
&lt;p&gt;前端统一使用 &lt;code&gt;/api&lt;/code&gt; 前缀调用后端接口：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fetch(&apos;/api/users&apos;, { method: &apos;POST&apos;, ... })
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;数据流转过程&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[前端页面]
    ↓ 用户提交表单
[fetch(&apos;/api/users&apos;)]
    ↓ Vite 代理拦截
[重写为 http://localhost:3000/users]
    ↓ 发送 HTTP 请求
[NestJS UserController @Post()]
    ↓ 调用 Service
[UserService.create()]
    ↓ Prisma ORM
[MySQL 数据库]
    ↓ 返回创建的用户
[响应返回前端]
    ↓ 显示成功消息
[用户界面更新]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;运行项目&lt;/h2&gt;
&lt;h3&gt;1. 启动后端&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cd db-connection

# 首次运行需要配置数据库
# 1. 编辑 .env 文件配置 DATABASE_URL
# 2. 生成 Prisma Client
pnpm prisma:generate

# 3. 同步数据库结构
pnpm prisma:push

# 启动开发服务器
pnpm start:dev

# 服务将运行在 http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;后端可用脚本：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm start:dev        # 开发模式（热重载）
pnpm start:prod       # 生产模式
pnpm prisma:generate  # 生成 Prisma Client
pnpm prisma:push      # 推送数据库架构
pnpm prisma:studio    # 打开 Prisma Studio
pnpm build            # 构建项目
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 启动前端&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 在新的终端窗口
cd db-connect-front

# 安装依赖（如果还没有）
pnpm install

# 启动开发服务器
pnpm dev

# 服务将运行在 http://localhost:5173
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;前端可用脚本：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm dev      # 开发模式
pnpm build    # 构建生产版本
pnpm preview  # 预览生产构建
pnpm lint     # 运行 ESLint
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 测试前后端联动&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;确保后端运行在 &lt;code&gt;http://localhost:3000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;确保前端运行在 &lt;code&gt;http://localhost:5173&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;打开浏览器访问 &lt;code&gt;http://localhost:5173&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;填写表单提交，观察控制台和数据库&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;API 接口文档&lt;/h2&gt;
&lt;h3&gt;基础 URL&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;开发环境: &lt;code&gt;http://localhost:3000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;前端代理: &lt;code&gt;/api&lt;/code&gt; (会被重写到后端)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;用户接口&lt;/h3&gt;
&lt;h4&gt;1. 创建用户&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;POST /users
Content-Type: application/json

{
  &quot;email&quot;: &quot;user@example.com&quot;,
  &quot;name&quot;: &quot;张三&quot;  // 可选
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;响应示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;id&quot;: 1,
  &quot;email&quot;: &quot;user@example.com&quot;,
  &quot;name&quot;: &quot;张三&quot;,
  &quot;createdAt&quot;: &quot;2025-12-29T10:00:00.000Z&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. 获取所有用户&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;GET /users
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;响应示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[
  {
    &quot;id&quot;: 1,
    &quot;email&quot;: &quot;user@example.com&quot;,
    &quot;name&quot;: &quot;张三&quot;,
    &quot;createdAt&quot;: &quot;2025-12-29T10:00:00.000Z&quot;
  }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. 获取单个用户&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;GET /users/:id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;响应示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;id&quot;: 1,
  &quot;email&quot;: &quot;user@example.com&quot;,
  &quot;name&quot;: &quot;张三&quot;,
  &quot;createdAt&quot;: &quot;2025-12-29T10:00:00.000Z&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4. 更新用户&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;PATCH /users/:id
Content-Type: application/json

{
  &quot;email&quot;: &quot;newemail@example.com&quot;,
  &quot;name&quot;: &quot;李四&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;5. 删除用户&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;DELETE /users/:id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;响应示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;success&quot;: true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;完整代码示例&lt;/h2&gt;
&lt;h3&gt;前端调用所有接口示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 创建用户
const createUser = async (email: string, name?: string) =&amp;gt; {
  const response = await fetch(&apos;/api/users&apos;, {
    method: &apos;POST&apos;,
    headers: { &apos;Content-Type&apos;: &apos;application/json&apos; },
    body: JSON.stringify({ email, name }),
  })
  return response.json()
}

// 获取所有用户
const getAllUsers = async () =&amp;gt; {
  const response = await fetch(&apos;/api/users&apos;)
  return response.json()
}

// 获取单个用户
const getUserById = async (id: number) =&amp;gt; {
  const response = await fetch(`/api/users/${id}`)
  return response.json()
}

// 更新用户
const updateUser = async (id: number, data: { email?: string; name?: string }) =&amp;gt; {
  const response = await fetch(`/api/users/${id}`, {
    method: &apos;PATCH&apos;,
    headers: { &apos;Content-Type&apos;: &apos;application/json&apos; },
    body: JSON.stringify(data),
  })
  return response.json()
}

// 删除用户
const deleteUser = async (id: number) =&amp;gt; {
  const response = await fetch(`/api/users/${id}`, {
    method: &apos;DELETE&apos;,
  })
  return response.json()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;后端完整的 package.json&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;db-connection&quot;,
  &quot;version&quot;: &quot;0.0.1&quot;,
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;nest build&quot;,
    &quot;prisma:generate&quot;: &quot;prisma generate&quot;,
    &quot;prisma:push&quot;: &quot;prisma db push&quot;,
    &quot;prisma:studio&quot;: &quot;prisma studio&quot;,
    &quot;start&quot;: &quot;nest start&quot;,
    &quot;start:dev&quot;: &quot;nest start --watch&quot;,
    &quot;start:prod&quot;: &quot;node dist/main&quot;
  },
  &quot;dependencies&quot;: {
    &quot;@nestjs/common&quot;: &quot;^11.0.1&quot;,
    &quot;@nestjs/core&quot;: &quot;^11.0.1&quot;,
    &quot;@nestjs/mapped-types&quot;: &quot;*&quot;,
    &quot;@nestjs/platform-express&quot;: &quot;^11.0.1&quot;,
    &quot;@prisma/client&quot;: &quot;^5.21.1&quot;,
    &quot;reflect-metadata&quot;: &quot;^0.2.2&quot;,
    &quot;rxjs&quot;: &quot;^7.8.1&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;@nestjs/cli&quot;: &quot;^11.0.0&quot;,
    &quot;@nestjs/schematics&quot;: &quot;^11.0.0&quot;,
    &quot;prisma&quot;: &quot;^5.21.1&quot;,
    &quot;typescript&quot;: &quot;^5.7.3&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;常见问题&lt;/h2&gt;
&lt;h3&gt;1. 前端请求后端失败（CORS 错误）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题：&lt;/strong&gt; 控制台显示 CORS 错误&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;确保 Vite 代理配置正确&lt;/li&gt;
&lt;li&gt;或者在后端启用 CORS：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// main.ts
const app = await NestFactory.create(AppModule);
app.enableCors();  // 启用 CORS
await app.listen(3000);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Prisma Client 未生成&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题：&lt;/strong&gt; 导入 &lt;code&gt;@prisma/client&lt;/code&gt; 报错&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd db-connection
npx prisma generate
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 数据库连接失败&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题：&lt;/strong&gt; Prisma 无法连接数据库&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;检查 &lt;code&gt;.env&lt;/code&gt; 文件中的 &lt;code&gt;DATABASE_URL&lt;/code&gt; 是否正确&lt;/li&gt;
&lt;li&gt;确保 MySQL 服务已启动&lt;/li&gt;
&lt;li&gt;确保数据库已创建&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 创建数据库（MySQL）
mysql -u root -p
CREATE DATABASE database_name;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 端口冲突&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题：&lt;/strong&gt; 端口 3000 或 5173 已被占用&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 后端修改端口（main.ts）
await app.listen(3001);

# 前端修改端口（vite.config.ts）
server: {
  port: 5174,
  proxy: { ... }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. Vite 代理不生效&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题：&lt;/strong&gt; 前端无法通过代理访问后端&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检查清单：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✓ 后端是否已启动&lt;/li&gt;
&lt;li&gt;✓ Vite dev server 是否已重启（修改配置后需要重启）&lt;/li&gt;
&lt;li&gt;✓ 请求路径是否以 &lt;code&gt;/api&lt;/code&gt; 开头&lt;/li&gt;
&lt;li&gt;✓ &lt;code&gt;rewrite&lt;/code&gt; 函数是否正确移除前缀&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6. TypeScript 类型错误&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题：&lt;/strong&gt; 类型不匹配或找不到类型&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 后端
pnpm prisma generate  # 重新生成 Prisma 类型

# 前端
# 确保 tsconfig.json 配置正确
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;开发最佳实践&lt;/h2&gt;
&lt;h3&gt;1. 环境变量管理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 创建 .env.example 作为模板
DATABASE_URL=&quot;mysql://user:password@localhost:3306/dbname&quot;
PORT=3000

# .env 添加到 .gitignore
echo &quot;.env&quot; &amp;gt;&amp;gt; .gitignore
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 数据库迁移&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 开发环境使用 db push（快速迭代）
pnpm prisma:push

# 生产环境使用迁移（版本控制）
npx prisma migrate dev --name add_user_model
npx prisma migrate deploy  # 生产部署
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 错误处理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 前端统一错误处理
const handleApiError = (error: unknown) =&amp;gt; {
  if (error instanceof Error) {
    console.error(&apos;API Error:&apos;, error.message)
  }
  // 显示用户友好的错误消息
}

// 后端全局异常过滤器
// 参考：https://docs.nestjs.com/exception-filters
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 类型共享（高级）&lt;/h3&gt;
&lt;p&gt;可以考虑创建共享的类型定义包，避免前后端类型重复定义：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 创建共享包
mkdir shared-types
# 定义 User、CreateUserDto 等类型
# 前后端都引用这个包
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;调试技巧&lt;/h2&gt;
&lt;h3&gt;后端调试&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 查看详细日志
pnpm start:dev

# 使用 Prisma Studio 查看数据
pnpm prisma:studio
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;前端调试&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 在浏览器控制台查看网络请求
// Network Tab -&amp;gt; 查看请求和响应

// 添加调试日志
console.log(&apos;发送的数据:&apos;, userData)
console.log(&apos;响应结果:&apos;, result)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;数据库调试&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 查看生成的 SQL
npx prisma studio

# 或直接连接数据库
mysql -u root -p
USE database_name;
SELECT * FROM User;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;本文档详细介绍了如何使用 NestJS + Prisma + React + Vite 实现前后端分离项目。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心知识点：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;NestJS 模块化架构（Controller、Service、Module）&lt;/li&gt;
&lt;li&gt;Prisma ORM 数据库操作&lt;/li&gt;
&lt;li&gt;Vite 开发代理配置&lt;/li&gt;
&lt;li&gt;RESTful API 设计&lt;/li&gt;
&lt;li&gt;TypeScript 类型安全&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;关键配置：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;后端运行在 &lt;code&gt;localhost:3000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;前端运行在 &lt;code&gt;localhost:5173&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Vite 代理 &lt;code&gt;/api/*&lt;/code&gt; → &lt;code&gt;http://localhost:3000/*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过这个项目，你已掌握了现代前后端分离开发的核心技能。可以在此基础上扩展更多功能，如身份认证、文件上传、WebSocket 等。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;文档创建时间&lt;/strong&gt;: 2025-12-29
&lt;strong&gt;技术栈版本&lt;/strong&gt;: NestJS 11.x | React 19.x | Prisma 5.x | Vite 7.x&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>部署一个属于自己的Memos想法随记</title><link>https://sukha.dev/posts/deploy-memos</link><guid isPermaLink="true">https://sukha.dev/posts/deploy-memos</guid><pubDate>Mon, 05 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;起因&lt;/h2&gt;
&lt;p&gt;今天在bilibili上发现了一个好玩的记录个人想法的开源项目&lt;a href=&quot;https://github.com/usememos/memos&quot;&gt;memos&lt;/a&gt;，感觉界面非常符合我的审美，交互也很好，所以我就想在我的mac上面部署一下，方便来记录我日常的想法。&lt;/p&gt;
&lt;h2&gt;环境准备&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;首先我们需要安装一下colima&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;brew install colima
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;我们需要安装一下docker-credential-helper&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;brew install docker-credential-helper
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;我们需要安装一下docker cli&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;brew install --formula docker
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;docker cli安装完成之后我们需要启动colima&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;colima start
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;memos部署&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;docker cli安装完成之后我们在进行memos镜像的拉取和运行&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;docker run -d --name memos --restart unless-stopped -p 5230:5230 -v ~/.memos:/var/opt/memos neosmemo/memos:stable
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;运行完成之后我们打开浏览器，访问&lt;code&gt;http://localhost:5230&lt;/code&gt;，就可以看到memos的登录页面啦。&lt;img src=&quot;./memos.png&quot; alt=&quot;memos-login&quot; /&gt;&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><author>sukha</author></item><item><title>容器挤压引发的思考</title><link>https://sukha.dev/posts/container-squeeze</link><guid isPermaLink="true">https://sukha.dev/posts/container-squeeze</guid><description>如何避免容器收起时内部元素挤压变形的问题</description><pubDate>Wed, 07 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;起因&lt;/h2&gt;
&lt;p&gt;最近在学next的时候发现siderbar收起的时候文字会发生挤压变形的现象，看起来非常不舒服，于是我就想找一个方法来避免这种现象的发生。&lt;/p&gt;
&lt;h2&gt;解决方法&lt;/h2&gt;
&lt;p&gt;我们可以通过transition + onTransitionEnd事件来实现一个过渡的效果，从而避免文字挤压变形的问题。下面是一个简单的示例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;use client&apos;
import { MenuIcon } from &apos;lucide-react&apos;
import { cn } from &apos;@/lib/utils&apos;
import { useState } from &apos;react&apos;
export default function Sidebar() {
  const [isToogled, setIsToggled] = useState(false)
  const [isLabelVisible, setIsLabelVisible] = useState(true)

  const toogle = () =&amp;gt; {
    setIsToggled((value) =&amp;gt; {
      if (!value) setIsLabelVisible(false)
      return !value
    })
  }

  return (
    &amp;lt;aside
      onTransitionEnd={(event) =&amp;gt; {
        if (event.propertyName === &apos;width&apos; &amp;amp;&amp;amp; !isToogled) setIsLabelVisible(true)
      }}
      className={cn(
        &apos;hidden shrink-0 overflow-hidden border-r bg-card/80 px-4 py-6 lg:block&apos;,
        !isToogled ? &apos;w-63&apos; : &apos;w-16&apos;,
        &apos;transition-[width] duration-300 ease-in-out&apos;
      )}
    &amp;gt;
      &amp;lt;div className=&quot;w-full flex items-center gap-4&quot;&amp;gt;
        &amp;lt;MenuIcon onClick={toogle} className=&apos;cursor-pointer&apos;/&amp;gt;
        {isLabelVisible &amp;amp;&amp;amp; &amp;lt;span className=&quot;text-lg whitespace-nowrap&quot;&amp;gt;side bar&amp;lt;/span&amp;gt;}
      &amp;lt;/div&amp;gt;
    &amp;lt;/aside&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;效果&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./siderbar-width.gif&quot; alt=&quot;width-image&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;原理&lt;/h2&gt;
&lt;p&gt;通过监听宽度变化的过渡结束事件，我们可以在容器收起时先隐藏文字标签，等过渡完成后再显示出来，从而避免了文字挤压变形的问题。这样用户在交互时就不会感受到不适。
这种方法不仅适用于侧边栏的收起效果，还可以应用于其他需要动态调整宽度或高度的容器中，提升整体的用户体验。&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>How to fix your entire life in 1 day</title><link>https://sukha.dev/posts/fullchangeme</link><guid isPermaLink="true">https://sukha.dev/posts/fullchangeme</guid><description>How to fix your entire life in 1 day</description><pubDate>Sun, 18 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you&apos;re anything like me, you think new years resolutions are stupid.
Because most people go about changing their lives in the completely wrong way. They create these resolutions because everyone else does – we create a superficial meaning out of status games – but they don’t meet the requirements for true change, which goes a lot deeper than convincing yourself you’re going to be more disciplined or productive this year.
If you&apos;re one of these people, I&apos;m not here to talk down on you (I tend to be a bit harsh in my writing). I’ve quit 10x more goals than I’ve achieved. I think that should be the case for most people. But the fact that people try to change their lives and utterly fail almost every time holds true.
However, as much as I think new years resolutions are stupid, it’s always wise to reflect on the life you hate so you can launch yourself toward something that much better, as we will discuss.
So whether you want to start the business, transform your body, or take the risk toward a more meaningful life without quitting after 2 weeks, I want to share 7 ideas you probably haven’t heard before on behavior change, psychology, and productivity so you can do just that in 2026.
This will be comprehensive.
This isn’t one of those letters that you read through and forget about.
This is something you will want to bookmark, take notes on, and set aside time to think about.
The protocol at the end (to dig deep into your psyche and uncover what you truly want in life) will take about a full day to complete, with effects that last far longer than that.
Let’s begin.
I – You aren’t where you want to be because you aren’t the person who would be there
When it comes to setting big goals, people tend to focus on one of the two requirements for success:
Changing your actions to make progress toward the goal (least important, second order)
Changing who you are so that your behavior naturally follows (most important, first order)
Most people set a surface-level goal, hype themselves up to remain disciplined for the first few weeks, then go back to their old ways without much struggle, because they were trying to build a great life on a rotting foundation.
If this doesn’t make sense, let’s run through an example.
Think of somebody successful. It can be a bodybuilder with a great physique, a founder/CEO worth hundreds of millions, or a charismatic dude who can chat up a group without a shred of anxiety entering his mind.
Do you think the bodybuilder has to “grind” to eat healthy? Does the CEO have to discipline themselves to show up and lead the team? To you, it may seem like that on the surface, but the truth is that they can’t see themselves living any other way. The bodybuilder has to grind to eat unhealthily. The CEO has to force themself to lie in bed past their alarm clock, and they hate every second of it (there is nuance here, just entertain me for a second).
To some people, my own lifestyle seems a bit extreme and disciplined. To me, it’s natural, and I don’t say that to contrast it with any other kind of lifestyle. I simply enjoy living this way. When my mom tells me that I should take a break, go out, and have some fun... I hold my tongue from telling her, “If I weren’t having fun, why would I be doing what I’m doing?”
This next sentence may sound simple, but it is baffling how many people don&apos;t get it.
If you want a specific outcome in life, you must have the lifestyle that creates that outcome long before you reach it.
If someone says they want to lose 30 pounds, I often don’t believe them. Not because I don’t think they are capable, but because there are too many times when that same person says, “I can’t wait until I&apos;m done losing weight so I can start to enjoy life again.” I hate to break it to you, but if you don’t adopt the lifestyle that led to you losing the weight, for life, and find a reason with a higher gravitational pull than the one tying you to your previous ways, then you will go straight back to where you started, and you can unhappily say that you wasted the resource you will never get back: time.
When you truly change yourself, all of your habits that don’t move the needle toward your goal become disgusting, because you have a deep and profound awareness of what kind of life those actions compound into. You are okay with your current standards because you are not fully aware of what they are or what they lead to. We will discuss how to uncover this, but we need to build up to that.
You say you want to change. You say you want to “become financially free” and “get healthy,” but your actions show otherwise for a reason. And it goes a lot deeper than you think.
II – You aren’t where you want to be because you don’t want to be there
Trust only movement. Life happens at the level of events, not of words. Trust movement.&lt;/p&gt;
&lt;p&gt;– Alfred Adler
If you want to change who you are, you must understand how the mind works so that you can start to reprogram it.
The first step to understanding the mind is to understand that all behavior is goal-oriented. It&apos;s teleological. When you think about it, this is kinda obvious, but when we dig into it, most people don’t want to hear it.
You take a step forward because you want to reach a certain location.
You scratch your nose because you want to make the itch go away.
Those ones are clear, but most of the time, your goals are unconscious. You may not realize that when you sit on the couch in the middle of the day, you are trying to burn time before your next responsibility, as one simple example.
On an even more unconscious and complex level, you pursue goals that can harm you, but you justify your actions in a way that is socially acceptable and doesn’t make you seem like a loser.
As an example, if you can’t stop procrastinating your work, you may justify it with the fact that you “lack discipline,” but in reality, you are attempting to achieve a goal like you always are. In this case, that goal could be to protect yourself from the judgment that comes from finishing and sharing your work.
If you say you want to quit your dead-end job, but stay in it without any real reason, you may start to think you don’t have enough courage, or that you were never really a “risk taker,” but the truth is that you are pursuing the goal of safety, predictability, and an excuse to not look like a failure to everyone else in your life who sees working a dead-end job as a sign of success.
The lesson here is that real change requires changing your goals.
I don’t mean setting some surface-level goal because the act of doing that serves an unconscious goal that is actually harming you. That’s been ran through enough in the productivity space. I mean changing your point of view. Because that’s what a goal is. A goal is a projection into the future that acts as a lens of perception which allows you to notice information, ideas, and resources that aid in you achieving that goal.
Now let’s dig a bit deeper, because if you don’t understand this, it only becomes more difficult to get out.
I send out letters like these 1-2x a week. If you don’t want to miss them, join here. You can also read my book free, other letters, etc.
III – You aren’t where you want to be because you’re afraid to be there
The important thing for you to remember is that it does not matter in the least how you got the idea or where it came from. You may never have met a professional hypnotist. You may never have been formally hypnotized. But if you have accepted an idea - from yourself, your teachers, your parents, friends, advertisements, from any other source - and further, if you are firmly convinced that idea is true, it has the same power over you as the hypnotist’s words have over the hypnotized subject.&lt;/p&gt;
&lt;p&gt;– Maxwell Maltz
Here’s how you’ve become who you are today, and how you will become who you will be tomorrow. This is the anatomy of identity:
You want to achieve a goal
You perceive reality through the lens of that goal
You only notice “important” information and ideas that allows you to achieve that goal (learning)
You act toward that goal and receive feedback that you are progressing toward it
You repeat that behavior until it becomes automatic and unconscious (conditioning)
That behavior becomes a part of who you think you are (”I am the type of person who...”)
You defend your identity to maintain psychological consistency
Your identity shapes new goals, restarting the cycle, and if that identity is disadvantageous toward a good life, this gets bad very quick
The unfortunate reality is that you must break the cycle between steps 6 and 7, but this process starts when you are a child.
You have the goal of survival.
You are dependent on your parents to teach you how to survive. You had to conform. And since the way most people teach is through reward and punishment, unless you adopt their beliefs and values, you will be punished. You don’t actually think for yourself until you see through this.
But your parents have also gone through this process throughout their entire lives. That’s where it can get dangerous. Your parents, unless they broke the pattern themselves, were conditioned by the culturally accepted ideas of success from the Industrial age. They also carry the best and worst conditioning from their parents and their parents’ parents.
To take it a layer deeper, once you fulfill your physical survival needs (which is quite easy to do in today’s world, you’re practically born into safety), you start to survive on the conceptual or ideological level. You may not try to protect and reproduce your body, but you absolutely protect and reproduce your mind. It’s not difficult to see the war of ideas on the internet, and the participants are individual and group identities.
When your body feels threatened, you go into fight or flight.
When your identity feels threatened, the same thing happens.
If you are heavily identified with a political ideology (by the process we talked about just before), you will feel threatened when someone challenges your beliefs. You literally feel the stress. You feel, emotionally, like you were just slapped in the face. Since most people don’t analyze their emotions for truth, you tend to get stuck in echo chambers and double down on claims that harm yourself and others.
If you were raised in a religious household, and did not think for yourself, you will fight and attack others who threaten your psychological safety within that little bubble.
The same thing happens when you unconsciously see yourself as a lawyer, a gamer, or somebody else who would not take the actions to achieve a better life.
IV – The life you want lies within a specific level of mind
The mind evolves through predictable stages over time.
When you’re born, you’re like a little survival sponge that absorbs whatever beliefs you can (which are heavily dictated by your culture) so that you can feel safe and secure. And if you don’t be careful, your mind may crystalize and it may make it difficult to live a meaningful life.
This has been documented enough in models like Maslow’s Hierarchy, Greuter’s stages of ego development, Spiral Dynamics, and Integral Theory, each building off of one another, but it’s also not difficult to observe in society.
I’ve talked about these many times, and synthesized them into my own Human 3.0 model with various AI prompts to uncover your level of development and a path forward (open in a tab to read after if you&apos;d like), but here’s the 80/20 of the 9 stages of ego development as a refresher (because repetition helps reveal things you didn’t notice before, and there are new people reading these letters):
Impulsive — No separation between impulse and action. Black and white thinking. I.e. A toddler hits when angry because the feeling and the behavior are the same thing.
Self-Protective — The world is dangerous and you learn to look out for yourself. I.e. A kid learns to hide report cards, lie about chores, and figure out what adults want to hear.
Conformist — You are your group and its rules feel like reality itself. I.e. Someone who genuinely cannot fathom why anyone would vote differently than their family or group.
Self-Aware — You notice you have an inner life that doesn’t match the exterior. I.e. Sitting in church and realizing you’re not sure you believe what everyone around you seems to believe, but not knowing what to do with that feeling yet.
Conscientious — You build your own system of principles and hold yourself accountable to them. I.e. Leaving your family’s religion after careful study and adopting a personal philosophy you can defend, or building a career plan with clear milestones because you believe the right effort yields the right results.
Individualist — You see that your principles were shaped by context and start holding them more loosely. I.e. Realizing your political views have more to do with where you grew up than objective truth, or noticing that your ambitious career goals were really about earning your father’s approval.
Strategist — You work with systems while aware of your own involvement in them. I.e. Leading an organization while actively questioning your own blind spots, or engaging in politics knowing your perspective is partial and shaped by bias you can’t fully see.
Construct-Aware — You see all frameworks, including your identity, as useful fictions. I.e. Holding your spiritual beliefs with metaphorically not literally, knowing the map is not the territory, or watching yourself play the role of “founder” or “thought leader” with a kind of gentle amusement.
Unitive — Separation between self and life dissolves. I.e. Work, rest, and play feel like the same thing. There’s no one left who needs to become something, just presence responding to what arises.
For most people reading this, I would assume you hover between 4 and 8, which is a huge gap. Those closer to 8 are reading this are doing so to either learn something or pass time in a non-destructive way. Those closer to 4 are really looking for a change. You feel like you are meant for more, but you can’t make sense of everything yet, because there’s obviously a lot at play.
The good thing is, it doesn’t really matter what stage you are in, because moving through any of them follows a pattern.
V – Intelligence is the ability to get what you want out of life
The only real test of intelligence is if you get what you want out of life.&lt;/p&gt;
&lt;p&gt;– Naval Ravikant
There is a formula for success.
One ingredient is agency.
One ingredient is opportunity (which many people like to mistake as “privilege” - because they the other ingredients).
The last ingredient is intelligence.
If you have high agency but low opportunity, it doesn’t matter how likely you are to act toward a goal, because it isn’t a goal that will bear much fruit.
If you have opportunity and agency but low intelligence, then you will never be fully able to benefit from that opportunity.
First, we’ve talked about agency before here. In terms of opportunity, I can’t tell you to change your physical location, but if you don’t see the abundance of digital opportunity right in front of you, I don’t know what to tell you.
With that said, I want to focus on what intelligence is in the context of these two other ingredients and this letter. For that, we look to cybernetics.
Cybernetics comes from the greek word kybernetikos which means “to steer” or “good at steering.”
It’s also known as “the art of getting what you want.”
So, if Naval’s definition of intelligence is getting what you want out of life, understanding cybernetics helps you do that much faster.
Cybernetics illustrates the properties of intelligent systems.
To have a goal.
Act toward that goal.
Sense where you are.
Compare it to the goal.
And act again based on that feedback.
You can judge intelligence based on the system’s ability to iterate and persist with trial and error.
A ship blown off course that corrects toward its destination. A thermostat sensing a change in heat and turning on. The pancreas excreting insulin after blood glucose spikes.
What does this have to do with getting what you want out of life?
Everything.
Acting, sensing, comparing, and understanding the system from a meta-perspective is fundamental to high intelligence (with the definition we are using here).
High intelligence is the ability to iterate, persist, and understand the big picture. The mark of low intelligence is the inability to learn from your mistakes.
Low-intelligence people get stuck on problems rather than solving them. They hit a roadblock and quit. Like a writer who fails to build a readership and quits because they lack the ability to try new things, experiment, and figure out a process that works for them (to think that there isn’t an effective process you can create is verifiably false, no matter your limiting beliefs, hence being low intelligence.)
High intelligence is realizing any problem can be solved on a large enough timescale. The reality is that you can achieve any goal you set your mind to.
Intelligence is realizing that there is a series of choices you can make which lead to achieving the goal you want. You understand that ideas are hierarchical and that you can’t go from papyrus to Google docs in one fell swoop. Even if that goal is impossible right now, you simply don’t have the resources – which may be invented over the next few years – to achieve that thing.
When I talk about “goals,” and as I will continue repeating, I am not speaking from the typical lens of self-help, although that’s a helpful lens to adopt at times.
I am speaking from the lens of teleology or the Greek kosmos – that everything serves a purpose. That everything is a part of a greater whole.
Goals determine how you see the world.
Goals determine what you consider “success” or “failure.”
You can try to “enjoy the journey,” but if you pursue the wrong goal, you will not enjoy it.
Your mind is the operating system for reality.
That system is composed of goals.
For most people, those goals are assigned to them. Programmed like lines of code in your psyche.
Go to school. Get the job. Get offended. Play victim. Retire at 65.
A known path that doesn’t work.
To become more intelligent, you must:
Reject the known path
Dive into the unknown
Set new, higher goals to expand your mind
Embrace the chaos and allow for growth
Study the generalized principles of nature
Become a deep generalist
I understand this may not be the traditional definition of intelligence, but that sequence of steps leads to an extraordinary level of connections in your brain, leading to what we would observe as an intelligent person. Pair that with agency and you&apos;ve got a winner.
That leads us into the next section perfectly.
VI – How to launch into a completely new life (in 1 day)
The best periods of my life always came after a period of getting absolutely fed up with the lack of progress I was making.
How do you dig into your mind?
How do you become aware of your conditioning?
How do you reach profound insights and truths that change the trajectory of your life?
Through the simple, but often painful act of questioning.
Something that so few people do, and you can tell by how they speak or give their thoughts on a specific topic. Questioning is thinking, and very few people do it.
I want to give you a comprehensive protocol that you can use every year to reset your life and launch into a season of intense progress. This protocol helps you ask the right questions.
These questions will cover the macro to the micro: where you want to be, what you need to do to get there, and what you can do immediately to start moving the needle toward that reality.
This will require one full day to complete, so I recommend you follow along with the exact protocol. You will need a pen, paper, and an open mind.
When I observe patterns in people who successfully flip their identity, it happens fast after a build up of tension. Specifically, I’ve noticed 3 phases that people tend to go through.
Dissonance – They feel like they don’t belong in their current life, and become sufficiently fed up with their lack of progress.
Uncertainty – They don’t know what comes next, so they either experiment or get lost and feel worse.
Discovery – They discover what they want to pursue and make 6 years of progress in 6 months.
So, our goal with this protocol is to help you reach the point of dissonance, navigate through uncertainty, and discover what it truly is that you want to achieve, so much so that the clarity is overwhelming and distractions no longer hold their weight.
This protocol is structured so that it can be completed in one day. In the morning, you do a psychological excavation to uncover your own hidden motives. During the day, you prompt yourself with interrupts to keep you out of autopilot and contemplate your life. At night, you synthesize the insights into a direction you will start to move in tomorrow.
I cannot guarantee that this will work for everyone, because I cannot guarantee that everyone reading this is in the right chapter of their own story that would make these points impactful. You can’t place the climax at the start of the book and expect it to be interesting.
Part 1) Morning – Psychological Excavation – Vision &amp;amp; Anti-Vision
First we must create a new frame, or lens of perception, for your mind to operate from.
This is like creating a new shell, leaving your old one, and slowly growing into it over time. It won’t feel like it fits at first. That’s a good thing.
Set aside 15-30 minutes (the length of one YouTube video... you can do it) to think about and answer these questions. Do not attempt to outsource this contemplation to AI. I want you to break past the limiter that is on your mind. If you can’t answer these immediately, come back to them later.
What is the dull and persistent dissatisfaction you’ve learned to live with? Not the deep suffering but what you’ve learned to tolerate. (If you don’t hate it, you will tolerate it)
What do you complain about repeatedly but never actually change? Write down the three complaints you’ve voiced most often in the past year.
For each complaint: What would someone who watched your behavior (not your words) conclude that you actually want?
What truth about your current life would be unbearable to admit to someone you deeply respect?
Those questions are meant to make you aware of the pain in your current life. Now, we need to turn those into what I call an “anti-vision,” which is a brutal awareness of the life you do not want to live. That way, you can use that negative energy to aim your efforts in a positive direction and act from a place of intrinsic motivation.
If absolutely nothing changes for the next five years, describe an average Tuesday. Where do you wake up? What does your body feel like? What’s the first thing you think about? Who’s around you? What do you do between 9am and 6pm? How do you feel at 10pm?
Now do it but for ten years. What have you missed? What opportunities closed? Who gave up on you? What do people say about you when you’re not in the room?
You’re at the end of your life. You lived the safe version. You never broke the pattern. What was the cost? What did you never let yourself feel, try, or become?
Who in your life is already living the future you just described? Someone five, ten, twenty years ahead on the same trajectory? What do you feel when you think about becoming them?
What identity would you have to give up to actually change? (”I am the type of person who...”) What would it cost you socially to no longer be that person?
What is the most embarrassing reason you haven’t changed? The one that makes you sound weak, scared, or lazy rather than reasonable?
If your current behavior is a form of self-protection, what exactly are you protecting? And what is that protection costing you?
If you answered those truthfully, and if you are in the right chapter of your life, you will feel a deep sense of dis-ease and possibly disgust for how you are currently living. Now, we need to orient that energy in a positive direction. We need to create a minimum viable vision, because your vision is like a product. It starts out unclear, but with time and experience, it grows stronger and more potent.
Forget practicality for a minute. If you could snap your fingers and be living a different life in three years, not what’s realistic, what you actually want? What does an average Tuesday look like? Same level of detail as question 5.
What would you have to believe about yourself for that life to feel natural rather than forced? Write the identity statement: “I am the type of person who...”
What is one thing you would do this week if you were already that person?
Answer all of those first thing in the morning tomorrow.
Part 2) Throughout The Day – Interrupting Autopilot – Breaking Unconscious Patterns
These journaling exercises are cute, but we want real change.
Frankly, that’s not going to happen if you don’t break the current unconscious patterns that are keeping you the same.
Throughout the day, I want you to contemplate on everything you journaled in part one. Beyond that, I don’t want you to forget to contemplate. Please take this seriously. You aren’t going to change by doing the same thing for the rest of your life. You need to consciously force a pattern break.
Take the time right now to create reminders or calendar events in your phone. Include the question in the reminder or event so that you can immediately start thinking about it.
The more random and non-conflicting with your schedule there are, the better.
11:00am: What am I avoiding right now by doing what I’m doing?
1:30pm: If someone filmed the last two hours, what would they conclude I want from my life?
3:15pm: Am I moving toward the life I hate or the life I want?
5:00pm: What’s the most important thing I’m pretending isn’t important?
7:30pm: What did I do today out of identity protection rather than genuine desire? (Hint: it’s most things you do)
9:00pm: When did I feel most alive today? When did I feel most dead?
To add a bit more fuel to the fire, schedule these questions during times where you are either commuting, walking, or lying around.
What would change if I stopped needing people to see me as [the identity you wrote in question 10]?
Where in my life am I trading aliveness for safety?
What’s the smallest version of the person I want to become that I could be tomorrow?
Part 3) Evening – Synthesizing Insight – Entering A Season Of Progress
If you followed that process, I would be surprised if you didn’t have at least one profound insight that could alter the course of your life. Now, we need to make those known, integrate them into who we are, and act on them to begin solidifying our journey to a new level of mind.
After today, what feels most true about why you’ve been stuck?
What is the actual enemy? Name it clearly. Not circumstances. Not other people. The internal pattern or belief that has been running the show.
Write a single sentence that captures what you refuse to let your life become. This is your anti-vision compressed. It should make you feel something when you read it.
Write a single sentence that captures what you’re building toward, knowing it will evolve. This is your vision MVP.
Lastly, we need to create goals.
Again, these aren’t goals that you set for the sake of achievement, because goals are just projections. They are unreliable and make you feel bound to something that will inevitably change. Instead, think of goals as a point of view. A lens that you can exchange to enter the right state of mind to perform the action that will lead away from the life you don’t want. Do not worry about some kind of finish line, because as we will find, it doesn’t exist. Enjoyment is found in progress.
One-year lens: What would have to be true in one year for you to know you’ve broken the old pattern? One concrete thing.
One-month lens: What would have to be true in one month for the one-year lens to remain possible?
Daily lens: What are 2-3 actions you can timeblock tomorrow that the person you’re becoming would simply do?
That was a lot.
Hopefully it was helpful.
But we have one last piece to lock it all in.
Stick with me.
VII – Turn Your Life Into A Video Game
The optimal state of inner experience is one in which there is order in consciousness. This happens when psychic energy—or attention—is invested in realistic goals, and when skills match the opportunities for action. The pursuit of a goal brings order in awareness because a person must concentrate attention on the task at hand and momentarily forget everything else.&lt;/p&gt;
&lt;p&gt;– Mihaly Csikszentmihalyi
You now have all of the components that lead to a good life.
Now, it may be helpful to organize all of your insights into one coherent plan. Pull out a new page and write down these 6 components:
Anti-vision – What is the bane of my existence, or the life I never want to experience again?
Vision – What is the ideal life that I think I want and can improve as I work toward it?
1 year goal – What will my life look like in 1 year time, and is that closer to the life I want?
1 month project – What do I need to learn? What skills do I need to acquire? What can I build that will move me closer to the one year goal?
Daily levers – What are the priority, needle-moving tasks that bring my project closer to completion?
Constraints – What am I not willing to sacrifice to achieve my vision from the ground up?
Why is this so powerful?
Because these components literally create your own little world. If you are meant to pursue this hierarchy of goals at this stage of your life, you will have no other option but to become obsessed. You will feel the pull to something greater. You will not see anything else as an option.
You turn your life into a video game.
Because games are the poster child for obsession, enjoyment, and flow states. They have all the components that lead to focus and clarity, so if we reverse engineer what those components are, we can live in a state of deeper enjoyment, less distractions, and more success.
Your vision is how you win. At least until the game evolves.
Your anti-vision is what’s at stake. What happens if you lose or give up.
Your 1 year goal is the mission. This is your sole priority in life.
Your 1 month project is the boss fight. How you gain XP and acquire loot.
Your daily levers are the quests. The daily process that unlocks new opportunities.
Your constraints are the rules. The limitations that encourage creativity.
All of these act as a concentric set of circles, like a forcefield, that guard your mind from distractions and shiny objects.
The more you play the game, the stronger this force becomes, and soon enough it becomes who you are, and you wouldn’t have it any other way.
– Dan&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>在linux中设置clash代理</title><link>https://sukha.dev/posts/set-proxy</link><guid isPermaLink="true">https://sukha.dev/posts/set-proxy</guid><description>本文记录笔者在linux中设置clash代理的过程</description><pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;linux配置快速教程&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;wget https://github.com/MetaCubeX/mihomo/releases/download/v1.19.23/mihomo-linux-amd64-v1.19.23.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;解压&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;gunzip mihomo-linux-amd64-*.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;改名&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;mv mihomo-linux-amd64-* clash
chmod +x clash
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;配置文件&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p ~/.config/clash
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;下载订阅&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;curl -o ~/.config/clash/config.yaml &quot;你的订阅链接&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;配置代理&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;export http_proxy=http://127.0.0.1:7890
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;配置alias&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;clash() {
  ~/clash -d ~/.config/clash
}

clashbg() {
  nohup clash -d ~/.config/clash &amp;gt; ~/.config/clash/clash.log 2&amp;gt;&amp;amp;1 &amp;amp;
}

clashlog() {
  tail -f ~/.config/clash/clash.log
}

clashstop() {
  pkill -f &quot;$HOME/bin/clash -d $HOME/.config/clash&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;验证&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;curl -I https://auth.openai.com
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><author>sukha</author></item><item><title>yield &amp; send in python</title><link>https://sukha.dev/posts/python-yield</link><guid isPermaLink="true">https://sukha.dev/posts/python-yield</guid><description>Python 中 `yield` 和 `send()` 的执行过程详解</description><pubDate>Tue, 19 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Python 中 &lt;code&gt;yield&lt;/code&gt; 和 &lt;code&gt;send()&lt;/code&gt; 的执行过程详解&lt;/h1&gt;
&lt;h2&gt;1. 示例代码&lt;/h2&gt;
&lt;p&gt;先看这段代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def consumer():
    r = &apos;&apos;
    while True:
        n = yield r
        if not n:
            return
        print(&apos;[CONSUMER] Consuming %s...&apos; % n)
        r = &apos;200 OK&apos;


def produce(c):
    c.send(None)
    n = 0
    while n &amp;lt; 5:
        n = n + 1
        print(&apos;[PRODUCER] Producing %s...&apos; % n)
        r = c.send(n)
        print(&apos;[PRODUCER] Consumer return: %s&apos; % r)
    c.close()


c = consumer()
produce(c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码演示的是一个简单的 &lt;strong&gt;生产者 - 消费者协程模型&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;produce()&lt;/code&gt; 是生产者，负责生产数据；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;consumer()&lt;/code&gt; 是消费者，负责消费数据；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;yield&lt;/code&gt; 用来暂停消费者；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;send()&lt;/code&gt; 用来恢复消费者，并向消费者传入数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;2. &lt;code&gt;yield&lt;/code&gt; 的基本作用&lt;/h2&gt;
&lt;p&gt;普通函数遇到 &lt;code&gt;return&lt;/code&gt; 就结束了。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def f():
    return 1

x = f()
print(x)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是，如果一个函数里出现了 &lt;code&gt;yield&lt;/code&gt;，这个函数就不再是普通函数，而是一个 &lt;strong&gt;生成器函数&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def g():
    yield 1
    yield 2
    yield 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;调用它时：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gen = g()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;函数体不会立刻执行，而是返回一个生成器对象。&lt;/p&gt;
&lt;p&gt;只有调用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;next(gen)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gen.send(None)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;时，函数才会开始执行。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. &lt;code&gt;yield x&lt;/code&gt; 的含义&lt;/h2&gt;
&lt;p&gt;在生成器中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yield x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表示：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;暂停当前函数，并把 &lt;code&gt;x&lt;/code&gt; 返回给外部。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def g():
    yield 100

gen = g()

result = next(gen)
print(result)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说，&lt;code&gt;yield&lt;/code&gt; 后面的值会成为外部调用 &lt;code&gt;next()&lt;/code&gt; 或 &lt;code&gt;send()&lt;/code&gt; 的返回值。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. &lt;code&gt;send()&lt;/code&gt; 的作用&lt;/h2&gt;
&lt;p&gt;生成器除了可以用 &lt;code&gt;next()&lt;/code&gt; 恢复执行，还可以用 &lt;code&gt;send()&lt;/code&gt; 恢复执行。&lt;/p&gt;
&lt;p&gt;区别是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;next(gen)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只能恢复生成器执行。&lt;/p&gt;
&lt;p&gt;而：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gen.send(value)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不仅可以恢复生成器执行，还可以把 &lt;code&gt;value&lt;/code&gt; 发送到生成器内部。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. &lt;code&gt;n = yield r&lt;/code&gt; 的真正含义&lt;/h2&gt;
&lt;p&gt;你这段代码里最关键的一句是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = yield r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这句代码同时包含两个方向：&lt;/p&gt;
&lt;h3&gt;第一：向外返回 &lt;code&gt;r&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;yield r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表示把 &lt;code&gt;r&lt;/code&gt; 返回给外部。&lt;/p&gt;
&lt;p&gt;所以外面这句：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;result = c.send(value)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拿到的返回值，就是 &lt;code&gt;yield r&lt;/code&gt; 中的 &lt;code&gt;r&lt;/code&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;第二：接收外部 send 进来的值&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;n = yield r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当外部下一次执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;c.send(value)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;时，&lt;code&gt;value&lt;/code&gt; 会被传回生成器内部，作为 &lt;code&gt;yield r&lt;/code&gt; 这个表达式的结果，然后赋值给 &lt;code&gt;n&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;也就是说：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = yield r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以理解成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1. 把 r 返回给外部
# 2. 暂停函数
# 3. 等外部 send(value)
# 4. 把 value 赋值给 n
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = yield r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;里面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;r&lt;/code&gt; 是返回给外部的值；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;n&lt;/code&gt; 是接收外部传进来的值。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;6. &lt;code&gt;send()&lt;/code&gt; 的返回值是不是一直等于 &lt;code&gt;yield&lt;/code&gt; 后面的值？&lt;/h2&gt;
&lt;p&gt;是的，但要说准确一点：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;每一次 &lt;code&gt;send()&lt;/code&gt; 的返回值，等于生成器恢复执行后遇到的下一个 &lt;code&gt;yield&lt;/code&gt; 后面的值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;也就是说：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yield x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并不是规定以后 &lt;code&gt;send()&lt;/code&gt; 永远返回 &lt;code&gt;x&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;而是说：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;当前这一次暂停时，生成器把此时的 &lt;code&gt;x&lt;/code&gt; 返回给外部。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果 &lt;code&gt;x&lt;/code&gt; 改变了，那么下一次 &lt;code&gt;send()&lt;/code&gt; 的返回值也会改变。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def g():
    x = 1
    while True:
        n = yield x
        x += 1

gen = g()

print(gen.send(None))  # 1
print(gen.send(&apos;a&apos;))   # 2
print(gen.send(&apos;b&apos;))   # 3
print(gen.send(&apos;c&apos;))   # 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;虽然每次都是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = yield x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但 &lt;code&gt;x&lt;/code&gt; 的值一直在变，所以 &lt;code&gt;send()&lt;/code&gt; 的返回值也在变。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 为什么第一次要 &lt;code&gt;c.send(None)&lt;/code&gt;？&lt;/h2&gt;
&lt;p&gt;在你的代码中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;c = consumer()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一步只是创建了生成器对象。&lt;/p&gt;
&lt;p&gt;此时 &lt;code&gt;consumer()&lt;/code&gt; 内部代码还没有开始执行。&lt;/p&gt;
&lt;p&gt;也就是说，这时候还没有执行到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = yield r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成器还没有停在任何一个 &lt;code&gt;yield&lt;/code&gt; 上。&lt;/p&gt;
&lt;p&gt;所以第一次启动生成器时，必须先用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;c.send(None)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;next(c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它们的作用类似，都是让生成器运行到第一个 &lt;code&gt;yield&lt;/code&gt; 并暂停。&lt;/p&gt;
&lt;p&gt;如果第一次直接写：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;c.send(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;会报错：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TypeError: can&apos;t send non-None value to a just-started generator
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原因是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;生成器还没有运行到第一个 &lt;code&gt;yield&lt;/code&gt;，所以没有地方接收你 send 进去的值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 第一次 &lt;code&gt;c.send(None)&lt;/code&gt; 做了什么？&lt;/h2&gt;
&lt;p&gt;看 &lt;code&gt;consumer()&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def consumer():
    r = &apos;&apos;
    while True:
        n = yield r
        if not n:
            return
        print(&apos;[CONSUMER] Consuming %s...&apos; % n)
        r = &apos;200 OK&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一次执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;c.send(None)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成器开始运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;r = &apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后进入循环：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;while True:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = yield r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时 &lt;code&gt;r&lt;/code&gt; 是空字符串：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;r = &apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以第一次 &lt;code&gt;yield r&lt;/code&gt; 返回的是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是在你的代码中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;c.send(None)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;没有用变量接收这个返回值，所以这个空字符串被忽略了。&lt;/p&gt;
&lt;p&gt;如果写成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;first = c.send(None)
print(first)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么会打印一个空字符串。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;9. 第一次正式发送数据：&lt;code&gt;c.send(1)&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;生产者代码中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = n + 1
print(&apos;[PRODUCER] Producing %s...&apos; % n)
r = c.send(n)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一次循环时：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;r = c.send(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时候消费者之前停在这里：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = yield r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在外部发送了 &lt;code&gt;1&lt;/code&gt; 进来。&lt;/p&gt;
&lt;p&gt;于是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = yield r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;中的 &lt;code&gt;yield r&lt;/code&gt; 这个表达式的结果就是 &lt;code&gt;1&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;所以消费者内部变成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后继续执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if not n:
    return
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为 &lt;code&gt;n = 1&lt;/code&gt;，所以不会 return。&lt;/p&gt;
&lt;p&gt;接着执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(&apos;[CONSUMER] Consuming %s...&apos; % n)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打印：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[CONSUMER] Consuming 1...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;r = &apos;200 OK&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着循环继续，又回到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = yield r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时 &lt;code&gt;r&lt;/code&gt; 已经变成了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;200 OK&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以这次 &lt;code&gt;yield r&lt;/code&gt; 会把：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;200 OK&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回给外面的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;r = c.send(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此生产者中的 &lt;code&gt;r&lt;/code&gt; 得到的值就是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;200 OK&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以打印：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[PRODUCER] Consumer return: 200 OK
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;10. 完整执行流程&lt;/h2&gt;
&lt;h3&gt;创建生成器&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;c = consumer()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时只是创建生成器，&lt;code&gt;consumer()&lt;/code&gt; 内部代码还没有执行。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;启动生成器&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;c.send(None)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;消费者执行到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = yield r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;r = &apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以第一次返回空字符串，并暂停。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;生产者生产 1&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;print(&apos;[PRODUCER] Producing 1...&apos;)
r = c.send(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[PRODUCER] Producing 1...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;消费者收到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后消费数据：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[CONSUMER] Consuming 1...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;消费完成后：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;r = &apos;200 OK&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后再次执行到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = yield r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把 &lt;code&gt;&apos;200 OK&apos;&lt;/code&gt; 返回给生产者。&lt;/p&gt;
&lt;p&gt;生产者拿到返回值后打印：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[PRODUCER] Consumer return: 200 OK
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;生产者生产 2&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;print(&apos;[PRODUCER] Producing 2...&apos;)
r = c.send(2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[PRODUCER] Producing 2...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;消费者收到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打印：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[CONSUMER] Consuming 2...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后再次返回：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;200 OK&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生产者打印：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[PRODUCER] Consumer return: 200 OK
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;后续 3、4、5 也是一样&lt;/h3&gt;
&lt;p&gt;每次流程都是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;生产者生产一个数字
send 给消费者
消费者消费这个数字
消费者把 r 设置为 &apos;200 OK&apos;
下一次 yield r
生产者拿到 &apos;200 OK&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;11. 最终输出结果&lt;/h2&gt;
&lt;p&gt;这段代码最终大致输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：第一次 &lt;code&gt;c.send(None)&lt;/code&gt; 返回的是空字符串，但是代码没有打印它。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;12. 用表格理解执行过程&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;步骤&lt;/th&gt;
&lt;th&gt;外部调用&lt;/th&gt;
&lt;th&gt;发送给消费者的值&lt;/th&gt;
&lt;th&gt;消费者内部 &lt;code&gt;n&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;消费者 &lt;code&gt;yield&lt;/code&gt; 返回值&lt;/th&gt;
&lt;th&gt;外部收到的返回值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;c.send(None)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;暂未赋值&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;&apos;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;&apos;&lt;/code&gt;，但被忽略&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;c.send(1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;200 OK&apos;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;200 OK&apos;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;c.send(2)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;200 OK&apos;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;200 OK&apos;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;c.send(3)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;200 OK&apos;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;200 OK&apos;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;c.send(4)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;200 OK&apos;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;200 OK&apos;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;c.send(5)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;200 OK&apos;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;200 OK&apos;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;13. 关键点总结&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;yield r&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;表示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;把 r 返回给外部，然后暂停函数。
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;code&gt;c.send(n)&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;表示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;把 n 发送进生成器，并恢复生成器继续执行。
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;code&gt;r = c.send(n)&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;表示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;把 n 发给生成器；
生成器运行到下一次 yield；
yield 后面的值作为 c.send(n) 的返回值；
赋值给外部变量 r。
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;code&gt;n = yield r&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;这是双向通信：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;r：返回给外部；
n：接收外部 send 进来的值。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以这句可以记成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;外面拿到 r；
里面收到 n。
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;14. 为什么后面的返回值一直是 &lt;code&gt;200 OK&lt;/code&gt;？&lt;/h2&gt;
&lt;p&gt;因为消费者每次消费完数据后，都会执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;r = &apos;200 OK&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后循环回到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = yield r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时 &lt;code&gt;yield r&lt;/code&gt; 中的 &lt;code&gt;r&lt;/code&gt; 就是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;200 OK&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以外部：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;r = c.send(n)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每次拿到的返回值都是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;200 OK&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是它不是因为 &lt;code&gt;send()&lt;/code&gt; 固定返回 &lt;code&gt;&apos;200 OK&apos;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;而是因为消费者内部每次都把 &lt;code&gt;r&lt;/code&gt; 设置成了 &lt;code&gt;&apos;200 OK&apos;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果你改成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;r = f&apos;{n} consumed&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么返回值就会变化。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def consumer():
    r = &apos;&apos;
    while True:
        n = yield r
        if not n:
            return
        print(&apos;[CONSUMER] Consuming %s...&apos; % n)
        r = f&apos;{n} consumed&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么输出会变成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 1 consumed
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 2 consumed
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 3 consumed
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;15. 如果发送的是 &lt;code&gt;0&lt;/code&gt; 会怎么样？&lt;/h2&gt;
&lt;p&gt;消费者中有这段代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if not n:
    return
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 Python 中，下面这些值都会被认为是 &lt;code&gt;False&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0
None
&apos;&apos;
False
[]
{}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以如果执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;c.send(0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if not n:
    return
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;条件成立，消费者结束。&lt;/p&gt;
&lt;p&gt;生成器结束后，会抛出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;StopIteration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;c.send(0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不会正常返回 &lt;code&gt;&apos;200 OK&apos;&lt;/code&gt;，而是终止生成器。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;16. &lt;code&gt;close()&lt;/code&gt; 的作用&lt;/h2&gt;
&lt;p&gt;生产者最后调用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;c.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表示关闭生成器。&lt;/p&gt;
&lt;p&gt;关闭之后，生成器不能再继续执行。&lt;/p&gt;
&lt;p&gt;如果关闭后再调用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;c.send(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;会抛出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;StopIteration
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;17. 最重要的一句话&lt;/h2&gt;
&lt;p&gt;这句代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = yield r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以记成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yield 后面的 r，是返回给外面的值；
send 进来的值，会赋给里面的 n。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;r = c.send(n)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以理解成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;外部把 n 发送进去；
内部接收为 n；
内部处理完后 yield r；
外部拿到 r。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这就是 &lt;code&gt;yield&lt;/code&gt; + &lt;code&gt;send()&lt;/code&gt; 实现协程通信的核心。&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>Python 正则表达式规则</title><link>https://sukha.dev/posts/python_regex_notes</link><guid isPermaLink="true">https://sukha.dev/posts/python_regex_notes</guid><description>Python 正则表达式规则整理</description><pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Python 正则表达式规则整理&lt;/h1&gt;
&lt;h2&gt;1. 基础匹配符&lt;/h2&gt;
&lt;p&gt;符号   含义                             示例&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;code&gt;.&lt;/code&gt;    匹配任意字符（默认不包含换行）   &lt;code&gt;a.c&lt;/code&gt; 匹配 &lt;code&gt;abc&lt;/code&gt;
&lt;code&gt;\d&lt;/code&gt;   数字                             &lt;code&gt;123&lt;/code&gt;
&lt;code&gt;\D&lt;/code&gt;   非数字                           &lt;code&gt;abc&lt;/code&gt;
&lt;code&gt;\w&lt;/code&gt;   字母、数字、下划线               &lt;code&gt;user_1&lt;/code&gt;
&lt;code&gt;\W&lt;/code&gt;   非字母数字下划线                 &lt;code&gt;@#$&lt;/code&gt;
&lt;code&gt;\s&lt;/code&gt;   空白字符（空格、Tab、换行）&lt;br /&gt;
&lt;code&gt;\S&lt;/code&gt;   非空白字符&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;2. 数量控制符&lt;/h1&gt;
&lt;h2&gt;&lt;code&gt;*&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;匹配 0 次或多次。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;a*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;匹配：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;&quot;
&quot;a&quot;
&quot;aaa&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;code&gt;+&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;匹配 1 次或多次。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;a+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;匹配：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;a
aaa
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;code&gt;?&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;匹配 0 次或 1 次。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;colou?r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;匹配：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;color
colour
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;code&gt;{n}&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;固定次数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\d{4}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;匹配：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2026
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;code&gt;{n,m}&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;范围次数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\d{2,4}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;匹配：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;12
123
1234
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;3. 贪婪匹配和非贪婪匹配&lt;/h1&gt;
&lt;h2&gt;贪婪匹配&lt;/h2&gt;
&lt;p&gt;默认情况下，正则会尽可能多匹配。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a&amp;gt;.*&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;文本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a&amp;gt;hello&amp;lt;/a&amp;gt;&amp;lt;a&amp;gt;world&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a&amp;gt;hello&amp;lt;/a&amp;gt;&amp;lt;a&amp;gt;world&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;非贪婪匹配&lt;/h2&gt;
&lt;p&gt;在数量符后添加 &lt;code&gt;?&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a&amp;gt;.*?&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a&amp;gt;hello&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;常见转换：&lt;/p&gt;
&lt;p&gt;贪婪      非贪婪&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;code&gt;*&lt;/code&gt;       &lt;code&gt;*?&lt;/code&gt;
&lt;code&gt;+&lt;/code&gt;       &lt;code&gt;+?&lt;/code&gt;
&lt;code&gt;?&lt;/code&gt;       &lt;code&gt;??&lt;/code&gt;
&lt;code&gt;{n,m}&lt;/code&gt;   &lt;code&gt;{n,m}?&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;4. 分组&lt;/h1&gt;
&lt;h2&gt;捕获组&lt;/h2&gt;
&lt;p&gt;使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;作用：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;提取需要使用的数据。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hello (world)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Python：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;m.group(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;world
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;非捕获组&lt;/h2&gt;
&lt;p&gt;使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(?:...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;作用：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;只进行逻辑分组，不保存结果。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(?:cat|dog)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;5. 选择&lt;/h1&gt;
&lt;p&gt;使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;|
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat|dog
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;匹配：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat
dog
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;6. 字符集合&lt;/h1&gt;
&lt;h2&gt;匹配其中一个字符&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;[abc]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;匹配：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;a
b
c
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;范围&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;[a-z]
[A-Z]
[0-9]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;排除&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;[^0-9]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表示：&lt;/p&gt;
&lt;p&gt;不是数字。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;7. 边界&lt;/h1&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;^
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;^Hello
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;匹配：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Hello world
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;结束&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;world$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;匹配：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hello world
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;8. Lookaround（约束条件）&lt;/h1&gt;
&lt;p&gt;特点：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;检查条件，但不会消耗匹配字符。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;正向先行&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;(?=xxx)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后面必须是 xxx。&lt;/p&gt;
&lt;p&gt;例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hello(?= world)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;匹配：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hello
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要求后面存在：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; world
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;负向先行&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;(?!xxx)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后面不能是 xxx。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;正向后行&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;(?&amp;lt;=xxx)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;前面必须是 xxx。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;负向后行&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;(?&amp;lt;!xxx)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;前面不能是 xxx。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;9. Python re 常用参数&lt;/h1&gt;
&lt;h2&gt;DOTALL&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;re.DOTALL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;作用：&lt;/p&gt;
&lt;p&gt;让 &lt;code&gt;.&lt;/code&gt; 可以匹配换行。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;IGNORECASE&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;re.I
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;忽略大小写。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;MULTILINE&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;re.M
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;让 &lt;code&gt;^&lt;/code&gt; 和 &lt;code&gt;$&lt;/code&gt; 匹配每一行。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;10. Agent ReAct 输出解析案例&lt;/h1&gt;
&lt;h2&gt;Thought 提取&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;r&quot;Thought:\s*(.*?)(?=\nAction:|$)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解析：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Thought:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;固定标识。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\s*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;跳过空白。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(.*?)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;捕获 Thought 内容。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(?=\nAction:|$)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;约束结束位置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;遇到 Action&lt;/li&gt;
&lt;li&gt;或文本结束&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Action 提取&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;r&quot;Action:\s*(.*?)$&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解析：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Action:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;固定标识。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\s*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;忽略空白。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(.*?)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;捕获 Action 内容。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;直到文本结束。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;11. 阅读复杂正则的方法&lt;/h1&gt;
&lt;p&gt;推荐顺序：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;找捕获组 &lt;code&gt;(...)&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;我要提取什么？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;找边界：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;^&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(?=)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(?&amp;lt;=)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;找数量：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;?&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;找特殊字符：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\s&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\d&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\w&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h1&gt;12. 工程建议&lt;/h1&gt;
&lt;p&gt;简单固定格式：&lt;/p&gt;
&lt;p&gt;优先：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;split()
index()
find()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;复杂文本解析：&lt;/p&gt;
&lt;p&gt;使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;re
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LLM 输出解析：&lt;/p&gt;
&lt;p&gt;正则通常足够，因为文本规模小，瓶颈一般在模型推理。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;核心记忆&lt;/h1&gt;
&lt;p&gt;最常用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;()
(?:)
.*
.*?
\s*
^
$
(?=)
(?&amp;lt;=)
re.DOTALL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;掌握这些，基本可以处理大部分工程正则场景。&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>go的位运算</title><link>https://sukha.dev/posts/go</link><guid isPermaLink="true">https://sukha.dev/posts/go</guid><description>关于go的位运算的学习心得</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;位运算就是把二进制&lt;strong&gt;上下对齐，逐位计算&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var x uint8 = 1&amp;lt;&amp;lt;1 | 1&amp;lt;&amp;lt;5
var y uint8 = 1&amp;lt;&amp;lt;1 | 1&amp;lt;&amp;lt;2
fmt.Printf(&quot;%08b\n&quot;, x) // &quot;00100010&quot;, the set {1, 5}
fmt.Printf(&quot;%08b\n&quot;, y) // &quot;00000110&quot;, the set {1, 2}
fmt.Printf(&quot;%08b\n&quot;, x&amp;amp;y)  // &quot;00000010&quot;, the intersection {1}
fmt.Printf(&quot;%08b\n&quot;, x|y)  // &quot;00100110&quot;, the union {1, 2, 5}
fmt.Printf(&quot;%08b\n&quot;, x^y)  // &quot;00100100&quot;, the symmetric difference {2, 5}
fmt.Printf(&quot;%08b\n&quot;, x&amp;amp;^y) // &quot;00100000&quot;, the difference {5}
for i := uint(0); i &amp;lt; 8; i++ {
    if x&amp;amp;(1&amp;lt;&amp;lt;i) != 0 { // membership test
        fmt.Println(i) // &quot;1&quot;, &quot;5&quot;
    }
}
fmt.Printf(&quot;%08b\n&quot;, x&amp;lt;&amp;lt;1) // &quot;01000100&quot;, the set {2, 6}
fmt.Printf(&quot;%08b\n&quot;, x&amp;gt;&amp;gt;1) // &quot;00010001&quot;, the set {0, 4}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;x = 00100010
y = 00000110
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;运算&lt;/th&gt;
&lt;th&gt;规则&lt;/th&gt;
&lt;th&gt;结果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;x &amp;amp; y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;两位都是 &lt;code&gt;1&lt;/code&gt; 才为 &lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;00000010&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;x | y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;至少一位是 &lt;code&gt;1&lt;/code&gt; 就为 &lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;00100110&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;x ^ y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;两位不同才为 &lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;00100100&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;x &amp;amp;^ y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;x 是 &lt;code&gt;1&lt;/code&gt;、y 是 &lt;code&gt;0&lt;/code&gt; 才为 &lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;00100000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;位的编号&lt;strong&gt;从右往左，从 0 开始&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;&amp;lt; i&lt;/code&gt;：把 &lt;code&gt;1&lt;/code&gt; 移到第 &lt;code&gt;i&lt;/code&gt; 位。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x &amp;amp; (1 &amp;lt;&amp;lt; i) != 0&lt;/code&gt;：检查 x 的第 &lt;code&gt;i&lt;/code&gt; 位是不是 &lt;code&gt;1&lt;/code&gt;，因此循环打印 &lt;code&gt;1&lt;/code&gt;、&lt;code&gt;5&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x &amp;lt;&amp;lt; 1&lt;/code&gt;：整体左移一位，右边补 &lt;code&gt;0&lt;/code&gt; → &lt;code&gt;01000100&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x &amp;gt;&amp;gt; 1&lt;/code&gt;：整体右移一位，左边补 &lt;code&gt;0&lt;/code&gt; → &lt;code&gt;00010001&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;%08b&lt;/code&gt;：用二进制显示，不足 8 位就在左边补 &lt;code&gt;0&lt;/code&gt;。&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>go的数组切片的长度与容量</title><link>https://sukha.dev/posts/go-slice-length-capacity</link><guid isPermaLink="true">https://sukha.dev/posts/go-slice-length-capacity</guid><description>关于go的数组切片的小知识</description><pubDate>Fri, 23 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Go 切片的长度与容量&lt;/h1&gt;
&lt;p&gt;设数组 &lt;code&gt;a&lt;/code&gt; 的长度为 &lt;code&gt;N&lt;/code&gt;：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;写法&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;th&gt;&lt;code&gt;len&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;cap&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a[:]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;整个数组&lt;/td&gt;
&lt;td&gt;&lt;code&gt;N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;N&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a[:high]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从头截取&lt;/td&gt;
&lt;td&gt;&lt;code&gt;high&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;N&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a[low:]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从起点截到末尾&lt;/td&gt;
&lt;td&gt;&lt;code&gt;N - low&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;N - low&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a[low:high]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;指定当前范围&lt;/td&gt;
&lt;td&gt;&lt;code&gt;high - low&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;N - low&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a[low:high:max]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;指定范围，并限制容量&lt;/td&gt;
&lt;td&gt;&lt;code&gt;high - low&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;max - low&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;记忆方法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;len&lt;/code&gt;：当前结束位置减去起点。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cap&lt;/code&gt;：允许扩展的结束位置减去起点。&lt;/li&gt;
&lt;li&gt;所有结束位置都不包含。&lt;/li&gt;
&lt;li&gt;三下标中的 &lt;code&gt;max&lt;/code&gt; 是边界位置，不是容量。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果 &lt;code&gt;a&lt;/code&gt; 本身是切片：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;省略 &lt;code&gt;low&lt;/code&gt; 时取 &lt;code&gt;0&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;省略 &lt;code&gt;high&lt;/code&gt; 时取 &lt;code&gt;len(a)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;普通切片的容量为 &lt;code&gt;cap(a) - low&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;三下标切片的容量为 &lt;code&gt;max - low&lt;/code&gt;，且 &lt;code&gt;max&lt;/code&gt; 不能省略。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;直接使用 &lt;code&gt;len(s)&lt;/code&gt; 和 &lt;code&gt;cap(s)&lt;/code&gt; 可以获取切片当前的长度和容量。&lt;/p&gt;
</content:encoded><author>sukha</author></item><item><title>go关于切片的问题</title><link>https://sukha.dev/posts/go-about-slice</link><guid isPermaLink="true">https://sukha.dev/posts/go-about-slice</guid><description>关于go中的数组切片的问题</description><pubDate>Tue, 27 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Q: 我有一个疑惑的地方，为什么x的切片x[:cap(x)]可以比x的元素更多，我觉得有点奇怪&lt;/p&gt;
&lt;p&gt;A: 你的疑惑在于：&lt;strong&gt;把 &lt;code&gt;x&lt;/code&gt; 理解成了“只有两个位置的容器”。实际上，&lt;code&gt;x&lt;/code&gt; 是底层数组的一段视图，底层数组可能更大。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;比如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;x := make([]int, 2, 4)
x[0], x[1] = 10, 20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里实际创建了一个有 &lt;strong&gt;4 个位置&lt;/strong&gt;的底层数组，只是 &lt;code&gt;x&lt;/code&gt; 当前只包含前两个位置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;下标：        0   1   2   3
底层数组：   [10, 20,  0,  0]
x 的范围：   [10, 20]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;len(x) = 2&lt;/code&gt;：当前切片包含 2 个元素。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cap(x) = 4&lt;/code&gt;：从当前起点开始，可以把切片扩展到 4 个元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;all := x[:cap(x)]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就是&lt;strong&gt;基于同一个底层数组，创建一个范围更大的切片&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;底层数组：   [10, 20,  0,  0]
x 的范围：   [10, 20]
all 的范围： [10, 20,  0,  0]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;额外的两个位置本来就存在，因此这里没有新增或复制元素，只是让 &lt;code&gt;all&lt;/code&gt; 把它们也包含进来。&lt;code&gt;x&lt;/code&gt; 本身的长度仍然是 2。&lt;/p&gt;
&lt;p&gt;Go 对这两种操作的限制不同：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;x[2]  // 越界：访问元素，下标必须小于 len(x)

x[:4] // 合法：重新切片，上界可以到 cap(x)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;len&lt;/code&gt; 限制当前能直接访问的元素，&lt;code&gt;cap&lt;/code&gt; 限制重新切片时能扩展到哪里。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;:::note
通常可以用 &lt;strong&gt;&lt;code&gt;x[:cap(x)]&lt;/code&gt;，查看切片从起点开始、容量范围内的所有元素&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;x := make([]int, 2, 4)
x[0], x[1] = 10, 20

z := append(x, 30)

fmt.Println(x)          // [10 20]
fmt.Println(z)          // [10 20 30]
fmt.Println(x[:cap(x)]) // [10 20 30 0]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应关系是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;底层数组：     [10, 20, 30, 0]
x：            [10, 20]
x[:cap(x)]：   [10, 20, 30, 0]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，&lt;strong&gt;&lt;code&gt;x[:cap(x)]&lt;/code&gt; 得到的仍是切片，它与 &lt;code&gt;x&lt;/code&gt; 共享底层数组&lt;/strong&gt;，因此修改会影响原数据：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;all := x[:cap(x)]
all[0] = 99

fmt.Println(x) // [99 20]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果确实需要一个&lt;strong&gt;数组指针&lt;/strong&gt;，已知容量为 4 时，可以这样写：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;p := (*[4]int)(x[:cap(x)])
fmt.Println(*p) // [99 20 30 0]，类型为 [4]int
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;p&lt;/code&gt; 指向原来的底层数组，没有复制。这里的数组长度 &lt;code&gt;4&lt;/code&gt; 必须是编译时常量，不能写成 &lt;code&gt;cap(x)&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;还有一个边界：如果 &lt;code&gt;x&lt;/code&gt; 是从数组中间截取的，&lt;code&gt;x[:cap(x)]&lt;/code&gt; 只能看到 &lt;strong&gt;从 &lt;code&gt;x&lt;/code&gt; 起点往后&lt;/strong&gt; 的部分，无法取回起点之前的元素。
:::&lt;/p&gt;
</content:encoded><author>sukha</author></item></channel></rss>