跳过主要内容

React特性标志: 完整实现指南

了解如何使用我们的完整指南实现React特性标志。涵盖架构模式、发布策略、CI/CD和现代应用程序的最佳实践。

React特性标志: 完整实现指南

你已经完成了该功能。拉取请求干净。QA说它看起来不错。然而,你仍然不想一次性将它部署到所有人身上。

通常,这种感觉是React应用程序已经超出了简单部署的第一个迹象。一旦产品有了真正的用户,发布就不再仅仅是一个技术事件。它变成了一个风险决策。如果新的搜索UI破坏了它,如果检出变体混淆了用户,或者如果移动构建部署了code你无法快速撤销,你需要比 if (process.env.NODE_ENV) 和希望。

那就是 react 特性标志 开始发挥作用。不是作为一个可爱的布尔值在组件中,而是一个发布控制层,让您可以单独发布code而不暴露它。在Web应用中,这意味着更安全的发布。在打包应用程序,如Capacitor或Electron中,它更重要,因为回滚速度受商店审查、安装延迟和更慢的发布周期的限制。

目录

为什么特性标志对于现代React应用至关重要

星期五下午发布。新的计费摘要UI已经部署,支持团队有一个发布清单开启,并且一个企业客户仍然需要旧的流程直到星期一。在一个Web应用中,这已经很紧张。在一个通过桌面安装程序或移动商店分发的捆绑React应用中,情况会更糟糕,因为回滚可能需要几个小时或几天,而不是几分钟。

特性标志让React团队控制这一时刻。它们让你可以发布code,让它保持休眠状态,并决定哪些用户应该看到它。这样改变了发布工作从一个全或无的事件变成了一个受控的操作。

一个标题为“为什么特性标志对于现代React应用至关重要”的图表,解释了部署策略和好处。

部署和发布是不同的工作

部署回答,“code是否在生产环境中?”发布回答,“谁可以执行这个行为?”

当 React 应用程序有实时流量、多个环境和影响收入、权限或导航的功能时,区别就变得很重要。团队可以合并早期版本,使用内部小组测试生产环境,并在他们信任行为后才扩大访问权限。对于像 Capacitor 应用程序、Electron 应用程序和商店审查的移动构建这样的较慢发布平台,这种控制就更加宝贵,因为二进制文件可能已经在用户手中了,而功能还没有准备好供所有人使用。

旗帜在以下三个常见情况下非常有用:

  • 控制发布: 首先暴露给一个小组
  • 实验: 比较变体而不需要维护单独的部署
  • 快速关闭: 禁用一个风险功能而不等待新版本

一个简单的规则在这里很有效。如果生产问题需要花费大量时间来逆转,code 就应该在旗帜后面发布。

新到旗帜的团队通常会停留在 UI 条件化上。 flag ? <NewUI /> : <OldUI /> 这是可见的部分,但这不是最有趣的部分。它的核心价值是运营性的。远程配置、确定性目标和快速关闭功能是旗帜在生产环境中的有用之处。如果您的 React 应用程序还需要应用程序级的运行时设置,则需要一个 remote config plugin for Capacitor apps 适合同一发布控制模型。

标志在没有人信任它们时就不再有用。

我在不断增长的前端代码库中看到相同的失败模式。团队快速添加标志,环境之间的名称漂移,fallback值隐藏配置错误, nobody知道“on”是否表示全局开启,仅限员工,还是仅限测试环境。到那时,标志系统开始产生风险,而不是减少风险。

类型安全有所帮助,但它并不能解决整个问题。团队仍然需要一个清晰的注册表,拥有者和一致的方式来评估标志跨应用。否则,React组件最终会在发布或部分回滚时打破本地假设。

区别很容易看出:

用例 弱版本 强版本
UI切换 组件本地布尔值 拥有者和发布规则的远程标志
发布安全 手动回滚部署 通过远程配置立即禁用
实验 临时分支比较 稳定分组分配和可测量的曝露

重要的思维转变很简单。 React 特性标志属于您的发布过程,而不仅仅是您的 JSX。 尤其是在部署新版本的速度很慢的应用程序中,处理它们时,标志成为减少生产环境混乱时爆炸半径的少数工具。

在 React 应用程序中架构特性标志

架构决策比第一个标志更重要。如果您直接将标志连接到随机组件中,会出现重复的逻辑、加载闪烁和无法信任的源代码的代码库。

使用运行时提供者,而不是散布的条件语句

对于 React 应用程序,可靠的方法是将标志视为 运行时数据. Guidance for React flagging recommends three things: evaluate flags on the server or in a local SDK cache, persist cohort assignment deterministically, and render the final UI state before hydration or use anti-flicker protection so users don’t see the wrong default first (React 标准方法).

这会影响你的 code 应该存放的位置。将标志加载放在应用程序根部。使其简单。避免在叶子组件内部获取标志。

实践形状如下:

  1. 在主树渲染之前加载或注水标志。
  2. 通过提供者暴露它们。
  3. 通过一个钩子或一个包装模式读取它们。
  4. 将评估逻辑保持在展示组件之外。

如果您需要一个远程配置层来管理应用程序级别的设置以及标志,则像 __CAPGO_KEEP_0__ 远程配置插件这样的工具自然适合在混合 React 应用程序中使用。 Capacitor remote config plugin 这是我通常推荐的默认模式。它是明确的、可测试的,并且如果您切换供应商后可以轻松迁移。

模式二:使用 __CAPGO_KEEP_0__ 远程配置插件

模式三:使用 __CAPGO_KEEP_0__ 远程配置插件

import React, { createContext, useContext, useMemo } from 'react';

type FlagValue = boolean | 'control' | 'variant-a' | 'variant-b';

type Flags = {
  newCheckout: boolean;
  checkoutExperiment: FlagValue;
  deleteTaskEnabled: boolean;
};

const defaultFlags: Flags = {
  newCheckout: false,
  checkoutExperiment: 'control',
  deleteTaskEnabled: false,
};

const FeatureFlagContext = createContext<Flags>(defaultFlags);

export function FeatureFlagProvider({
  flags,
  children,
}: {
  flags: Flags;
  children: React.ReactNode;
}) {
  const value = useMemo(() => flags, [flags]);
  return (
    <FeatureFlagContext.Provider value={value}>
      {children}
    </FeatureFlagContext.Provider>
  );
}

export function useFeatureFlag<K extends keyof Flags>(key: K): Flags[K] {
  return useContext(FeatureFlagContext)[key];
}

使用方式保持平淡,这正是你想要的:

function DeleteTaskButton() {
  const enabled = useFeatureFlag('deleteTaskEnabled');

  if (!enabled) return null;
  return <button>Delete task</button>;
}

这种模式很有效,因为你的组件只会问一个最终答案。它们不关心答案是如何计算出来的。

模式二:使用高阶组件

高阶组件 高阶组件是有用的,当你想要在整个屏幕、路由元素或遗留类组件上添加一个门控器,而不需要在每个地方添加hook调用时。 使用方式:

import React from 'react';
import { useFeatureFlag } from './FeatureFlagProvider';

export function withFeatureFlag<P>(
  flagKey: 'newCheckout' | 'deleteTaskEnabled',
  Fallback?: React.ComponentType<P>
) {
  return function wrap(Component: React.ComponentType<P>) {
    return function FeatureFlaggedComponent(props: P) {
      const enabled = useFeatureFlag(flagKey);

      if (!enabled) {
        return Fallback ? <Fallback {...props} /> : null;
      }

      return <Component {...props} />;
    };
  };
}

缺点是间接性。Hook在现代React中更容易追踪,而HOC在DevTools中可能会使组件树变得杂乱。然而,对于路由级别的门控,它们是干净的。

const CheckoutPage = () => <div>New checkout</div>;
const LegacyCheckoutPage = () => <div>Legacy checkout</div>;

export default withFeatureFlag('newCheckout', LegacyCheckoutPage)(CheckoutPage);

不要让组件决定发布策略。组件应该消费一个标志结果,而不是实现分桶、用户目标或缓存刷新规则。

React特性标志模式的比较

标准

上下文+Hook __CAPGO_KEEP_0__ 高阶组件 (HOC)
最佳用例 组件级别的决策和变体 包裹完整页面、路由或遗留组件
灵活性
开发者体验 在现代函数组件中很强 当钩子不方便时有用
包裹清晰度 清晰的导入和直接读取 更多抽象化在树中
测试 通过提供商轻松模拟 易于包装的集成案例
长期可维护性 通常更好 当使用时谨慎

如果您第一次实现 React 特性标志,请从 上下文 + 钩子. 只有当您有特定的需要时才添加 HOC。

实施发布和回滚策略

发布计划在功能发布后出现问题的那一天最为重要。 UI 可能只显示一个新按钮或屏幕,但最关键的任务是决定谁先看到它、暴露速度如何以及如何快速关闭它而不必等待重新部署。 这在 React 应用程序中尤其重要,应用程序被打包在移动或桌面应用程序中,回滚取决于远程配置,因为应用商店审查或桌面分发需要时间。

一个展示软件特性标志从内部到全球发布的滚动和回滚策略的漏斗图。

百分比滚动需要粘性分配。

百分比滚动只有在分配稳定时才有效。如果同一用户在一次访问中获得新版的支付页面,而在下一次访问中获得旧版的支付页面,支持团队就无法复制问题,分析数据变得混乱,用户也会失去信任。

解决方案很简单。使用一个稳定的标识符和标志键的确定性哈希来分桶用户。用户ID通常是正确的输入。匿名会话可以使用安装ID或设备ID,如果有的话。 Math.random() 在浏览器中使用的工具是错误的,因为它会不受控制地重新分配用户。

一个实际的滚动路径如下:

  • 首先使用内部用户和QA。
  • 将其发布到一个小的试验组。
  • 在检查错误率、转化影响和支持票后,逐步扩大。
  • 保持标志的分配粘性,直到标志的整个生命周期。

最后一点容易低估。粘性分组不仅适用于实验。它们使事故响应更快,因为工程师可以立即回答一个基本问题:哪些用户暴露了?

如果您运行实验,务必在发布之前确定样本大小。Optimizely提供的样本大小计算器显示了流量量、基准转化率和最小可检测效果如何影响每个变体所需的用户数量Optimizely样本大小计算器如果没有检查,团队经常会将噪音误认为是信号,并提前推广一个功能。

对于外部浏览器的阶段性更新有一个有用的参考是 Capacitor阶段性发布的实时更新同样的发布纪律适用于React应用程序在打包壳中运行时,二进制回滚速度较慢。

目标和环形发布可以减少爆炸半径

有些功能不应以随机百分比开始。账单流程、权限提示、数据迁移以及任何可能锁定用户的功能通常需要先进行目标发布。

目标发布在第一批受众被定义为已知特征时效果很好:

  • 内部员工进行内部测试
  • 同意存在粗糙边缘的beta测试者
  • 特定账户等级
  • 具有不同法律或语言要求的地区
  • 设备或应用版本支持该功能的安全性

环形发布使目标更具操作性。环 0 是员工。环 1 是信任的外部测试者。随后环的暴露范围会随着信心的提高而扩大。这一结构有助于团队避免将所有用户视为一个池来处理风险,而事实上风险是明显不均衡的。

以下是嵌入式教程,配套使用此发布模型:

杀死switch是旗帜的价值所在

每个有风险的功能都需要一个快速的脱离路径。在实践中,这通常意味着一个顶级的操作性旗帜,禁用整个功能流,而不是一个仅仅隐藏一个入口点的显示旗帜,而后台请求、效果或导航路径仍然在运行。

在发布前设计杀死switch:

  • 在应用启动时尽早评估它。
  • 缓存最后一次安全值。
  • 如果旗帜服务不可用时选择一个安全的默认值。
  • 确保禁用功能停止了副作用,而不是仅仅停止渲染。
  • 在意外事件中,确保谁可以翻转它。

对于仅限Web应用,减少发布风险。对于移动和桌面React应用,它可以是区分轻微意外和等待几天时间让用户获取修复版本的差异。如果code已经在捆绑包中发布,远程旗帜就成为回滚策略的一部分,而不是仅仅是发布策略的一部分。

测试可观察性和管理标志债务

添加标志的容易部分是添加一个。 expensive 部分开始后,出现了很多标志,没人记得哪些仍然重要

现代服务器房,服务器排列成行,灯光闪烁,网络线路有序

每个标志都增加了必须信任的状态

马丁·福勒的警告仍然有效:一旦存在标志,团队必须验证 开启 关闭 状态).

马丁·福勒关于特性开关

  • 这对 React 应用程序有直接后果: A个页面可以有多个branch在任何人注意之前。
  • 触发hydrate不匹配变得更容易: 客户端和服务器如果评估发生在错误的时间,可能会产生分歧。
  • 快照测试变得不那么有用: 如果相反的flag状态没有测试,一个happy-path渲染并不能告诉你太多。

一个实用的测试栈看起来像这样:

  1. 单元测试评估逻辑。
  2. 组件测试关键flag branch。
  3. 只为风险路径添加端到端覆盖。
  4. 明确验证默认fallback。

不要追求每种combination。通常会在自己的重量下崩溃。测试那些可能伤害用户或破坏布局的状态。

flag债务是真实的,它会在安静中变得很昂贵

旧标志变成了code的陈腐之物。它们停留在条件语句、注释、仪表板和运行手册中。然后有人在几个月后编辑“临时” branch,因为没有人删除它。

实践中有效的清理规则很简单:

问题 做什么
没有负责人 在创建标志时分配一个团队或个人
没有结束状态 决定标志是否删除、保留或转换为配置
标志控制太多 将其分解为更小、更窄的标志
核心逻辑隐藏在标志后面 将商业规则从渲染条件语句中移出

Cleanup 规则: 每个标志都应有一个拥有者、一个目的和一个移除计划。第一天就应该有了。

这也是团队被“信任”问题所伤害的地方。标志名称存在,但回退是错误的。仪表板条目发生了变化,但应用类型没有。code路径已经死亡,但仍然可达。因此,类型生成和注册表验证在更大的系统中很重要,即使初始实现看起来很简单。

可观察性告诉你标志是否有帮助还是只是存在

滚动部署不是因为标志达到最大曝光度而完成的。它是完成的,因为团队知道发生了什么。

至少跟踪这些问题:

  • 曝光度: 哪些用户看到哪个变体?
  • 错误: 标志路径是否触发了更多的客户端故障?
  • 采用率: 用户是否使用了你暴露的功能?
  • 回滚信号: 您会在什么阈值下关闭它?

如果您的旗帜平台无法回答这些问题,您仍然会在发布审查中猜测。

保护您的旗帜并使用CI/CD自动化

A bad deploy is obvious. A bad flag change is quieter, and in some cases more dangerous, because it changes production behavior without going through the same review path as code.

使用CI/CD流程和工具来保护旗帜并自动化工作流程的图表。

对旗帜更改进行生产级别的处理

旗帜是发布控制。如果一个团队可以在生产环境中切换旗帜,那么这个团队就可以改变用户看到的内容、什么code路径运行,以及有时哪些集成触发。那样值得像部署访问一样严格。

最基本的控制措施很明显:

  • 基于角色的访问控制: 限制谁可以更改生产旗帜,并分离读取访问权限与编辑访问权限。
  • 审计日志: 保持对标志的更改记录,包括谁修改了它、什么时候修改的以及哪个环境被修改了。
  • 环境隔离: 预发布、预览和生产标志应该是不同的,以便测试更改不会影响到在线流量。
  • 服务器端检查敏感决策: 客户端标志可以隐藏UI,但不应决定计费权限、权利或授权。

一个常见的错误是把标志仪表板当作一个共享的电子表格。产品为客户开启某个功能,支持团队关闭它以解决客户的反馈,工程团队则认为没有人修改它,因为没有发布。这种设置在你需要解释一个事故时会出现问题。

打包应用程序会提高风险。在一个Web应用程序中,一个code修复可以快速发布。在一个Capacitor或桌面应用程序中,可能已经在设备上等待远程标志暴露的code版本可能已经存在。构建 React mobile apps with Capacitor 团队应该更加严格地遵守审批规则,因为回滚通常意味着禁用已发布的功能而不是替换二进制文件。

将标志操作放入管道中:

标志变得难以信任时,它们不再是你的交付过程的一部分。更安全的模式是将它们管理为同一工作流程的一部分,这个工作流程负责发布功能。

这通常意味着:

  • 在同一 PR 中创建或更新标志 code
  • 在 CI 阶段验证类型化的标志定义与远程注册表
  • 根据目的在环境中设置默认值
  • 如果必需的标志缺失或配置错误,则阻止发布
  • 为标志设置过期日期或滚动结束状态的任务

我有一个简单的规则:如果生产中断可能由标志引起,CI 应该在发布之前捕获设置。包括缺失的默认值、重命名的键、陈旧的环境映射以及存在于 code 中但不在控制平面中的标志。

如果您需要一个管道结构的起点,请参阅 Git Action CI/CD 工作流 保留机密信息和 __CAPGO_KEEP_0__ 选择

前端团队有时会过度复杂化标志安全性并忽略明显的部分。公共客户端 SDK 密钥通常是安全的,如果供应商为浏览器使用而设计。管理员令牌、写入凭据和环境管理密钥不属于此类。它们只应在 CI 或后端服务中使用。

Frontend teams sometimes overcomplicate flag security and miss the obvious part. Public client-side SDK keys are usually fine if the vendor designed them for browser use. Admin tokens, write credentials, and environment management keys are not. Those belong in CI or backend services only.

__CAPGO_KEEP_0__

在较慢的发布环境中,这个界限更为重要。 Web 团队可以通过快速部署恢复。 Mobile 和 desktop 团队通常需要将标志系统作为恢复机制。如果错误的人可以编辑生产标志,或者 CI never 验证标志合同,回滚会迅速变得混乱。

超越 Web Feature Flags for Capacitor 和 Mobile Apps

大多数关于 React 特性标志的文章假设一个可以立即重新部署的 Web 应用。 一旦您的 React code 内部 Capacitor, Electron或另一个捆绑的运行时。

捆绑应用改变了发布的数学

在混合应用中,您通常会将 JavaScript、CSS、资产和配置文件打包在一起,用户不会立即更新。 一项功能可能已经在设备上部署之前您希望任何人使用它。 这改变了标志的作用完全。

最近关于混合发布策略的讨论指出,现有的 React 标志内容很少涉及Capacitor 或 Electron 应用的发布风险模型。 对于这些团队,主要需求是结合标志、目标通道和回滚保护的发布协调层,而不是简单的开关,尤其是避免商店审查延迟时混合应用发布风险讨论).

完全正确。在捆绑应用中,标志不再是关于条件渲染,而是关于 远程激活已经部署的能力.

In 移动或桌面 React 应用中,标志通常控制发布时间比 UI 出现更重要。

This is also why channel-based distribution matters. If you’re building hybrid apps and need the app shell plus web code release model to make sense together, 创建 React 移动应用程序与 Capacitor 是实用的起点。

标志在配对时最有效。配对时,标志可以与更新分发一起使用。

对于移动和桌面团队,标志独自无法解决每个发布问题。它们可以隐藏或启用 code 路径,但无法取代将已修复的资产或逻辑发送到商店时的固定资产或逻辑。

因此更强大的模型是:

  • 在您的平台允许的情况下,分发 code 更新,
  • 针对这些更新的目标是通过频道或受众
  • 并使用标志来控制激活、回滚和阶段性曝光。

一起使用实时更新和标志,混合团队可以获得更接近 web-style 发布控制的东西。这种情况并没有消除团队的纪律要求。它只是给了团队更多的控制手段,当出现问题时。


如果您的团队发布 Capacitor 或 Electron 应用程序,并需要该发布控制层, Capgo 这是一个可供选择的方案。它可以将签名的Web包分发到目标渠道,支持回滚保护和可观察性,并适合混合应用的工作流程,特性标志需要与实时更新一起工作,而不是替换它们。

继续阅读:React特性标志:完整的实现指南

如果您正在使用 React特性标志:完整的实现指南 来规划渠道路由和阶段性发布,连接它与 渠道 为渠道的实现细节 渠道 为渠道的实现细节 渠道 为渠道的实现细节 Beta 测试解决方案 为 Beta 测试解决方案中的产品工作流程, 和 版本目标解决方案 为版本目标解决方案中的产品工作流程.

实时更新Capacitor应用

当Web层面的bug活跃时,通过Capgo将修复发送给用户,而不是等待几天的应用商店审批。用户在后台接收更新,而原生变化仍然在正常的审批路径中。

来自马丁的专业支持

立即开始

最新博客

Capgo为您提供创建真正专业的移动应用所需的最佳见解