__CAPGO_KEEP_0__ 首页

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

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

马丁·多纳迪厄

马丁·多纳迪厄

内容营销

React特征标志:完整的实现指南

你已经完成了特征。拉取请求很干净。QA说它看起来很好。但是你仍然不想一次性将其部署到所有人身上。

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

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

目录

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

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

功能标志让React团队控制这一时刻。它们让您部署code,让它保持休眠状态,并决定哪些用户应该看到它。这样改变了发布工作从一次性事件转变为受控操作。

标题为《为什么功能标志对于现代React应用至关重要》的Infographic,解释了部署策略和好处。

发布和发布是不同的工作

发布回答,“code是否在生产环境中?”发布回答,“谁可以立即执行此行为?”

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

标志在三个常见情况下都有所帮助:

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

在这里,一个简单的规则是有效的。如果生产问题的逆转将很昂贵,建议在code后面将其部署。

新加入旗帜的团队通常会停留在UI条件上。 flag ? <NewUI /> : <OldUI /> 是可见的部分,但这并不是最有趣的部分。其核心价值在于运营。远程配置、确定性目标和快速关闭功能的能力使旗帜在生产环境中有用。如果您的React应用还需要应用级的运行时设置, 适用于Capacitor应用的远程配置插件 符合相同的发布控制模型。

当没有人信任它们时,旗帜就不再有用了

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

类型安全有所帮助,但这并不能解决整个问题。团队仍然需要一个清晰的注册表、所有权和一致的方式来评估旗帜的应用。否则,React组件最终会根据回滚状态做出局部假设,这些假设在发布或部分回滚时会破裂。

区别很容易看出:

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

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

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

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

使用一个runtime提供者,而不是散布的条件语句

对于React应用程序,可靠的方法是将标志视为 runtime数据。有关React标志化的指导建议三件事:在服务器上或本地SDK缓存中评估标志,确定团队分配并渲染最终UI状态之前的抗闪烁保护,React标志方法).

这改变了您的code应该存放的地方。将标志加载放在应用程序根部。使其简单。避免在叶子组件内部获取标志

一个实际的形状看起来像这样:

  1. 在主树渲染之前加载或重hydrating标志
  2. 将它们暴露在提供者中
  3. 通过一个钩子或一个包装模式读取它们
  4. 将评估逻辑从呈现组件中移除

如果您需要一个远程配置层来处理应用程序级别的设置以及标志,则可以使用类似于 Capacitor 远程配置插件 适合与此模式在混合 React 应用中自然融合。

模式一:使用 React Context 和自定义钩子

这是我通常推荐的默认模式。它明确、可测试,并且如果您切换供应商后可以轻松迁移。

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>;
}

这种模式有效,因为您的组件只要求最终答案。它们不关心答案是如何计算出来的。

模式二:使用高阶组件

高阶组件 在您希望在整个屏幕、路由元素或遗留类组件上添加钩子调用而不必在每个组件上添加时非常有用。 使用方法:

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} />;
    };
  };
}

缺点是间接性。钩子在现代 React 中更容易跟踪,而 HOC 可以在 DevTools 中使组件树更嘈杂。尽管如此,在路由级别上,仍然很干净。

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

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

A

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

React特征标志模式比较

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

如果您第一次实现 React 特性标志,请从这里开始 上下文 + 钩子. 只有在需要包装式控制时才添加 HOC。

实施回滚和回滚策略

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

软件功能标志的回滚和回滚策略的漏斗图表,展示从内部到全球发布的过程。

百分比回滚需要稳定的分配

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

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

实际的回滚路径如下:

  • 首先是内部用户和 QA。
  • 然后是向小型团队发布。
  • 根据错误率、转换影响和支持票数进行检查后,逐步扩大。
  • 为旗帜的整个生命周期保持分配的粘性。

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

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

有用的参考资料是 Capacitor的阶段性更新外部浏览器

。同样的发布纪律适用于React应用程序在打包的壳中运行时,二进制回滚速度更慢。

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

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

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

环形发布使目标更具操作性。环0是员工。环1是信任的外部测试者。后续环扩大曝光度,随着信心的提高。这种结构有助于团队避免将所有用户视为一个池时的常见错误:风险明显不均衡。

此发布模型配对的嵌入式教程:

杀死switch是赚取其存在的旗帜

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

在发布前设计杀死switch:

  • 在应用启动时尽早评估它。
  • 缓存最后一次安全值。
  • 如果标志服务不可用时,选择一个安全的默认值。
  • 确保禁用功能不会仅仅停止渲染,而是停止功能的所有副作用。
  • 在事故期间,谁可以切换它的文档。

对于仅限Web应用,减少发布风险。对于移动和桌面React应用,可能是区分轻微事故和等待用户获取修复版本的差异。如果code已经在捆绑包中发布,远程标志就成为回滚策略的一部分,而不是仅仅是发布策略的一部分。

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

添加一个功能标志的容易部分是添加一个。然而,当有很多标志时,很难记住哪些仍然重要,这才是昂贵的部分。

一间现代的服务器房,装有行列的服务器机柜,闪烁的灯光和有序的网络线。

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

马丁福勒的警告仍然有效:一旦存在功能标志,团队必须验证 开启 关闭 每个标志都增加了你必须信任的状态数量 __CAPGO_KEEP_0__、并且当有多个标志时,可能的状态 combination 会 combinatorially 增长,这会提高回归风险(Martin Fowler 关于功能开关的说法).

这对 React 应用程序有直接的后果:

  • 条件渲染路径会迅速扩散: 一个页面可以有多个 branch 在任何人注意到之前:
  • hydration mismatches 变得更容易触发: 如果评估发生在错误的时间,客户端和服务器可能会意见不一:
  • 单元测试变得不那么有用: 如果相反的标志状态未测试,快照测试就不太有用:

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

  1. 单元测试评估逻辑:
  2. 组件测试关键 flag 分支:
  3. 仅对风险路径添加端到端覆盖。
  4. 明确默认的fallback。

不要试图测试所有组合。那样通常会因为自身的重量而崩溃。测试那些可能伤害用户或破坏布局的状态。

债务标志是真实的,它会悄悄地变得很昂贵。

旧的标志会变成一种code的腐烂。它们会留在条件语句、注释、仪表板和运行书中。然后有人几个月后编辑“临时” branch,因为没有人移除它。

实际上有效的清理规则是简单的:

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

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

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

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

滚动部署不仅仅是标志达到最大暴露度时完成的。它是完成的时团队知道发生了什么。

至少跟踪这些问题:

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

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

使用CI/CD保护您的标记并自动化

一个坏的部署很明显。一个坏的标记更静默,甚至在某些情况下更危险,因为它会在没有经过同样的审查路径的情况下改变生产行为,例如code。

使用CI/CD流程和工具保护标记并自动化工作流程的示意图。

将标记更改视为生产更改

标记是发布控制。如果一个团队可以在生产中切换标记,那么这个团队就可以改变用户看到的内容、code路径的执行以及某些集成的触发。那样值得像部署访问一样严格的纪律。

{"targetLanguage":"Simplified Chinese","protectedTokens":["Cloudflare","Capacitor","GitHub","Capgo","code","API","SDK","CLI","npm","bun"],"texts":["最小控制元件是簡單明瞭的:","角色基礎存取:","限制誰可以變更生產旗標,分離讀取存取權限和編輯存取權限。",

  • 審計記錄: 記錄誰變更了旗標、什麼時間變更它以及觸碰了哪個環境。",
  • 環境隔離: 測試變更不應該影響線上流量,應該將預覽、測試和生產旗標區分開來。",
  • 伺服器端檢查敏感決策: 客戶端旗標可以隱藏UI,但不應該決定帳單存取、權益或授權。",
  • 一個常見的錯誤是把旗標儀表板當作共享表格。產品為客戶啟用某個功能,客服則關閉它以停止抱怨。工程師則假設沒有人觸碰它,因為沒有發佈。這種設定在需要解釋事件時會失效。", 捆綁應用程式提高了風險。在網頁應用程式中,__CAPGO_KEEP_0__修復可以快速發佈。在__CAPGO_KEEP_1__或桌面應用程式中,已經在設備上等待遠端旗標啟動的__CAPGO_KEEP_2__可能已經出現了。建構

React移動應用程式的__CAPGO_KEEP_0__團隊"]}

Bundled apps raise the stakes. In a web app, a code fix can go out quickly. In a Capacitor or desktop app, the broken code may already be sitting on devices, waiting for a remote flag to expose it. Teams building React mobile apps with Capacitor 应该更加严格地审批规则,因为回滚通常意味着禁用已部署的功能而不是替换二进制文件。

将标志操作放在管道内

当标志存活在外部时,它们变得难以信任。更安全的模式是将它们管理为同一工作流程的一部分,这个工作流程负责部署功能。

这通常意味着:

  • 在同一PR中创建或更新标志code
  • 在CI期间验证类型化的标志定义与远程注册表
  • 故意在环境中设置默认值
  • 如果标志配置不正确或缺失,阻止发布
  • 为标志设置过期日期或滚动结束状态的任务

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

如果您需要一个管道结构的起点,请看 Git Action CI/CD工作流程 是构建检查、部署门槛和自动化步骤的坚实参考,供您扩展标志验证。

保留机密和SDK选择的枯燥

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

实践分界线很简单。 使用客户端评估进行呈现更改和低风险实验。 使用服务器端评估进行定价、权限、敏感流程中的杀switch和您不信任本地JavaScript的任何内容。

这个界限在较慢的发布环境中更为重要。 网络团队可以通过快速部署恢复。 移动和桌面团队通常需要标志系统作为恢复机制。 如果错误的人可以编辑生产标志,或者CI永远不会验证标志契约,回滚会迅速变得混乱。

超越Web特性标志Capacitor和移动应用

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

捆绑应用改变了发布数学

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

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

这是完全正确的。在打包应用中,标志不再是关于条件渲染,而是关于 远程激活已经打包的功能.

在移动或桌面 React 应用中,标志通常控制发布时间而不是 UI 的存在。

这也是为什么基于渠道的分发很重要。如果您正在构建混合应用并需要应用壳和 web code发布模型一起工作, 使用Capacitor创建 React 移动应用 是一个实用的起点。

标志在配对使用更新分发时最有效。

对于移动和桌面团队,标志单独无法解决每个发布问题。它们可以隐藏或启用code路径,但无法替代将固定的资产或逻辑打包在一起时的 bug。

这就是为什么更强大的模型是:

  • deliver code updates outside full store cycles when your platform allows it,
  • 针对特定频道或受众推送更新,
  • 并使用标志来控制激活、回滚和分阶段发布.

结合使用实时更新和标志,混合团队可以拥有更接近web-style发布控制的功能。然而,这并不意味着需要放松纪律。相反,它提供了多个调节器来应对问题.


如果您的团队开发并发布 Capacitor 或 Electron 应用,并需要发布控制层, Capgo 是一个值得考虑的选项。它可以将签名的web包分发到特定频道,支持回滚保护和可观察性,并且适合于特征标志需要与实时更新一起工作,而不是取代它们的混合应用工作流.

继续阅读来自React Feature Flags: A Complete Implementation Guide

如果您正在使用 React Feature Flags: A Complete Implementation Guide 来规划频道路由和分阶段发布,连接它与 Channels 为 Channels 的实现细节, Channels 为 Channels 的实现细节, Channels 为 Channels 的实现细节, Beta 测试解决方案 为 Beta 测试解决方案的产品工作流程,和 版本目标解决方案 为版本目标解决方案的产品工作流程。

实时更新Capacitor应用

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

立即开始

最新博客文章

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