如何在 Fable 中切换 show/hide 元素?

How to toggle show/hide element in Fable?

我想在 Fable 中有一个琐碎的 show/hide 内容行为。 就像 this.

function myFunction() {
  var x = document.getElementById("myDIV");
  if (x.style.display === "none") {
    x.style.display = "block";
  } else {
    x.style.display = "none";
  }
}
<button onclick="myFunction()">Click Me</button>

<div id="myDIV">
  This is my DIV element.
</div>

最合适的方法是什么?

我最好的尝试是将问题中 link 中的 JavaScript 笨拙地翻译成:

module Markup

open System
open Fable.Helpers.React
open Fable.Helpers.React.Props
open Fable.Import

let toggleButton text content =
    let id = Guid.NewGuid().ToString()

    let toggle _ = 
        let contentElement = Browser.document.getElementById id
        let display = if contentElement.style.display = "none" then "block" else "none"
        contentElement.style.display <- display

    div [] 
        [
            button [ OnClick toggle ] [ str text ]
            div [ Id id ] [ str content ]
        ]

Markup.toggleButton "title" "a lot of content"

一样使用

这个问题比你想象的要大得多,因为在它下面是"how should I best arrange my Fable application"的问题?您在回答中采用的方法与在 Javascript 应用程序中使用 JQuery 大致平行:您编写的代码直接操纵 DOM 以获得您想要的结果。如果那是您选择的架构风格——并且对于非常简单的应用程序,这是一个非常好的选择——那么您的答案就不能再改进了。 "manipulate the DOM directly" 方法唯一会 运行 出现问题的情况是您的应用程序增长到非常大的大小。到那时,您将需要一个更好的架构。稍后我会推荐一个更好的架构,但首先,让我推荐对您的代码进行一些小的调整。将 toggle 函数移到 toggleButton 之外会使它更加通用:

module Markup

open System
open Fable.Helpers.React
open Fable.Helpers.React.Props
open Fable.Import

let toggleBlockElem id _ = 
    let contentElement = Browser.document.getElementById id
    let display = if contentElement.style.display = "none" then "block" else "none"
    contentElement.style.display <- display

let toggleButton text content =
    let id = Guid.NewGuid().ToString()

    div [] 
        [
            button [ OnClick (toggleBlockElem id) ] [ str text ]
            div [ Id id ] [ str content ]
        ]

这允许您在 toggleLinktoggleCheckbox 等函数中重复使用此函数(如果您发现自己需要这些函数)。

现在,我之前提到过,如果您的 Web 应用程序变大,我会推荐不同的架构。我推荐的架构是 Elmish. It's based on the architecture used in Elm; if you're not familiar with it, the basic idea is similar to React. It's a model/update/view architecture. The model is an immutable data structure. There's also a message type defined; in Elmish, this is probably an F# discriminated union. The update function takes a model and a message as its two parameters, and returns a new model. The view function takes a model and a "dispatch" function as its two parameters (the "dispatch" function will be supplied by Elmish, you don't have to write it) and returns an abstract tree of HTML-like elements. Elmish then passes those elements to something like React to do the actual DOM update. (React will, essentially, diff the "old" DOM tree and the "new" DOM tree, according to a process much like the one described here for Elm).

所有这些都有点难以接受,所以让我们看一个简单的例子,它只是切换 div 的可见性。 注意:我没有测试过这个,只是把它输入到 Stack Overflow 答案框中。以下代码可能存在错误;它旨在作为说明性示例而不是工作示例。 (有关 Elmish 的一个非常详尽的实际工作示例,请参阅 https://mangelmaxime.github.io/fulma-demo/ — 尽管请注意,该示例具有一个高级架构,在数据模型中包含多个 "layers" 父子层次结构,并且可能很难第一眼就把你的思绪包围起来)。

让我们从定义数据模型开始。由于这是一个微不足道的例子,因此该模型同样微不足道;如此微不足道,事实上,我要向它添加一些额外的数据,以便使用记录类型而不是单个 bool:

type Model = { Visible: bool;
               DataNotAppearingInThisFilm: int }

(更高级的模型可能涉及一个 Map<string, bool> 类型来跟踪多个 div 的可见状态)。

消息类型也是微不足道的,所以为了让它更有趣一些,我们将考虑 "Show"、"Hide" "Toggle" 消息:

type Msg =
    | Show
    | Hide
    | Toggle

(更高级的版本是Show of string等,通过div的ID来显示)。

update 函数同样简单:

let update msg model =
    match msg with
    | Show -> { model with Visible = true }
    | Hide -> { model with Visible = false }
    | Toggle -> { model with Visible = not model.Visible }

最后,view 函数将如下所示:

let view model dispatch =
    div [] 
        [
            button [ OnClick (fun _ -> dispatch Toggle) ] [ str (if model.Visible then "Hide" else "Show") ]
            div [ Display (if model.Visible then "block" else "none") ] [ str "content" ]
        ]

或者,将 toggleButton 拉出到它自己的函数中,这样我就可以展示它是如何工作的:

let toggleButton dispatch text content =
    div [] 
        [
            button [ OnClick (fun _ -> dispatch Toggle) ] [ str text ]
            div [ Display (if model.Visible then "block" else "none") ] [ str content ]
        ]

let view model dispatch =
    div []
        [
            str "Other elements might go here"
            toggleButton dispatch (if model.Visible then "Hide" else "Show") "content"
        ]

请注意我需要如何将 dispatch 函数作为参数传递给 toggleButton,以便 toggleButton 能够为其 [=31= 构建正确的行为].

将所有这些放在一起看起来像这样:

open ALotOfModules

type Model = { Visible: bool;
               DataNotAppearingInThisFilm: int }

type Msg =
    | Show
    | Hide
    | Toggle

let update msg model =
    match msg with
    | Show -> { model with Visible = true }
    | Hide -> { model with Visible = false }
    | Toggle -> { model with Visible = not model.Visible }

let toggleButton dispatch text content =
    div [] 
        [
            button [ OnClick (fun _ -> dispatch Toggle) ] [ str text ]
            div [ Display (if model.Visible then "block" else "none") ] [ str content ]
        ]

let view model dispatch =
    div []
        [
            str "Other elements might go here"
            toggleButton dispatch (if model.Visible then "Hide" else "Show") "content"
        ]