我目前正在用寓言般的Elmish架构在F#中制作一个应用程序,其记录类型如下(为了节省空间而缩减了记录,但希望您能想到这个主意)。
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
所有处理新条目的特定功能完全相同,除了明显不同的记录。这个功能很好,但是代码重用还是很痛苦的。是否有一些更优雅的方法可以减少重复代码,从而达到相同的结果?
答案 0 :(得分: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"
]
]
我认为您可以将其引入自己的功能中,而只是使输入字段的列表不同。而且,至关重要的是,将模型的 parts 传递给这些函数。例如,
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 form
和DestMerd form
部分收到“不完全匹配的情况”警告。我真正想要的是让它们具有 entry 的类型(原始代码的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
参数重命名为{ {1}},请不要混淆自己)。也就是说,它看起来像这样:
msg
每当您说“嘿,这里有很多重复”时,通常都有一种将其提取为通用函数的方法。 F#类型系统是您的朋友:当您沉迷于这样的重构中时,您将不会总是记住您已经更改了哪些功能以及没有更改过哪些功能。不过,只需查找红色的波浪线,您就会知道仍需要处理哪些功能。希望本示例可以激发您发现可以提取到其自身功能的其他通用代码。