Feliz: Mon parcours avec Feliz | Une comparaison entre Fable.React et Feliz

Créé le 7 avr. 2020  ·  23Commentaires  ·  Source: Zaid-Ajaj/Feliz

Bonjour,

ce numéro est pour partager mon expérience avec Feliz et Feliz.Bulma.

Ce retour est basé sur la conversion de Fable Repl de Fable.React + Fulma en Feliz + Feliz.Bulma .

Dans la première section, je vais faire une liste de toutes les choses que j'ai aimées ou avec lesquelles j'ai eu des problèmes lors de l'utilisation de Feliz. Je ferai quelque chose de similaire pour Fable.React et Fulma car ils ne sont pas exempts de bonne et de moins bonne partie.

Ensuite, dans un second temps, je vais essayer d'expliquer comment s'est déroulée mon expérience avec Feliz et Feliz.Bulma. Le but est de partager avec vous comment mon point de vue a évolué au fil du temps et pourquoi.

Important

Je sais que le sujet que j'analyse est sensible et que quelque chose me fait peur. Mais s'il vous plaît, n'oubliez pas de garder la section des commentaires positive.

Table des matières

Système de catégorisation

J'ai essayé de trouver un moyen d'organiser mes retours, le classique "pour vs contre" me paraissait trop limité et agressif.

Au lieu de cela, j'utiliserai des symboles :

  • ✔️ représente quelque chose que j'ai aimé
  • ⚠️ peut représenter plusieurs choses :

    • quelque chose qui m'a causé des problèmes parce que c'est différent de ce à quoi j'ai l'habitude

    • quelque chose qui pourrait être amélioré

    • zones où l'utilisateur doit être prudent

  • ℹ représente quelque chose qui n'était pas évident pour moi au début mais qui n'est pas directement lié à un problème dû à Feliz

Une même entrée peut avoir plusieurs symboles :)

Féliz


✔️ Feliz et Fable.React peuvent être mélangés car Feliz est une couche au-dessus de Fable.React


✔️ Le code est plus confortable à indenter par rapport à Fable.React car nous n'avons qu'une seule liste. Il est plus facile de suivre la tabulation.

Cliquez ici pour plus de détails

**Fable.Réagir + Fulma**

div [ ]
    [ Hero.hero [ Hero.IsFullHeight ]
        [ Hero.body [ ]
            [ Container.container [ ]
                [ img [ Src "img/fable-ionide.png"
                        Style [ Display DisplayOptions.Block
                                Width "auto"
                                Margin "auto" ] ]
                  br [ ]
                  Heading.h3 [ Heading.Modifiers [ Modifier.TextAlignment (Screen.All, TextAlignment.Centered) ] ]
                    [ str "Fable REPL" ]
                  Heading.p [ Heading.IsSubtitle
                              Heading.Is5
                              Heading.Modifiers [ Modifier.TextAlignment (Screen.All, TextAlignment.Centered) ] ]
                    [ str "is only available on desktop" ] ] ] ] ]
**Feliz + Feliz.Bulma**
Html.div [
    Bulma.hero [
        hero.isFullHeight
        prop.children [
            Bulma.heroBody [
                Bulma.container [
                    Html.img [
                        prop.src "img/fable-ionide.png"
                        prop.style [
                            style.display.block
                            style.width length.auto
                            style.margin length.auto
                        ]
                    ]

                    Html.br [ ]

                    Bulma.title3 [
                        text.hasTextCentered
                        prop.text "Fable REPL"
                    ]

                    Bulma.subtitle5 [
                        text.hasTextCentered
                        prop.text "is only available on desktop"
                    ]
                ]
            ]
        ]
    ]
]


✔️ Grâce au point précédent, il est également facile de déplacer le code

feliz_move_code_demo


✔️ API fortement typée pour les propriétés DOM


✔️ API fortement typée pour les unités CSS et CSS via ICSSUnits

style.marginLeft (length.em 0.5)
style.width (length.percent (model.PanelSplitRatio * 100.))

✔️ Ne pollue pas le contexte, la plupart des choses sont sous Html.* ou Prop.*


✔️ ⚠️ Feliz offre la possibilité d'éviter le bruit dans le code

Cliquez ici pour plus de détails

Html.span "Fable REPL"

// instead of

// Feliz verbose version
span [
    str "Fable REPL"
]

// Fable.React

span [ ]
    [ str "Fable REPL" ]

// --------------------

Bulma.title4 "Fable REPL"

// instead of

// Feliz.Bulma verbose version
Bulma.title4 [
    prop.text "Fable REPL"
]

// Fable.React
Headings.h4 [ ]
    [ str "Fable REPL" ]
Comme il existe plusieurs façons d'écrire le même code, nous ne pouvons pas simplement « lire » le code. Nous avons parfois besoin de prendre du recul pour comprendre le contexte extérieur. Cela rend également le code moins "cohérent" car tout n'est pas écrit de la même manière.
Html.tr [
    Html.th "Steps"
    Html.th [
        prop.className "has-text-right"
        prop.text "ms"
    ]
]


⚠️ Il ne supporte pas (encore) SSR


⚠️ Découvrir la surcharge méthodes/propriétés n'est pas facile (c'est peut-être une limitation d'Ionide que je ne connais pas)

J'ai dû:

  • expérimenter par moi-même et vérifier l'erreur du compilateur. Par exemple, tester si prop.text 2.0 est pris en charge ou non lorsque j'avais un float.
  • regardez directement le code source de Feliz

⚠️ Rendre plus difficile l'utilisation du rappel en prenant plus d'un paramètre. Dans Fable.REPL, j'ai dû forcer le uncurry en utilisant System.Func<_,_,_> .

Cliquez ici pour plus de détails

[<Erase>]
type editor =
    /// <summary>Triggered when the Editor has been mounted</summary>
    static member inline editorDidMount (f : System.Func<Monaco.Editor.IStandaloneCodeEditor, Monaco.IExports, unit>) = Interop.mkAttr "editorDidMount" f
Ce qui donne ce code côté appelant :
let private registerCompileCommand dispatch =
    System.Func<_,_,_>(
        fun (editor : Monaco.Editor.IStandaloneCodeEditor) (monacoModule : Monaco.IExports) ->
            let triggerCompile () = StartCompile None |> dispatch
            editor.addCommand(monacoModule.KeyMod.Alt ||| int Monaco.KeyCode.Enter, triggerCompile, "") |> ignore
            editor.addCommand(monacoModule.KeyMod.CtrlCmd ||| int Monaco.KeyCode.KEY_S, triggerCompile, "") |> ignore
    )


✔️ ⚠️ Feliz propose des surcharges pour faciliter un peu la vie des utilisateurs, mais cela a un coût.

Pour prop.onChange , comme 6 surcharges selon ce que vous voulez écouter :

  • static member inline onChange (handler: bool -> unit)
  • static member inline onChange (handler: Event -> unit)
  • static member inline onChange (handler: File -> unit)
  • static member inline onChange (handler: File list -> unit)
  • static member inline onChange (handler: string -> unit)
  • static member inline onCheckedChange (handler: bool -> unit)

C'est bien parce que cela évite aux gens d'écrire "aucun" code amusant, mais la contrepartie est que vous devez dire quel type d'événement vous voulez explicitement.

// Feliz
prop.onChange (fun (e : Types.Event) -> e.Value |> ChangeGistToken |> dispatch)

// Fable.React
prop.onChange (fun e -> e.Value |> ChangeGistToken |> dispatch)

✔️ ⚠️ L'écosystème Feliz est principalement typé mais n'empêche pas d'écrire du code invalide. Lorsque vous utilisez Feliz et l'une de ses extensions comme Feliz.Bulma, vous pouvez facilement mélanger des propriétés, mais vous devez être prudent lorsque vous le faites.

Cliquez ici pour plus de détails

Ce code semble OK du point de vue de Feliz et du compilateur F# mais il ne vous donnera pas le résultat attendu.

Html.p [
    text.isUppercase
    text.isItalic
    color.hasTextSuccess
    prop.text "Hello Feliz"
]
`text.isUppsercase`, `text.isItalic` et `color.hasTextSuccess` produisent tous quelque chose comme `ClassName "my-css-class`. Mais dans React, seul le dernier aura un effet donc dans notre cas le code sera produire:
<p class="has-text-success">Hello Feliz<\p>
à la place de
<p class="is-uppercase is-italic has-text-success>Hello Feliz</p>
La solution à cela est d'utiliser le `++` proposé par Feliz.Bulma :
open Feliz.Bulma.Operators

Html.p [
    text.isUppercase
    ++ text.isItalic
    ++ color.hasTextSuccess
    prop.text "Hello Feliz"
]
C'est toujours une bonne possibilité car cela signifie que vous pouvez ajouter un comportement provenant d'une autre bibliothèque Feliz à un "élément Feliz" s'ils sont compatibles. Les gens ont juste besoin d'y faire attention.


ℹ Au début, je pensais que Feliz n'offrait pas le sucre de syntaxe ev.Value que nous obtenons en utilisant Fable.React, mais ce n'est pas le cas.

Fable.React.Extension héberge la syntaxe sugar, nous pouvons donc simplement l'ouvrir pour ne pas polluer le contexte avec toutes les fonctions Fable.React.

open Feliz
open Fable.React.Extensions

ℹ La syntaxe ne reproduit pas la façon de penser HTML. Feliz est plus un sucre de syntaxe au-dessus de l'API React que du HTML. Feliz pense en termes de propriétés car même children est une propriété.


Feliz Bulma

✔️ S'intègre bien avec Feliz


✔️ Plus facile à indenter par rapport à Fulma

Cliquez ici pour plus de détails

// Fulma
Card.card [ ]
    [ Card.header [ Common.Props [ OnClick (fun _ -> ToggleWidget title |> dispatch ) ] ]
        [ Card.Header.title [ ]
            [ Icon.icon [ Icon.Props [ Style [ MarginRight ".5em" ] ] ]
                [ Fa.i [ icon; Fa.Size Fa.FaLarge ] [] ]
                str title ]
            Card.Header.icon [ ]
            [ Icon.icon [ ] [ Fa.i [ headerIcon ; Fa.Size Fa.FaLarge ] [] ] ] ]
        ofOption content ]

// Feliz.Bulma
Bulma.card [
    Bulma.cardHeader [
        prop.onClick (fun _ -> ToggleWidget title |> dispatch )
        prop.children [
            Bulma.cardHeaderTitle [
                Bulma.icon [
                    prop.style [
                        style.marginRight (length.em 0.5)
                    ]
                    prop.children [
                        Fa.i [ icon; Fa.Size Fa.FaLarge ] []
                    ]
                ]
                Html.text title
            ]

            Bulma.cardHeaderIcon [
                Bulma.icon [
                    Fa.i [ headerIcon ; Fa.Size Fa.FaLarge ] []
                ]
            ]
        ]
    ]

    Html.ofOption content
]


✔️ Il est facile d'identifier les composants Bulma grâce au préfixe Bulma


✔️ ⚠️ Les propriétés sont accessibles mais polluent le contexte button.* , help.* , columns.* .

En théorie, les gens ne devraient utiliser qu'un seul framework CSS, donc je ne pense pas qu'ils auront un conflit sur des propriétés génériques comme button.* , columns.* , etc.


✔️ La hiérarchie des composants semble assez facile à saisir

Bulma.card > Bulma.cardHeader > Bulma.cardHeaderTitle

Cliquez ici par exemple

Bulma.card [
    Bulma.cardHeader [
        prop.onClick (fun _ -> ToggleWidget title |> dispatch )
        prop.children [
            Bulma.cardHeaderTitle [
                // ...
            ]

            Bulma.cardHeaderIcon [
                // ...
            ]
        ]
    ]
]


⚠️ Mais certains composants ne suivent pas la même convention

Bulma.passwordInput au lieu de Bulma.inputPassword

Dans ce cas, vous ne pouvez pas facilement explorer les différents types d'entrées que vous avez car elles ne commencent pas par le même "préfixe".


✔️ ⚠️ Feliz.Bulma permet de mélanger facilement les comportements des composants.

Cliquez ici pour plus de détails

Bulma.button [
    // Properties specific to a button coming from Feliz.Bulma
    button.isOutlined
    // Properties specific to a tooltip coming from Feliz.Bulma
    tooltip.hasTooltipRight
    tooltip.text tooltipText
    // General properties coming from Feliz
    prop.disabled isCompiling
    prop.onClick (fun _ -> dispatch msg)
    prop.children [
        Bulma.icon [
            icon.isLarge
            prop.children faIcon
        ]
    ]
]
C'est bien car comme vous le voyez, il est facile d'ajouter un nouveau comportement à un composant existant. Ici, nous avons ajouté l'info-bulle de comportement à un bouton. Mais cela signifie également que vous pouvez écrire du code invalide comme :
Html.select [
    select.isFullwidth
]
à la place de
Bulma.select [
    select.isFullwidth
]
Fulma est plus strict sur la séparation des composants et ne vous permet pas de mélanger le comportement à moins que vous ne passiez les classes CSS via les accessoires `CustomClass`.


✔️ ⚠️ Feliz.Bulma ne permet pas de contrôler l'élément HTML que nous voulons sortir.

Par exemple, nous n'avons que Bulma.field qui génère un div . Cependant, vous voulez parfois un élément p comme sortie.


✔️ ⚠️ Feliz.Bulma est une cartographie exacte sur Bulma

C'est bien parce que c'est mince.

Mais cela signifie aussi :

  • Vous devez bien connaître Bulma ou avoir ouvert la documentation Bulma
  • Cela n'évite pas le code bruyant

Par exemple, lorsque vous écrivez un composant Bulma.tabs , vous n'êtes pas guidé et devez savoir que tabs besoin de ul suivi de li avec un a dedans.

Cliquez ici pour voir le code

// Feliz + Feliz.Bulma
Bulma.tabs [
    tabs.isCentered
    tabs.isMedium
    tabs.isToggle
    prop.children [
        Html.ul [
            Html.li [
                if (activeTab = CodeTab.FSharp) then
                    prop.className "is-active"
                prop.onClick (fun _ -> SetCodeTab CodeTab.FSharp |> dispatch)
                prop.children [
                    Html.a [
                        prop.text "F#"
                    ]
                ]
            ]

            Html.li [
                if (activeTab = CodeTab.Html) then
                    prop.className "is-active"
                prop.onClick (fun _ -> SetCodeTab CodeTab.Html |> dispatch)
                prop.children [
                    Html.a [
                        prop.text "HTML"
                    ]
                ]
            ]

            Html.li [
                if (activeTab = CodeTab.Css) then
                    prop.className "is-active"
                prop.onClick (fun _ -> SetCodeTab CodeTab.Css |> dispatch)
                prop.children [
                    Html.a [
                        prop.text "CSS"
                    ]
                ]
            ]
        ]
    ]
]
Fulma permet à l'utilisateur de réfléchir davantage en termes de "composants Bulma".
// Fable.React + Fulma
    Tabs.tabs [ Tabs.IsCentered
                Tabs.Size Size.IsMedium
                Tabs.IsToggle ]
        [ Tabs.tab [ Tabs.Tab.IsActive (activeTab = CodeTab.FSharp)
                     Tabs.Tab.Props [
                        OnClick (fun _ -> SetCodeTab CodeTab.FSharp |> dispatch)
                     ] ]
            [ a [ ] [ str "F#" ] ]
          Tabs.tab [ Tabs.Tab.IsActive (activeTab = CodeTab.Html)
                     Tabs.Tab.Props [
                         OnClick (fun _ -> SetCodeTab CodeTab.Html |> dispatch)
                     ] ]
            [ a [ ] [ str "HTML" ] ]
          Tabs.tab [ Tabs.Tab.IsActive (activeTab = CodeTab.Css)
                     Tabs.Tab.Props [
                         OnClick (fun _ -> SetCodeTab CodeTab.Css |> dispatch)
                     ] ]
            [ a [ ] [ str "CSS" ] ] ]

Noter:

Fulma considère tab comme un composant et propose un Tabs.Tab.* wrapper spécifique.

Fulma a quand même besoin que vous sachiez qu'un élément a est nécessaire, mais nous pourrions l'ajouter par défaut (c'est le cas pour certains composants).


✔️ ⚠️ Feliz.Bulma a un impact plus faible sur la taille du bundle sur un petit projet mais aura un impact plus important lorsque la taille du projet augmentera.

Cliquez ici pour plus de détails

Fulma utilise beaucoup de DU pour modéliser les classes Bulma. Vous pouvez consulter [Common.fs](https://github.com/Fulma/Fulma/blob/2f99474cd6c793776001d07da009f7211be2f30c/src/Fulma/Common.fs); aussi chaque composant a ses propres DU. Cela nécessite que Fulma implémente un appel de fonction `parseOptions` qui, en termes simples, convertit les DU en classes. Feliz.Bulma adopte une approche plus directe en ne créant pas de DSL au-dessus des classes Bulma mais en sortant directement les classes.

// Fulma
Column.column
    [
        Column.Width (Screen.Desktop, Column.IsHalf)
        Column.Width (Screen.Mobile, Column.IsFull)
    ]
    [
        // ...
    ]

// Feliz
Bulma.column [
    column.isHalfDesktop
    column.isFullMobile
    prop.children [
        // ...
    ]
]
Grâce à l'utilisation directe des classes et à la manipulation de la liste de propriétés, Feliz.Bulma n'a pas le coût élevé de tous les DU et du code ajoutés dans Fulma. Cependant, il a encore besoin d'un pass supplémentaire pour "rejoindre" toutes les classes. Cette partie se fait via [module Feliz.Bulma.ElementBuilders.Helpers](https://github.com/Dzoukr/Feliz.Bulma/blob/3ecbba1579d2a26281f24e6a6664b5d9c5222603/src/Feliz.Bulma/ElementBuilders.23) mais #L6- ces fonctions sont alignées. C'est pourquoi plus votre projet est grand, plus l'impact sur votre code est grand.


⚠️ Il n'est pas facile d'ajouter un nouveau support de couleur.

Dans Feliz.Bulma, chaque composant possède sa propre implémentation de couleur comme button.isWarning , help.isWarning , etc.

Donc si vous voulez ajouter votre couleur, vous devez implémenter tous les button.isMyNewColor , help.isMyNewColor

Dans Fulma, ils partagent tous le même type de couleur . Voir la documentation


Fable.Réagir

✔️ Suit la structure HTML lorsque vous la considérez comme une liste de propriétés et une liste d'enfants


⚠️ Les règles d'indentation sont difficiles à établir car nous devons organiser une double liste et en général, nous faisons beaucoup de cas exceptionnels.

Cliquez ici pour une explication détaillée

Exemple:

// When I want to put an empty div
div [ ] [ ]

// When I want to put a div with a single property
div [ ClassName "button" ] [ ]
// or
div [ ClassName "button" ]
    [ ]

// When I want to put a div with several property
div [ ClassName "button"; Id "my-button" ]
    [ ]
// yes but what if one of my property is not that simple?
div
    [
        ClassName "button"
        OnClick (fun _ ->
            // do something
        )
    ]
    [ ]

// It's also possible to have consistent indentation if you do something like that
div [ ] [
    // Children 1
    // Children 2
]

// But if you have non simple property you still have problems, I don't personally don't find it easy to read
// I am not sure if that's how people would write it because I don't use this format personally
div [
    ClassName "button"
    OnClick (fun _ ->
        // do something
    )
] [
    div [ ] [
        str "Children 1"
    ]
]

// and so on
Comme vous pouvez le voir juste en traitant quelques cas sur la liste des propriétés, nous avons plusieurs syntaxes possibles. Si je devais établir une syntaxe cohérente dans mon projet, ce serait quelque chose comme ça :
div
    [
        // Property 1 ...
        // Property 2 ...
        // Property 3 ...
    ]
    [
        div
            [
                // Property 1 ...
                // Property 2 ...
                // Property 3 ...
            ]
            [
                // Children 1 ...
                // Children 2 ...
                // Children 3 ...
            ]
        div
            [
                // Property 1 ...
                // Property 2 ...
                // Property 3 ...
            ]
            [
                // Children 1 ...
                // Children 2 ...
                // Children 3 ...
            ]
    ]

// So for an empty element
div
    [

    ]
    [

    ]

// Awesome... 🙄
A titre de comparaison à Feliz, je fais :
// Empty div
Html.div [ ]

// Non empty div
Htmldiv [
    // Property 1
    // Property 2
    // Property 3
    prop.children [
        Html.div [
            // Property 1
            // Property 2
            // Property 3
        ]

        Html.div [
            // Property 1
            // Property 2
            // Property 3
            // Complexe property
            OnClick (fun _ ->
                // Do something
            )
        ]
    ]
]
Je n'ai donc plus que 2 cas maintenant, et je pourrais même écrire la version vide sur plusieurs lignes sans trop de bruit si je veux vraiment une seule façon de structurer le code Feliz.


⚠️ La plupart de l'API n'est pas typée

type HTMLAttr =
    | DefaultChecked of bool
    | DefaultValue of obj
    | Accept of string
    | AcceptCharset of string
    | AccessKey of string
    | Action of string
    | AllowFullScreen of bool
    | AllowTransparency of bool

✔️ ⚠️ Une partie de l'API est typée mais pas toutes, ce qui signifie que le code n'est pas cohérent.


✔️ Il prend en charge la RSS


Fulma

✔️ API de type sécurisé

Modifier.TextAlignment (Screen.All, TextAlignment.Centered)

Button.button [ Button.Color IsWhite ]
    [ str "White" ]

✔️ L'API Fulma est facile à explorer via intellisense lorsque vous comprenez comment elle est structurée


✔️ Fulma vous oblige à penser en terme de composants


✔️ Facilitez l'extension de la couleur prise en charge grâce à IsCustomColor

// All you need to add `custom-purple` support to all your components is this line
let isCustomPurple = IsCustomColor "custom-purple"

✔️ ⚠️ Je pense qu'il a une bonne documentation avec des exemples pour tous les composants, mais tous les composants ne contiennent pas le même niveau d'information


✔️ ⚠️ L'impact de Fulma sur la taille de votre bundle est "stable". Ce point a été décrit dans la section Feliz.Bulma


⚠️ Les gens ne trouvent pas facilement les « aides spéciales ».

  • ClassePersonnalisée
  • Accessoires
  • Modificateurs

⚠️ Le code Fulma ajoute du bruit à votre code à cause du DSL fortement typé

Cliquez ici pour un exemple

// Fulma
Heading.p [ Heading.IsSubtitle
            Heading.Is5
            Heading.Modifiers [ Modifier.TextAlignment (Screen.All, TextAlignment.Centered) ] ]
    [ str "is only available on desktop" ]

// Feliz.Bulma
Bulma.subtitle5 [
    text.hasTextCentered
    prop.text "is only available on desktop"
]


⚠️ Le code Fulma n'est pas si facile à formater correctement car il a beaucoup de listes imbriquées lorsque vous combinez Props , Modifiers


J'ai travaillé sur cette analyse sur une période de 2-3 semaines et je pouvais encore continuer. Mais je dois y mettre fin car cela prend beaucoup de temps et je pense avoir maintenant une meilleure vision de la situation.

Tout ça pour dire qu'il pourrait y avoir d'autres bons points ou problèmes à lister notamment sur Fable.React et Fulma mais c'est aussi la partie la plus difficile pour moi. Je les utilise et les conçois depuis plus de 3 ans donc c'est difficile d'avoir un regard objectif sur eux.

La reconversion a-t-elle été difficile ?

La conversion de Fable.React à Feliz était assez facile à faire. Feliz étant compatible avec Fable.React vous pouvez faire la conversion de manière progressive fichier par fichier, composant par composant, etc.

Pour être sûr d'avoir tout converti, j'ai supprimé toutes les instructions open Fable.React et open Fable.React.Props . J'ai également supprimé Fulma en tant que dépendance.

Bien sûr, j'avais oublié certains fichiers, j'ai donc corrigé les erreurs du compilateur et cela fonctionnait.

Vous pouvez voir la demande de fusion hébergeant la conversion ici .


Mon voyage avec Feliz

En découvrant Feliz au début, j'étais sur la défensive car il ne suivait pas la "voie HTML/JSX" et c'est un projet alternatif pour des choses dans lesquelles j'ai investi beaucoup d'efforts.

Puis je me suis souvenu que c'est ainsi que fonctionne l'innovation et que c'est ce qui nous permet de grandir. Par exemple, grâce à ce que nous avons aujourd'hui Elmish.

Une autre chose dont je me suis souvenu est que JSX n'est pas la "vraie API React", en effet l'API React est React.createElement . JSX est un sucre de syntaxe en plus, il en va de même pour Fable.React et Feliz. J'insiste là-dessus car pour moi cela m'a permis d'avancer et d'être curieux à son sujet.

Pour tester Feliz, j'ai décidé d'utiliser un projet de taille moyenne qui est le Fable REPL. Fable REPL n'est pas une application complexe mais elle utilise beaucoup de fonctionnalités de Fable et React.

Une liste non exhaustive de choses que j'ai testé grâce à cette décision :

  • Féliz
  • Feliz Bulma
  • Dans quelle mesure Feliz et Fable.React sont compatibles
  • Comme il est facile de faire la transition de Fable.React à Feliz
  • Utilisation d'un composant fonctionnel avec un appel natif au DOM de Feliz
  • Comment écrire une liaison et une interopérabilité avec un composant personnalisé non trivial écrit en JavaScript à la manière de Feliz
  • La transition de Fulma à Feliz.Bulma pour que je puisse voir ce que pourrait être un équivalent Fulma dans l'écosystème Feliz

Lorsque j'ai commencé la conversion, j'ai été très satisfait de la facilité de formatage du code. Je pense que la convention pour écrire une vue basée sur Feliz pourrait être vraiment facile à suivre. Plus mes applications grandissent, plus je trouve le formatage du code important.

Puis au milieu, j'ai commencé à être ennuyé par quelque chose mais je ne pouvais pas trouver quoi.

J'ai fini de convertir Fable REPL en Feliz et je ne savais pas si j'aimais ou non mon expérience avec.

C'est seulement maintenant, que j'essaie de conclure mon analyse, que je comprends ce qui me retient. Ce n'est pas beaucoup lié à Feliz mais plus à Feliz.Bulma.

Il est important de se rappeler que depuis plus de 2,5 ans, je travaille sur Fulma et je l'améliore. Fulma est l'un de mes plus gros projets. Ecrire du code avec c'est super surtout parce que tout est typé et ça oblige à penser en terme de composants.

Feliz.Bulma adopte une approche différente et est plus jeune que Fulma ; il n'a que 5 mois.

Cela dit, je pense qu'il est maintenant temps de passer à ma conclusion :)

Conclusion

Je pense que le plus grand avantage de Feliz est le style de liste unique. C'est quelque chose que j'ai souvent mentionné, mais pour moi, pouvoir formater facilement mon code et ne pas avoir à penser par où commencer la ligne suivante est un gros plus.

Deuxièmement, l'API de type sécurisé est un incontournable. C'est plus détaillé que l'équivalent CSS mais je pense que c'est une bonne fonctionnalité. Je n'ai pas fait de trucs fantaisistes avec, donc je ne peux pas dire si tout est pris en charge, mais si ce n'est pas le cas, nous pouvons toujours faire un PR pour résoudre ce problème :).

Feliz.Bulma montre un moyen prometteur d'étendre Feliz mais n'est pas encore assez mature pour être un équivalent direct de Fulma. C'est quelque chose que je veux explorer davantage avec @Dzoukr et j'espère que nous pourrons trouver un juste milieu entre la simplicité de Feliz.Bulma et l'exhaustivité mais la verbosité de Fulma.

Il y a 3 ans, le 8 mars 2017 , Tomas, Eugene et moi avons décidé de fusionner Fable.Arch avec Elmish. Cela nous a permis en tant que communauté de construire l'incroyable écosystème que nous avons aujourd'hui.

Je pense que nous vivons actuellement une période similaire avec Feliz et Fable.React. Je pense que Feliz gagnera plus de terrain que Fable.React
mais les deux coexisteront ensemble. En fin de compte, les deux fonctionnent bien ensemble, ce qui signifie que les gens peuvent choisir celui qu'ils préfèrent utiliser.

Merci d'avoir lu mon analyse et je serai ravie d'en discuter avec vous dans la section commentaire :)

awesome blog

Commentaire le plus utile

Salut @MangelMaxime ,

Tout d'abord, je tiens à vous remercier d'avoir pris le temps et les efforts non seulement d'essayer Feliz sur un projet, mais aussi d'écrire une analyse aussi approfondie. Je suis sûr que beaucoup d'utilisateurs de Fable le trouveront très utile et informatif.

J'apprécie vraiment ce genre de retour d'information et d'ouverture et je pense qu'il est crucial pour que cette bibliothèque soit la meilleure possible avec son écosystème :pray:

Je vais parler de Feliz.Bulma dans un instant parce que je pense que c'est un peu spécial dans l'écosystème, d'abord je vais répondre à certains problèmes que vous avez qui peuvent déjà être corrigés sans changements dans la bibliothèque.

Surcharges de onChange

vous devez dire quel type d'événement vous voulez explicitement.

En fait, vous ne le faites pas. Le type doit être déduit de votre type de message comme suit :

prop.onChange (ChangeGistToken >> dispatch)

Selon le type de ChangeGistToken , la surcharge correcte sera déduite. Lorsque le cas DU est ChangeGistToken of string alors la surcharge de string -> unit sera choisie. Mais cela peut aussi être FileSelected of File lorsque le type d'entrée est file auquel cas la surcharge File -> unit sera déduite et ainsi de suite. Vous n'avez pas besoin du sucre de syntaxe de ev.Value de Fable.React

prop.onCheckedChange sera supprimé car onChange gère déjà le cas booléen

Rappels multi-arguments

@Shmew a déjà abordé le problème et c'est en effet le moyen de résoudre ces types de propriétés. Il est crucial de comprendre que lors de l'implémentation des extensions Feliz, vous pouvez effectuer des transformations avant de définir la propriété :

type myExtension = 
    static member inline extenstionProperty value = 
        // transform value here
        let transformedValue = doWeirdStuffWith value
        Interop.mkAttr "extenstionProperty" transformedValue

En suivant cette logique, vous pouvez convertir 'A -> 'B -> unit en Func<'A, 'B, unit> interne :

[<Erase>]
type editor =
    /// <summary>Triggered when the Editor has been mounted</summary>
    static member inline editorDidMount (f : Monaco.Editor.IStandaloneCodeEditor -> Monaco.IExports -> unit) = 
        let transformedFunction = System.Func<Monaco.Editor.IStandaloneCodeEditor, Monaco.IExports, unit>(fun editor exported -> f editor exported)
        Interop.mkAttr "editorDidMount" transformedFunction

Il s'agit d'une version plus explicite que celle fournie par @Shmew (Comme vous le savez avec les fixations, vous devez toujours jouer un peu avec elles pour les obtenir correctement). Un exemple fonctionnel de ceci est ici

Type-Safety dans l'écosystème Feliz

Comme @Shmew mentionné ci-dessus : une extension Feliz appropriée ne vous permet pas d'écrire du code qui se comporte de manière incorrecte et la contrainte des types de propriétés vous permet de l'implémenter. Vous pouvez soit créer des propriétés qui renvoient IReactProperty comme le fait Feliz.Recharts qui permet une compatibilité descendante avec les propriétés existantes dans prop ou vous pouvez implémenter un type plus spécialisé ISpecialiedProperty pour prendre en charge seulement un sous-ensemble des propriétés tout en dupliquant les noms de certaines propriétés à partir de IReactProperty pour assurer la rétrocompatibilité :

type felizExtension = 
    static member inline specializedProp (value: string) : ISpecializedProperty = unbox (prop.text value) 

C'est ce que j'ai fait avec Feliz.AntDesign, par exemple ici où je duplique certaines propriétés pour les rendre disponibles à partir du point d'entrée button (plus tard, je pourrais rendre les propriétés plus spécialisées avec des types stricts mais au début , la compatibilité descendante est l'endroit où vous commencez pour la première fois). Je crois que Feliz.MaterialUI fait des choses similaires pour étendre IReactProperty plutôt que de spécialiser les propriétés. Cela nous amène au cas de Feliz.Bulma.

Le cas de Feliz.Bulma

Avant l'implémentation de Feliz.Bulma, j'avais en tête l'idée qu'il n'était pas nécessaire de créer des extensions Feliz pour les frameworks CSS uniquement. Feliz facilite déjà la composition de plusieurs noms de classes et leur utilisation conditionnelle. Maintenant, avec les yield implicites en F#, c'est encore plus facile. Combinez cela avec TypedCssClasses et vous supprimez le besoin d'implémenter un framework CSS uniquement. Coûts de mise en œuvre et de maintenance nuls. Utilisez simplement la feuille de style dactylographiée et utilisez la très bonne documentation du projet lui-même au lieu d'avoir à créer vous-même un site Web de documentation.

Cependant, après avoir vu l'implémentation de Feliz.Bulma par @Dzoukr, cela avait du sens : vous pourriez simplifier beaucoup le code en introduisant des "points d'entrée" spécifiques aux composants CSS d'un module de Bulma afin que vous obteniez Bulma.button , Bulma.card etc. Ils sont vraiment agréables à utiliser. En ce qui concerne les modificateurs, je pense que l'approche de Roman est facile à suivre et à utiliser mais comme vous l'avez dit, cela peut parfois donner des résultats intéressants. Il y a sûrement plus à améliorer dans ce département et en tout cas, je pense que Roman a fait un excellent travail en suivant la vision de Feliz en créant quelque chose de facile à apprendre et à utiliser.

Gestion des packages de Feliz

Je veux juste souligner une énorme différence entre les Fable.React et les Feliz actuels : le versioning et la gestion des packages. Les versions de Feliz sont toutes ses bibliothèques d'écosystèmes compatibles Femto, ce qui évite aux utilisateurs d'avoir à installer manuellement les packages npm requis ou de savoir avec quelle version ils sont compatibles. Ceci est différent de beaucoup sinon de toutes les bibliothèques dérivées Fable.React et même Fable.React lui-même où ce n'est pas le cas.

Plans futurs

  • Rédaction (encore plus) de documentation sur la façon de créer des extensions de style Feliz et sur ce qui doit être respecté.
  • Construire un analyseur Feliz pour Feliz lui-même mais éventuellement pour son écosystème

Tous les 23 commentaires

Excellente analyse.

Pour moi personnellement, même si le formatage de la double liste est parfois pénible, j'ai l'impression qu'en regardant le code Fable.React, je peux mieux voir la structure du code html sous-jacent. C'est div [.. , pas Html.div [.... (etc). Ce n'est pas grand chose, mais mon cerveau semble avoir une Fable.React plus facile à analyser.

Excellente écriture @MangelMaxime , vous avez fait beaucoup de bons points.

D'après ce que j'ai rassemblé, les principaux points à retenir sont principalement des problèmes liés à l'extension de la bibliothèque et aux problèmes qui surviennent lorsqu'ils ne sont pas exécutés de manière optimale.

Je suis tout à fait d'accord pour dire que l'écriture de bibliothèques pour Feliz demande beaucoup plus de travail au développeur, mais à mon avis, cela en vaut la peine lorsque l'API résultante est si agréable à utiliser.

J'ai écrit un bon nombre d'extensions pour Feliz , je pense donc pouvoir vous donner un aperçu de la façon d'atténuer bon nombre de ces problèmes :

Surcharges

J'utilise Visual Studio et n'ai aucun problème à trouver quelles surcharges sont disponibles pour moi, c'est peut-être quelque chose qui peut être amélioré du côté de l'ionide ?

Nous pourrions simplement faire en sorte que prop.text soit une fonction en ligne unique qui nécessite le membre toString et accepte n'importe quel type ?

Rappels :

À la place de:

[<Erase>]
type editor =
    /// <summary>Triggered when the Editor has been mounted</summary>
    static member inline editorDidMount (f : System.Func<Monaco.Editor.IStandaloneCodeEditor, Monaco.IExports, unit>) = Interop.mkAttr "editorDidMount" f

Écrivez:

[<Erase>]
type editor =
    /// <summary>Triggered when the Editor has been mounted</summary>
    static member inline editorDidMount (f : Monaco.Editor.IStandaloneCodeEditor -> Monaco.IExports -> unit) = Interop.mkAttr "editorDidMount" (Func<_,_,_> f)

Vous pouvez ensuite appeler la propriété comme vous vous en doutez du côté client.

Écriture de code invalide

Je n'ai vu personne d'autre appliquer cela dans la mesure où j'en ai dans certaines de mes bibliothèques, et peut ne pas être entièrement applicable lors de l'extension de Html . La façon d'éviter cela est d'ajouter des types d'interface supplémentaires aux propriétés que vous définissez, dans ma plus grande bibliothèque Feliz.Plotly Feliz Feliz.Plotly Je l'ai configuré de telle manière que vous obtiendrez une erreur de compilateur si vous essayez d'utiliser une propriété qui n'est pas valide. Le seul inconvénient est que c'est un peu de travail pour le développeur de la bibliothèque.


Cliquez pour agrandir

[<AutoOpen;EditorBrowsable(EditorBrowsableState.Never)>]
module Types =
  type IPlotProperty = interface end
  type IModeBarButtonsProperty = interface end
  type IAaxisProperty = interface end
  type IAggregateProperty = interface end
  type IAggregationProperty = interface end
  type IAggregationsProperty = interface end
  type IAngularaxisProperty = interface end
  type IAnimationProperty = interface end
  type IAnnotationProperty = interface end
  type IAnnotationsProperty = interface end
  type IAreaProperty = interface end
  type IAspectratioProperty = interface end
  type IAxisProperty = interface end
  type IBarProperty = interface end
  type IBarpolarProperty = interface end
  type IBaxisProperty = interface end
  type IBorderProperty = interface end
  type IBoxProperty = interface end
  type IButtonProperty = interface end
  type ICameraProperty = interface end
  type ICandlestickProperty = interface end
  type ICapsProperty = interface end
  type ICarpetProperty = interface end
  type ICaxisProperty = interface end
  type ICellsProperty = interface end
  type ICenterProperty = interface end
  type IChoroplethProperty = interface end
  type IChoroplethmapboxProperty = interface end
  type ICircleProperty = interface end
  type IColoraxisProperty = interface end
  type IColorbarProperty = interface end
  type IColorscaleProperty = interface end
  type IColorscalesProperty = interface end
  type IConcentrationscalesProperty = interface end
  type IConeProperty = interface end
  type IConfigProperty = interface end
  type IConnectorProperty = interface end
  type IContourProperty = interface end
  type IContourcarpetProperty = interface end
  type IContoursProperty = interface end
  type ICumulativeProperty = interface end
  type ICurrentvalueProperty = interface end
  type IDecreasingProperty = interface end
  type IDeltaProperty = interface end
  type IDensitymapboxProperty = interface end
  type IDiagonalProperty = interface end
  type IDimensionProperty = interface end
  type IDimensionsProperty = interface end
  type IDomainProperty = interface end
  type IEditsProperty = interface end
  type IErrorXProperty = interface end
  type IErrorYProperty = interface end
  type IErrorZProperty = interface end
  type IEyeProperty = interface end
  type IFillProperty = interface end
  type IFilterProperty = interface end
  type IFontProperty = interface end
  type IFrameProperty = interface end
  type IFramesEntryProperty = interface end
  type IFramesProperty = interface end
  type IFunnelProperty = interface end
  type IFunnelareaProperty = interface end
  type IGaugeProperty = interface end
  type IGeoProperty = interface end
  type IGradientProperty = interface end
  type IGridProperty = interface end
  type IGroupbyProperty = interface end
  type IHeaderProperty = interface end
  type IHeatmapProperty = interface end
  type IHeatmapglProperty = interface end
  type IHistogram2dProperty = interface end
  type IHistogram2dcontourProperty = interface end
  type IHistogramProperty = interface end
  type IHoverlabelProperty = interface end
  type IImageProperty = interface end
  type IImagesProperty = interface end
  type IIncreasingProperty = interface end
  type IIndicatorProperty = interface end
  type IInsidetextfontProperty = interface end
  type IIsosurfaceProperty = interface end
  type ILabelfontProperty = interface end
  type ILataxisProperty = interface end
  type ILayerProperty = interface end
  type ILayersProperty = interface end
  type ILayoutProperty = interface end
  type ILeafProperty = interface end
  type ILegendProperty = interface end
  type ILightingProperty = interface end
  type ILightpositionProperty = interface end
  type ILineProperty = interface end
  type ILinkProperty = interface end
  type ILonaxisProperty = interface end
  type IMapboxProperty = interface end
  type IMarginProperty = interface end
  type IMarkerProperty = interface end
  type IMeanlineProperty = interface end
  type IMesh3dProperty = interface end
  type IModebarProperty = interface end
  type INodeProperty = interface end
  type INumberProperty = interface end
  type IOhlcProperty = interface end
  type IOutsidetextfontProperty = interface end
  type IPadProperty = interface end
  type IParcatsProperty = interface end
  type IParcoordsProperty = interface end
  type IPathbarProperty = interface end
  type IPieProperty = interface end
  type IPointcloudProperty = interface end
  type IPolarProperty = interface end
  type IProjectProperty = interface end
  type IProjectionProperty = interface end
  type IRadialaxisProperty = interface end
  type IRangebreakProperty = interface end
  type IRangebreaksProperty = interface end
  type IRangefontProperty = interface end
  type IRangeselectorProperty = interface end
  type IRangesliderProperty = interface end
  type IRotationProperty = interface end
  type ISankeyProperty = interface end
  type IScatter3dProperty = interface end
  type IScatterProperty = interface end
  type IScattercarpetProperty = interface end
  type IScattergeoProperty = interface end
  type IScatterglProperty = interface end
  type IScattermapboxProperty = interface end
  type IScatterpolarProperty = interface end
  type IScatterpolarglProperty = interface end
  type IScatterternaryProperty = interface end
  type ISceneProperty = interface end
  type ISelectedProperty = interface end
  type IShapeProperty = interface end
  type IShapesProperty = interface end
  type ISlicesProperty = interface end
  type ISliderProperty = interface end
  type ISlidersProperty = interface end
  type ISortProperty = interface end
  type ISpaceframeProperty = interface end
  type ISplomProperty = interface end
  type IStartsProperty = interface end
  type IStepProperty = interface end
  type IStepsProperty = interface end
  type IStreamProperty = interface end
  type IStreamtubeProperty = interface end
  type IStyleProperty = interface end
  type IStylesProperty = interface end
  type ISunburstProperty = interface end
  type ISurfaceProperty = interface end
  type ISymbolProperty = interface end
  type ITableProperty = interface end
  type ITernaryProperty = interface end
  type ITextfontProperty = interface end
  type IThresholdProperty = interface end
  type ITickfontProperty = interface end
  type ITickformatstopProperty = interface end
  type ITickformatstopsProperty = interface end
  type ITilingProperty = interface end
  type ITitleProperty = interface end
  type ITotalsProperty = interface end
  type ITracesProperty = interface end
  type ITransformsProperty = interface end
  type ITransitionProperty = interface end
  type ITreemapProperty = interface end
  type IUniformtextProperty = interface end
  type IUnselectedProperty = interface end
  type IUpProperty = interface end
  type IUpdatemenuProperty = interface end
  type IUpdatemenusProperty = interface end
  type IViolinProperty = interface end
  type IVolumeProperty = interface end
  type IWaterfallProperty = interface end
  type IXProperty = interface end
  type IXaxisProperty = interface end
  type IXbinsProperty = interface end
  type IYProperty = interface end
  type IYaxisProperty = interface end
  type IYbinsProperty = interface end
  type IZProperty = interface end
  type IZaxisProperty = interface end
  type IButtonsProperty = inherit IModeBarButtonsProperty
  type IMeasureProperty = interface end
  type ITemplateProperty = interface end

Dans la même veine, vous pouvez également implémenter des éléments dans le code de la bibliothèque pour combiner "automatiquement" les propriétés si vous le souhaitez. Je ne sais pas comment cela fonctionne lorsque vous utilisez des éléments en dehors de votre propre bibliothèque. L'astuce consiste à faire de ces éléments un type différent de celui de IReactProperty . Par exemple, dans Feliz.Plotly je fais beaucoup de choses où, en fonction des configurations données, je vais modifier/collecter/changer les entrées des utilisateurs pour les traduire correctement en ce à quoi le code réel doit ressembler. C'est vraiment sympa à certains égards pour enlever des choses qui sont toujours présentes.

Conclusion

Je pense qu'une grande partie de la douleur que vous avez eue pourrait être résolue avec une meilleure documentation sur la façon de naviguer dans l'écriture de vos propres bibliothèques à la manière "Feliz". Peut-être que @Zaid-Ajaj et moi pouvons travailler là-dessus à l'avenir.

Merci @l3m

Pour moi personnellement, même si le formatage de la double liste est parfois pénible, j'ai l'impression qu'en regardant le code Fable.React, je peux mieux voir la structure du code html sous-jacent. C'est div [.. , pas Html.div [.... (etc). Ce n'est pas grand chose, mais mon cerveau semble avoir une Fable.React plus facile à analyser.

En effet, changer la façon de lire le code n'est pas facile et c'est pourquoi j'ai mentionné que nous ne devrions pas le voir comme HTML dans mon cas, cela m'a aidé à faire le changement.

Je suppose qu'avec F# 5 et la possibilité d'"ouvrir des classes statiques", Feliz pourra prendre en charge à la fois la syntaxe Html.div et div selon que vous ouvrez ou non la classe Html.

Merci @Shmew pour le commentaire et en effet j'avais oublié qu'il existe plusieurs types de "bibliothèques" pour Feliz.

A propos du "Writing invalid code" oui mais dans certaines bibliothèques, nous voulons "étendre" le type IReactProperty afin d'éviter la situation Fulma où vous devez passer des propriétés standard via un assistant spécial comme Props . La raison étant que les "composants" Bulma ne sont qu'une spécialisation sur les éléments DOM.

Ce ne sont que du code qui injecte une classe par défaut dans l'élément HTML.

Bulma.button génère <div class="button">

Dans le cas d'une bibliothèque qui fonctionne sur des composants "réels" (désolé je ne sais pas comment la nommer différemment) votre solution est en effet la bonne. Par exemple, c'est l'approche adoptée par Fable.ReactLeaflet où je

À propos de votre solution pour les "Rappels", cela n'a pas fonctionné pour moi. Mais la fonction passée par Fable avait des arguments curry et à moins que je ne veuille écrire quelque chose comme myFunction(arg1)(arg2) alors je devais forcer la version non curry via System.Func .

Si vous savez comment le faire fonctionner, je serai heureux de voir un PR envoyé pour le corriger dans https://github.com/fable-compiler/repl/pull/108/files

Salut @MangelMaxime ,

Tout d'abord, je tiens à vous remercier d'avoir pris le temps et les efforts non seulement d'essayer Feliz sur un projet, mais aussi d'écrire une analyse aussi approfondie. Je suis sûr que beaucoup d'utilisateurs de Fable le trouveront très utile et informatif.

J'apprécie vraiment ce genre de retour d'information et d'ouverture et je pense qu'il est crucial pour que cette bibliothèque soit la meilleure possible avec son écosystème :pray:

Je vais parler de Feliz.Bulma dans un instant parce que je pense que c'est un peu spécial dans l'écosystème, d'abord je vais répondre à certains problèmes que vous avez qui peuvent déjà être corrigés sans changements dans la bibliothèque.

Surcharges de onChange

vous devez dire quel type d'événement vous voulez explicitement.

En fait, vous ne le faites pas. Le type doit être déduit de votre type de message comme suit :

prop.onChange (ChangeGistToken >> dispatch)

Selon le type de ChangeGistToken , la surcharge correcte sera déduite. Lorsque le cas DU est ChangeGistToken of string alors la surcharge de string -> unit sera choisie. Mais cela peut aussi être FileSelected of File lorsque le type d'entrée est file auquel cas la surcharge File -> unit sera déduite et ainsi de suite. Vous n'avez pas besoin du sucre de syntaxe de ev.Value de Fable.React

prop.onCheckedChange sera supprimé car onChange gère déjà le cas booléen

Rappels multi-arguments

@Shmew a déjà abordé le problème et c'est en effet le moyen de résoudre ces types de propriétés. Il est crucial de comprendre que lors de l'implémentation des extensions Feliz, vous pouvez effectuer des transformations avant de définir la propriété :

type myExtension = 
    static member inline extenstionProperty value = 
        // transform value here
        let transformedValue = doWeirdStuffWith value
        Interop.mkAttr "extenstionProperty" transformedValue

En suivant cette logique, vous pouvez convertir 'A -> 'B -> unit en Func<'A, 'B, unit> interne :

[<Erase>]
type editor =
    /// <summary>Triggered when the Editor has been mounted</summary>
    static member inline editorDidMount (f : Monaco.Editor.IStandaloneCodeEditor -> Monaco.IExports -> unit) = 
        let transformedFunction = System.Func<Monaco.Editor.IStandaloneCodeEditor, Monaco.IExports, unit>(fun editor exported -> f editor exported)
        Interop.mkAttr "editorDidMount" transformedFunction

Il s'agit d'une version plus explicite que celle fournie par @Shmew (Comme vous le savez avec les fixations, vous devez toujours jouer un peu avec elles pour les obtenir correctement). Un exemple fonctionnel de ceci est ici

Type-Safety dans l'écosystème Feliz

Comme @Shmew mentionné ci-dessus : une extension Feliz appropriée ne vous permet pas d'écrire du code qui se comporte de manière incorrecte et la contrainte des types de propriétés vous permet de l'implémenter. Vous pouvez soit créer des propriétés qui renvoient IReactProperty comme le fait Feliz.Recharts qui permet une compatibilité descendante avec les propriétés existantes dans prop ou vous pouvez implémenter un type plus spécialisé ISpecialiedProperty pour prendre en charge seulement un sous-ensemble des propriétés tout en dupliquant les noms de certaines propriétés à partir de IReactProperty pour assurer la rétrocompatibilité :

type felizExtension = 
    static member inline specializedProp (value: string) : ISpecializedProperty = unbox (prop.text value) 

C'est ce que j'ai fait avec Feliz.AntDesign, par exemple ici où je duplique certaines propriétés pour les rendre disponibles à partir du point d'entrée button (plus tard, je pourrais rendre les propriétés plus spécialisées avec des types stricts mais au début , la compatibilité descendante est l'endroit où vous commencez pour la première fois). Je crois que Feliz.MaterialUI fait des choses similaires pour étendre IReactProperty plutôt que de spécialiser les propriétés. Cela nous amène au cas de Feliz.Bulma.

Le cas de Feliz.Bulma

Avant l'implémentation de Feliz.Bulma, j'avais en tête l'idée qu'il n'était pas nécessaire de créer des extensions Feliz pour les frameworks CSS uniquement. Feliz facilite déjà la composition de plusieurs noms de classes et leur utilisation conditionnelle. Maintenant, avec les yield implicites en F#, c'est encore plus facile. Combinez cela avec TypedCssClasses et vous supprimez le besoin d'implémenter un framework CSS uniquement. Coûts de mise en œuvre et de maintenance nuls. Utilisez simplement la feuille de style dactylographiée et utilisez la très bonne documentation du projet lui-même au lieu d'avoir à créer vous-même un site Web de documentation.

Cependant, après avoir vu l'implémentation de Feliz.Bulma par @Dzoukr, cela avait du sens : vous pourriez simplifier beaucoup le code en introduisant des "points d'entrée" spécifiques aux composants CSS d'un module de Bulma afin que vous obteniez Bulma.button , Bulma.card etc. Ils sont vraiment agréables à utiliser. En ce qui concerne les modificateurs, je pense que l'approche de Roman est facile à suivre et à utiliser mais comme vous l'avez dit, cela peut parfois donner des résultats intéressants. Il y a sûrement plus à améliorer dans ce département et en tout cas, je pense que Roman a fait un excellent travail en suivant la vision de Feliz en créant quelque chose de facile à apprendre et à utiliser.

Gestion des packages de Feliz

Je veux juste souligner une énorme différence entre les Fable.React et les Feliz actuels : le versioning et la gestion des packages. Les versions de Feliz sont toutes ses bibliothèques d'écosystèmes compatibles Femto, ce qui évite aux utilisateurs d'avoir à installer manuellement les packages npm requis ou de savoir avec quelle version ils sont compatibles. Ceci est différent de beaucoup sinon de toutes les bibliothèques dérivées Fable.React et même Fable.React lui-même où ce n'est pas le cas.

Plans futurs

  • Rédaction (encore plus) de documentation sur la façon de créer des extensions de style Feliz et sur ce qui doit être respecté.
  • Construire un analyseur Feliz pour Feliz lui-même mais éventuellement pour son écosystème

En fait, vous ne le faites pas. Le type doit être déduit de votre type de message comme suit :

prop.onChange (ChangeGistToken >> dispatch)

En effet, je n'avais pas pensé à cet usage.

Rappels multi-arguments

@Shmew a déjà abordé le problème et c'est en effet le moyen de résoudre ces types de propriétés.

Oh désolé, je n'ai pas vu la fin de l'extrait fourni par @Shmew. Je viens de voir la déclaration de signature et j'ai dit que non ne fonctionnait pas.

Vous avez tous les deux raison, nous pouvons faire la transformation avant de passer la valeur à la propriété.

Le cas de Feliz.Bulma

Oui, TypedCssClasses est correct mais comparé à Fulma ou Feliz.Bulma, il ne peut pas fournir le même niveau de fonctionnalités. Car tout n'est pas contenu dans le fichier CSS.

Une autre chose est qu'en mode dev, nous n'avons pas toujours le fichier généré sur le disque et je ne sais pas si TypedCssClasses prend en charge ce scénario.

Je pense que Roman a fait un excellent travail en suivant la vision de Feliz en créant quelque chose de facile à apprendre et à utiliser.

Je suis d'accord, je pense juste que nous devons ajouter un peu du travail effectué dans Fulma pour le rendre similaire. Comme je l'ai dit, nous devons trouver le juste milieu entre les deux.

Les fonctionnalités manquantes que j'ai à l'esprit en ce moment permettent de contrôler l'élément de sortie et facilitent l'extension de l'ensemble des couleurs prises en charge. Je ne pense pas que cela ajoutera beaucoup de complexité.

@Zaid-Ajaj merci pour les commentaires et surtout pour m'avoir indiqué la bonne direction sur la façon d'utiliser le potentiel de Feliz.
J'admets que je n'ai pas vraiment lu la documentation de Feliz (c'est mauvais je sais) alors peut-être qu'ils y sont documentés. Sinon, je suppose que nous savons quelques autres choses à ajouter :)

Merci @MangelMaxime - c'est une excellente comparaison. Vous n'avez mentionné Fantomas nulle part pour le formatage automatique du code - avez-vous eu l'occasion de travailler avec Fantomas avec Fable.React ou Feliz ?
Dans l'un de mes projets Fable où j'utilise Fantomas, j'ai activé le formatage automatique lors de l'enregistrement de fichiers dans Ionide et je dois dire que c'est vraiment un booster de productivité - pas besoin de s'inquiéter du formatage manuel du tout !
Je pense vraiment que Fantomas prend son élan maintenant et aimerait voir son adoption plus large, également en conjonction avec Fable et React / Feliz.

@theimowski Je n'ai pas utilisé Fantomas depuis quelques temps.

Principalement parce que cela perturbait mon code et le rendait vraiment difficile à lire. D'après mon expérience, je ne jouais pas bien avec Fable.React et Feliz.

Je n'aime pas non plus la valeur par défaut de Fantomas. Je devrai la vérifier à nouveau à l'avenir lorsque je saurai comment je veux que mon code soit indenté. Mais, d'après ce que j'ai regardé dans le référentiel, je ne pense pas qu'il répondra à mes besoins, principalement à cause du formatage du code par Fantomas en fonction de la taille du contexte.

Par exemple, si j'écris quelque chose comme ça :

type Test =
    {
        Prop1 : string
    }

Je veux qu'il reste comme ça et ne devienne pas type Test = { Prop1 : string } juste parce qu'il peut tenir sur une seule ligne.

Peut-être qu'un disque n'est pas un bon exemple, mais j'espère que vous comprenez ce que je veux dire :)

@theimowski c'est super à entendre, j'utilise des fantomas sur mes projets non-Fable. Je vais devoir lui donner un autre coup et voir comment il le gère.

@MangelMaxime merci pour votre travail. D'une manière ou d'une autre, votre projet Fulma m'a attaqué dans le monde de la fable. Et j'apprécie vraiment le voyage.

J'aime aussi la liste unique mais j'aime aussi les DU que les remplacements des membres des classes. Parfois, je me demande si DU prend en charge la dérogation, alors ce serait super.
Parce que j'utilise déjà beaucoup Fable.React mais que je veux toujours une liste unique, j'ai donc créé un opérateur> et je peux l'utiliser comme:

let myView =
    div </> [
        Classes [ ""; ""; "" ]
        Children [
              ...
        ]
    ]

L'opérateur est assez simple et tire également parti de DU, vous pouvez vérifier ma mise en œuvre approximative ici : https://github.com/albertwoo/Fun.LightForm/blob/master/src/Fun.LightForm.Fable/Fable.React. Extra.fs

@Zaid-Ajaj Merci aussi pour votre excellent travail, j'ai également commencé à convertir certains de mes projets en Feliz et Feliz.Material.

Merci!

Salut @MangelMaxime ,
tout d'abord merci ! Franchement, je ne m'attendais pas à ce que quelqu'un ait un regard aussi approfondi sur la bibliothèque que j'ai créée principalement pour mes propres projets de dogfood et publiée comme effet secondaire. C'est drôle comme la pensée initiale de "hé, comment @Zaid-Ajaj a conçu Feliz est génial - faisons la même chose pour Bulma" s'est enflée.

J'ai déjà créé un nouveau problème dans le dépôt Feliz.Bulma et j'adore discuter de la façon d'améliorer encore la v2. Il semble que nous ayons un accord sur la plupart des problèmes, donc cela ne devrait pas poser de problème de démarrer la branche version2 . Examiner également le plan de Zaid pour la prochaine version majeure pourrait être utile pour que l'API entre les dernières versions majeures soit 100% compatible.

Quoi qu'il en soit, merci encore - excellente analyse!

Au risque est la réouverture de vieilles discussions (désolé!) Je voulais juste ajouter mes deux cents sur le débat d'indentation - c'est (plus ou moins) le style que nous suivons ce qui ne permet de bien, cohérente et suivant indentation pour la Fable.React style.

Le seul endroit auquel cela ne s'applique pas tout à fait, cependant, est pour le style non trivial. Cependant, dans notre expérience, cela ne représente qu'une minorité d'éléments (peut-être 10 à 20 %).

div [ ] [
    Hero.hero [ Hero.IsFullHeight ] [
        Hero.body [ ] [
            Container.container [ ] [
                img [ Src "img/fable-ionide.png"
                      Style [ Display DisplayOptions.Block
                              Width "auto"
                              Margin "auto" ]
                    ]
                br [ ]
                Heading.h3 [ Heading.Modifiers [ Modifier.TextAlignment (Screen.All, TextAlignment.Centered) ] ] [
                    str "Fable REPL"
                ]
                Heading.p [ Heading.IsSubtitle
                            Heading.Is5
                            Heading.Modifiers [ Modifier.TextAlignment (Screen.All, TextAlignment.Centered) ] ] [
                    str "is only available on desktop"
                ]
            ]
        ]
    ]
]

De plus, si vous utilisez VS Code et quelque chose comme Rainbow Brackets, cela peut vraiment aider à éviter la surcharge de brackets.

Une autre chose à considérer (peut-être que j'ai raté cela ci-dessus) est que GiraffeViewEngine utilise le style Fable.React, c'est-à-dire l'élément attr-list children-list, donc juste quelque chose à penser en termes de transition entre les deux.

@Zaid-Ajaj fait un bon point sur Femto; y a-t-il une raison pour laquelle cela ne pourrait pas être implémenté par les composants de style Fable.React, ou y a-t-il quelque chose d'inhérent au style Feliz auquel Femto est couplé?

y a-t-il une raison pour laquelle cela ne pourrait pas être implémenté par les composants de style Fable.React, ou y a-t-il quelque chose d'inhérent au style Feliz auquel Femto est couplé?

@isaacabraham

Les bibliothèques dérivées de Fable.React pourraient (et devraient) ajouter des métadonnées Femto, mais le Fable.React lui-même ne le pourrait pas car il contient un mélange incompatible de react , react-dom et react-native . @MangelMaxime a commencé à travailler sur le fractionnement des packages, mais c'était beaucoup plus de travail que prévu et introduira probablement des changements de rupture.

Cela signifie pour les bibliothèques dérivées de Fable.React qu'elles doivent inclure non seulement les métadonnées npm de leurs dépendances, mais également les versions compatibles de react et react-dom car Fable.React ne Ne faites pas ça pour eux (contrairement à Feliz qui commence par react et react-dom pour que les extensions Feliz n'ajoutent que ce dont elles ont besoin). Malheureusement, je n'ai encore vu aucune de ces bibliothèques prendre l'initiative d'améliorer la situation, même si la solution existe depuis un certain temps déjà.

Concernant le formatage : Fantomas n'est pas encore capable de formater les DSL de manière sensée et nécessite un certain travail pour rendre le formatage de liste configurable ou avoir une sorte de drapeau "Feliz-mode" pour formater le React DSL en utilisant de bons paramètres par défaut (que nous devons venir avec)

@Zaid-Ajaj Le problème avec les dépendances React npm est également plus complexe car il devrait être possible d'utiliser preact au lieu de réagir, sans casser Femto.

@l3m En effet, mais dans ce cas, nous pourrions faire un ajout à Femto pour prendre en charge ce cas. Cela me rappelle quelque chose alors peut-être que nous avons un problème à ce sujet dans le dépôt Femto (désolé, je n'ai pas le temps de vérifier tout de suite ^^)

@l3m Preact peut être utilisé à la place de React quelles que soient les métadonnées Femto npm, car vous n'avez besoin de configurer les alias d'importation de module conformément aux directives de Basculer vers Preact que lorsque vous avez une application React existante.

Construire une pure liaison Preact (code 100% non-React) n'est pas l'objectif de Feliz et devrait probablement être implémenté en tant que bibliothèque autonome (Feliz.Preact qui ne dépend pas de Feliz) à partir de laquelle les liaisons Preact uniquement seront étendues et utilisé au cas où vous voudriez rester à l'écart de preact/compat

@Zaid-Ajaj J'utilise preact en ce moment dans mon application Elmish et cela fonctionne bien. Je me méfiais simplement de la modification par Femto de packages.json s'il voyait un nuget avec les deps React et je voulais le souligner. Si ce n'est pas un problème, tant mieux.

De plus, preact/compat n'est plus nécessaire avec le dernier preact, il est maintenant dans le noyau.

Au risque de rouvrir de vieilles discussions (désolé !) Je voulais juste ajouter mes deux cents sur le débat sur l'indentation - c'est (plus ou moins) le style que nous suivons qui _permet_ une identification et un suivi agréables et cohérents pour la Fable.React style.

J'indente le code d'une manière très similaire à celle d'Isaac, et c'est assez lisible.
Je sais qu'il est difficile de comparer avec une seule liste de façons de faire les choses, mais l'indentation du message d'origine pourrait refléter quelque chose de similaire à son code pour mieux comparer.

Je suppose qu'avec F# 5 et la possibilité d'"ouvrir des classes statiques", Feliz pourra prendre en charge à la fois la syntaxe Html.div et div selon que vous ouvrez ou non la classe Html.

Ce serait super cool. Serait-il possible de rendre la classe HTML statique afin qu'elle puisse être ouverte ?

@fc1943s Html , prop et style sont toutes des classes statiques. Cependant, je ne recommande pas de les ouvrir lorsque la fonctionnalité devient disponible dans F# 5, car l'essentiel est d'avoir un regroupement et un point d'entrée de découverte des fonctions disponibles au sein de cette classe statique.

Comment l'ouverture de la classe statique entraverait-elle la découverte ? Vous pouvez toujours taper Feliz.Html.

@MaxWilsonMS Cela nécessitait des étapes supplémentaires pour sortir le code "final".

Et vous écririez Feliz.Html.div puis effaceriez la partie Feliz.Html. qui semble étrange.

Ce qui est bien, c'est que les gens peuvent choisir d'ouvrir la classe statique ou non en fonction de leurs préférences. :)

Je ne sais pas à quel point cela sera utile car je me fais l'écho de ce qui a été dit ci-dessus, mais des expériences en tant que débutant complet :

  • Je préfère l'approche Feliz pour l'indentation. J'ai essayé des approches similaires à celles suggérées par Isaac, mais parce que les retraits ne sont pas tous des 2s/4, "je passe parfois du temps à y penser". Avec Feliz, je n'y pense pas du tout.

  • Si je créais un élément personnalisé, je le ferais à la manière Fable.React pour le moment. J'ai jeté un coup d'œil à la construction de choses "à la manière de Feliz" et je me suis enfui instantanément (je reviendrai à un moment donné!).

  • Je trouverais vraiment utile que la documentation priorise plus régulièrement l'aspect "voici comment créer une propriété personnalisée". Je pense qu'il est juste d'accepter qu'avec seulement quelques auteurs, ces bibliothèques vont souvent avoir du mal à offrir une surface d'API pour tout ce qui est possible en CSS (par exemple), donc si un débutant se bloque alors qu'il ne peut pas définir "fontSize" dans "em", ou ne peuvent pas trouver "grid-template-rows", ou n'ont aucune idée qu'ils sont censés fournir un argument "Screen.All" - ce serait bien s'ils savaient comment le contourner.

  • Je pense que certaines améliorations de la "qualité de vie" pourraient être apportées et commencer à réduire certains problèmes potentiels... par exemple, lorsque je regarde de longues listes verticales d'accessoires de style dans Feliz, je commence à me demander s'il pourrait y avoir un style.paddingHorizontal et style.paddingVertical . Je me rends compte que je pourrais créer mes propres aides, et ce n'est pas l'endroit pour faire des demandes de fonctionnalités cependant, et qu'il peut y avoir un désir d'éviter d'introduire des choses qui ne sont pas standard :)

  • J'aime l'idée d'ouvrir la classe statique Html dans le nouveau F#. De manière critique - mes collègues seraient en mesure de comprendre cela lorsqu'ils regarderaient par-dessus mon épaule - et je pense qu'il est important d'impliquer davantage de personnes. Pour jouer à l'avocat du diable : div ne prend pas longtemps à taper et si j'ai besoin de le découvrir, c'est le moindre des défis que je vais relever en utilisant une configuration qui fait de la magie tout en me cacher Javascript, React, CSS etc. etc. En général, cependant, la découvrabilité de l'API est excellente.

  • Particulièrement pour les transitions / animations sur les styles, la méthode Feliz a tellement de sens - vraiment agréable à travailler.

  • De manière plus générale et probablement tangentielle : le défi que j'ai constamment relevé consiste à comprendre où se termine le monde elmish et où commence le monde React. Ce n'est pas vraiment une préoccupation pour l'une ou l'autre bibliothèque - mais comme je n'ai la moindre idée de quoi que ce soit de React, le fait que je puisse obtenir un composant de fonction de Fable.React ou Feliz me confond. Bien sûr, apprendre ceci est ma responsabilité - mais j'ai pensé que cela valait la peine de le mentionner. Je mélange actuellement les approches Elmish et React et je pense que cela entraîne un nouveau rendu de tous les composants lors de la mise à jour - mais je ne connais pas la "bonne" façon de faire les choses pour éviter cela, car des exemples d'applications entièrement construites comme celle-ci sont difficile à trouver. Parfois, j'ai l'impression que je devrais d'abord apprendre à créer des applications React de qualité production en Javascript, puis passer à Fable/Elmish - mais cela semble être une barrière assez importante à l'entrée. Avis de non-responsabilité : je n'ai pas lu la documentation de Feliz en détail, et c'est vraiment bien - donc c'est probablement sur moi !

Merci à tous pour le partage de ces bibliothèques !

Merci pour votre contribution @drk-mtr !

Si je créais un élément personnalisé, je le ferais à la manière Fable.React pour le moment. J'ai jeté un coup d'œil à la construction de choses "à la manière de Feliz" et je me suis enfui instantanément (je reviendrai à un moment donné!).

Oui, cela peut sembler assez intimidant au début, mais c'est assez simple une fois que vous vous y êtes habitué. Cela étant dit, il s'agit d'un coût initial assez élevé pour le développeur lors de l'écriture de bibliothèques. Je trouve que c'est un compromis assez acceptable puisque la grande majorité des utilisateurs ne vont pas écrire des bibliothèques. Heureusement, le travail lui-même est assez simple, et un copier-coller peut accélérer une grande partie du travail (ou comme certains d'entre nous, écrire des générateurs pour créer le code pour nous).

Je trouverais vraiment utile que la documentation priorise plus régulièrement l'aspect "voici comment créer une propriété personnalisée". Je pense qu'il est juste d'accepter qu'avec seulement quelques auteurs, ces bibliothèques vont souvent avoir du mal à offrir une surface d'API pour tout ce qui est possible en CSS (par exemple), donc si un débutant se bloque alors qu'il ne peut pas définir "fontSize" dans "em", ou ne peuvent pas trouver "grid-template-rows", ou n'ont aucune idée qu'ils sont censés fournir un argument "Screen.All" - ce serait bien s'ils savaient comment le contourner.

Juste au cas où vous ne le sauriez pas (je ne saurais pas tout à fait dire à partir de ce contexte):

Il existe une méthode custom sur les types prop et style .

Tout ce qui accepte ICssUnit peut être défini comme : style.fontSize (length.em 1)

La mise à jour de la doc devrait être assez simple.

Je pense que certaines améliorations de la "qualité de vie" pourraient être apportées et commencer à réduire certains problèmes potentiels... par exemple, lorsque je regarde de longues listes verticales d'accessoires de style dans Feliz, je commence à me demander s'il pourrait y avoir un style.paddingHorizontal et style.paddingVertical. Je me rends compte que je pourrais créer mes propres aides, et ce n'est pas l'endroit pour faire des demandes de fonctionnalités cependant, et qu'il peut y avoir un désir d'éviter d'introduire des choses qui ne sont pas standard :)

Tout ce qui aide à rendre l'expérience utilisateur plus facile est définitivement le bienvenu dans mon livre. J'aimerais voir un problème/pr pour l'une de ces choses que vous avez remarquées !

De manière plus générale et probablement tangentielle : le défi que j'ai constamment relevé consiste à comprendre où se termine le monde elmish et où commence le monde React. Ce n'est pas vraiment une préoccupation pour l'une ou l'autre bibliothèque - mais comme je n'ai la moindre idée de quoi que ce soit de React, le fait que je puisse obtenir un composant de fonction de Fable.React ou Feliz me confond. Bien sûr, apprendre ceci est ma responsabilité - mais j'ai pensé que cela valait la peine de le mentionner. Je mélange actuellement les approches Elmish et React et je pense que cela entraîne un nouveau rendu de tous les composants lors de la mise à jour - mais je ne connais pas la "bonne" façon de faire les choses pour éviter cela, car des exemples d'applications entièrement construites comme celle-ci sont difficile à trouver. Parfois, j'ai l'impression que je devrais d'abord apprendre à créer des applications React de qualité production en Javascript, puis passer à Fable/Elmish - mais cela semble être une barrière assez importante à l'entrée. Avis de non-responsabilité : je n'ai pas lu la documentation de Feliz en détail, et c'est vraiment bien - donc c'est probablement sur moi !

Mon expérience dans le monde de Fable (avant Feliz) était que j'écrivais tout comme des fonctions normales pour la plupart (comme un composant pour chaque .fs). Le résultat a été que j'ai perdu beaucoup des avantages offerts par le système React. Depuis que j'ai commencé avec Feliz, j'ai dû vraiment me plonger dans le fonctionnement de React (une bonne chose imo). Depuis que je m'y suis habitué, je n'y pense même plus vraiment consciemment. Chaque fonction que j'écris et qui effectue le rendu est un composant de fonction.

Si vous voulez vraiment apprendre à écrire React de manière idiomatique, je pense que c'est aussi simple que de vous restreindre à ne pas utiliser Elmish et à n'utiliser que des composants/hooks de fonction dans votre application de démonstration. Vous pouvez assez facilement suivre tout ce qui est écrit en JS car l'API est presque exactement la même.

Avoir une application open source de qualité écrite en Feliz est certainement quelque chose qui serait bénéfique, j'ai créé un problème pour cela il y a quelque temps #67. La chose la plus proche en ce moment est probablement l'application docs elle-même, ou Electron Demo de @cmeeren .

Cette page vous a été utile?
0 / 5 - 0 notes