1

我目前正在用 F# 制作一个具有寓言 elmish 架构的应用程序,其中记录类型如下(减少以节省空间,但希望您能明白)。

type NewOriginMerdEntry =
    | AddOriginMerdID of string
    | AddMerdNumber of int
    | AddAverageWeight of float
    | AddPD of int

type NewTreatmentEntry =
    | AddTreatmentID of string

type NewDestMerdEntry =
    | AddDestMerdID of string

 ....etc

现在我已经将它们编译成一个有区别的联合类型,比如这样

type NewEntry =
    | NewOriginMerdEntry of NewOriginMerdEntry
    | NewTreatmentEntry of NewTreatmentEntry
    | NewDestMerdEntry of NewDestMerdEntry
    ...etc

最后主要消息类型如下所示:

type Msg = {
     NewEntry of NewEntry
}

这很干净,但是在视图函数中,我需要为每种类型创建一个新函数来表示视图以及在文本输入更改时需要调度的特定消息。

这是这样的:

let originMerdView (dispatch : Msg -> unit) (model : Model) =
    let dispatch' = NewOriginMerdEntry >> NewEntry >> dispatch
    let form = match model.form with
                | OriginMerd o -> o
                | _ -> None

    R.scrollView[
        P.ViewProperties.Style [
            P.FlexStyle.FlexGrow 1.
            P.BackgroundColor "#000000"
        ]
    ][
        //these functions are simply calls to various input text boxes
        inputText "ID" AddOriginMerdID dispatch'
        numinputText "MerdNumber" AddMerdNumber dispatch'
        floatinputText "average Weight" AddAverageWeight dispatch'
        numinputText "PD" AddPD dispatch'
        button "save" form SaveOriginMerd (SaveEntry >> dispatch)
    ]


let inputText label msg dispatch =


    R.textInput[

        P.TextInput.OnChangeText ( msg >> dispatch )
    ]

所以第一个问题是,是否有可能以某种方式概括这一点,因为主视图将根据模型状态决定运行哪些函数。它工作正常,但代码重复量相当痛苦。

此外,每个新条目都将发送到此函数:

let handleNewEntry (model : Model) (entry : NewEntry) =
    match entry with
    | NewOriginMerdEntry e -> handleNewOriginMerdEntry model e
    ... etc


let handleNewOriginMerdEntry (model : Model) (entry : NewOriginMerdEntry) =
    let form =
        match model.form with
        | OriginMerd o -> match o with
                            | Some f -> f
                            | None -> OriginMerd.New
        | _ -> failwithf "expected origin type got something else handleNewOriginMerd"

    let entry =
        match entry with
        | AddOriginMerdID i -> {form with originMerdID = i}
        | AddMerdNumber n -> {form with merdNumber = n}
        | AddPD p -> {form with pD = p}
        | AddAverageWeight w -> {form with averageWeight = w}

    {model with form = OriginMerd (Some entry)}, Cmd.none

除了明显不同的记录外,所有特定的句柄新条目功能完全相同。这功能很好,但代码重用再次非常痛苦。有没有更优雅的方法可以用更少的代码重复实现相同的结果?

4

1 回答 1

2

在我看来,至少您的这部分观点将被分享:

let form = match model.form with
           | OriginMerd o -> o  // With a different match target each time
           | _ -> None

R.scrollView[
    P.ViewProperties.Style [
        P.FlexStyle.FlexGrow 1.
        P.BackgroundColor "#000000"
    ]
]

我认为您可以将其拉出到它自己的函数中,只是让输入字段列表有所不同。而且,至关重要的是,将模型的一部分传递给这些函数。例如,

let originMerdForm (dispatch : Msg -> unit) (OriginMerd form) =
    let dispatch' = NewOriginMerdEntry >> NewEntry >> dispatch
    [
        //these functions are simply calls to various input text boxes
        inputText "ID" AddOriginMerdID dispatch'
        numinputText "MerdNumber" AddMerdNumber dispatch'
        floatinputText "average Weight" AddAverageWeight dispatch'
        numinputText "PD" AddPD dispatch'
        button "save" form SaveOriginMerd (SaveEntry >> dispatch)
    ]

let destMerdForm (dispatch : Msg -> unit) (DestMerd form) =
    let dispatch' = NewDestMerdEntry >> NewEntry >> dispatch
    [
        inputText "ID" AddDestMerdID dispatch'
        button "save" form SaveDestMerd (SaveEntry >> dispatch)
    ]

let getFormFields (model : Model) =
    match model.form with
    | OriginMerd _ -> originMerdForm model.form
    | DestMerd _ -> destMerdForm model.form
    // etc.
    | _ -> []

let commonView (dispatch : Msg -> unit) (model : Model) =
    R.scrollView[
        P.ViewProperties.Style [
            P.FlexStyle.FlexGrow 1.
            P.BackgroundColor "#000000"
        ]
    ] (getFormFields model)

请注意,按照我编写此代码的方式,您将在各个函数的OriginMerd formDestMerd form部分收到“不完全匹配案例”警告。我真正想要的是让它们具有条目的类型(o在您OriginMerd o的原始代码行中),但我不知道您将其命名为什么。然后需要发生的唯一更改是您希望将button调用提取到公共视图,例如

let commonView (dispatch : Msg -> unit) (model : Model) =
    let formFields, saveMsg = getFormFields model
    R.scrollView[
        P.ViewProperties.Style [
            P.FlexStyle.FlexGrow 1.
            P.BackgroundColor "#000000"
        ]
    ] (formFields @ [button "save" model.form saveMsg (SaveEntry >> dispatch))

然后您的originMerdForm,destMerdForm将返回一个元组,(form fields, msg)其中msg包含SaveOriginMerd,SaveDestMerd等。

您的handleNewFooEntry函数也可以从输入参数的类似更改中受益:您可以只传入适当的条目类型,而不是传入整个模型(并将entry参数重命名为msg,请不要混淆自己)。即,它看起来像这样:

let handleNewEntry (model : Model) (msg : NewEntry) =
    let form' =
        match msg, model.form with
        | NewOriginMerdEntry m, OriginMerd o -> handleNewOriginMerdEntry o m
        | NewOriginMerdEntry m, _ -> failwithf "expected origin type got something else"
        | NewDestMerdEntry m, DestMerd d -> handleNewDestMerdEntry d m
        | NewDestMerdEntry m, _ -> failwithf "expected dest type got something else"
    {model with form = form'}, Cmd.none

let handleNewOriginMerdEntry (formOpt : OriginMerdEntry option) (msg : NewOriginMerdEntry) =
    let form = formOpt |> Option.defaultValue OriginMerd.New
    let result =
        match msg with
        | AddOriginMerdID i -> {form with originMerdID = i}
        | AddMerdNumber n -> {form with merdNumber = n}
        | AddPD p -> {form with pD = p}
        | AddAverageWeight w -> {form with averageWeight = w}
    OriginMerd (Some result)

let handleNewDestMerdEntry (formOpt : DestMerdEntry option) (msg : NewDestMerdEntry) =
    let form = formOpt |> Option.defaultValue DestMerd.New
    let result =
        match msg with
        | AddDestMerdID i -> {form with destMerdID = i}
    DestMerd (Some result)

任何时候你说,“嘿,这里有很多重复”,通常有一种方法可以将它提取到一个通用函数中。F# 类型系统在这里是您的朋友:当您深入进行这样的重构时,您不会总是记得您已经更改了哪些函数以及您没有更改哪些函数。不过,只需寻找红色波浪线,您就会知道您还需要处理哪些功能。希望这个示例能启发您发现其他可以提取到自己的函数中的常见代码。

于 2019-06-27T21:05:55.353 回答