Feliz: ¿Tu opinión sobre la idea de la v2?

Creado en 17 mar. 2021  ·  9Comentarios  ·  Fuente: Zaid-Ajaj/Feliz

Hola amigo,
como usuario diario de Feliz (y Feliz.Bulma de hecho) siempre estoy luchando con anidar prop.children lo que lo hace de alguna manera más feo para mí. Me gustaría saber su opinión sobre la propuesta v2 donde tendríamos:

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

Tal vez algo así como que no es ni siquiera posible, pero vi @alfonsogarciacaro trabajo 's en Feliz.Engine y parece tan tentador tener apoyos y los niños en el mismo nivel. 😄

discussion

Comentario más útil

¿Petición para cambiar el título para indicar el tema de discusión? 😆

@Dzoukr Ha habido discusiones previas sobre esto, definitivamente estoy de su lado.

Las funciones auxiliares no son ideales porque recuperan la incómoda sintaxis de doble lista, básicamente eliminando uno de los beneficios clave de esta biblioteca en primer lugar, y no ayudan con el uso real de accesorios, que es mucho más que solo div + class.

@ Zaid-Ajaj ¿Estarías abierto a un espacio de nombres alternativo para explorar esta área? Como Feliz ¿Experimental o algo así? Por lo tanto, el espacio de nombres principal de Feliz mantiene el objetivo de capa delgada sobre React, mientras que el alternativo puede probar un enfoque más obstinado.

De todos modos, me gusta algo como esto para mantener la agrupación de accesorios:

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

El anidamiento de accesorios parece más limpio que el anidamiento de elementos, ya que los accesorios no están sujetos al anidamiento infinito de la forma en que lo están los elementos. El contenedor props compartiría una interfaz con los elementos Html.* , modelando ambos como elementos secundarios válidos del elemento contenedor.

El rendimiento sería algo a tener en cuenta, pero no creo que el impacto se conozca sin intentarlo. Quién sabe, tal vez el trabajo adicional solo suene aterrador pero resulte insignificante en la práctica en relación con todo lo demás que sucede en el renderizado.

Todos 9 comentarios

Agregar este tipo de soporte hará que la inferencia de tipos sea mucho más permisiva (incluso diría inútil) porque entonces tendremos Attribute = Properties = ReactElement . Básicamente, todo será aceptado como del mismo tipo ...

Además, para que eso sea posible, necesitaremos agregar una capa adicional sobre React para dividir los atributos de los hijos "manualmente", lo que afectará el rendimiento, ya que siempre tendremos pasos adicionales necesarios. Esto también aumentará el tamaño del paquete para almacenar la información necesaria para realizar la separación.

Personalmente, no estoy a favor de esta función.

Tengo sentimientos encontrados sobre este enfoque, principalmente debido a la sobrecarga de tiempo de ejecución que agrega al renderizado. También preferiría mantener la idea de ReactElement separada de ReactAttribute . Así que personalmente prefiero mucho más la API actual.

En mi experiencia, escucho mucho sobre este problema de parte de los usuarios, pero _sólo_ en el contexto de div con un nombre de clase e hijos. A lo que mi respuesta habitual es escribir una pequeña función auxiliar que te permite escribir algo como esto o similar

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

Me parece que no hay razón para no escribir sus propios métodos de extensión si se adaptan mejor al efecto de aplicar clases u otras propiedades de uso frecuente como lo hice aquí con Material UI. La capacidad de composición es muy fácil con F # sin confundir más al compilador ni reducir la seguridad de tipos.

[<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
        ]

Gracias por todas las opiniones, solo quería que la discusión fluyera. 👍 También tengo mis propios ayudantes como divClassed "myClass" [ children ] o menos, pero quería conocer el punto de vista de los demás.

En realidad, esto debería ser posible manteniendo las restricciones de tipo si cambiamos los elementos de React de ser una lista a ser expresiones de cálculo:

div {
    className "myClass"

    div { ... }
}

La implementación de CE aumentaría el tamaño del código base, definitivamente lo haría más difícil de mantener, y manejar algunas cosas como las primitivas cuando se definen los accesorios es un poco complicado / tendría que fallar en la compilación en lugar de durante el desarrollo como lo hace ahora:

// 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()

De lo que no estoy seguro es de cómo hacer que Fable compile correctamente cualquier / toda la plomería CE, así como hacer que las operaciones sean detectables (como lo es actualmente con el tipo prop ).

¿Petición para cambiar el título para indicar el tema de discusión? 😆

@Dzoukr Ha habido discusiones previas sobre esto, definitivamente estoy de su lado.

Las funciones auxiliares no son ideales porque recuperan la incómoda sintaxis de doble lista, básicamente eliminando uno de los beneficios clave de esta biblioteca en primer lugar, y no ayudan con el uso real de accesorios, que es mucho más que solo div + class.

@ Zaid-Ajaj ¿Estarías abierto a un espacio de nombres alternativo para explorar esta área? Como Feliz ¿Experimental o algo así? Por lo tanto, el espacio de nombres principal de Feliz mantiene el objetivo de capa delgada sobre React, mientras que el alternativo puede probar un enfoque más obstinado.

De todos modos, me gusta algo como esto para mantener la agrupación de accesorios:

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

El anidamiento de accesorios parece más limpio que el anidamiento de elementos, ya que los accesorios no están sujetos al anidamiento infinito de la forma en que lo están los elementos. El contenedor props compartiría una interfaz con los elementos Html.* , modelando ambos como elementos secundarios válidos del elemento contenedor.

El rendimiento sería algo a tener en cuenta, pero no creo que el impacto se conozca sin intentarlo. Quién sabe, tal vez el trabajo adicional solo suene aterrador pero resulte insignificante en la práctica en relación con todo lo demás que sucede en el renderizado.

@zanaptak Me gusta bastante esa idea porque, como dices, los accesorios no tienden a anidar tan profundamente como los niños.


Sobre el tema de los ayudantes y la sintaxis de lista doble

no ayudan con el uso real de accesorios, que es mucho más que solo 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"
]

Incluso podría definir un operador para usar la sintaxis similar a una "lista doble" con cualquier elemento si lo desea:

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" ]

Este es un tema complicado porque lo que "se ve mejor" puede ser subjetivo. Probablemente lo más importante sea evitar cambios rotos, pero si solo quieres experimentar puedes usar Feliz.Engine para eso, ya que es muy fácil adaptarlo a React. Como se mencionó en otros números, no quiero publicar Feliz.Engine.React porque sería confuso tener dos paquetes "competidores" similares. Pero, por ejemplo, si desea tener todo en el mismo nivel, solo necesita un módulo como este (sería fácil adaptarlo para tener accesorios en una lista separada también):

// 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))

Prueba:

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

let myElAsReactEl = myEl.AsReactEl // ReactElement 

Las funciones auxiliares no son ideales porque recuperan la incómoda sintaxis de doble lista.

Las funciones de div [ "class" ] [ ] fue solo un ejemplo de un escenario común que tomó dos listas. Estas funciones también pueden ser componentes que simplifiquen la vista.

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

Aunque técnicamente puede anidar infinitamente las listas, esa no es la idea. Existe un equilibrio al usar funciones o componentes más pequeños en su código de interfaz de usuario más grande.

Sin embargo, idealmente no necesitaría desglosar el código con tanta frecuencia y la única función debería ser lo más legible posible porque pensar en organizar las cosas quita el _flujo_ a veces. Este era el problema con el DSL de Fable. Reacciona donde tenías que _pensar_ sobre cómo formatear las cosas cada vez. Feliz arregla eso a costa de dos niveles de anidación de children .

Para ser honesto, no sé cuál es la fórmula mágica. Como dijo @alfonsogarciacaro

Este es un tema complicado porque lo que "se ve mejor" puede ser subjetivo.

Sobre el tema de los experimentos:

¿Estaría abierto a un espacio de nombres alternativo para explorar esta área? Como Feliz ¿Experimental o algo así? Por lo tanto, el espacio de nombres principal de Feliz mantiene el objetivo de capa delgada sobre React, mientras que el alternativo puede probar un enfoque más obstinado.

En este momento, no hay planes para mantener diferentes DSL en el proyecto Feliz. Estoy muy limitado en tiempo / capacidad de OSS y espero dirigir esfuerzos para mejorar el enfoque actual: documentación, muestras y bibliotecas de ecosistemas más probadas.

Dicho esto, siéntete libre de experimentar con Feliz.Engine de @alfonsogarciacaro y usa el DSL que más te convenga.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

cmeeren picture cmeeren  ·  6Comentarios

alfonsogarciacaro picture alfonsogarciacaro  ·  6Comentarios

Zaid-Ajaj picture Zaid-Ajaj  ·  6Comentarios

mastoj picture mastoj  ·  3Comentarios

cmeeren picture cmeeren  ·  13Comentarios