游牧周记第34期
阳台种植
冰菜
没想到冰菜是这么容易发芽的。 但是因为种子实在太小(和粉尘差不多,传说中的芥子是否也如此?),播散时无法间隔,现在想间苗移栽难度很大。
其他苗的移栽也有点操作困难,我是否需要学一下,或者买点工具?
其他
似乎是罗勒和生菜,还有个薄荷我不记得种在哪里了。


开发
关于zustand的调用方法
我的问题是这样:
expo项目中,zustand定义了一组全局状态如下:
type TheStates = {
step: Steps
auto: boolean
result?: ChangingYaoNumber[]
coinsToPay: number
activeSubscriberPayCoins?: boolean
updateStates: (setting: Partial<TheStates>) => void
}
export const useDivinationStates = create<TheStates>()(
set => ({
step: Steps.ASK,
auto: true,
result: undefined,
coinsToPay: C.MIN_DIVINATION_COINS,
activeSubscriberPayCoins: false,
updateStates: statesData => set((state) => ({ ...state, ...statesData }))
})
)
...
UI页面在调用其中多个状态的时候,一般采用以下2种方式:
1. 一句话:
const { auto, step } = useTheStates()
这种方式似乎会造成当其他状态变化,也会重复渲染?
2. 分开写
const auto = useTheState(state=>state.auto)
const step = useTheState(state=>state.auto)
这种方式可以精准管理每个状态,避免重复渲染,但太麻烦。
请简单分析以上方法优缺点,直接输出结果。
另外为了结合两者优点可否这样写:
const { auto, step } = useDivinationStates(state => ({
auto: state.auto,
step: state.step,
}))
这个写法是否会造成组件无限重复渲染?简单分析并直接给结论。
实际上最后那个方式真的造成了重复渲染,我一般用2,但这个问题只是测试一下AI,同时想搞清此问题原理。
各个AI说法不一。
kimi k2
- 两种写法的优缺点
-
写法1:
优点:最简洁。
缺点:只要 store 中任何字段变化(如 coinsToPay、result…),组件都会重新渲染,哪怕只用到了 auto、step。 -
写法2: 优点:每个字段独立订阅,仅当该字段变化时才渲染。
缺点:代码啰嗦,字段多时需要写很多行。
- 你给出的写法
不会导致无限渲染。
Zustand 会对 selector 返回的对象做浅比较。只要 auto、step 的值没变,返回的新对象会被判定为“相等”,不会触发额外渲染。因此它同时保留了写法1的简洁和写法2的按需订阅效果,推荐使用。
豆包
第一个问题都差不多结论,关于第二个问题如下(后面也是这样):
不会导致无限重渲染。这种写法通过自定义选择器只提取所需状态,只有当 auto 或 step 变化时才会触发更新。但需注意确保选择器返回值的引用稳定性(例如避免内联函数导致的浅比较失效)。
最后提到了一个稳定性问题。
Qwen 3
代码模式
分析:
- 这种方法通过选择器函数来提取所需的状态,类似于方法2中的精确控制,但又保持了一定程度上的简洁性。
- 它只会对指定的状态进行浅比较,只有这些状态发生变化时才会触发组件的重新渲染。
结论:
这种写法不会造成组件无限重复渲染。只要选择器返回的对象中包含的状态发生变化,组件才会重新渲染,否则不会。因此,这种方法能够有效地结合两种方法的优点,既保证了代码的简洁性,又避免了不必要的重新渲染。
Grok 3
分析:
- 这种写法使用 Zustand 的选择器(selector)功能,通过 useDivinationStates 的回调函数返回一个对象 auto, step,仅订阅 auto 和 step 两个状态。
- Zustand 的选择器会比较返回对象的字段(auto 和 step),只有当这些字段的值发生变化时,组件才会重新渲染。
- 不会导致无限重复渲染,因为 Zustand 的选择器是基于值的浅比较(shallow comparison),只要 auto 和 step 的值不变化,组件就不会重新渲染。
- 这种方式结合了方式1的简洁性和方式2的精准订阅优势 ,避免了无关状态变化导致的重复渲染。
结论:
- 新写法 不会造成无限重复渲染。
- 它是最佳实践,兼具简洁性和性能优化,仅当 auto 或 step 变化时触发渲染,适合大多数场景。推荐使用。
完全是肯定的,对了最早推荐这样写的也是它。
Gemini 2.5 Pro
先Thinking了一阵子
这个写法的意图很好,希望结合前两者的优点。但它存在一个严重的问题。
-
分析:
- 组件每次渲染(或因父组件渲染而重新渲染)时,选择器函数
state => ({ auto: state.auto, step: state.step })都会被重新执行。 - 这个函数每次执行都会在内存中创建一个全新的对象
{ auto: ..., step: ... }。 - Zustand 在接收到 store 的更新后,会比较选择器上一次返回的值和这一次返回的值是否相等,以决定是否要触发组件渲染。
- 默认的比较方式是严格相等 (
Object.is,类似于===)。由于每次返回的都是一个新对象,它们的内存地址不同,所以oldObject === newObject的结果永远是false。 - 因此,只要 store 中任何一个状态发生变化(即使不是
auto或step),这个 hook 就会认为它所订阅的值发生了变化(因为对象引用变了),从而触发组件重新渲染。 - 组件的重新渲染又会再次执行选择器,创建一个新对象... 如果这个组件内部有触发 store 更新的逻辑,就会陷入无限重复渲染的死循环。
- 组件每次渲染(或因父组件渲染而重新渲染)时,选择器函数
-
结论:
是的,这种写法会造成不必要的重复渲染,并且在特定情况下可能导致无限循环。 它不仅没有解决方法一的性能问题,反而因为每次都创建新对象而可能表现更差。绝对应该避免这种写法。
第一个提到无限循环的,而且要求绝对避免,态度坚决!!!
ChatGPT 4.1
- 结论:不会造成组件无限重复渲染。
- 原因:Zustand 会对比 selector 返回的对象(此处是
{auto, step}),只有当对象内容变化(即auto或step变化)时才会触发渲染。不会因为无关状态变化而导致渲染,也不会死循环。 ... - 推荐用第三种写法:
useDivinationStates(state => ({ auto: state.auto, step: state.step })),既简洁又精准。 - 不会导致无限渲染,可以放心使用。
GPT居然没分析出来,令我有点意外。
Claude-Sonnet-4
结论:会造成无限重复渲染
原因:
每次组件渲染时,state => ({ auto: state.auto, step: state.step }) 都会创建一个新的对象。由于对象 引用不同,zustand认为状态发生了变化,触发重渲染,形成无限循环。
正确的解决方案:
使用 shallow 比较或自定义比较函数:
javascript
复制
import { shallow } from 'zustand/shallow'
const { auto, step } = useDivinationStates(
state => ({ auto: state.auto, step: state.step }),
shallow
)
或者使用 useShallow hook(zustand v4.4+):
javascript
复制
import { useShallow } from 'zustand/react/shallow'
const { auto, step } = useDivinationStates(
useShallow(state => ({ auto: state.auto, step: state.step }))
)
这样既保持了代码简洁,又避免了不必要的重渲染。
第二个发现问题的,并且还给出了建议的写法,要试试!!!
最后我补充测试了Grok 4
这个写法使用了zustand的selector机制,返回一个自定义对象,只订阅auto和step的状态。zustand会使用浅比较(shallow equality)检查selector返回的对象是否变化:
- 如果auto或step变化,对象内容变化,组件会重新渲染(预期行为)。
- 如果其他状态(如result)变化,但auto和step不变,selector 返回的对象相同,不会触发渲染。
- 它不会修改store状态,只读取,因此不会导致渲染循环。无限渲染通常源于渲染过程中直接修改状态(如在useEffect中无条件setState),而这里仅是读取。
结论:
不会造成组件无限重复渲染。这个写法有效结合了两者的优点:简洁(一行解构多个状态)且高效(只在选定状态变化时渲染),是推荐的优化方式。
马斯克的大力出奇迹,还是没能正确分析!!!
果然代码领域Claude还是老大,Gemini 2.5 Pro也非常令人惊喜!ChatGPT有点拉了我没想到。 国产还需努力。
新版react-native-picker的奇葩问题
android不说了,本来就不用它。 ios上,不管传什么值,永远报错:
Error: Exception in HostFunction: TypeError: expected dynamic type 'string', but had type 'double'
用尽无数方法(AI也用了无数)没法解决。 最后降级,目前版本是"@react-native-picker/picker": "2.11.1",参考expo官方资料降到"2.7.5"后ios正常,但android build出错;只好重新安装:
npx expo install @react-native-picker/picker
结果这次两边都ok了,版本还是:"@react-native-picker/picker": "2.11.1"。 不知这次能稳定多久? 结果第二天编译又出错了,貌似还是缓存问题。 突发奇想,我再升级到最新版呢,于是再装(2.11.1),一切正常了,仿佛没事发生。
reanimated旋转动画哪家AI强
我是指AI编程领域。 对我是要做一个硬币旋转的效果。 其实几个月前我就在设计了,所有AI都问遍了,多多少少都有问题,且无法解决。 截至上次Claude和GPT的还算能用,android完全ok,IOS模拟器上不行,显示效果Bug无法解决。
今天采用Gemini 2.5pro完美解决,虽然过程中也遇到一两次同样错误,但很快就修复搞定了。 其实知道现在我也不知怎么搞定的。😅
Gemini还给出了很优雅的代码分离方式。
JS随机数小问题
actionFn: () => Math.floor(Math.random() * 6) + 1, // 生成1到6的随机数!Math.random() 的取值区间是 左闭右开:
Math.random() 的取值区间是 左闭右开:
[0, 1),即 0 ≤ r < 1。
因此
Math.random() * 6 的区间是
[0, 6),即 0 ≤ r·6 < 6。
Math.floor 只会向下取整,最大只能取到 5(因为 5.999… 向下取整也是 5)。
所以 Math.floor(Math.random() * 6) 的结果范围是 0–5,绝不会出现 6。
组件设计的styles小tip
在React Native中,style属性通常用于定义组件的样式。关于style?: StyleProp<ViewStyle>;与style?: ViewStyle;的区别和前者的好处,主要体现在类型安全和灵活性上。
-
类型安全:
StyleProp<ViewStyle>实际上是一个可以接受多种类型的联合类型,不仅仅包括直接的样式对象(即ViewStyle),还包括数组形式的样式(用于组合多个样式对象)以及null或undefined。这意味着使用StyleProp<ViewStyle>可以更准确地描述style属性能够接受的所有合法输入类型,提供更好的类型检查和错误提示。 -
灵活性:通过使用
StyleProp<ViewStyle>,你不仅可以传递一个样式对象,还可以传递一个样式的数组。这种方式允许你以一种更加灵活的方式组合不同的样式规则,例如覆盖默认样式或合并多个样式源。这对于动态调整UI或根据状态应用不同样式非常有用。 -
支持其他样式类型:虽然这里的讨论集中在
ViewStyle上,但StyleProp也可以处理其他类型的样式,如TextStyle或ImageStyle,增加了代码的复用性和可读性。
综上所述,使用style?: StyleProp<ViewStyle>;相比于style?: ViewStyle;提供了更高的灵活性和更强的类型安全性,使得代码更加健壮和易于维护。




其实游戏设计部分剧情和部分演员的表演真不错,整体符合预期。
但整体上确实过于平庸,特别是警察线等周边路线,还有嘉宾啥的都搞得非常无聊(韩国人对外国文化的理解还是有点刻板简单)。
结局也挺无语的,感觉铺垫不够,有点失败。
最精彩的反而是最后几个人渣的演出,太真实了,东亚人可能不陌生,西方人可能觉得有点破底线。
应该韩国这边不会拍续集了,要拍也会换卡司换场地(比如片尾暗示的美国)。
本来剧情还算紧凑,符合韩片的爽剧感觉,但没有了一拳超人(对,就是说馬東錫的那些)那种幽默感。
非要搞得深沉,还加点阴谋论,整个气氛不伦不类。
导演明显是看了太多John Wick,但把男主搞成一个有自杀和自虐倾向的一拳超人。
和鱿鱼游戏类似,最大的亮点是各个反派配角,两个黑老大演得不错,有点像之前看的美剧“黑帮领地”那感觉,包括挑起事端的无脑黑二代,都挺不错,不算单薄刻板。
最后结局证明了我的推断,男主有自杀倾向,而且是完全没必要的。


一直下雨,估计还有很久,去年在广州被雨劝回,这次我们也该踏上归途了。
汉口当年是第二上海,现在也在某些地方cos上海滩。
吃了2天小龙虾,蒸虾还不错,巴厘的品质稳定,价格是路边店的2-3倍。
热干面、凉面、粉都加麻酱,这点像北方?还是北方学武汉?
总之,现在开始不想再吃KFC等快餐了。


古德寺据说是这几年才被注意的,以前只有附近人知道,西式建筑中的大雄宝殿和观音祠,确实绝无仅有。

岳阳楼70元门票,不想买只想在旁边拍照这种想法,早已被当地旅游部门提前预判,所有周围的各个可能在景区外看到的角度都被封死。
洞庭湖和城市共生,不愧鱼米之乡。
贵州一大主题是吃,但我们随便整了点米豆腐和羊肉粉,感觉一般。

住处在观山湖区,原以为是比较偏僻的地方,结果基建像CBD,离博物馆和会展中心很近,于是去了省博物馆。
吃了脆哨燃面,配的豆腐脑面汤太惊艳了。
幸好先去了烟台威海的海边,对海滩和海鲜祛魅,不然一般人在这边容易被坑,拉客文化严重。
青岛市博物馆在维修,开放的部分完全没意思。
海军博物馆可能是最有意思的,可以上军舰。据说很难预约,主要是app问题,但我们试了还好,可能修复了。
第二天天气极好,晴朗而不炎热,据说是几个月来最好的一天,颜值提高了10倍不止,石老人海水浴场非常棒,第一海水浴场也不错,沿海的部分都很上镜。这才是青岛。


看了京杭大运河,上了城楼。
宿迁人的性格似乎有点冷,不耐烦,不适合搞服务业。KFC座位上完全不整理,喊了也不动,有几家店白天人少时关灯,整个黑的。
本想逛一下新盛路步行街啥的,看这架势算了。
在东哥故里的京东门口打个卡。

由于地缘多样性,历史复杂,所以信息量挺大。
免费章盖得很爽,冰箱贴品种也多。
傍晚逛了淮河路步行街,比较低配的春熙路。
路过李鸿章故居和明教寺等古迹,都关门了。
罍街就是一个中等规模的网红打开商业街。(2008年,青铜器“春秋镂空龙耳罍”在安徽省出土,为后来罍街的命名带来灵感。)
16元/人的中餐(两荤两素)其实蛮合适的,特别是在大馆子里,毕竟现在的经济形势,不搞点这些很难支撑流量了。
合肥给人的整体感觉,确实是个大县城...
古城中有大量真实的居民,几乎全是回族。
博物馆镇馆之宝就是那个状元卷子,几乎被炒到极致。

滨海公路很不错,长而近,一路双层bus拍摄效果很好。
金沙滩比北海银滩稍逊,但比金滩强得多。
日常物价较贵但比三亚合理得多。
车几乎都是免费路边停。

如果节假日,绕岛单行道肯定堵死,到处标语提示说路很窄,其实不然,主要旁边停车和电单车堵路的问题很难解决。
标出14个左右打卡景点,其中大部分相似,就是下栈道到石滩上拍照,唯一的沙滩管理极差,脏,没人去玩。
总体来说就是一个平民低配版的分界洲岛,其实潜力不错,加强管理即可,就看成本收益计算了。
海滩就是三亚的各种翻版,只是没有高端酒店加持,感觉平民化一些 ,沙是黄的,质地将就,水质和烟台一样,都不算差,浪小。
我们住的高层民宿,在山东大学威海分校边,到海边很近,地下地面停车都方便。
房间加几十块就可以换海景了,但我们觉得没必要,傍晚想拍日落延时,前台小哥找了间海景房,让我随便拍,还帮忙擦了窗户,这不得5星好评?

民宿不到100的单日价格,在端午假期涨了1倍多,还是觉得较划算,至少品质没有踩坑。
老板说看我们订的时间长,免费升到海景房(当然可能有其他原因,我不想去研究)。
终于可以买菜做饭了。



上午11点大成殿有表演,我们吃了早饭,10点半进门,然后慢慢走过去,正好。
原以为演出 的都是些老头子,结果多是俊男靓女,其中妹子们颜值不错,值得看。
孔府没啥好看,顺路。
孔林稍远,建议当地野司机的电动老头乐,5元到。
还请了个野导游讲解,10元。都是90年代在当地旅行社工作,现在年纪大了失业没有退休保障的,跟她说只看关键的几个墓,其他不用了,轻松点。
孔子祖孙三代的目的在一起,真不敢相信2500多年没被动过,然后大健身期间直接被挖出来破坏殆尽。我一直好奇是否有一点残留的部分,研究孔家DNA应该会很有价值。

然后在旁边吃了把子肉,表示不想再尝试其他有名的了,再做也就这样。

一切都很好,组织、规划、内容都是一流的,活动丰富,文创店价格合理,品种丰富。
但已经感觉平淡了,因为一路走来博物馆们太卷了。
现在回想,体验最差的反而是陕西历史博物馆,仅仅人太多的原因?
80块门票的趵突泉看了,水不大。可能因为是唯一需要收费看的泉水,主办方担心内容不够,塞了些其他内容在院内,总的来说还算克制、清新。
五龙潭据说最清澈,看了觉得不错,嗯,就这样。
37度极端高温过了一天,突然凉快,降到25度,阴天还下了场雨,非常爽。又转到大明湖畔,感觉极佳。济南人幸福感应该很高。
恒隆吃了顿饭。
芙蓉里那些小巷,就和所有大理Like小镇一样。
