Skip to main content

游牧周记第44期

· 2 min read
Suhe
This site owner

标签

sora2

k签证

AI

Sora 2

这玩意号称会颠覆tiktok,我的ios账号是美区,可找到安装,然后vpn地址必须调整到非hk ip,接下来就是邀请码,找了好久,最后加入一个微信群,终于有可以用的了。 做了第一个视频:猫哥谈福报。 邀请码:VEMCRE

智谱的Coding模型

这几天都在说高端大气的Claude 4.5,但同时跟随另一个号称差不了多少的智谱# GLM-4.6,有一个DeepSeek? 模型是开源(当然普通电脑就算了),网页端免费,api收费但便宜。 我看了一下开发方是:北京智谱华章科技股份有限公司

关注

人民公园说AI

这帮家伙有海外华人或国内从业者,虽然没有请过任何重量级嘉宾,谈话风格也不够高大上,但当背景听还是挺放松的,抓热点很快,有热情,算是言之有物吧。 youtube

硅谷101

一个高品质的频道,内容当然就是美国财经创新领域的东西,女主落落大方,个人感觉比已经解决破音问题的小Lin好多了。 探讨较深,有很多嘉宾的参与,有时候较长,他们也有播客。 youtube

兴趣

F1直播哪里看

前几天突然决定培养一下F1赛车的兴趣,新加坡已经开赛,接下来阿布扎比,我该找个直播来源。 结果发现国内很难。 首先CCTV 5 (+)只播正式赛,且不稳定(也就是随时可能被其他比赛和内容替代)。 据说广东的五星体育和上海那边有些本地台会转播,但没上星,其他地方看不到。 然后是收费的腾讯体育等,我也没法在新买的电视上看,毕竟只接了机顶盒和apple TV。 当然F1 app等也要注册收费。 后来发现youtube有直播,👌,但是没有中文解说的(英文、日文都有),算是一个小缺憾吧。

游牧周记第43期

· 5 min read
Suhe
This site owner

日常

阳台种植

新买了一些东西:

  • 2颗来自德宏的树莓苗,一颗黑色一颗红色,但只是苗,我分不出,分别用两个盆栽上,看来品质还行。
  • 一袋(10斤)云南森林腐叶土,纯黑色,希望可以弥补有些植物酸性不足和肥少的问题(以前买的营养土似乎太寡淡了...)。
  • 苦楝油和皂液,混装加水后用,但最近似乎没发现红蜘蛛和虫害了。

Diablo IV s10

最近这个游戏打得有点频繁了,s9一直玩亡灵,但没啥突破,感觉冲层无力,最大的成就是最后几天得到一个4星的堕狱头盔,但赛季也结束了。 9月24日,s10上线,当天又以熟悉的亡灵试玩,一口气冲到55。现在大家都说新赛季德鲁伊最好玩,于是第二天改小德,2天打到凌晨,60(153)级,早中期的拍拍装配齐,感觉攻击力确实可以,但会暴毙,最大的问题是我们玩惯了纯招单手逛街的人,觉得有点腰酸背痛了,等凑齐装备,下次改渡鸦流派,看会否轻松点。

出行

高铁去老挝计划

原计划国庆假期后触发,后来又有点急想再提早两天。 办签证有2个方法,在网上电子签或到昆明的领事馆办。

领事馆据说最近热门到要排队,要实现拍摄合适的2寸白底照片,带上身份证等一堆证件,估计300多块钱/人手续费。 那是不是电子签好点呢?我开始也这么想,于是上了https://laoevisa.gov.la,填写了一些内容,上传以前的证件照和护照照片,每人收费36usd(看似便宜很多),正要提交,突然想起一个问题:如果填写有误,可以修改吗,会不会退钱?没地方问就问了GPT,结果是否的,听说照片要求6个月以内拍的,一些文字细节也不能出错,我真的担心可能过不了,那就打水飘了。 考虑良久,还是下楼找个小店拍照了,多年前去泰国也是这家拍的签证照。 原计划第二天就去领事馆,后来突然想改晚点去(比如11月),主要还是因为今年出游较多,有点倦怠了,琅勃拉邦一个地方对我的吸引力有限(真有趣的反而是高铁),旅行目标还不明确,就再等等吧。

开发

Unistyles的theme切换太懒了怎么办

上周解决了整体theme切换的问题,这周发现复杂点的app,部分页面和组件不更新,特别是tabs中和list中,ChatGPT给了个直接又简单的方案。如下:

问题其实是 react-native-unistyles v3+ 在 Expo SDK 54 里切换主题时,部分组件没有重新刷新。
这是因为 UnistylesRuntime.setTheme() 并不会强制触发整个 React 树的 re-render,尤其是被缓存的 screen / VirtualizedList item。


1. 为什么会发生

  • Tabs 场景:React Navigation 默认会缓存 tab screen,不会因为 theme 改变而自动 unmount/remount。

  • FlatList 场景:子项渲染由 renderItem 控制,如果 item 的 key 和 props 没变,React 不会 re-render。

  • UnistylesRuntime 的限制:调用 setTheme 实际只是更新内部 runtime theme,只有订阅 hook(useStyles, useTheme)的地方才会触发 re-render。


2. 常见坑

  1. 你用了 UnistylesRuntime.setAdaptiveThemes(true/false),但如果之前的组件挂载时拿到的 theme 是“老的”,它不会自动更新。

  2. React Navigation tab 屏幕缓存(默认 lazyunmountOnBlur: false),不会重新构建。

  3. FlatList 的 item 如果不依赖 theme 作为 extraData,不会刷新。


3. 解决方案

✅ 方法 A:确保 theme 改变能触发 re-render

const theme = useTheme(); // Unistyles hook

<FlatList
data={data}
renderItem={renderItem}
extraData={theme.name} // 强制当 theme.name 变化时刷新
/>

✅ 方法 B:给 Navigation 强制刷新

React Navigation 可以利用 key 来强制重新挂载:

<Tab.Navigator
key={theme.name} // 当 theme 变化时,强制整个 Tab.Navigator 重建
>
...
</Tab.Navigator>

或者在 _layout.tsx 根节点:

const theme = useTheme();

return (
<Stack key={theme.name}>
{/* theme 改变时整个 Stack 重新渲染 */}
</Stack>
);

✅ 方法 C:避免滥用 setAdaptiveThemes

  • 如果只想手动切换主题,用 UnistylesRuntime.setTheme(...) 就够了,不需要先关再开 adaptive。

  • adaptive 主要给 系统 theme 跟随 用。


4. 推荐写法(_layout.tsx)

useEffect(() => {
devLog('[Root Layout] new theme setting:', settingTheme)

if (settingTheme === 'system') {
UnistylesRuntime.setAdaptiveThemes(true)
} else {
UnistylesRuntime.setAdaptiveThemes(false)
UnistylesRuntime.setTheme(settingTheme as keyof UnistylesThemes)
}
}, [settingTheme])

然后在 Navigation 根加:

const theme = useTheme();

return (
<Stack key={theme.name}>
{/* screens */}
</Stack>
)

👉 总结:

  • FlatList 要用 extraData={theme}

  • Navigation 要用 key={theme.name} 来强制刷新缓存的 screen。

  • 不要频繁调用 setAdaptiveThemes,只在系统跟随模式时启用。


巧妙使用key参数,高! 控制不用太细,在根_layout.tsx或相应的总体导航组件加入即可。 我个人在(tabs)_layout.tsx加入。

skia组件在expo SDK 54后无法响应

升级expo到SDK 54后,发现skia绘制的组件,在外包裹上pressable或touchable,均无法响应点按。ChatGPT没有找到佐证材料,自己编了些理由,但居然得到了正确的方法。

它这样说的:

这个问题其实不是你代码本身的锅,而是 **Expo SDK 54 升级后 react-native-skia的事件穿透机制变了**。简单说就是: Skia 画布默认会「吃掉」触摸事件,导致外层的Pressable/Touchable 都接收不到点击。

✅ 解决方案

1. 禁用 Canvas 的触摸拦截

Canvas 加上:

<Canvas style={{ flex: 1 }} pointerEvents="none"> {/* 绘制 */} </Canvas>

这样 Canvas 就不会「吃掉」点击事件,父级 Pressable 就能响应。

于是就ok了。

游牧周记第42期

· 6 min read
Suhe
This site owner

标签

kirk, 西贝

出行

淡季高铁去大理

上午8:44有一趟昆明站出发到保山的复兴号。 2小时到大理站,一等座不到200,相比自驾需要4小时,且光过路费160来说,确实更有吸引力,代价就是放弃了深度游和周边游,巍山和剑川怕是不去了。

住宿

最后关头才决定不开车的,但带车位的酒店已经订了,就试试嘛,第一次没有住古镇。 在网红的兴盛桥头。 各种折扣打完158(有完整大窗户可以看点风景,否则可以再便宜20)。 各方面都不错,智能化和卫生都好,再加点钱据说有更适合睡眠的床品。 22平米。 但总的来说,大理即使淡季,酒店价格都不算低的,尤其是对于民宿酒店如此密集的大理而言。 旁边有公交站,可以到古城或火车站。 我休整一下就下楼搭旅游快线去古城了(5元,半小时一班车)。

跟团环洱海

就算在整个云南,我也是第一次跟团,纯粹是闲的无聊又好奇,而且太便宜了。 古城拉团的是白族本地人,就聊起来,然后50块钱参加了个团(其他人至少80),导游是白族妹,颜值可以、热情大方,也不算太庸俗。出发时快4点,一圈回来9点过(我是回到下关)。 n个S湾之一骑单车,然后喜洲古镇看田、买冰箱贴、吃冰棒冰牛奶,这两个环节体验还行,后面的双廊、看孔雀等等,都挺无聊,到海东那些什么圣托里尼都完全黑了,大家急着回去,于是也没安排了。

这几天洱海开鱼,据说晚上鱼灯好看,其实沿路看就那样,可能还是需要角度。

NCC数字游民社区

石碓(duì, duī)村上坡到顶,就是2023年我们第一次离职出行来住的客栈上方,有个跑马场,旁边小路进去,挺隐秘的院子。爬坡有点累,对宅男宅女有好处。 上午10点去了一次,只有个小伙子,下楼到会议室(工作室?)开电脑看片。我问了一下说其他人都没起床或者没出门。让我加了小程序,聊了一下。

中午1点过又来看,多了一个男人在逗猫。都懒洋洋的,不吭气不打招呼(我除非看到妹子或艺术家气质的人,一般也不主动打招呼😅)。 老板不知道哪里人,盘了这个院子搞社区,现在人在黄山,还有昆山、三亚,共4个点。 相当于这边没有管理人和总的组织人,不过我看了黑板墙壁通告,还不时有活动开展的。 上午和小伙聊天,他说这边一共20多人在住着,多数住一个月就走(纯体验),有程序员和设计师等,租房都是在小程序下单,单人间2500/月,还有双人间。 我看了一下小程序,注册时要写明自己的定位(程序员、艺术家等等)、星座和MBTI模型等等,有一些远程接单的活,还有个纽约的票务创业项目,找React Native合作,每月2W-4W(cny),不知为啥我一定兴趣都没。

相关视频在B站

心得

  • 大理还是有魅力的,昆明人觉得在这边看天看云居然都挺感动,难道地州的云更美?(似乎比昆明更加柔和一点)
  • 最好的交通工具还是电单车,古城没有通用平台共享的,只能租,还要再租点还,除非住宿一体,否则不方便。如果有人能把大理古城租车平台统一掉就好了。
  • 没有交通工具,靠腿的话还是很累。
  • 古城吃的肯定不如下关市区,即使耙肉饵丝也是,就算为了吃也应该留在下关。不过云南总的来说大理除了生皮和海稍鱼,似乎也没啥特别。
  • 没有共享单车(除了公园内和专门开辟的区域外),这也可以理解,不然更混乱难管了,坡多路复杂也不好骑。

应用

Perplexity当新闻头条看

好久没有使用Perplexity了,因为其搜索功能大多数模型都有。 其app经常推新闻和一些信息,今天看了一下感觉还行,当然显然是有个人化训练的,对比一下和头条网易的区别吧。

小画家

https://jspaint.app/ 哈哈,居然把这种老物挖出来了,都怪macos的预览编辑图片多年不长进,没有背景改色功能(油漆桶),reddit查到6年前的帖子提到这个网址,居然还在还能用。 都是因为iOS的液体玻璃后,icon要改一改。

想法

技术资料网

想把expo.dev这种有开源资源的技术docs搬运,并翻译成中文,再部署。 发现大批量翻译是比较麻烦且有成本的,一般会用到python调api。 后来发现这样做的网站太多了,如expo中文等,大不了更新慢点。 文档网站还是原创或冷门的好点。

开发

expo的ThemeProvider放在外层才行

原来的代码:



<SubscriptionProvider>

<SQLiteProvider databaseName={C.DATABASE_FILE_NAME} onInit={migrateDbIfNeeded} useSuspense>

<GestureHandlerRootView>

<ThemeProvider value={NavTheme}>

<Stack>

<Stack.Screen name="index" />

<Stack.Screen name="settings" options={{ presentation: 'modal' }} />

<Stack.Screen name="iap" options={{ presentation: 'modal', headerShown: C.isAndroid, headerTransparent: C.isAndroid, title: '' }} />

<Stack.Screen name="ai-token-bill" options={{ presentation: 'modal' }} />

<Stack.Screen name="history" options={{ presentation: 'modal', headerShown: false }} />

<Stack.Screen name="+not-found" />

</Stack>

</ThemeProvider>

</GestureHandlerRootView>

<StatusBar style={isDark ? 'light' : 'dark'} />

</SQLiteProvider>

</SubscriptionProvider>


无论NavTheme如何变化,都不会改变整体UI风格的风格。 Claude Code也检查不出原因,给了一堆解法完全无效。

然而问题在这里,修改ThemeProvider包裹范围即可,实在不想一个个试,就包在最外层,然后一切恢复正常。

估计是中间哪一层限制了刷新导致的,就这个项目而言,我发现是SQLiteProvider导致的,估计和里面的Suspend啥的有关。对了把<StatusBar style={isDark ? 'light' : 'dark'} />也移到外面。

最后是:

<SubscriptionProvider>

<ThemeProvider value={NavTheme}>

<SQLiteProvider databaseName={C.DATABASE_FILE_NAME} onInit={migrateDbIfNeeded} useSuspense>

<GestureHandlerRootView>

<Stack>

<Stack.Screen name="index" />

<Stack.Screen name="settings" options={{ presentation: 'modal' }} />

<Stack.Screen name="iap" options={{ presentation: 'modal', headerShown: C.isAndroid, headerTransparent: C.isAndroid, title: '' }} />

<Stack.Screen name="ai-token-bill" options={{ presentation: 'modal' }} />

<Stack.Screen name="history" options={{ presentation: 'modal', headerShown: false }} />

<Stack.Screen name="+not-found" />

</Stack>

</GestureHandlerRootView>

</SQLiteProvider>

<StatusBar style={isDark ? 'light' : 'dark'} />

</ThemeProvider>

</SubscriptionProvider>

游牧周记第41期

· 15 min read
Suhe
This site owner

日常

阳台种菜

九层塔、冰菜、生菜、羽衣甘蓝又割了一茬。

正好在B站看到一个视频,关于吃蔬菜的危害(我是一名生物专业的学生,这就是为什么我不吃蔬菜),其实是有道理的,我懂。 当然他还有关于不吃水果等的视频。

关注

HTX Studio

虽然知道何同学很厉害,但他们在国内发布的视频我都看得不多(感觉比影视飓风强点不多)。 本周突然在Youtube看到了他的英文版,而且发音不太像AI(如果是真的,那他的English is quite good.。 现在有117万订阅,每个视频10w+播放,这是只有6个长视频的情况下,BTW,我全部看完了。 几乎所有的评论都在感叹其制作质量和内容是降维打击。 可能老外们不会注意到这些优秀的团队都来自杭州。

开发

Expo SDK 54后发生的问题

发现ThemeProvider似乎不能动态的调整了,也就是value不能变化。 还在找原因。

CLI 型 AI 开发工具

装了Claude Code和Gemini Cli,下一步还有Codex,总得用到的,先看看吧。 CC由于封号原因,先用了国内某代理,价格比官方贵一倍,还随时抽风。 Gemini Cli用Google Account的API Key连接auth,每次用之前把终端proxy调到位。 Codex还没开始,因为必须注册pro账号,还是等代理吧。 哎国内开发就是找罪受。

React Native Dimensions函数

  • 问题:react native中, Dimensions.get('screen')和 Dimensions.get('window')获取的数据有何不同?
  • 回答(by Kimi):

在 React Native 中,Dimensions.get('screen')Dimensions.get('window') 都可以获取设备的尺寸信息,但它们返回的数据含义不同,适用于不同的场景。


✅ 一句话区别:

  • Dimensions.get('window'):获取的是应用窗口的尺寸(不包括状态栏、导航栏等系统 UI)。

  • Dimensions.get('screen'):获取的是整个屏幕的尺寸(包括状态栏、导航栏等系统 UI)。


📌 举个例子(以竖屏 Android 手机为例):

表格

复制

属性windowscreen
width应用可用宽度屏幕总宽度
height应用可用高度(不含状态栏/导航栏)屏幕总高度(含状态栏/导航栏)

⚠️ 注意事项:

  • iOS 上,windowscreen 通常是一样的(除非有状态栏隐藏或特殊布局)。

  • Android 上,两者经常不同,尤其是有虚拟导航栏的设备。

  • 如果你在做全屏布局适配刘海屏,建议优先使用 window,因为它反映的是应用实际可用区域


✅ 最佳实践:

  • Dimensions.get('window') 来布局页面内容。

  • Dimensions.get('screen') 来调试或获取设备总尺寸(很少用于布局)。


🔍 示例代码:

JavaScript

复制

import { Dimensions } from 'react-native';

const window = Dimensions.get('window');
const screen = Dimensions.get('screen');

console.log('Window:', window);
console.log('Screen:', screen);

✅ 总结一句话:

window 是你能用的, screen 是你能看的。布局用 window,调试看 screen

React Native组合样式的性能问题

由于我的个人代码最佳styling实践中,大量采用了style=[...]方式的应用,以保持一定程度的灵活性,并希望至少做到超过或与tailwindcss(NativeWind)一致的性能。

但与单一style参数比,组合会多大程度上影响性能开销呢? 问了grok。

react native 代码:


const styles=StyleSheet.create({
a: { margin: 16},
b:{ padding: 16},
c:{ margin: 16, padding: 16 }})

...

<View style={[styles.a, styles.b]}> ...

<View style={styles.c}> ...

这两种写法,性能上差距大吗?如果[...]中组合数增加呢?

在 React Native 中,使用 StyleSheet.create 定义样式并通过 style 属性应用样式时,[styles.a, styles.b]styles.c 这两种写法的性能差异需要从样式合并和渲染的机制来分析。以下是详细解答:

1. 两种写法的本质

  • [styles.a, styles.b]:这是将多个样式对象组合成一个数组,React Native 会在运行时将这些样式合并为一个单一的样式对象。合并过程会按照数组中样式的顺序,后面样式的属性会覆盖前面样式的同名属性(如果有冲突)。
  • styles.c:这是一个单一的样式对象,React Native 直接应用这个样式,没有合并过程。

2. 性能差异分析

a. 单一样式对象(styles.c

  • 优点:React Native 直接使用 styles.c 中的样式定义,无需额外的合并操作。样式解析和应用过程更直接,理论上性能开销最小。
  • 适用场景:当样式固定且无需动态组合时,单一样式对象是性能最佳的选择。

b. 样式数组([styles.a, styles.b]

  • 合并开销:React Native 在运行时会对样式数组进行合并,生成一个最终的样式对象。这个合并过程涉及对象属性的复制和覆盖,会有一定的性能开销。
  • 开销大小:对于 [styles.a, styles.b] 这样只有两个样式的数组,合并开销非常小,在大多数情况下几乎可以忽略。React Native 的样式系统经过优化,处理少量样式的合并性能影响微乎其微。
  • 动态性和灵活性:样式数组允许动态组合样式(例如根据条件添加或移除样式),这在需要动态调整 UI 的场景下非常有用。

c. 组合数增加的影响

  • 如果样式数组中的组合数增加(例如 [styles.a, styles.b, styles.d, styles.e]),合并开销会略微增加,因为需要处理更多的样式对象。
  • 性能影响
    • 合并多个样式对象的时间复杂度大致是线性的,取决于样式对象的属性数量。每个样式对象的属性越多,合并过程的开销越大。
    • 在实际应用中,除非样式数组非常长(例如几十个样式对象)或样式对象包含大量属性(例如几十个属性),否则性能影响通常不会明显。
    • React Native 的样式系统是高效的,合并操作在 JavaScript 层执行,且最终样式会被缓存,减少重复计算。

d. 缓存与优化

  • React Native 的 StyleSheet.create 创建的样式对象会被缓存,样式 ID 会被传递到原生层,避免每次都重新解析样式。
  • 对于样式数组,React Native 会在第一次合并时生成一个新的样式 ID,之后的渲染会复用这个 ID,因此重复渲染的性能开销较小。
  • 如果样式数组的内容是动态变化的(例如根据状态或 props 动态添加样式),每次变化都会触发新的合并操作,可能会增加少量开销。

3. 实际性能差距

  • 少量样式组合(例如 [styles.a, styles.b]):性能差距可以忽略不计。现代设备的 JavaScript 引擎和 React Native 的优化使得这种场景下的合并开销非常小。
  • 大量样式组合(例如超过 10 个样式对象):性能开销会略有增加,但除非在高频渲染的场景(如 FlatList 的每一项都动态组合大量样式)或低性能设备上,否则用户难以感知到差异。
  • 单一样式对象styles.c):始终是最优选择,因为它避免了合并步骤,但灵活性较低。

4. 建议与最佳实践

  • 优先使用单一样式对象:如果样式是固定的,尽量将所有样式定义在一个对象中(如 styles.c),避免不必要的合并开销。
  • 合理使用样式数组:当需要动态组合样式(例如根据条件应用不同样式)时,使用样式数组是合理的选择。尽量控制数组长度,避免不必要地拆分样式。
  • 优化动态样式
    • 如果动态样式频繁变化,可以使用 useMemo 缓存样式数组,减少重复计算。例如:
      const combinedStyles = useMemo(() => [styles.a, condition ? styles.b : styles.d], [condition]);
    • 避免在高频渲染组件(如 FlatList 或 SectionList 的渲染项)中使用过于复杂的动态样式组合。
  • 测试性能:在低端设备或复杂场景下,使用 React Native 的性能监控工具(如 Performance MonitorReact DevTools)测试实际性能影响。

5. 结论

  • [styles.a, styles.b]styles.c 的性能差距在大多数情况下微乎其微,特别是在样式数组较短(2-3 个样式)时。
  • 如果样式数组中的组合数显著增加(例如 10 个以上样式对象),合并开销会略有增加,但通常不会对用户体验产生明显影响。
  • 为追求最佳性能,优先使用单一样式对象(如 styles.c),但在需要动态性和灵活性的场景下,合理使用样式数组并配合优化手段(如 useMemo)是可接受的。

感觉和我想的差不多。 如果和tailwindcss的方式对比呢?

在 React Native 中使用 NativeWind(Tailwind CSS 的 React Native 实现)时,多个样式组合的写法(如 className="m-4 p-4 bg-blue-500") 与原生 React Native 的 StyleSheet.create 样式组合(如 [styles.a, styles.b]styles.c)在性能上的处理机制有相似之处,但也有一些关键差异。以下是详细分析,针对 NativeWind 的样式组合与性能处理,并与 React Native 原生样式系统进行对比。

1. NativeWind 的样式组合机制

NativeWind 将 Tailwind CSS 的类名(如 m-4 p-4)转换为 React Native 的样式对象。它在构建时或运行时将类名映射到对应的样式属性,生成类似于 StyleSheet.create 的样式对象,最终传递给 React Native 的原生渲染层。

  • 样式组合的处理

    • 当你写 className="m-4 p-4 bg-blue-500",NativeWind 会解析这些类名,生成一个合并后的样式对象,类似于:
      {
      margin: 16,
      padding: 16,
      backgroundColor: '#3B82F6'
      }
    • 多个类名会被合并为单一的样式对象,类似于 React Native 的 [styles.a, styles.b] 合并过程。
    • NativeWind 的样式解析通常在 JavaScript 层完成,解析后的样式对象会被缓存(通过 StyleSheet.create 或类似机制),并传递到原生层。
  • 与 React Native 原生样式的对比

    • 相似之处:NativeWind 的样式最终会被转换为 React Native 的样式对象,性能上依赖于 React Native 的样式系统。因此,合并多个样式的开销与 [styles.a, styles.b] 的合并过程类似。
    • 不同之处:NativeWind 需要额外的类名解析步骤(将字符串类名映射到样式对象),这在首次解析时会引入少量开销。React Native 的 StyleSheet.create 直接使用预定义的 JavaScript 对象,没有解析步骤。

2. 性能分析

a. NativeWind 的性能开销

  • 类名解析
    • NativeWind 在首次遇到某个类名组合(如 m-4 p-4)时,会解析这些类名并生成对应的样式对象。这个过程涉及字符串解析和映射,可能比直接使用 StyleSheet.create 多一些开销。
    • 一旦样式对象生成,NativeWind 会缓存结果(类似于 StyleSheet.create 的缓存机制),后续使用相同的类名组合时会复用缓存,避免重复解析。
  • 合并开销
    • 当使用多个类名(如 className="m-4 p-4 bg-blue-500 text-white"),NativeWind 会将这些类名对应的样式合并为一个样式对象。合并过程与 [styles.a, styles.b] 类似,性能开销取决于类名数量和样式属性的复杂性。
    • 对于少量类名(例如 2-5 个),合并开销微乎其微,接近于原生样式的数组合并。
  • 运行时动态性
    • 如果类名是动态生成的(例如 className={condition ? 'm-4' : 'm-2'}),NativeWind 需要在每次类名变化时重新解析和合并样式,可能增加少量运行时开销。
    • NativeWind 的 AOT(Ahead-of-Time)编译模式(如果启用)可以在构建时预生成样式,减少运行时解析开销。

b. 与 React Native 原生样式的性能对比

  • 单一样式对象(如 styles.c vs className="m-4 p-4"):
    • 原生 styles.c 直接使用预定义的样式对象,无需解析,性能略优。
    • NativeWind 的 className="m-4 p-4" 需要解析类名,首次渲染时有轻微额外开销,但缓存后性能接近原生。
  • 多个样式组合(如 [styles.a, styles.b] vs className="m-4 p-4"):
    • 原生样式的 [styles.a, styles.b] 合并是简单的对象合并,性能开销小。
    • NativeWind 的多个类名合并涉及类名解析和对象合并,首次解析的开销略高于原生样式,但缓存后差异不大。
  • 大量样式组合(如 className="m-4 p-4 bg-blue-500 text-white flex-1 rounded-lg ..."):
    • 如果类名数量增加,NativeWind 的解析和合并开销会略微增加,但由于缓存机制,实际影响通常不明显。
    • 原生样式的数组合并(如 [styles.a, styles.b, styles.c, ...])在样式对象数量多时也有类似开销,性能差异不大。

c. NativeWind 的性能优化

  • 缓存机制:NativeWind 会缓存解析后的样式对象,类似于 StyleSheet.create。相同的类名组合(如 m-4 p-4)在后续渲染中会复用缓存,减少重复解析。
  • AOT 编译:NativeWind 提供 AOT 编译(通过 @nativewind/vite-plugin 等工具),在构建时预生成样式,消除运行时解析开销。这种模式下,性能接近原生 StyleSheet.create
  • Tree Shaking:NativeWind 支持 Tree Shaking,移除未使用的样式定义,减少打包体积和运行时开销。
  • 静态类名:当类名是静态的(不动态变化),NativeWind 的性能接近原生样式,因为解析和合并的结果会被缓存。

3. NativeWind 是否有更好的性能处理?

  • 与原生样式相比
    • 原生 StyleSheet.create 在单一样式对象(如 styles.c)的情况下性能最优,因为没有解析步骤。
    • NativeWind 在首次解析类名时有额外开销,但通过缓存和 AOT 编译,性能可以接近原生样式,尤其是在静态类名场景下。
  • NativeWind 的优势
    • 开发效率:Tailwind 的类名写法更简洁,减少手动编写样式对象的工作量,适合快速迭代。
    • 一致性:与 Web 开发中的 Tailwind CSS 保持一致,便于跨平台开发。
    • 动态性:通过动态类名(如 className={condition ? 'm-4' : 'm-2'}),可以更方便地实现条件样式,代码更简洁。
  • NativeWind 的劣势
    • 首次解析类名的开销略高于原生样式,尤其在动态类名频繁变化时。
    • 如果滥用大量动态类名(例如在高频渲染的列表中),可能导致性能下降。

4. 性能影响与类名数量

  • 少量类名(如 className="m-4 p-4"):性能与 [styles.a, styles.b] 几乎无差异,解析和合并开销可以忽略。
  • 大量类名(如 className="m-4 p-4 bg-blue-500 text-white flex-1 rounded-lg shadow-md ..."):
    • 解析和合并的开销略有增加,但由于缓存机制,影响通常不明显。
    • 如果类名动态变化频繁(例如在 FlatList 的每一项中动态生成类名),可能导致性能瓶颈,建议优化动态逻辑。
  • 与原生样式的对比:当组合数增加时,NativeWind 的解析开销可能略高于原生样式的数组合并,但实际影响取决于具体场景(如设备性能、渲染频率)。

5. 建议与最佳实践

  • 优先静态类名:尽量使用静态类名(如 className="m-4 p-4"),利用 NativeWind 的缓存机制,性能接近原生样式。
  • 启用 AOT 编译:在生产环境中使用 NativeWind 的 AOT 编译模式,预生成样式,消除运行时解析开销。
  • 优化动态类名
    • 使用 useMemo 缓存动态类名,减少重复解析。例如:
      const className = useMemo(() => condition ? 'm-4 p-4' : 'm-2 p-2', [condition]);
    • 避免在高频渲染组件(如 FlatList 项)中使用复杂动态类名。
  • 控制类名数量:尽量精简类名组合,避免不必要的冗余样式(例如用 p-4 替代 pt-4 pr-4 pb-4 pl-4)。
  • 性能测试:在低端设备或复杂场景下,使用 React Native 的性能工具(如 Performance MonitorReact DevTools)测试 NativeWind 的实际性能影响。
  • 与原生样式混合使用:在性能敏感的场景下,可以结合 StyleSheet.create 和 NativeWind。例如,使用 NativeWind 处理布局样式,使用 StyleSheet.create 处理复杂动画或高频更新的样式。

6. 结论

  • 性能差异:NativeWind 的多个样式组合(如 className="m-4 p-4") 与 React Native 的 [styles.a, styles.b] 在性能上道理相似,都有合并开销。NativeWind 因类名解析有轻微额外开销,但通过缓存和 AOT 编译,性能可以接近原生样式。
  • 大量样式组合:当类名数量增加时,NativeWind 的解析和合并开销略高于原生样式的数组合并,但实际影响通常不明显,除非在高频动态渲染场景下。
  • 更好的性能处理:NativeWind 通过 AOT 编译和缓存机制优化性能,在静态场景下表现接近原生样式。动态类名场景下需注意优化(如使用 useMemo)。
  • 选择建议
    • 如果追求极致性能且样式固定,使用 StyleSheet.create 的单一样式对象(如 styles.c)是最佳选择。
    • 如果需要开发效率和动态性,NativeWind 是一个优秀选择,性能差距在大多数场景下可以接受。

AI的回答考虑到了动态变化问题,大家都不会太好,当然那会有其他的处理方式,大不了为了性能暂不用任何组合而是多个style切换。

游牧周记第40期

· 3 min read
Suhe
This site owner

开发

nextjs的google ads问题

我的nextjs网站,似乎在集成ads时都一直有问题。 原来的代码:

<Script async src={`https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${process.env.NEXT_PUBLIC_ADSENSE_ID}`} crossOrigin="anonymous" strategy="afterInteractive" />

AD不会显示,并有warning:

adsbygoogle.js?client=undefined:225 AdSense head tag doesn't support data-nscript attribute.

按照ChatGpt说法:

问题就出在 Next.js 的 <Script> 组件:它会额外加上 data-nscript 之类的属性,而 Google AdSense 的脚本是 严格要求只能原样引入 的。

✅ 正确做法

直接用原生 <script>,不要用 next/script

// app/layout.tsx 或者 _document.tsx <head> <script async src={`https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${process.env.NEXT_PUBLIC_ADSENSE_ID}`} crossOrigin="anonymous" ></script> </head>


❗️另外注意

  1. process.env.NEXT_PUBLIC_ADSENSE_ID 必须在 .env 文件里定义,例如:

    NEXT_PUBLIC_ADSENSE_ID=ca-pub-1234567890123456

    并且要以 NEXT_PUBLIC_ 开头,否则 Next.js 不会暴露到前端。

  2. Google AdSense 要求 必须在 <head> 内加载,不要放到 <body>

当然我还是硬编码ads id方便一些。

关注

江边学社

佛学频道,介绍里有一句:“特别感谢@闲者知无涯-吴魏老师,他是本频道一切内容的缘起;”。 这个信息量大,我才知道自己听了很久,随时被封的闲者知无涯叫做吴魏。 另外闲者知无涯在youtube的频道多年未更新了,关注也就几百人。 而这位有2万+,每个视频点击也是数千到数万。 音频完全来自闲者知无涯,配了动画和图,很不错。 看了几集,回顾当时听的情况,感觉真的可以理解他为啥被封多次了,什么“永远健康”之类的😂。

影视

星期三season2

第4-8集出来了,原来当爆米花片看的,没想到越来越惊艳,后面剧情和演员表现都非常厉害。难得的不落俗套的娱乐佳片。 小狼女灵魂互换那集真是太棒了,隐身妹也没那么刻板和工具人了。 我就觉得乌鸦女面熟,果然是久违的Lady Gaga。

异形地球

各方面评价都很高,我觉得还行。 Fox的片,暴力方面不打折扣。 刚看了第5集,有那味了,愚蠢的人类啊。 异形不是只有一种,他们之间有竞争,神仙(妖怪)打架,眼球怪的压迫力超过JJ怪了。

游牧周记第39期

· 2 min read
Suhe
This site owner

开发

expo的scrollView只能触摸边缘滑动

expo app中的scrollview,发现只有触摸边缘才能滑动,而中间等部分无反应,什么原因?参考代码:


<ScrollView
ref={scrollViewRef}
style={s.flex} contentContainerStyle={s.p}>
<Text style={tx.para_inline}>
{response}
</Text>
</ScrollView>

AI给出一个方法,为 <Text> 添加pointerEvents="none"。 实际上无效。

最后我发现原来是ScrollView的style问题,去掉s.flex就好了,只需要contentContainerStyle。

后来发现还是无效。😓

新版本学易发布

其实只是4.0.+小版本升级,但改变了首页问卦按钮,加入了一个新界面:指引,也就是向导。

算是多年来界面和操作难得的更新了。

和一个湖南妹子推荐App,不懂易经只想算卦的前提下,我自以为已经很直观的首页还是让人一头雾水,必须要有个更加傻瓜的方法引导用户一步步操作。

推广

去图书馆和ChatGPT沟通了一上午,希望得到点app推广的建议。 答案都很好,但没有超出我想象的部分。 总的来说就是要持续不断地制作发布有一定价值和吸引力的作品(图、文、视频)。 后来请AI帮我做了新头像,用于数字游牧类视频,关于app推广的单独用app logo,并单独开了新的youtube频道。

美剧

广告狂人

久仰大名多年就是一直错过的神剧,无意之中在B站看了一段就停不下来了。 没错B站非官方发布的啥都有,除了两个限制:露点的要打码,TV版放不了(我的TCL)。

男主就是所有男人心目中希望自己成熟后的样子,原来今年看的苹果好剧"掩耳盗邻"就是他啊。

游牧周记第38期

· 3 min read
Suhe
This site owner

关注

最近听(看)了不少哲学方面的内容,颇有种信息量巨大,需要整理提炼的冲动,我看来应该做笔记,写点啥了。

大问题

哲学。喉咙不清,可能有轻度炎症。 但内容极好,选题精彩,节奏紧凑。 B站

徒梦的学习笔记

哲学对话,非视频! 作为背景听太棒了,台湾的。以2人对话方式展开。 Bilibili 后来发现内容是从其他语音平台搬运的,仅B站就还有类似频道。 原频道还没有找。

# PowerfulJRE

没啥好说的,Joe Rogan的访谈记录, 2000万+订阅。 Youtube 用来练英语听力不错。 B站的思维黑洞Lab,只有2k多粉,收集了大量的这类谈话节目,也不错看。

我的视频

App快速开发上架挑战

7天能完成一个新App的开发和上架吗? 我的实际挑战纪录片B站

阳台种菜

昆明降雨大降温,结果冰菜长得最好。

开发

expo-router和unistyle的冲突

我第一次使用unistyle,这时已经是v3+了,网上和AI掌握的很多资料失效过时。

核心在于执行顺序,要保证最早执行unistyle的configure,只有这个办法了:

// app.json中
"main": "index.ts"

// 新建index.ts(根目录)
import "./src/styles/unistyles"; // 初始化 unistyles 必须最先执行
import "expo-router/entry"; // 启动 Expo Router

不然再怎么配置也没有用。 官网说明

App开发的这些边距尺寸概念

下面这张图能帮你一眼看懂它们各自「量」的是哪一块:

┌────────────────────────────────────────┐
│ Status Bar ← statusBar.height = 20 │
├────────────────────────────────────────┤
│ │
│ APP CONTENT │
│ │
├────────────────────────────────────────┤
│ Navigation Bar │
│ (虚拟三大键/手势指示条) │
│ ← navigationBar.height = 44 │
└────────────────────────────────────────┘
  • insets
    告诉你「内容区域」距离屏幕四周安全距离是多少。
    例如:

    insets = {
    top: 28, // 避开刘海 + Status Bar
    bottom: 40, // 避开 Navigation Bar
    left: 0,
    right: 0,
    ime: 0 // 键盘弹起时为键盘高度
    }

    所以:
    insets.top 通常 ≈ Status Bar 高度 + 刘海高度
    insets.bottom 通常 ≈ Navigation Bar 高度 + 底部圆角高度

  • statusBar
    只量系统状态栏本身(电量、信号、时间那一栏)的 绝对尺寸
    在 iOS 刘海屏和 Android 状态栏高度不同,但 不会 把刘海额外高度算进来。

  • navigationBar
    只量虚拟导航栏(三大键或手势提示条)的 绝对尺寸
    如果设备启用了全面屏手势,这个高度可能是 0。


一句话总结

名称描述
insets内容可安全放置的区域 ↔️ 你需要用它来做自适应布局
statusBar系统状态栏的精确尺寸
navigationBar虚拟导航栏的精确尺寸

布局时 优先用 insets,只有当你要精确知道状态栏或导航栏本身多高时才去看 statusBar / navigationBar

游牧周记第37期

· 11 min read
Suhe
This site owner

美剧

安道尔2

第一季就很好看,个人觉得是星战宇宙中超过曼达洛人的存在,其实也就这2部我看得下去。 这一季开局一般,到了后半截开始起飞,非常有后劲。

异形.地球

看了2集,蛮好的。 太空船和外星生物这次没有掉到纽约或西方主流大城市,轮到泰国了(新暹罗,应该指曼谷吧)。

星期三2

这调调还在。 说实话前段时间看了些演技极差的烂片后,发现这部片子不管怎么扯,所有演员的演技在线。

听小机 Listen Bot

没错,这就是我花了1周开发上架的App。 我的app网站 8月13日凌晨终于审核完成。 主体开发就2天,然后第三天进入IOS发布审核。 play市场1天完成。 appStore审核了4天,所以总计7天。

一周上架一个可以赚钱的app?

  1. idea酝酿时间不能算在内,其中最主要的不是核心功能思路,而是落实有没有成熟的同类产品。
  2. 从动手写第一个代码开始算起,这时候遇到第一个问题,包名怎么取?虽然是个英文名,甚至是简写而已,但影响到写代码过程中的思路和体验,最好基本确定,不要coding过程中一直想改,影响心情。
  3. 名称用了4个钟头才定下来,其中90%都是在开启上网功能的ChatGpt上查重和想点子,最后出门溜达一圈自己想出了一个,回家用AI落实没啥太成熟的同行应用,还被夸了一道,就定了。中英文一起。
  4. 这是一个和语音识别有关的应用,AI从我描述的核心功能出发,设计了技术栈总结,结果发现ChatGPT建议的好多是第三方包和服务,反而是国产Kimi推荐了一个靠设备自身识别功能的,非常令人兴奋。
  5. 这时已经是晚上,用新的包名在macbook上建了一个expo最新版(sdk都54+了),就累不动睡了。
  6. 第二天坐公交去五华区图书馆,从10点干到13点,终于把核心的语音识别功能调试成功,高高兴兴回家去,准备开着大屏幕接着写。
  7. 然后就是轻车熟路的AI LLM服务接入,唯一问题是什么时候切入,发现是个非常细节的问题,调试到自己一头雾水的时候,已经凌晨了,还是睡了。
  8. 已经算第三天了,想着继续降低一点体验感要求,就图个能尽快提交市场。首先简化了AI介入为手动,然后UI部分完全套用之前用熟了的自创useStyle框架(各位看我教程都明白),快速搞了个基本能用的版本。
  9. 这时候想到必须今晚提交,那还得有logo(icon)啊,开启免费版ChatGPT,说了想法,结果生成一个还不错的小人,就是手有点怪,让他改,改更怪了,运气好在5张的今日免费额度用完前搞定了。
  10. 基本文案也用AI弄了,当然自己加工时间也不短,差不多耗了2个小时。中英日文版,没错,及时如此紧张的时间,我还是用了多语言。先写简体中文的json,然后在IDE中AI生成其他版本。顺便说一句,这次IDE用回了VSCode,Cursor把人整郁闷了这次放弃。总之AI没有花一分钱。
  11. 然后截图,准备上线。这时候才发现问题来了,Eas网络抽风,死活都没法在线打包,本地也卡得要死,有时试上十多次才能进入正常运行环节,等到地老天荒才能打成一个包。这个过程中也没闲着继续调试App功能,不断发现bug和可以优化的地方,同时请AI检查逻辑错误。就这么边调试边打包,都第n个build了,发现天已经黑了。
  12. 说明一下,现在还没试过任何android的命令,不是说要完全放弃安卓,而是以苹果为目标,抓住快速变现人群才是关键,iOS版上线成功,才有做其他市场的心情。
  13. 这天真累啊,RevenueCat的配置也快速搞定了,虽然只是iOS,这可是我最关心的功能,虽然做了无数次,还是有点担心它抽风。事实证明还好,集成得很快。
  14. App store上架资料准备期间,国内ICP备案的申请也在进行,这时候发现已经是周五凌晨,我理论上已经到第4天了。3天完成的计划被打破。ICP备案如果不加入安卓,以后要补充可能麻烦,于是在基本没调试过android版的情况下,先打包apk,这个漫长的调试啊,整完已经3点am了。累死,睡了。
  15. 第4天,基本都在折腾打包和上架提交,这期间解锁了GitHub Action对接Eas的新技能,打包效率大幅提升,直到Eas鸡贼的免费额度用完,不过iOS版也基本可用了,终于提交了AppStore,发现第一次提交不需要ICP备案也没问题,即使后面出了问题只影响国内市场发布,等等也无所谓。秉承来都来了的原则,把Google Play市场的资料也整了。第一次运行了android版,算是意外的顺利,这次开发过程中比较幸运的一件事,没花多少时间修改安卓UI就ok了。看来安卓要先上线,唯一的问题是我考虑安卓打包签名用本地文件,不想依赖云服务,于是也只能本地打包,那个速度不比eas的ios本地打包快多少。过程中不断发现bug和优化点,打了n个包以后提交测试,然后推进正式版,居然实现了android先上线。这时候又到子夜了,今天结束。
  16. 第5天周末,发现iOS还在待审核状态,ICP备案阿里云方面的工作和工信部那边完成了,管局还没开工,估计要等周一了。今天状态稍微放松点了,不断测试调试,iOS等审核不敢发新版,android连续提交2个版本,包括RevenueCat的集成,Google这边挺折腾的。今晚不用熬夜了,下午同学通知聚会吃菌子,然后就骑单车出门了,算是锻炼一把。
  17. 截至目前iOS还在待审核状态,我个人的经验证明3天上架不太可能,除非只是Google Play,加上iOS的话4-5天算是极限了。至于国内市场,本人可不敢想,作为个人开发者基本放弃了。整个过程中代码时间大约只占一半,Eas打包意外的烦人,大约浪费了我1/4的时间,还有1/4的时间是文案、图片和国内ICP备案等琐事,包括想名字等。

下次看是否出一个审核过程的要点说明。

Expo开发

一个新app从开发到上架,再次归纳

  1. ideas
  2. AI确认名称和技术栈
  3. coding和测试,包括GitHub的代码管理
  4. 集成RevenueCat,如果有其他第三方服务包括广告等,继续
  5. 真机测试,先集中力量搞iOS,毕竟是付费主力
  6. Eas打包测试,配置GitHub Actions触发打包,这一步也可以更靠前到3
  7. AppStore id和证书的申请
  8. AppStore新建App,AI辅助完善多语言文字、icon,搞点截图(注意大小,还有iPad13寸版本)
  9. 这时候如果有国内市场,就要考虑ICP认证了,这件事涉及到Android最好一起做
  10. 不管Android版本测试与否,先打包apk,为了ICP
  11. 阿里云去申请ICP备案,前面一些工作是为了提取信息
  12. 备案要几天,这时候继续提交AppStore,先testFlight,但也不要太细,差不多就提交
  13. 测试Android,差不多了就打包提交GooglePlay,先申请,截图和文字可以先用iOS的
  14. 第一次aab打包,之前建立新的keystore文件签名(下方有详细介绍)
  15. 提交GooglePlay,也是测试一下,发布正式版

新android项目的本地签名

云端的选择有两个,在GooglePlay或Eas服务中设置。

不想依赖云服务,所以最终选择了自己建立keystore文件。 这里发现以前项目的keystore不能复用,不然GooglePlay不批准,也就是说一个app要对应一个不同的签名。

macOs上这样做:

# 确认安装了keytool
keytool -version

# 如果没有,安装java,没错,这玩意是java自带的
brew install openjdk

然后生成keystore文件:

keytool -genkeypair \
-v \
-storetype JKS \
-keystore my-release-key.keystore \
-alias my-key-alias \
-keyalg RSA \
-keysize 2048 \
-validity 10000

参数含义
-keystorekeystore 文件名(会生成在当前目录)
-alias你给密钥取的别名(之后构建要用)
-keyalg加密算法(Android 推荐 RSA)
-keysize密钥长度(2048 够用)
-validity有效期(单位天,10000 天≈27 年)
执行上面命令后,会提示你输入:
  • keystore 密码(必须记住)
  • 姓名、组织、城市、省份、国家代码
  • key 密码(建议和 keystore 密码相同,方便记忆)

生成后可以验证一下:

keytool -list -v -keystore my-release-key.keystore

会提示输入密码,然后显示:

  • SHA1 / SHA256 证书指纹
  • 证书有效期

比较容易犯的错误之一是把alias搞错了,可以这样找回:

keytool -list -v -keystore path/to/my-release-key.keystore

然后你的expo项目中eas.json:

 
{
"build": {
"apk": {
"android": {
"buildType": "apk"
},
"credentialsSource": "local"
}
}
}

local就是指本地的加密配置文件: 建立 credentials.json(不要传到 Git,前面的key store文件也是)

{
"android": {
"keystore": {
"keystorePath": "./my-release-key.keystore",
"keystorePassword": "你的_keystore_密码",
"keyAlias": "my-key-alias",
"keyPassword": "你的_key_密码"
}
}
}

最后打包:

eas build --platform android --profile apk

tailwindcss v4+的theme怎么搞?

查遍全网,只有这个靠谱!AI全部懵了

tailwindcss中global.css的三个层次概念

我遇到的问题:

@layer base {
a {
text-decoration: none;
}
}

这段代码无效,但把内容从@layer base中提到最外层,就ok了。

AI解读原因:

Tailwind CSS 在编译时会@layer base 里的样式权重降到低于 base 层之外的普通 CSS,而浏览器对 <a> 的 UA 样式(text-decoration: underline)又属于「user-agent stylesheet」,权重很低,所以放在最外层能覆盖,放进 @layer base 反而被 UA 样式顶掉。

这里涉及一个Tailwind 的三层概念。 ──────────────

  1. base
    浏览器 UA 样式 + 你的 @layer base {} 代码 → 权重最低(0-0-0)。
    典型用途:重置 <a><h1> 等标签默认样式。

  2. components
    @layer components {} → 权重 0-0-1。
    典型用途:你写的 .btn.card 等工具类。

  3. utilities
    .text-red-500 等原子类 → 权重 0-1-0,足够高,不会被 base/components 顶掉

──────────────

为什么放在 @layer base 里有时“无效”?

• 浏览器 UA 对 <a>text-decoration: underline 权重也是 0-0-0,
但「后出现的规则」会覆盖「先出现的规则」。
• Tailwind 把 base 层放在 最前面,所以
base → UA → 你的最外层 CSS,最终 UA 样式反而在最后生效。
• 因此需要:

  • 把样式写在 Tailwind 之后(最外层),或

  • !important / :where() 提升/降低权重。

什么时候该用 @layer base

• 想整理所有「全局标签重置」到一处,而不是散落各处。
• 不介意权重 0-0-0,或愿意配合 !important / :where()
• 方便 Tailwind 在 PurgeCSS 时知道「这段代码属于 base 层,不要误删」。

简记
@layer base ≠ 高权重,只是一个「分组 + 排序」指令。

那最初的问题这样做,定义就生效了:

@layer base {
a {
text-decoration: none !important;
}
}

游牧周记第36期

· 8 min read
Suhe
This site owner

游戏

Diablo IV的卡死问题

2025之后,就卡顿极其严重,问了无数AI,查了无数资料均无效果。 后来还是Gemini 2.5Pro给出了稍微有用的答案。 去掉一个配置文件就好了。 LocalPrefs.txt,说是在Diablo IV的文档目录下,我建议搜一下。 其实就是彻底把游戏配置归零,然后进入游戏提醒重新配置,你也可以直接跳过。 然后真的就顺了很多,虽然和几年前刚刚安装时比有时候还有点卡。 原理就是作为多年老用户,遗留了很多老的配置在配置文件中,互相可能有冲突了。

Gemini越来越神了,会否成为真正的AI一哥?

expo开发

iOS app如何去掉Apple Pay功能设置

我注意到remove的是com.apple.developer.in-app-payments merchant.com.your.merchant.id 貌似内购,而不是apple pay啊


🧠 结论先讲:

com.apple.developer.in-app-payments 是 Apple Pay(实体支付),不是内购(IAP)!


✅ 区分这两个容易混淆的权限

能力名称键名(Key)功能用途说明
🛒 In-App Purchase (IAP)com.apple.InAppPurchase(隐含于系统)订阅、金币、虚拟物品App Store 管理,Xcode 和 profile 默认支持
💳 Apple Pay(实体支付)com.apple.developer.in-app-payments实体商品支付、Apple Wallet 支付需要配置商户 ID,如:merchant.com.xxx

🔍 你看到的这个字段:

<key>com.apple.developer.in-app-payments</key>
<array>
<string>merchant.com.your.merchant.id</string>
</array>

正是 Apple Pay 的专属字段,它告诉 Xcode:

这个 App 打算使用 Apple Pay,接入商户 merchant.com.your.merchant.id


❗️它不是 IAP(内购)

  • IAP 并不需要你手动加任何 entitlement

  • 只要你的 App ID 开启了 In-App Purchase 能力,并在 App Store Connect 配置商品,就可以正常使用;

  • Apple Pay 才需要这个 entitlement,并配合后台商户配置使用。


✅ 你应该怎么做?

  • ✅ 如果你只使用 IAP(订阅、金币),删除这个字段是完全正确的

  • ✅ 同时在 Apple Developer Portal → App ID → Capabilities 中关闭 Apple Pay;

  • ✅ 用 eas credentials 删除旧 profile,重新生成干净的 App Store profile;

  • ✅ 在 eas build 时加入 --clear-cache,清除旧的 entitlements 残留。


✅ 为什么这个字段出现在 prebuild 后?

Expo 的模板可能默认为支持 Apple Pay 的插件(如 expo-payments-stripe)预留了这个能力。如果你:

  • 用了 expo-dev-client

  • 或者启用了其他支付相关插件(即使没用 Apple Pay)

就有可能在 prebuild 时被注入。

suhe注:不认同这点,以前没出现过,原因还是不详。


✅ 最后确认(只使用 IAP 的最小需求)

项目是否需要 Apple Pay
expo-in-app-purchases❌ 不需要
react-native-iap❌ 不需要
App Store Connect 商品配置❌ 不需要 Apple Pay,仅需要开启 IAP 能力
merchant.com.xxx✅ 仅 Apple Pay 需要,IAP 完全不需要

GitHub结合Eas打包的工作流程

一个人开发项目,而且希望主分支 main 继续承担日常开发任务,同时只在需要 EAS 构建时创建 PR + 贴 label 触发远程打包。这种需求非常清晰,也非常实用,我们可以为你设计一套轻量、流畅、零冗余的 Git 使用流程,专为“单人开发 + Expo GitHub App 构建”场景优化。


✅ 你的目标简化版

需求实现方式
日常开发就在 main 分支上继续直接用 main 写代码,无需每次都建分支
需要远程构建时触发 PR + Expo 构建快速从 main 创建一个构建分支,发 PR,贴 Label,完成后自动删除

🧭 推荐工作流设计(为你量身定制)

✅ Step 1:日常开发保持在 main

你继续在 main 分支做日常开发、调试、测试,无需变动流程。

git checkout main
# 编辑代码...
git commit -am "修复组件显示 bug"
git push origin main

✅ Step 2:需要构建时,创建一个构建专用分支

你只在需要构建的时候,从当前的 main 快速拉个构建用分支(不做额外开发):

git checkout -b build/eas-android-20250803
git push origin build/eas-android-20250803

📌 命名建议:build/eas-平台-日期,比如 build/eas-android-20250803


✅ Step 3:GitHub 上发起 PR(无需修改任何代码)
  1. 打开 GitHub,会自动提示你:

    Compare & pull request
  2. 点击进入 PR 页面。

  3. 在右侧添加 Label,例如:

    eas-build-android:prod_android
  4. 不需要写代码 diff,不需要添加 Reviewer,你自己点就好。


✅ Step 4:Expo GitHub App 识别 Label 并构建
  • 构建过程会在 PR → Checks 里展示;

  • Expo Dashboard 会显示构建进度和下载链接;

  • 你可以继续用电脑干别的事,等它构建完成。


✅ Step 5:构建完成后,合并 PR 并清理分支

如果你希望主分支也保留这次构建记录:

# 在 GitHub PR 页面点击 “Merge Pull Request”

然后回到本地:

git checkout main
git pull origin main
git branch -d build/eas-android-20250803
git push origin --delete build/eas-android-20250803

也可以 不合并,直接关闭 PR 然后删除分支也没关系 —— 因为你本来就在 main 写代码,构建用 PR 只是工具人 😎。


✅ 自动化版本(可选 Shell 脚本)

如果你想一条命令就完成构建流程(不用记这么多命令),你可以用如下脚本:

#!/bin/bash

DATE=$(date +%Y%m%d)
BRANCH=build/eas-android-$DATE

# 确保当前在 main
git checkout main
git pull

# 创建构建分支并推送
git checkout -b $BRANCH
git push origin $BRANCH

echo ""
echo "✅ 分支 $BRANCH 已创建并推送"
echo "👉 请访问 GitHub 页面创建 PR 并添加 label: eas-build-android:prod_android"

保存为 eas-build.sh,加权限:

chmod +x eas-build.sh

然后以后只需运行:

./eas-build.sh

就自动完成创建分支 + 推送,剩下你只需在 GitHub 上贴个 label。


✅ 为什么这个流程适合你(一人开发者)?
特性优点
main 分支持续开发不被强迫换分支、不打断手感
构建过程隔离不会把构建 commit 和构建控制信息混进 main
构建结束即可删PR 不需要合并,临时存在就好
label 构建精准控制不用担心误触发、不用写复杂 CI 文件
GitHub 自动记录构建日志PR 页面和 Expo Dashboard 保留构建历史,可审计

🧩 可选优化项(建议长期使用时加上)
  • .github/PULL_REQUEST_TEMPLATE.md:自动提示自己加 label;

  • .gitignore:确保构建相关的缓存文件不被提交;

  • eas.json 里 profile 明确配置好平台和镜像,便于稳定复用。


✅ 总结

你可以用这个结构保持开发流畅、构建可控:

开发场景操作
正常写代码就在 main
想打包构建main 拉个 build/eas-xxx 分支 → 发 PR → 贴 label
构建完成Merge 或 Close PR,然后删掉构建分支
重复这个流程所有构建 PR 都是干净、独立、不会干扰你 main 的

Eas云端打包pnpm项目的问题

报错情况:

eas打包expo app项目报错:Running "pnpm install --frozen-lockfile" in /Users/expo/workingdir/build/ directory ERROR  packages field missing or empty For help, run: pnpm help install pnpm install --frozen-lockfile exited with non-zero code: 1

之前没有发现过,但近期出现,可能两个原因:

  1. pnpm/eas/expo版本更新;
  2. eas云端服务是gitHub Action触发的; 之前的一个项目遇到过,差点忘了,解决方法就是在pnpm-workspace.yaml中加入:
# 告诉 pnpm 在哪些文件夹里寻找项目
packages:
# 如果您的 Expo 项目就在根目录,并且没有其他包,可以这样写:
- '.'

App 切换到后台的Expo优雅处理方式

首先做一个通用勾子。

// useAppStatePaulseTask.tsx

import { useEffect, useRef } from 'react';
import { AppState, AppStateStatus } from 'react-native';

export function useAppStatePauseTask(onPause: () => void) {
const appState = useRef(AppState.currentState);

useEffect(() => {
const subscription = AppState.addEventListener('change', (nextAppState: AppStateStatus) => {
if (appState.current.match(/active/) && nextAppState.match(/inactive|background/)) {
onPause(); // App 进入后台时触发
}
appState.current = nextAppState;
});

return () => subscription.remove();
}, [onPause]);

}

在页面调用的例子:


// 在 App 切换到后台(如按了 Home 键、切换到其他 App)时停止某些正在进行的任务

useAppStatePauseTask(() => {
handleAiStop(); // 中止 AI
ExpoSpeechRecognitionModule.stop(); // 停止语音识别
});

新作品

没错,我要开发新的app了,灵感来自于到中国的一个澳洲朋友。 和AI有关,和语言有关,先透露到这里。 总之先去AppStore注册,还有RevenueCat把付款机制做了,图标啥的AI赶紧整一个。 对了还有ICP备案啥的。 一切从简从速。

游牧周记第35期

· 5 min read
Suhe
This site owner

龙树谅从不失手

代码

我的App终于更新

学易从3.X 到 4.X时代了,上次大版本升级不知是3年还是几年前的事了? 这次筹划准备了半年吧,中间还在外旅游了几个月,回来后又被Diablo耽搁几周,实际整合代码的时间约3周,最累是发布前几天。 主要变动都在UI上,终于把几张封面图换了。

expo router的android奇异事件

半透明层覆盖,用animation: 'fade_from_bottom', 解决。 AI说:有时动画也会造成灰色背景干扰。可尝试这样的方法,反正不用default。

expo sdk53后android奇异事件

问题:

expo router项目从52升级到53后,代码没变,但android上出现之前正常的顶部显示贴边了,穿过了状态栏。这种现象原因及解法?

Grok回答:

问题原因

在将 Expo Router 项目从 SDK 52 升级到 SDK 53 后,Android 上出现顶部内容贴边、穿过状态栏的问题,主要原因是 Expo SDK 53 引入了对 Android **边到边(Edge-to-Edge)**显示的支持。这一新特性使得应用的内容默认会延伸到状态栏和导航栏下方,而不像 SDK 52 及之前版本那样默认避免与系统栏重叠。以下是具体原因:

  1. 边到边显示的引入
    • SDK 53 默认启用了 Android 的边到边布局,应用内容会绘制在状态栏和导航栏下方,导致顶部内容(如头部标题或导航栏)可能与状态栏重叠。
    • 在 Android SDK 34 及以下版本的设备上,这一问题尤为明显,因为边到边显示的适配可能不完全兼容旧版 Android 系统。
  2. 状态栏透明性(Translucent Status Bar)
    • 如果你在代码中使用了 expo-status-bar 且设置了 translucent 属性为 true,状态栏会变为透明,允许内容绘制在其下方,从而导致重叠问题。
  3. SafeAreaView 或安全区域处理未更新
    • 如果你的应用未正确使用 SafeAreaView 或 react-native-safe-area-context 来处理状态栏的安全区域,升级后内容可能会直接贴边,忽略状态栏的高度。
  4. Expo Router 布局问题
    • Expo Router 的根布局可能在 SDK 53 中默认添加了一个白色背景(或其他默认样式),当启用边到边显示时,内容会填充整个屏幕,可能导致视觉上的重叠问题。
  5. 新架构(New Architecture)的影响
    • SDK 53 默认启用了 React Native 的新架构(New Architecture),这可能影响某些组件(如 StatusBar 或导航相关组件)的行为,尤其是在与边到边显示交互时。

我采用的解决方法:

  • 在根布局文件(通常是 app/_layout.tsx)或相关屏幕组件中,使用 SafeAreaView 包裹内容。
  • 示例代码: ·
import { SafeAreaView } from 'react-native-safe-area-context';
import { View, Text } from 'react-native';
import { Stack } from 'expo-router';

export default function Layout() {
return (
<SafeAreaView style={{ flex: 1 }}>
<Stack />
</SafeAreaView>
);
}

expo模糊效果的问题

本来是另外一个问题引出的。 我用expo-image组件的blurhash来做模糊效果背景图,ios良好,Android问题很多,出现黑色穿透背景等。 突然想起expo-blur的BlurView在android上也是试验性的,多少年了似乎也没有正式支持。 果然ios才是亲儿子,哦不对,expo是ios的乖儿子(自认的?)。 问了ChatGPT,它推荐一个第三方react-native-blur组件,我看两个月前还在更新,star数只有20。问一下AI他们的区别是什么? 回答如下:

iOS 上两者都可以用;但 Android 上 expo-blur 的“模糊”其实是假的,性能差、bug 多,@sbaiahmed1/react-native-blur 才是真正能用、性能好、效果稳定的模糊组件。

如果你愿意配置原生或使用 EAS Build,强烈推荐切换到 @sbaiahmed1/react-native-blur

要么我还是用图片文件算了?

种植

继续跟踪羽衣甘蓝、冰菜、罗勒和其他,还有生菜也长出来了,移栽。 阳台不够大真麻烦啊。

看片

几个月前看到开头那些“正确角色”就被劝退的《杀戮人机》。现在片荒,又拿出来看,硬着头皮撑过前面半截,大脑麻木了一阵就进入状态了。 总的来说很苹果味,轻松还带点悬疑。 亚马逊的《基地》都第三季了,看预告似乎第一反派“骡”出现了,要进入剧情高潮。但我从第一季开始就抑制不住对这片子的恶心,即使跳着看也很少坚持看完一集。作为小说粉的我,实在受不了这个改变和表演的风格。如果是50-70年代科幻间谍风反而我会喜欢。 亚马逊的魔戒前传很多人不喜欢,我还觉得不错,但这部...真就算了。 《黑袍纠察队》是他们不可超越的神作。

《研究院》《倒计时》都看了第一集,感觉节奏好,吸引力还行,但没有追剧冲动。