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. 😄
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.
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:
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 elementosHtml.*
, 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.