SAFE Stack多页应用:如何将Login.fs登录结果传递至Main.fs?
问题场景
基于SAFE Stack开发多页面应用,采用Maxime Mangel提出的Elmish路由方案,同时遵循《Elmish Book》中Anonymous/LoggedIn的权限管控思路。但书中在Login.fs中使用活动模式let (|UserLoggedIn|_|) = ...并在主页面调用的方式无法生效。
目前临时方案是读取服务器存储的登录结果,权限逻辑虽正常但冗余——登录结果已在Login.fs中生成,无需重复从服务器获取。核心需求是:如何将客户端Login.fs的登录结果直接传递到Main.fs。
相关代码片段
Login.fs(客户端)
let update (msg: Msg) (model: Model) : Model * Cmd<Msg> = match msg with | SetUsrInput value -> { model with InputUsr = value }, Cmd.none | SetPswInput value -> { model with InputPsw = value }, Cmd.none | SendUsrPswToServer -> let buttonClickEvent = SharedLoginValues.create model.InputUsr model.InputPsw let cmd = Cmd.OfAsync.perform getLoginApi.login buttonClickEvent GetLoginResults model, cmd | GetLoginResults value -> let result = match value with | SharedApi.UsernameOrPasswordIncorrect -> { model with User = ApplicationUser.Anonymous} | SharedApi.LoggedIn user -> { model with User = ApplicationUser.LoggedIn user} result, Cmd.ofMsg AskServerForSecurityTokenFile
Main.fs临时权限逻辑
let private setRoute (optRoute: RouterM.Route option) model = let model = let applicationUser = // 临时方案:从服务器存储的token文件判断,应替换为Login页面的登录结果 match model.GetSecurityTokenFile with | true -> LoggedIn model.user | false -> Anonymous let currentRoute = // 临时方案:从服务器存储的token文件判断,应替换为Login页面的登录结果 match model.GetSecurityTokenFile with | true -> optRoute | false -> Some RouterM.Route.Home // 或跳转至登录页 { model with CurrentRoute = currentRoute User = applicationUser }
解决方案
在Elmish架构中,跨页面模块传递状态的核心思路是将用户认证状态提升至全局Model,并通过消息机制同步状态,具体步骤如下:
1. 调整全局Model结构
在Main.fs的主Model中添加User字段,类型为ApplicationUser,用于存储全局的用户认证状态:
type Model = { // 其他原有字段 User: ApplicationUser CurrentRoute: RouterM.Route option LoginModel: Login.Model // ... }
2. 定义跨模块同步消息
在主消息类型中添加用于同步用户状态的消息,同时将Login模块的消息纳入主消息体系:
// Main.fs中定义主消息 type MainMsg = // 其他原有消息 | AuthStateUpdated of ApplicationUser | LoginMsg of Login.Msg // ...
3. 修改Login模块的Update逻辑
当登录结果返回后,除了更新Login本地Model,还要发送同步全局状态的命令:
// Login.fs的update函数修改,增加同步全局状态的回调参数 let update (syncAuthState: ApplicationUser -> Cmd<MainMsg>) (msg: Msg) (model: Model) : Model * Cmd<Msg> = match msg with // ... 原有逻辑不变 | GetLoginResults value -> let updatedModel = match value with | SharedApi.UsernameOrPasswordIncorrect -> { model with User = ApplicationUser.Anonymous} | SharedApi.LoggedIn user -> { model with User = ApplicationUser.LoggedIn user} // 生成同步全局状态的命令,并映射为Login模块的消息格式 let syncCmd = syncAuthState updatedModel.User |> Cmd.map LoginMsg updatedModel, Cmd.batch [Cmd.ofMsg AskServerForSecurityTokenFile; syncCmd]
4. 主模块处理状态同步
在Main.fs的update函数中处理AuthStateUpdated消息,更新全局Model的用户状态:
let update (msg: MainMsg) (model: Model) : Model * Cmd<MainMsg> = match msg with // ... 其他消息处理 | AuthStateUpdated user -> { model with User = user }, Cmd.none | LoginMsg loginMsg -> // 传递同步回调给Login模块 let loginModel, loginCmd = Login.update (AuthStateUpdated >> Cmd.ofMsg) loginMsg model.LoginModel { model with LoginModel = loginModel }, loginCmd // ...
5. 简化Main的权限路由逻辑
直接使用全局Model中的User状态判断权限,不再依赖服务器存储:
let private setRoute (optRoute: RouterM.Route option) model = let currentRoute = match model.User with | ApplicationUser.LoggedIn _ -> optRoute | ApplicationUser.Anonymous -> Some RouterM.Route.Login // 未登录直接跳转登录页 { model with CurrentRoute = currentRoute }
内容的提问来源于stack exchange,提问作者Miroslav Husťák
相关产品推荐
相关产品推荐

