你好朋友,
作为 Feliz(和 Feliz.Bulma 确实)的日常用户,我总是在为嵌套prop.children
而苦苦挣扎,这让我觉得有点难看。 我想知道您对 v2 提案的看法,我们将有:
Html.div [
prop.className "myClass"
Html.div "Nested"
]
也许这样的事情甚至是不可能的,但我看到了@alfonsogarciacaro在 Feliz.Engine 上的工作,并且让道具和孩子处于同一级别似乎很诱人。 😄
添加这种支持将使类型推断更加宽容(我什至会说没用),因为那样我们将拥有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。
最有用的评论
请愿更改标题以表明讨论主题? 😆
@Dzoukr之前已经讨论过这个问题——我绝对站在你这边。
辅助函数并不理想,因为它们带回了笨拙的双列表语法,基本上首先消除了这个库的一个主要优点,并且它们对真正的 prop 使用没有帮助,而不仅仅是 div+class。
@Zaid-Ajaj 您愿意使用备用命名空间来探索这个区域吗? 像 Feliz.Experimental 什么的? 所以主要的 Feliz 命名空间在 React 上保持了薄层的目标,而替代的可以尝试更多自以为是的方法。
无论如何,我喜欢这样的东西来维持道具分组:
道具嵌套似乎比元素嵌套更干净,因为道具不像元素那样受到无限嵌套。
props
容器将与Html.*
元素共享一个接口,将两者都建模为包含元素的有效子元素。性能将是值得关注的事情,但我认为不尝试就不会知道影响。 谁知道呢,也许额外的工作听起来很可怕,但在实践中与渲染中发生的其他事情相比却微不足道。