跳到主要内容

游牧周记第44期

· 阅读需 2 分钟
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 分钟
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 分钟
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 分钟
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 分钟
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 分钟
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 分钟
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 分钟
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 分钟
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 分钟
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年代科幻间谍风反而我会喜欢。 亚马逊的魔戒前传很多人不喜欢,我还觉得不错,但这部...真就算了。 《黑袍纠察队》是他们不可超越的神作。

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