Feliz: 你对 v2 的想法有什么看法?

创建于 2021-03-17  ·  9评论  ·  资料来源: Zaid-Ajaj/Feliz

你好朋友,
作为 Feliz(和 Feliz.Bulma 确实)的日常用户,我总是在为嵌套prop.children而苦苦挣扎,这让我觉得有点难看。 我想知道您对 v2 提案的看法,我们将有:

Html.div [
    prop.className "myClass"
    Html.div "Nested"
]

也许这样的事情甚至是不可能的,但我看到了@alfonsogarciacaro在 Feliz.Engine 上的工作,并且让道具和孩子处于同一级别似乎很诱人。 😄

discussion

最有用的评论

请愿更改标题以表明讨论主题? 😆

@Dzoukr之前已经讨论过这个问题——我绝对站在你这边。

辅助函数并不理想,因为它们带回了笨拙的双列表语法,基本上首先消除了这个库的一个主要优点,并且它们对真正的 prop 使用没有帮助,而不仅仅是 div+class。

@Zaid-Ajaj 您愿意使用备用命名空间来探索这个区域吗? 像 Feliz.Experimental 什么的? 所以主要的 Feliz 命名空间在 React 上保持了薄层的目标,而替代的可以尝试更多自以为是的方法。

无论如何,我喜欢这样的东西来维持道具分组:

Html.div [
    props [
        prop.className "myClass"
    ]
    Html.div "Nested"
]

道具嵌套似乎比元素嵌套更干净,因为道具不像元素那样受到无限嵌套。 props容器将与Html.*元素共享一个接口,将两者都建模为包含元素的有效子元素。

性能将是值得关注的事情,但我认为不尝试就不会知道影响。 谁知道呢,也许额外的工作听起来很可怕,但在实践中与渲染中发生的其他事情相比却微不足道。

所有9条评论

添加这种支持将使类型推断更加宽容(我什至会说没用),因为那样我们将拥有Attribute = Properties = ReactElement 。 基本上,一切都将被接受为相同的类型......

此外,为了实现这一点,我们需要在 React 之上添加一个额外的层,以“手动”从子项中分离属性,这将影响性能,因为我们总是需要额外的步骤。 这也会增加包的大小,以便存储进行分离所需的信息。

就我个人而言,我不喜欢这个功能。

我对这种方法有复杂的感觉,主要是因为它增加了渲染的运行时开销。 此外,我更愿意将ReactElement的想法与ReactAttribute分开。 所以我个人更喜欢当前的 API。

根据我的经验,我经常从用户那里听到这个问题,但 _only 是在div的上下文中,带有类名和子项。 我通常的回应是编写一个小辅助函数,让你编写类似这样或类似的东西

div [ "myClass" ] [
  Html.div "nested"
]

在我看来,如果扩展方法更适合应用类或其他常用属性的效果,就像我在这里使用 Material UI 所做的那样,那么没有理由不编写自己的扩展方法。 F# 的可组合性非常简单,不会进一步混淆编译器或降低类型安全性。

[<AutoOpen>]
module MuiExtensions

open Feliz
open Feliz.MaterialUI

type Mui 
    with
    static member gridItem (children:seq<ReactElement>):ReactElement = 
        Mui.grid [
            grid.item true
            grid.children children
        ]

    static member gridItem (props:seq<IReactProperty>):ReactElement = 
        Mui.grid [
            grid.item true
            yield! props
        ]

谢谢大家的意见,我只是想讨论流程。 👍我也有自己的帮手,比如divClassed "myClass" [ children ]左右,但是想知道别人的看法。

如果我们将 React 元素从列表更改为计算表达式,这实际上应该是可能的,同时保持类型限制:

div {
    className "myClass"

    div { ... }
}

CE 实现会增加代码库的大小,肯定会使维护变得更加困难,并且在定义 props 时处理诸如原语之类的事情有点棘手/必须在编译时失败,而不是像现在这样在开发过程中失败:

// Would probably need to make the base builder generic so we can use a type restriction and define this DU for
// each element so we can properly restrict what types are valid as primatives
type ReactBuilderChild =
    | Child of ReactElement
    | Children of ReactElement list
    | Float of float
    | Int of int
    | String of string
    | None

    member this.GetChildren () =
        match this with
        | ReactBuilderChild.Children children -> children
        | _ -> failwith "Cannot combine children with primitive values."

type ReactBuilderState =
    { Children: ReactElement list
      Props: IReactProperty list } 

[<AbstractClass;NoEquality;NoComparison>]
type ReactBuilder () =
    [<EditorBrowsable(EditorBrowsableState.Never)>]
    member _.Yield (x: ReactElement) = { Children = ReactBuilderChild.Child x; Props = [] }
    [<EditorBrowsable(EditorBrowsableState.Never)>]
    member _.Yield (x: ReactElement list) = { Children = ReactBuilderChild.Children x; Props = [] }
    [<EditorBrowsable(EditorBrowsableState.Never)>]
    member _.Yield (x: float) = { Children = ReactBuilderChild.Float x; Props = [] }
    ...
    [<EditorBrowsable(EditorBrowsableState.Never)>]
    member _.Yield (x: unit) = { Children = ReactBuilderChild.None; Props = [] }

    [<EditorBrowsable(EditorBrowsableState.Never)>]
    member _.Combine (state: ReactBuilderState, x: ReactBuilderState) = 
        { Children = (state.Children.GetChildren()) @ (x.Children.GetChildren()); Props = state.Props @ x.Props }

    [<EditorBrowsable(EditorBrowsableState.Never)>]
    member _.Zero () = { Children = ReactBuilderChild.None; Props = [] }

    [<EditorBrowsable(EditorBrowsableState.Never)>]
    member this.For (state: ReactBuilderState, f: unit -> ReactBuilderState) = this.Combine(state, f())

    [<CustomOperation("className")>]
    member _.className (state: ReactBuilderState, value: string) =
        { state with Props = (Interop.mkAttr "className" value)::state.Props }

    [<CustomOperation("children")>]
    member _.children (state: ReactBuilderState, value: ReactElement list) =
        { state with Children = state.Children @ value }

    abstract Run : ReactBuilderState -> ReactElement

[<NoEquality;NoComparison>]
type DivBuilder () =
    inherit ReactBuilder()

    [<EditorBrowsable(EditorBrowsableState.Never)>]
    member _.Run (state: ReactBuilderState) = 
        match state with
        | { Children = ReactBuilderChild.None; Props = props } -> Interop.createElement "div" props
        | { Children = ReactBuilderChild.Children children } -> Interop.reactElementWithChildren "div" children
        | { Children = ReactBuilderChild.Float f } -> Interop.reactElementWithChild "div" f
        | _ -> ...

let div = DivBuilder()

我不确定的是如何使 Fable 正确编译掉任何/所有 CE 管道,以及使操作可发现(就像它目前使用prop类型一样)。

请愿更改标题以表明讨论主题? 😆

@Dzoukr之前已经讨论过这个问题——我绝对站在你这边。

辅助函数并不理想,因为它们带回了笨拙的双列表语法,基本上首先消除了这个库的一个主要优点,并且它们对真正的 prop 使用没有帮助,而不仅仅是 div+class。

@Zaid-Ajaj 您愿意使用备用命名空间来探索这个区域吗? 像 Feliz.Experimental 什么的? 所以主要的 Feliz 命名空间在 React 上保持了薄层的目标,而替代的可以尝试更多自以为是的方法。

无论如何,我喜欢这样的东西来维持道具分组:

Html.div [
    props [
        prop.className "myClass"
    ]
    Html.div "Nested"
]

道具嵌套似乎比元素嵌套更干净,因为道具不像元素那样受到无限嵌套。 props容器将与Html.*元素共享一个接口,将两者都建模为包含元素的有效子元素。

性能将是值得关注的事情,但我认为不尝试就不会知道影响。 谁知道呢,也许额外的工作听起来很可怕,但在实践中与渲染中发生的其他事情相比却微不足道。

@zanaptak我非常喜欢这个想法,因为正如你所说,道具不会像孩子们一样嵌套得很深。


关于助手和双列表语法的话题

他们对真正的道具使用没有帮助,这不仅仅是 div+class

let inline withProps elementFunction props (children: #seq<ReactElement>) =
    (prop.children (children :> ReactElement seq))
    :: props
    |> elementFunction

let myDiv = withProps Html.div

myDiv [ prop.className "myClass" ] [
    Html.div "Nested"
]

如果需要,您甚至可以定义一个运算符来对任何元素使用类似“双列表”的语法:

let inline (@+) elementFunction props =
    fun children -> withProps elementFunction props children

let inline (@++) elementFunction prop =
    fun children -> withProps elementFunction [ prop ] children

Html.div @+ [ prop.className "myClass" ]
<| [ Html.div "nested" ]

Html.div @++ prop.className "myClass"
<| [ Html.div "nested" ]

这是一个棘手的话题,因为“看起来更好”可能是主观的。 可能最重要的是避免破坏性更改,但如果你只是想尝试一下,你可以使用

// Dependencies:
// - Fable.React
// - Feliz.Engine.Event dependencies

open Fable.Core
open Fable.Core.JsInterop
open Feliz
open Fable.React

type Node =
    | El of ReactElement
    | Style of string * string
    | Prop of string * obj
    member this.AsReactEl =
        match this with
        | El el -> el
        | _ -> failwith "not an element"

let private makeNode tag nodes =
    let props = obj()
    let style = obj()
    let children = ResizeArray()
    nodes |> Seq.iter (function
        | El el -> children.Add(el)
        | Style(k, v) -> style?(k) <- v
        | Prop(k, v) -> props?(k) <- v
    )
    if JS.Constructors.Object.keys(style).Count > 0 then
        props?style <- style
    ReactBindings.React.createElement(tag, props, children) |> El

let Html = HtmlEngine(makeNode, str >> El, fun () -> El nothing)

let Svg = SvgEngine(makeNode, str >> El, fun () -> El nothing)

let prop = AttrEngine((fun k v -> Prop(k, v)), (fun k v -> Prop(k, v)))

let style = CssEngine(fun k v -> Style(k, v))

let ev = EventEngine(fun k f -> Prop("on" + k.[0].ToString().ToUpper() + k.[1..], f))

测试:

let myEl = // Node
    Html.div [
        prop.className "myClass"
        Html.div "Nested"
    ]

let myElAsReactEl = myEl.AsReactEl // ReactElement 

辅助函数并不理想,因为它们带回了笨拙的双列表语法。

@zanaptak Helper 函数不需要将两个列表作为输入, div [ "class" ] [ ]只是碰巧采用两个列表的常见场景的一个示例。 这些函数也可以是组件,让你的视图更简单

Todo.List [ for item in items -> Todo.Item item ]

尽管您可以在技术上无限嵌套列表,但这不是想法。 通过在较大的 UI 代码中使用较小的函数或组件来实现平衡。

但是,理想情况下,您不需要经常分解代码,并且一个函数应该尽可能具有可读性,因为有时考虑组织事物会带走_flow_。 这是 Fable.React 的 DSL 的问题,您必须每次都_思考_如何格式化事物。 Feliz 以children的两层嵌套为代价修复了这个问题。

老实说,我不知道银弹是什么。 就像@alfonsogarciacaro说的

这是一个棘手的话题,因为“看起来更好”可能是主观的

关于实验主题:

您是否愿意接受另一个命名空间来探索这个领域? 像 Feliz.Experimental 什么的? 所以主要的 Feliz 命名空间在 React 上保持了薄层的目标,而替代的可以尝试更多自以为是的方法。

目前,没有计划在 Feliz 项目中维护不同的 DSL。 我对 OSS 的时间/容量非常有限,并希望直接努力改进当前的方法:文档、示例和更多经过良好测试的生态系统库。

也就是说,可以随意尝试@alfonsogarciacaro 的Feliz.Engine并使用更适合您的 DSL。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

alfonsogarciacaro picture alfonsogarciacaro  ·  11评论

Zaid-Ajaj picture Zaid-Ajaj  ·  8评论

nojaf picture nojaf  ·  4评论

alfonsogarciacaro picture alfonsogarciacaro  ·  5评论

cmeeren picture cmeeren  ·  6评论