☰
Calypso Modular Signup Framework 完全指南:从零搭建自定义注册流程与步骤
2026/10/7 15:06:55 网站建设 项目流程
  • 前端
  • CMS

【免费下载链接】wp-calypso

The JavaScript and API powered WordPress.com

项目地址:https://gitcode.com/gh_mirrors/wp/wp-calypso
点击查看免费下载

导读

Modular Signup(模块化注册)是 Calypso 面向注册场景的核心框架,它把「将未登录、没有账号的用户转化为已登录用户」这一目标拆解为Flow(流程)与Step(步骤)两层抽象:一个 Flow 由一组按特定顺序组织的 Step 组成,每个 Step 是一个负责收集数据的 React 组件。本文以 client/signup/README.md 为骨架,结合client/signup/config/与client/lib/signup/的真实源码,完整讲解如何注册一个新 Flow、定义一个新 Step、实现步骤组件并通过SignupActions驱动流程前进,最后以官方 Hello World 教程串起全流程。读完本文,你将能够在 Calypso 中独立新增一条类似wordpress.com/start/your-flow的注册流程,并理解其背后的依赖存储(Dependency Store)与进度存储(Progress Store)机制。

框架核心概念:Flow 与 Step

Modular Signup 的定位在文档中表述得非常清楚:它是 Calypso 中用于创建「把没有账号的未登录用户变成已登录用户」的流程的框架。整个框架只围绕两个抽象展开:

  • Flow(流程):一组按特定顺序组织的步骤集合。例如account流程只有一步(创建账号),free流程则包含创建账号与选择域名两步。
  • Step(步骤):一个 React 组件,负责为流程收集数据。例如用户填写邮箱密码的user步骤、选择域名的domains步骤、选择套餐的plans步骤。

在仓库中,这两个概念分别由 client/signup/config/flows-pure.js 与 client/signup/config/steps-pure.js 定义。它们都遵循「纯函数 + 依赖注入」的模式:generateFlows( dependencies )与generateSteps( dependencies )接受一组可覆写的回调(如getSignupDestination、createAccount、addPlanToCart),默认值为noop,便于测试与复用。

从 flows-pure.js 可以看到,generateFlows接收的依赖注入项包括:

  • getRedirectDestination:账号创建后重定向目标;
  • getSignupDestination:通用注册完成后目标;
  • getLaunchDestination、getDomainSignupFlowDestination、getEmailSignupFlowDestination、getWithThemeDestination、getWithPluginDestination、getDIFMSignupDestination等各类细分场景的目标函数。

文件末尾通过Object.fromEntries( flows.map( ( flow ) => [ flow.name, flow ] ) )将数组转换为以name为键的对象并默认导出(flows-pure.js),steps-pure.js的generateSteps则直接返回一个以stepName为键的对象。

创建新的 Flow

根据原文档,新增一个 Flow 的方式是编辑flows-pure.js,在generateFlows函数的flows数组中追加一个属性对象。

Flow 的必需属性

  • steps:数组,按向用户展示的顺序列出该流程包含的所有步骤。可用的步骤清单见 client/signup/config/steps-pure.js。
  • destination:string或function,决定用户完成流程最后一步后重定向到哪个页面。若为函数,则会被调用,参数为注册流程中累积的所有依赖(dependencies),返回值作为重定向目标。

Flow 的可选属性

属性类型说明
descriptionstring对该流程用途的简要描述
lastModifiedstring流程最近更新的日期戳
disallowResumeboolean为true时,用户刷新页面会回到第一步(不保留进度)

原文档给出的最小示例:

const account = { steps: [ 'user-social' ], destination: '/' };

在真实仓库中,account流程的定义更为完整(flows-pure.js):

{ name: 'account', steps: [ userSocialStep ], destination: getRedirectDestination, description: 'Create an account without a blog.', lastModified: '2025-02-18', get pageTitle() { return translate( 'Create an account' ); }, showRecaptcha: true, providesDependenciesInQuery: [ 'toStepper' ], optionalDependenciesInQuery: [ 'toStepper' ], hideProgressIndicator: true, },

注意:steps数组中实际使用userSocialStep变量(isEnabled( 'signup/social-first' ) ? 'user-social' : 'user',见 flows-pure.js),这是仓库中为 social-first 注册模式预留的取舍逻辑。

文档还特别强调一条硬性约束:Flow 必须至少包含一个能创建用户并提供 bearer token 的步骤,这对应步骤定义中的providesToken属性(详见下文「创建新的 Step」)。浏览真实流程可以看到这条约束的体现:几乎每个面向新用户注册的流程(free、with-theme、with-plugin、onboarding、ecommerce、reader等)都把user或user-social放在步骤列表开头。

已注册的 Flow 示例(仓库现状)

flows-pure.js中定义了数十个流程,这里摘录几个有代表性的(数据截至当前仓库):

  • hosting:[ userSocialStep, 'hosting-decider' ],创建账号并跳转到托管站点流程分支步骤(flows-pure.js);
  • free:[ userSocialStep, 'domains' ],创建账号和博客并默认免费套餐(flows-pure.js);
  • with-theme:[ userSocialStep, 'domains-theme-preselected', 'plans-theme-preselected' ],从外部来源预选主题(flows-pure.js);
  • onboarding-registrationless:[ 'domains', 'plans-new', 'user-new' ],无账号无站点的直接结算流程(flows-pure.js);
  • plans-first:[ 'plans', 'domains', userSocialStep ],先选套餐再注册的流程(flows-pure.js)。

这些真实定义展示了文档未展开的扩展属性:showRecaptcha、providesDependenciesInQuery、optionalDependenciesInQuery(控制从 URL query 注入依赖)、hideProgressIndicator、persistsDomainsOnReEntry、enableBranchSteps、forceLogin等,说明 Flow 配置是一个高度可定制的声明式结构。

Flow 的路由可用性

一旦在flows-pure.js中添加了 Flow,它就会自动在/start/flow-name地址可用,其中flow-name是flows对象中该 Flow 的键(即name属性)。例如添加名为hello的流程后,访问/start/hello即可进入。

创建新的 Step

任何人都可以为 Flow 设计师创建可复用的 Step。新 Step 定义在 client/signup/config/steps-pure.js 中,由若干属性组成。

Step 的属性

  • stepName(必需):步骤的标识符,供flows-pure.js引用。
  • dependencies(可选):数组,声明本步骤处理时需要从其他步骤获取哪些依赖。
  • providesDependencies(可选):数组,向框架声明本步骤预期会提供哪些依赖。如果实际提供的依赖与声明不符(缺少或多余),框架会抛出错误(除非使用了optionalDependencies)。
  • optionalDependencies(可选):数组,列出providesDependencies中哪些是可选的;这些值在步骤提交时缺失不会报错。
  • delayApiRequestUntilComplete(可选):boolean,为true时,步骤的apiRequestFunction要等到用户提交完流程中的所有步骤后才被调用。适用于用户可能在注册过程中反复回退修改的步骤。
  • props(可选):对象,存放特定于某步骤的自定义属性。

仓库中domains步骤是这些属性的综合范例(steps-pure.js):

domains: { stepName: 'domains', apiRequestFunction: createSiteWithCart, providesDependencies: [ 'siteId', 'siteSlug', 'domainItem', 'themeItem', 'shouldHideFreePlan', 'isManageSiteFlow', 'signupDomainOrigin', 'siteUrl', 'lastDomainSearched', 'useThemeHeadstart', 'domainCart', ], optionalDependencies: [ 'shouldHideFreePlan', 'isManageSiteFlow', 'signupDomainOrigin', 'siteUrl', 'lastDomainSearched', 'useThemeHeadstart', ], props: { isDomainOnly: false }, delayApiRequestUntilComplete: true, },

user步骤则展示了providesToken与依赖声明(steps-pure.js):

user: { stepName: 'user', apiRequestFunction: createAccount, providesToken: true, providesDependencies: [ 'bearer_token', 'username', 'marketing_price_group', 'redirect', 'allowUnauthenticated', 'is_new_account', 'oauth2_client_id', 'oauth2_redirect', ], optionalDependencies: [ 'redirect', 'allowUnauthenticated', 'is_new_account', 'oauth2_client_id', 'oauth2_redirect', ], props: { isSocialSignupEnabled: config.isEnabled( 'signup/social' ) }, },

其中providesToken: true正是文档强调的「至少一个能创建用户并提供 bearer token 的步骤」的判定依据——bearer_token在providesDependencies中声明,由createAccount的 API 响应带回。

注册步骤的 React 组件

Step 的定义不止于数据声明,还需要在 client/signup/config/step-components.js 中把stepName映射到实现该步骤的 React 组件模块,并以内部依赖的方式 require 该组件。仓库的实现是一个stepNameToModuleName映射对象加一个异步加载函数(step-components.js):

const stepNameToModuleName = { 'clone-start': 'clone-start', domains: 'domains', 'domain-only': 'domains', 'hosting-decider': 'hosting-decider', plans: 'plans', user: 'user', 'user-social': 'user', // ... }; export async function getStepComponent( stepName ) { const moduleName = stepNameToModuleName[ stepName ]; if ( ! moduleName ) { console.error( 'Error: unknown `stepName` to retrieve the component for.' ); return; } const module = await import( /* webpackChunkName: "async-load-signup-steps-[request]" */ /* webpackInclude: /signup\/steps\/[0-9a-z/-]+\/index\.[j|t]sx$/ */ `calypso/signup/steps/${ moduleName }` ); return module.default; }

可以看到多个stepName可以复用同一个组件目录(如user、user-social、user-new、oauth2-user都指向user),并且组件通过webpackInclude限定为signup/steps/下的index.jsx/index.tsx,配合webpackChunkName实现按步骤分包的异步加载。

实现一个 Step 组件

Step 的 React 组件实现位于/signup/steps/下的独立目录中。组件必须使用SignupActions模块,以内部依赖方式引入:

import SignupActions from 'calypso/lib/signup/actions';

SignupActions让 Modular Framework 接管步骤收集到的数据。因此步骤组件必须包含一个让用户进入流程下一步的 UI 元素,且处理该元素的函数必须使用SignupActions的submitSignupStep方法。此外还应调用this.props.goToNextStep(),让框架渲染流程的下一步;通常还需要调用event.preventDefault()以跳过表单的原生提交处理。

文档中的完整示例:

function handleSubmit( event ) { event.preventDefault(); SignupActions.submitSignupStep( { stepName: this.props.stepName, } ); this.props.goToNextStep(); }

submitSignupStep的参数如下:

  • step对象:必填,属性stepName为正在提交的步骤名;
  • providedDependencies(可选):对象,描述该步骤向 Dependency Store 添加的数据。仅用于不来自 API 请求的数据——来自apiRequestFunction回调的依赖会自动进入依赖存储,无需在此重复声明;
  • wasSkipped(可选):标志位,为true表示某个可选步骤被跳过。

背后的状态模型:Progress Store 与 Dependency Store

文档指出,提交的步骤保存在Progress Store中,等待其依赖被满足;例如创建站点的步骤必须等待用户账号创建完成。一旦依赖满足,步骤被处理,其结果保存进Dependency Store,供其他步骤使用。client/lib/signup/README.md 补充了这两者的细节:

  • SignupProgressStore:通过SUBMIT_SIGNUP_STEPaction 收集用户提交的步骤列表。步骤进入存储后会带一个status字符串属性,取值包括in-progress、processing、pending、completed、invalid。通过SignupProgressStore#get()获取步骤数组。
  • SignupActions:核心 action 有两个——submitSignupStep( step, providedDependencies )(用户提交步骤)与completeSignupStep( step, errors, providedDependencies )(步骤被 API 处理)。step对象中的apiRequestFunction(可选)决定了步骤的status是pending还是completed。若errors非空,错误会附加到步骤上并将状态置为invalid;若传了providedDependencies,其信息会写入依赖存储。
  • SignupDependencyStore:凡是通过 action 提供了providedDependencies的步骤,其信息都会进入此存储。README 中的示例展示了跨模块的读写:
import SignupActions from 'calypso/lib/signup/actions'; import SignupDependencyStore from 'calypso/lib/signup/dependency-store'; SignupActions.completeSignupStep( { stepName: 'example' }, [], { userId: 1337 } ); SignupDependencyStore.get(); // => { userId: 1337 }
  • SignupFlowController:初始化一个注册流程,负责为带apiRequestFunction的步骤发起 API 请求,并提供「获取当前步骤组件」与「流程完成回调」的能力。典型用法(client/lib/signup/README.md):
import SignupFlowController from 'calypso/lib/signup/flow-controller'; class SignupComponent extends React.Component { constructor() { super(); this.signupFlowController = new SignupFlowController( { flowName: 'default', onComplete: function () { console.log( 'The user completed the flow. Redirect or log them in here.' ); }, } ); } render() { const CurrentStepComponent = this.signupFlowController.currentStep().component; return <CurrentStepComponent />; } }

apiRequestFunction:把步骤提交给后端 API

如果步骤需要先拿到其他步骤提供的数据才能向 API 提交,可以为其在steps-pure.js中配置apiRequestFunction。该函数在步骤所需数据就绪时被调用。文档示例:

const object = { stepName: 'user-social', dependencies: [ 'siteSlug' ], apiRequestFunction: function ( callback, dependencies ) { wpcom.req.post( '/some-endpoint', dependencies.siteSlug, function ( errors, response ) { callback( errors, { userId: response.userId } ); } ); }, };

解读如下:

  • apiRequestFunction接收两个参数:callback与dependencies;
  • 此例中它调用某个 API 端点并期望返回userId;
  • 请求成功时,response.userId通过callback( errors, { userId } )写入 Dependency Store——这正是 API 请求产出的依赖无需写进providedDependencies的原因;
  • dependencies参数中包含步骤dependencies属性声明的所有依赖(本例为siteSlug),在函数被调用时传入。

文档同时给出工程实践建议:不要把apiRequestFunction写成内联匿名函数,而应把实现放在StepActions(即signup/config/step-actions.js,当前仓库中为 client/lib/signup/step-actions/index.js)中,然后在步骤定义里引用:

const step = { apiRequestFunction: stepActions.createSiteWithCart, };

仓库中大量步骤遵循了这一模式。例如createAccount(client/lib/signup/step-actions/index.js)内部处理了多种分支:对onboarding-registrationless流程且在购买商品时直接跳过建号并返回allowUnauthenticated: true;对 social 注册区分created_account;成功响应中提取bearer_token、username、user_id等并组装registrationUserData。createSiteWithCart、addPlanToCart、addDomainToCart等也都在该文件中实现,与steps-pure.js中apiRequestFunction: createSiteWithCart、apiRequestFunction: addPlanToCart的引用一一对应。

Hello World 实战:从零新增 Flow 与 Step

文档提供了完整的 9 步实战教程,以下按原步骤复现并补充细节。

第 1 步:创建步骤目录

在/client/signup/steps下新建目录hello-world。

第 2 步:添加组件文件

在hello-world目录下创建index.jsx。(仓库中组件的命名规范是index.jsx或index.tsx,与step-components.js中webpackInclude的正则约束一致。)

第 3 步:编写 React 组件

export default function HelloWorld() { return <span>Hello world</span>; }

第 4 步:在step-components.js注册模块映射

在stepNameToModuleName对象中加入映射:

const stepNameToModuleName = { 'hello-world': 'hello-world-module-name', // Referencing signup/steps/hello-world-module-name/index.js };

第 5 步:在steps-pure.js中注册步骤

在generateSteps返回的对象中加入步骤定义,stepName必须与属性名一致:

const steps = { 'hello-world': { stepName: 'hello-world', // has to match the property name }, };

第 6 步:在 flows 配置中注册流程

在 flow 配置中加入新流程(文档此处给出的文件名flow.js即流程定义文件,当前仓库中对应 client/signup/config/flows-pure.js):

const flow = { hello: { // This will be the slug for the flow, i.e.: wordpress.com/start/hello steps: [ 'hello-world', 'user' ], // These are the steps that the user will be shown destination: '/', // This is where the user will be taken once the flow is complete }, };

说明:hello是流程 slug,访问地址为/start/hello;步骤数组中同时包含hello-world(自建步骤)与user(账号步骤),恰好满足「流程必须包含能创建用户并提供 bearer token 的步骤」的约束。

第 7 步:本地验证第一步

在隐身窗口打开https://calypso.localhost:3000/start/hello,会被重定向到流程第一步/start/hello/hello-world,页面应渲染出自定义的 React 组件。

第 8 步:让用户能前进到下一步

在步骤组件的render方法中增加表单与按钮:

function render() { return ( <form onSubmit={ this.handleSubmit }> <p>This is the step named { this.props.stepName }</p> <button className="button" type="submit"> Get started </button> </form> ); }

引入SignupActions:

import SignupActions from 'calypso/lib/signup/actions';

并实现表单提交处理函数:

handleSubmit = ( event ) => { event.preventDefault(); SignupActions.submitSignupStep( { stepName: this.props.stepName, } ); this.props.goToNextStep(); };

第 9 步:端到端验证

再次在隐身窗口打开https://calypso.localhost:3000/start/hello:应重定向到第一步并渲染更新后的组件;点击 "Get started" 按钮后应进入下一步。

机制小结:一次提交在框架中的完整旅程

综合文档与源码,一次「用户点击提交」在 Modular Signup 中的调用链可以归纳为:

  1. 步骤组件的事件处理器调用SignupActions.submitSignupStep( { stepName } )(必要时携带providedDependencies与wasSkipped);
  2. SignupProgressStore记录该步骤并标记状态(in-progress/pending/completed等,取决于是否有apiRequestFunction);
  3. 组件再调用this.props.goToNextStep(),由框架推进到流程的下一步;
  4. 若步骤声明了dependencies,框架等待这些依赖在 Dependency Store 中就绪后才触发apiRequestFunction;
  5. apiRequestFunction通过callback( errors, providedDependencies )把 API 结果写回 Dependency Store(如bearer_token、siteId、siteSlug、cartItems),供后续步骤消费;
  6. 全部步骤完成后,框架调用 Flow 的destination(字符串或依赖函数)决定用户重定向去向。

每一步的声明(steps-pure.js)、组件映射(step-components.js)、API 动作实现(client/lib/signup/step-actions/index.js)三者解耦,这正是 Modular Signup 让不同业务团队能独立贡献步骤、自由组合流程的架构基础。

  • 前端
  • CMS

【免费下载链接】wp-calypso

The JavaScript and API powered WordPress.com

项目地址:https://gitcode.com/gh_mirrors/wp/wp-calypso
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询