普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月26日首页

Skip 开源:从“卖工具”到“卖信任”的豪赌 - 肘子的 Swift 周报 #120

作者 Fatbobman
2026年1月26日 22:00

Skip Tools 日前宣布全面免费并开源核心引擎 skipstone。这意味着 Skip 彻底改变了经营方式:从“卖产品”转向“卖服务+社区赞助”。这次变化,既有对之前商业模式执行不佳而被迫调整的无奈,也体现了 Skip 团队的果敢——在当前 AI 盛行、开发工具格局固化的背景下,主动求变,力求突破。

海科新源:签署长期合作协议,向比亚迪锂电池湖北项目供应协议所购产品每年至少10万吨

2026年1月26日 19:24
36氪获悉,海科新源公告,公司与深圳市比亚迪锂电池有限公司(简称“比亚迪锂电池”)签署长期合作协议暨湖北项目管输合作协议,协议有效期3年,在协议有效期内,海科新源以管输方式向比亚迪锂电池湖北项目供应协议所购产品每年至少10万吨。产品的实际采购数量以双方签署的相关协议为准。

天岳先进:2025年预亏1.85亿元—2.25亿元

2026年1月26日 19:22
36氪获悉,天岳先进公告,预计2025年度实现营业收入14.5亿元至15亿元,与上年同期(法定披露数据)相比,同比减少17.99%到15.17%。预计2025年归属于母公司所有者的净利润为亏损1.85亿元—2.25亿元,上年同期盈利1.79亿元。

豆包手机助手:严格遵循用户授权与合规的原则,仅在用户明确授权的前提下调用必要能力

2026年1月26日 19:03
36氪获悉,豆包手机助手在微博发文表示,我们理解外界对于手机助手安全与隐私的关注。豆包手机助手严格遵循用户授权与合规的原则,仅在用户明确授权的前提下调用必要能力。此外,豆包手机助手在云端处理用户手机屏幕内容时,严格遵循 “不存储、不训练”的原则,数据传输过程加密,使用过程有严格的保护措施,确保用户隐私安全。

宝马确认:V8 和 V12 不会消失,还要开发下一代 4 系

作者 李华
2026年1月26日 18:54

当奥迪决定给 A5 Coupe 画上句号,奔驰把 C 级和 E 级的轿跑合二为一变成 CLE 时,大家似乎都默认了双门轿跑正走在消亡的路上。研发成本高,受众越来越窄,双门轿跑在效率至上的时代显得有些格格不入。

但宝马不这么想。

▲ 新一代 4 系假想图,图片来自:SugarDesign

宝马研发主管 Joachim Post 最近明确了新一代 4 系的开发计划。这则消息让内燃机的拥趸们松了一口气,因为宝马不只要留住 4 系,还要保住 V8 和 V12 这种大排量内燃机。他在接受 Autocar 采访时直言:

4 系对我们来说是一款非常重要的车,它更具运动感,而宝马作为一个运动品牌,在性能方面有着清晰的传承。它(4 系)在未来也将扮演重要角色。

Joachim 表示,下一代 4 系会沿着两条完全不同的技术路径演进。燃油版本继续在升级后的 CLAR 平台上迭代,纯电版的 i4 则会直接用上全新的 Neue Klasse 架构。

▲ i4 假想图,图片来自:SugarDesign

这种分工避开了折中的产品定义。燃油车型可以继续深挖内燃机的潜力,纯电车型能享受到原生平台带来的新体验。

Joachim Post 还在采访中反复提到了协同效应,只要 3 系和 X3 这些走量的主力还在更新,通过平台协同开发出来的 4 系就不需要背负太沉重的研发包袱。

开发衍生车型的成本并没有想象中那么高。每当我们开发 3 系或 X3 时,我们都会同步考虑到 4 系或 X4。我们通过这种协同效应,让衍生车型能非常容易地基于基准车型构建出来。

▲新 M3 假想图,图片来自:Carscoops

这种共享并不只是共用几个零件那么简单,它涉及到整条生产线的自动化排产以及通用的底层电气架构。

在竞品撤退的空白地带,宝马保留了一道宽厚的护城河。

具体到产品,经过了多个世代的打磨,CLAR 平台已经进化到了极其成熟的状态,它最大的优势在于那套纵置引擎的布局灵活性。不管是搭载 48V 轻混系统的四缸发动机,还是作为 M Power 灵魂所在的 S58 六缸引擎,CLAR 都能在不改变白车身核心结构的前提下,提供稳定的物理支撑和散热空间。

▲ 新一代 M3,图片来自:BMW

至于 Neue Klasse 架构下的纯电版 i4,按照目前披露的技术细节,它很有可能会搭载 108kWh 的高能量密度电池组,配合 800V 高压平台和宝马自研的第六代圆柱电池技术,整车的续航里程将指向 800 公里大关。

对于宝马来说,采用双平台策略不失为一个稳妥的选择——在欧洲和中国这种电动化相对成熟的市场,宝马必须拿出一套全新的技术标准去硬碰硬。而在北美这种对大排量内燃机依然有强烈路径依赖的地盘,CLAR 平台下的直列六缸发动机依然是最好的金字招牌。

至于大家最担心的欧 7 排放标准,宝马也有它的处理方式。

▲新 X7 假想图,图片来自:Carscoops

之前业内普遍认为这道门槛会成为大排量引擎的终结者,迫使厂家去砸数以亿计的欧元开发复杂的混动系统。但宝马工程团队发现,通过对排气系统和催化装置进行深度的细节优化,现有的 V8 甚至劳斯莱斯那台 V12 发动机,在低投入的前提下就能达成合规。

Joachim Post 对此非常有信心:「这也是我们的巨大优势。」

▲新一代 M3,图片来自:Kolesa

宝马战略布局的灵活性在市场端体现得更明显。当奥迪彻底放弃双门轿跑、奔驰选择精简合并产品线时,宝马成为了极少数依然愿意在该细分市场维持多维度布局的品牌。

宝马并没有急着给自己贴上全电化的标签,也没把自己困在内燃机的旧梦里。它利用成熟的平台效应,把 4 系列变成了一套灵活的组合。它既可以是搭载尖端三电技术的先锋,也可以是满足硬核玩家胃口的传统选择。

这让宝马在应对全球不同市场的转型阵痛时,拥有了更宽的缓冲余地。

带轮子的都关注,欢迎交流。 邮箱:tanjiewen@ifanr.com

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


风范股份收问询函:要求说明在业绩亏损情况下仍进行跨界收购的主要原因和考虑

2026年1月26日 18:51
36氪获悉,上交所发布关于对常熟风范电力设备股份有限公司购买炎凌嘉业事项的问询函。1月26日,公司披露,拟使用自有及自筹资金3.83亿元收购北京炎凌嘉业智能科技股份有限公司51%股权。上交所要求公司补充披露本次交易新增标的公司业务与原主业的关系,结合公司目前的主业经营情况、未来发展战略等,说明在公司业绩亏损的情况下仍进行跨界收购的主要原因和考虑。补充披露公司对标的公司的整合安排,并结合标的公司所需技术储备、核心能力及资源等,说明对标的公司是否具备业务整合和管控能力,并充分提示风险。

滴滴巴士上线滑雪专线

2026年1月26日 18:46
为了更好地服务用户冰雪游,滴滴巴士全新开通滑雪专线。北京出发可直达崇礼各大雪场,也可抵达崇礼多家酒店。即日起至2月28日,乘坐滴滴巴士滑雪专线限时优惠“连人带板”仅70元,还可以领取价值179元的“滑雪季”礼包。除了滑雪专线,滴滴巴士在其他冰雪资源富足的区域还有更多的冰雪主题路线。滴滴巴士表示,将不断拓展更多细分场景,为有需要的用户带来安全便捷的大巴出行服务。

smart 和以前不一样了,但 smart 和以前还是一样

作者 刘学文
2026年1月26日 18:32

2026 年,smart 将会发布两款新车:smart 首款掀背轿车 #6 EHD,将于 2026 年上半年正式发布;smart #2 原型概念车,将在 2026 年 4 月北京车展进行全球首秀,smart #2 是 smart 重返双门双座小车市场的标志性车型,由梅赛德斯-奔驰设计团队设计,基于 smart 专为超紧凑车型打造的全新 ECA 架构生产,预计该车型会在 2026 年下半年发布。

不过 2026 年,smart 发布的第一款车是 #3 Keith Haring 艺术特别版,这款车是 smart 和艺术家 Keith Haring 合作推出的限量车型,上市限时售价 18.49 万元,中国限量首发 99 台。

三则新闻交织在一起,呈现出了一个复杂的 smart,一个和以前大不一样的 smart,同时又是一个和以前一样的 smart。

smart 的历史与新生:两门两座新车,与联名狂魔

在 smart 发布 #3 Keith Haring 艺术特别版之前,这家主打有趣好玩的品牌,已经进行了 56 次联名,#3 Keith Haring 艺术特别版也就成为了 smart 旗下第 57 款联名车型。在此之前,与之联名的品牌包括迪士尼,山地车先锋品牌 Trek,潮流品牌 AAPE,高性能改装品牌 BRABUS,经典咖啡品牌 Peet’s Coffee 等等。

甚至,smart 品牌的出现,也是联名的结果。smart 品牌的真正起源可以追溯到 1972 年。当时,戴姆勒公司为了应对城市地区日益严重的交通拥堵和空气污染,提出了小型城市汽车的概念,不久之后,小型城市汽车的概念车从图纸变成了现实。

▲ smart Fortwo 的前身车型奔驰 NAFA

接着到了 1981 年,戴姆勒做出了一款名叫 NAFA (德语 Nahverkehrsfahrzeug 的缩写,意为短途汽车)的车型。它的优点当然是车身小巧,排放低,但是这款车没法满足戴姆勒的安全标准。直到十多年之后的 1994 年,小型汽车的安全技术终于过关,这一年戴姆勒和最大的手表集团斯沃琪(Swatch)合作,共同创立了 smart 品牌,完整含义是(swatch mercedes ART),接着就是 1997 年的法兰克福车展,smart 展出了首款概念车,然后量产车型在 1998 年投入生产。

从历史因素来看,推出两门两座车型,以及联名车型,就是最 smart 的行为。

从官方发布的预告图来看,smart #2 的定位类似于 smart 历史上的第一款车型 smart Fortwo 微型车 ,整车设计较为圆润,车头紧凑呈下溜造型,尾部配备小型扰流板设计。不久前 smart 预热这款微型车的时候解释说:

27 年前,首款 smart fortwo 车型风靡全球,成为品牌乃至整个行业的标志性车型。我们推出的城市轿车深受全球超过两百万用户的喜爱,成为城市出行解决方案和城市文化的重要象征。这款全新纯电动车型,作为超紧凑型 A 级车,将融合温暖科技,在初代 smart fortwo 经典车型的核心特质基础上进行迭代升级。这标志着 smart 品牌重返其所定义和擅长的细分市场。

2025 年的中国汽车市场,大车好卖,小车也好卖,诸如乐道 L90 ,蔚来 ES8,岚图泰山和极氪 9X 这样的三排六座全尺寸 SUV 是市场关注的焦点,销量情况持续向好,但销冠却是另外一位低调的选手:吉利星愿,一款起售价 6.88 万元,车长 4 米出头的微型车。

当然,无论吉利星愿还是五菱宏光 MINI EV,车身不管怎么小,还是要保证两排四座或者五座的,而 smart #2 的两座车型虽然会被划分到小车范畴,但目标受众完全不一样。前者可能更偏向于三四线城市的代步车型,主打一个入门和性价比,而 smart #2 的售价很可能和性价比无关,同时主要目标受众大概是一二线城市的都市丽人。

不过作为一个天生就国际化的品牌,smart 的两座小车在道路狭窄停车困难的欧洲也有不错的市场前景。

至于 #3 Keith Haring 艺术特别版就无需赘言,smart 的基本操作,看图就行。

当下的 smart 和二十多年前的 smart 在股权和产品已经变化颇多,外部市场环境上也已经完全不同。对于从 2022 年开始新生,由奔驰设计,吉利智造的新 smart 来说,当下中国汽车市场的竞争烈度前所未有,这个有趣的品牌如何与那些在智能,科技,性价比力量十足的品牌竞争,是需要一些巧劲儿的。

显然,#3 Keith Haring 艺术特别版和即将推出的 smart #2 就是那个巧劲儿,也是 smart 的底色,不管怎么变,这个品牌必须是有趣的,好玩的,与众不同的。

实际上,联名车型的发布,还有新车技术的透露,都发生在 smart times「灵感大赏」活动上,在这个密友(车主)年度活动上,smart 公布了中文品牌主张「天生爱不同」,与英文品牌主张「Open your mind」呼应。

最近的 smart 新车,倒是与以往的 smart 很不一样

smart 越来越大,这已经是不争的事实,尤其是 smart #5 的尺寸来到了 4705×1920×1705mm,轴距为 2900mm,和奥迪 Q5L 的尺寸相差不多,这完全和 smart 一贯的「小」不相关了。

smart 首款掀背轿车 #6 EHD 也不会小,据透露,这款车的尺寸大概介于领克 07 和领克 10 之间,同时内饰会具有明显的奔驰风格。smart #6 EHD 将会搭载最新一代雷神电混 2.0 技术,综合续航超 1800 公里,整车车长近 5 米(4906×1922×1508mm,轴距 2926mm),采用掀背+运动尾翼设计。

鉴于这款车已经到了工信部申报阶段,所以其外观等信息已经不是秘密。

总结起来,这款车在 smart 体系内的突破在于:

  • 车长近 5 米,是 smart 历史上最长的车型
  • 搭载激光雷达,可能具备高阶辅助驾驶功能
  • 掀背设计+可变尾翼,突出运动感
  • 雷神电混车型先于纯电车型推出,和 smart #5 一起,从城市走向远方

如果说 smart #2 作为两座车型,希望吸引独立的都市丽人的话,那么 smart #6 EHD 显然就想吸引家庭用户。

这个逻辑和领克一步步从领克 01 和领克 03 的驾趣取向车型发展到领克 08 和领克 900 这类家庭取向车型类似,任何一个年轻有趣的品牌,都会面临用户成熟用户成家立业的的挑战,适时推出更大的车型,同时保留驾驶趣味和品牌特色的车型是必经之路。

smart #6 EHD 就是这个品牌交出答案。

有趣的是,smart 在 2026 年的两款重磅新车,恰好组成了 20#2#6,一款致敬历史经典新生,一款不破不立面向未来,这就是当下 smart 的情况:它和以前不一样了,但和以前还是一样。

稳中向好。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


isexe@3.1.1源码阅读

作者 米丘
2026年1月26日 18:27

发布日期 2023 年 8 月 3 日

isexe跨平台检查文件是否为「可执行文件」的专用工具包,核心解决「Windows 和 Unix 系统判断可执行文件的规则完全不同」的问题。

unix系统根据文件权限判断;window系统根据文件扩展名判断。

入口文件 index.js

isexe-3.1.1/src/index.ts

import * as posix from './posix.js' // 导入 POSIX 系统(Linux/macOS 等)的实现
import * as win32 from './win32.js' // 导入 Windows 系统的实现
export * from './options.js' // 导出配置选项类型(如 IsexeOptions)
export { win32, posix }  // 允许直接访问特定平台的实现

const platform = process.env._ISEXE_TEST_PLATFORM_ || process.platform
const impl = platform === 'win32' ? win32 : posix

/**
 * Determine whether a path is executable on the current platform.
 */
export const isexe = impl.isexe
/**
 * Synchronously determine whether a path is executable on the
 * current platform.
 */
export const sync = impl.sync

posix.isexe(异步)

isexe-3.1.1/src/posix.ts

const isexe = async (
  path: string,  // 要检查的文件路径(比如 "/usr/bin/node" 或 "C:\\node.exe")
  options: IsexeOptions = {}  // 配置项,默认空对象
): Promise<boolean> => {
  
  const { ignoreErrors = false } = options

  try {
    // await stat(path):获取文件状态
    // checkStat(statResult, options):判断是否可执行
    return checkStat(await stat(path), options)
  } catch (e) {
    // 把错误转为 Node.js 标准错误类型(带错误码)
    const er = e as NodeJS.ErrnoException
    if (ignoreErrors || er.code === 'EACCES') return false
    throw er // 非预期错误,向上抛出
  }
}
import { Stats, statSync } from 'fs'
import { stat } from 'fs/promises'

checkStat

const checkStat = (stat: Stats, options: IsexeOptions) =>
  stat.isFile() && checkMode(stat, options)

checkMode

const checkMode = (
  // 文件的 Stats 对象(通常由 fs.stat 或 fs.lstat 获取)
  // 包含文件的权限位(mode)、所有者 ID(uid)、所属组 ID(gid)等元数据。
  stat: Stats, 
  // 配置对象,允许自定义用户 ID(uid)、组 ID(gid)、用户所属组列表(groups),默认使用当前进程的用户信息。
  options: IsexeOptions
) => {
  // 1、获取用户与组信息
  // 当前用户的 ID(优先使用 options.uid,否则调用 process.getuid() 获取当前进程的用户 ID)。
  const myUid = options.uid ?? process.getuid?.()
  // 当前用户所属的组 ID 列表(优先使用 options.groups,否则调用 process.getgroups() 获取)。
  const myGroups = options.groups ?? process.getgroups?.() ?? []
  // 当前用户的主组 ID(优先使用 options.gid,否则调用 process.getgid(),或从 myGroups 取第一个组 ID)。
  const myGid = options.gid ?? process.getgid?.() ?? myGroups[0]
  // 若无法获取 myUid 或 myGid,抛出错误(权限判断依赖这些信息)
  if (myUid === undefined || myGid === undefined) {
    throw new Error('cannot get uid or gid')
  }

  // 2、构建用户所属组集合
  const groups = new Set([myGid, ...myGroups])

  // 3、解析文件权限位与归属信息
  const mod = stat.mode // 文件的权限位(整数,如 0o755 表示 rwxr-xr-x)
  const uid = stat.uid // 文件所有者的用户 ID
  const gid = stat.gid // 文件所属组的组 ID

  // 4、定义权限位掩码
  // 八进制 100 → 十进制 64 → 对应所有者的执行权限位(x)
  const u = parseInt('100', 8)
  // 八进制 010 → 十进制 8 → 对应所属组的执行权限位(x)
  const g = parseInt('010', 8)
  // 八进制 001 → 十进制 1 → 对应其他用户的执行权限位(x)
  const o = parseInt('001', 8)
  // 所有者和所属组的执行权限位掩码(64 | 8 = 72)
  const ug = u | g

  // 5、权限判断逻辑
  return !!(
    mod & o || // 1. 其他用户有执行权限
    (mod & g && groups.has(gid)) || // 2. 所属组有执行权限,且当前用户属于该组
    (mod & u && uid === myUid) || // 3. 所有者有执行权限,且当前用户是所有者
    (mod & ug && myUid === 0)  // 4. 所有者或组有执行权限,且当前用户是 root(UID=0)
  )
}

mod (权限位) :Unix 系统中用 9 位二进制表示文件权限(分为所有者、所属组、其他用户三类,每类 3 位,分别控制读 r、写 w、执行 x 权限)。例如 0o755 对应二进制 111 101 101,表示:

  • 所有者(u):可读、可写、可执行(rwx)。
  • 所属组(g):可读、可执行(r-x)。
  • 其他用户(o):可读、可执行(r-x)。

posix.sync (同步)

isexe-3.1.1/src/posix.ts

const sync = (
  path: string,
  options: IsexeOptions = {}
): boolean => {
  
  const { ignoreErrors = false } = options
  try {
    return checkStat(statSync(path), options)
    
  } catch (e) {
    const er = e as NodeJS.ErrnoException
    if (ignoreErrors || er.code === 'EACCES') return false
    throw er
  }
}
import { Stats, statSync } from 'fs'
import { stat } from 'fs/promises'

win32.isexe (异步)

isexe-3.1.1/src/win32.ts

const isexe = async (
  path: string,
  options: IsexeOptions = {}
): Promise<boolean> => {
  
  const { ignoreErrors = false } = options
  try {
    return checkStat(await stat(path), path, options)
  } catch (e) {
    const er = e as NodeJS.ErrnoException
    if (ignoreErrors || er.code === 'EACCES') return false
    throw er
  }
}
import { Stats, statSync } from 'fs'
import { stat } from 'fs/promises'

checkStat

const checkStat = (stat: Stats, path: string, options: IsexeOptions) =>
  stat.isFile() && checkPathExt(path, options)

checkPathExt

isexe-3.1.1/src/win32.ts

const checkPathExt = (path: string, options: IsexeOptions) => {

  // 获取可执行扩展名列表
  const { pathExt = process.env.PATHEXT || '' } = options

  const peSplit = pathExt.split(';')
  // 特殊情况处理:空扩展名
  // 空扩展名通常表示 “任何文件都视为可执行”,这是一种特殊配置
  if (peSplit.indexOf('') !== -1) {
    return true
  }

  // 检查文件扩展名是否匹配
  for (let i = 0; i < peSplit.length; i++) {
    // 转小写:避免大小写问题(比如.EXE和.exe视为同一个)
    const p = peSplit[i].toLowerCase()
    // 截取文件路径的最后N个字符(N是当前扩展名p的长度),也转小写
    const ext = path.substring(path.length - p.length).toLowerCase()

    // 匹配条件:扩展名非空 + 文件扩展名和列表中的扩展名完全一致
    if (p && ext === p) {
      return true
    }
  }
  return false
}

win32.sync(同步)

isexe-3.1.1/src/win32.ts

const sync = (
  path: string,
  options: IsexeOptions = {}
): boolean => {
  
  const { ignoreErrors = false } = options
  try {
    return checkStat(statSync(path), path, options)
    
  } catch (e) {
    const er = e as NodeJS.ErrnoException
    if (ignoreErrors || er.code === 'EACCES') return false
    throw er
  }
}

国联人寿等出资5亿元在无锡成立股权投资合伙企业

2026年1月26日 18:24
36氪获悉,爱企查App显示,近日,无锡锡创盈泰股权投资合伙企业(有限合伙)成立,执行事务合伙人为无锡国联产业投资私募基金管理有限公司,出资额5亿元人民币,经营范围为股权投资,以自有资金从事投资活动,以私募基金从事股权投资、投资管理、资产管理等活动,由江苏原力产业投资有限公司、国联人寿保险股份有限公司、无锡国联产业投资私募基金管理有限公司共同出资。

React Native 中 Styled Components 配置指南

作者 sera
2026年1月26日 18:17

React Native 中 Styled Components 配置指南

什么是 Styled Components?

Styled Components 是一个 CSS-in-JS 库,让你可以在 JavaScript/TypeScript 代码中编写样式,并将样式与组件紧密结合。

核心特性

1. CSS-in-JS

// 传统方式
const styles = StyleSheet.create({
  container: { padding: 16 }
});

// Styled Components 方式
const Container = styled.View`
  padding: 16px;
`;

2. 自动样式隔离 每个 styled component 都有唯一的 class 名,避免样式冲突:

const Button = styled.TouchableOpacity`...`;
// 生成类似:.Button-asdf1234 { ... }

3. 主题支持 内置主题系统,轻松实现深色/浅色主题:

const Title = styled.Text`
  color: ${props => props.theme.colors.text};
`;

4. 动态样式 基于 props 动态改变样式:

const Button = styled.TouchableOpacity<{ variant: 'primary' | 'secondary' }>`
  background-color: ${props =>
    props.variant === 'primary' ? '#007AFF' : '#5856D6'};
`;

优势对比

特性 StyleSheet Styled Components
样式隔离 ❌ 需要手动管理 ✅ 自动隔离
主题支持 ❌ 需要额外配置 ✅ 内置支持
动态样式 ⚠️ 条件语句复杂 ✅ 简洁直观
TypeScript ✅ 支持 ✅ 完整类型推断
样式复用 ⚠️ 需要手动合并 ✅ 继承机制
组件封装 ❌ 样式和组件分离 ✅ 样式与组件一体

如何配置 Styled Components

第一步:安装依赖

# 安装 styled-components
yarn add styled-components

# 安装类型定义和 Babel 插件
yarn add -D @types/styled-components babel-plugin-styled-components

依赖说明

  • styled-components: 核心库
  • @types/styled-components: TypeScript 类型定义
  • babel-plugin-styled-components: 优化开发体验和性能

第二步:配置 Babel

编辑 babel.config.js

module.exports = {
  presets: ['module:@react-native/babel-preset'],
  plugins: [
    // ... 其他插件
    [
      'babel-plugin-styled-components',
      {
        displayName: true,              // 开发模式下显示组件名
        meaninglessFileNames: ["index", "styles"],
        pure: true,                     // 移除不必要的辅助代码
      },
    ]
  ],
};

配置说明

  • displayName: true - 开发时在 React DevTools 中显示组件名称
  • meaninglessFileNames - 忽略这些文件名,不生成 class 名
  • pure: true - 启用 tree-shaking 优化

第三步:配置 TypeScript 类型

创建 app/types/styled-components-native.d.ts

import 'styled-components/native';

declare module 'styled-components/native' {
  // 主题模式类型
  type ThemeModeType = 'dark' | 'light';

  // 间距类型
  type SpacingType = {
    xs: number;
    sm: number;
    md: number;
    lg: number;
    xl: number;
    xxl: number;
    screenPadding: number;
    cardPadding: number;
    inputPadding: number;
    negSm: number;
    negMd: number;
    negLg: number;
  };

  // 字体类型
  type FontSizeType = {
    xs: number;
    sm: number;
    base: number;
    lg: number;
    xl: number;
    xxl: number;
    xxxl: number;
  };

  type FontWeightType = {
    regular: number;
    medium: number;
    semibold: number;
    bold: number;
  };

  type TypographyType = {
    fontSize: FontSizeType;
    fontWeight: FontWeightType;
  };

  // 颜色类型
  type ColorsType = {
    primary: string;
    secondary: string;
    background: string;
    text: string;
    textWhite: string;
    success: string;
    warning: string;
    error: string;
    info: string;
    border: string;
    overlay: string;
    transparent: string;
  };

  // 主题接口
  export interface DefaultTheme {
    mode: ThemeModeType;
    colors: ColorsType;
    spacing: SpacingType;
    typography: TypographyType;
  }
}

第四步:配置路径别名

更新 babel.config.jstsconfig.json 中的别名配置:

babel.config.js

module.exports = {
  plugins: [
    [
      'module-resolver',
      {
        root: ['./app'],
        alias: {
          '@': './app',
          '@providers': './app/providers',
          // ... 其他别名
        },
      },
    ],
  ],
};

tsconfig.json

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@providers": ["app/providers"],
      "@providers/*": ["app/providers/*"]
    }
  }
}

第五步:创建主题系统

1. 主题结构

创建以下文件结构:

app/styles/theme/
├── custom/
│   ├── spacing.ts      # 间距系统
│   └── typography.ts   # 字体系统
├── dark/
│   └── index.ts        # 深色主题颜色
├── light/
│   └── index.ts        # 浅色主题颜色
└── index.tsx           # 主题生成器
2. 定义间距系统

app/styles/theme/custom/spacing.ts

export const spacing = {
  // 基础间距(4px 基准)
  xs: 4,
  sm: 8,
  md: 16,
  lg: 24,
  xl: 32,
  xxl: 48,

  // 特殊间距
  screenPadding: 16,
  cardPadding: 16,
  inputPadding: 12,

  // 负间距
  negSm: -8,
  negMd: -16,
  negLg: -24,
} as const;

export type Spacing = typeof spacing;
3. 定义字体系统

app/styles/theme/custom/typography.ts

export const typography = {
  fontSize: {
    xs: 12,
    sm: 14,
    base: 16,
    lg: 18,
    xl: 20,
    xxl: 24,
    xxxl: 32,
  },
  fontWeight: {
    regular: 400,
    medium: 500,
    semibold: 600,
    bold: 700,
  },
} as const;

export type Typography = typeof typography;
4. 定义颜色

app/styles/theme/light/index.ts

import { ColorsType } from "styled-components/native";

const colors: ColorsType = {
  primary: '#007AFF',
  secondary: '#5856D6',
  background: '#FFFFFF',
  text: '#000000',
  textWhite: '#FFFFFF',
  success: '#34C759',
  warning: '#FF9500',
  error: '#FF3B30',
  info: '#5AC8FA',
  border: '#C6C6C8',
  overlay: 'rgba(0, 0, 0, 0.5)',
  transparent: 'transparent'
};

export { colors };

app/styles/theme/dark/index.ts

import { ColorsType } from "styled-components/native";

const colors: ColorsType = {
  primary: '#0A84FF',
  secondary: '#5E5CE6',
  background: '#121212',
  text: '#FFFFFF',
  textWhite: '#FFFFFF',
  success: '#32D74B',
  warning: '#FF9F0A',
  error: '#FF453A',
  info: '#64D2FF',
  border: '#3A3A3C',
  overlay: 'rgba(0, 0, 0, 0.7)',
  transparent: 'transparent'
};

export { colors };
5. 创建主题生成器

app/styles/theme/index.tsx

import { DefaultTheme, ThemeModeType } from 'styled-components/native';
import { colors as darkColor } from './dark';
import { colors as lightColor } from './light';
import { spacing } from './custom/spacing';
import { typography } from './custom/typography';

const getTheme: (type: ThemeModeType) => DefaultTheme = type => {
  const theme = type === 'dark' ? darkColor : lightColor;
  return {
    mode: type,
    spacing,
    typography,
    colors: theme,
  };
};

export { getTheme };

第六步:创建 ThemeProvider

app/providers/ThemeProvider/index.tsx

import { getTheme } from '@/styles';
import { createContext, PropsWithChildren, useCallback, useState } from 'react';
import { useColorScheme } from 'react-native';
import {
  DefaultTheme,
  ThemeModeType,
  ThemeProvider as StyledThemeProvider,
} from 'styled-components/native';

// Context 类型定义
type ContextProps = {
  mode: ThemeModeType;
  theme: DefaultTheme;
  toggleTheme: () => void;
};

// 默认主题
const defaultTheme: ContextProps = {
  mode: 'light',
  theme: getTheme('light'),
  toggleTheme: () => {},
};

// 创建 Context
export const ThemeContext = createContext<ContextProps>(defaultTheme);

// ThemeProvider 组件
export const ThemeProvider = ({ children }: PropsWithChildren) => {
  const isDarkMode = useColorScheme() === 'dark';
  const [mode, setMode] = useState<ThemeModeType>(isDarkMode ? 'dark' : 'light');

  // 切换主题函数
  const toggleTheme = useCallback(() => {
    setMode(prev => (prev === 'light' ? 'dark' : 'light'));
  }, []);

  const theme = getTheme(mode);

  return (
    <ThemeContext.Provider value={{ mode, theme, toggleTheme }}>
      <StyledThemeProvider theme={theme}>
        {children}
      </StyledThemeProvider>
    </ThemeContext.Provider>
  );
};

app/providers/index.ts

export { ThemeContext, ThemeProvider } from './ThemeProvider';

第七步:导出样式系统

app/styles/index.ts

// 主题 Design Tokens
export * from './theme';

// 通用样式
export * from './common';

第八步:验证配置

创建一个测试组件 app/index.tsx

import styled from 'styled-components/native';
import { ThemeProvider, ThemeContext } from '@providers';
import { useContext } from 'react';

const Container = styled.View`
  padding: ${props => props.theme.spacing.md}px;
  background-color: ${props => props.theme.colors.background};
`;

const Title = styled.Text`
  font-size: ${props => props.theme.typography.fontSize.xl}px;
  font-weight: ${props => props.theme.typography.fontWeight.bold};
  color: ${props => props.theme.colors.text};
`;

const Button = styled.TouchableOpacity`
  background-color: ${props => props.theme.colors.primary};
  padding: ${props => props.theme.spacing.md}px;
  border-radius: 8px;
  margin-top: ${props => props.theme.spacing.md}px;
`;

const ButtonText = styled.Text`
  color: ${props => props.theme.colors.textWhite};
  text-align: center;
`;

function App() {
  return (
    <ThemeProvider>
      <AppContent />
    </ThemeProvider>
  );
}

function AppContent() {
  const { toggleTheme, mode } = useContext(ThemeContext);

  return (
    <Container>
      <Title>Styled Components 配置成功!</Title>
      <Title>当前主题: {mode}</Title>
      <Button onPress={toggleTheme}>
        <ButtonText>切换主题</ButtonText>
      </Button>
    </Container>
  );
}

export default App;

第九步:重新构建

配置完成后,必须重新构建应用:

# 清理缓存并重启
yarn start --reset-cache

# 或者重新构建
# iOS
yarn ios

# Android
yarn android

配置检查清单

  • ✅ 安装了 styled-components
  • ✅ 安装了 @types/styled-components
  • ✅ 安装了 babel-plugin-styled-components
  • ✅ 配置了 babel.config.js
  • ✅ 创建了类型定义文件
  • ✅ 配置了路径别名(@providers
  • ✅ 创建了主题文件结构
  • ✅ 定义了间距系统
  • ✅ 定义了字体系统
  • ✅ 定义了颜色(深色/浅色)
  • ✅ 创建了主题生成器
  • ✅ 创建了 ThemeProvider
  • ✅ 导出了样式系统
  • ✅ 重新构建了应用

常见配置问题

1. TypeScript 类型错误

问题props.theme 报类型错误

解决

  • 确保 app/types/styled-components-native.d.ts 文件存在
  • 确保 DefaultTheme 接口定义了所有需要的字段
  • 重启 TypeScript 服务器(VSCode 中 Cmd+Shift+P -> "Restart TS Server")

2. 主题切换不生效

问题:点击切换主题,样式不变

检查

  1. 组件是否在 ThemeProvider 内部?
  2. 是否使用了 props.theme.colors.xxx 而不是硬编码颜色值?
  3. 是否重新构建了应用?

3. Babel 配置不生效

解决

  1. 清理缓存:yarn start --reset-cache
  2. 检查 babel.config.js 语法
  3. 重启 Metro bundler

4. 找不到模块 '@providers'

解决

  1. 检查 babel.config.jstsconfig.json 别名配置
  2. 确保路径正确:'./app/providers'
  3. 重启 TS 服务器

参考资源

❌
❌