游牧周记第34期
· 10 min read
阳台种植
冰菜
没想到冰菜是这么容易发芽的。 但是因为种子实在太小(和粉尘差不多,传说中的芥子是否也如此?),播散时无法间隔,现在想间苗移栽难度很大。
其他苗的移栽也有点操作困难,我是否需要学一下,或者买点工具?
其他
似乎是罗勒和生菜,还有个薄荷我不记得种在哪里了。
开发
关于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 中任何一个
- 组件每次渲染(或因父组件渲染而重新渲染)时,选择器函数