-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace the term attr by prop #2
Comments
It is not necessarily what I meant to use, in short: I chose this syntax because it is consistent and easy to work with from F# perspective and it is something that I will use in my code.
Actually in React, children are not props, they are passed as the second argument of the <div id="main">
<h1>Content</h1>
</div>
// translates to
import { createElement } from 'react'
createElement("div", { id: "main" }, [ createElement("h1", { }, "Content") ])
I am not against |
Children can be passed as the third argument to The resolution strategy is
Here is an live example I add to make my own binding for type TestProps =
{
children : ReactElement
}
type IReactApi =
abstract createElement: comp: obj * props: obj -> ReactElement
[<Global("React")>]
let reactApi : IReactApi = jsNative
div [] [
reactApi.createElement("a", {
children =
div [ ]
[ str "Hello" ]
})
] |> mountById "elmish-app" This means that we can avoid at least one iteration over the property list here to avoid finding the And we can even go further: Here is an implementation of the current version of Feliz (compatible with the REPL):// More info about Fulma at https://mangelmaxime.github.io/Fulma/
module Fulma.Container
open Fable.Core
open Fable.Core.JsInterop
open Fable.React
open Fable.React.Props
open Fulma
type IReactApi =
abstract createElement: comp: obj * props: obj -> ReactElement
abstract createElement: comp: obj * props: obj * [<ParamList>] children: ReactElement seq -> ReactElement
[<Global("React")>]
let reactApi : IReactApi = jsNative
type IReactAttribute = interface end
module Interop =
let reactElement (name: string) (props: 'a) (children: 'b) : ReactElement = reactApi.createElement(name, props, children)
let inline mkAttr (key: string) (value: obj) : IReactAttribute = unbox (key, value)
[<Emit("$2[$0] = $1")>]
let setProperty (key: string) (value: obj) (objectLiteral: obj) = jsNative
let createObj (properties: obj list) =
let propsObj = obj()
let propsList = unbox<(string * obj) list> properties
for (key, value) in propsList do
if key <> "children" then
setProperty key value propsObj
propsObj
let extract (pred: 't -> bool) (xs: 't list) : 't option * 't list =
let rec extract' pred xs elem acc =
match xs, elem with
| [ ], Some _ -> elem, acc
| [ ], None -> None, acc
| values, Some _ -> elem, List.append values acc
| x :: rest, None when pred x -> Some x, List.append acc rest
| x :: rest, None -> extract' pred rest None (List.append [x] acc)
extract' pred xs None [ ]
let createElement name (properties: IReactAttribute list) : ReactElement =
let props = unbox<(string * obj) list> properties
match extract (fun (key, value) -> key = "children") props with
| None, rest ->
let restProps = createObj (unbox rest)
reactElement name restProps null
| Some (key, value), rest ->
let restProps = createObj (unbox rest)
reactElement name restProps (unbox value)
// [<Erase>]
type attr() =
static member inline className(value: string) = Interop.mkAttr "className" value
/// Alias for inline `attr.children [ Html.content value ]`
static member inline content(value: string) = attr.children [ unbox value ]
/// Alias for inline `attr.children [ Html.content value ]`
static member inline content(value: int) = attr.children [ unbox value ]
/// Alias for inline `attr.children [ Html.content value ]`
static member inline content(value: ReactElement) = attr.children [ value ]
static member children (elems: ReactElement list) = Interop.mkAttr "children" (Array.ofList elems)
[<Erase>]
type Html =
/// The `<div>` tag defines a division or a section in an HTML document
static member inline div xs = Interop.createElement "div" xs
let view =
Html.div [
attr.className "button"
attr.content "Click me"
]
mountById "elmish-app" view This generates 109 lines of JavaScript Generated JavaScriptimport { ofList } from "fable-library/Array.js";
import { ofArray, append } from "fable-library/List.js";
import { some } from "fable-library/Option.js";
import { type } from "fable-library/Reflection.js";
import { iterate } from "fable-library/Seq.js";
import { declare, List } from "fable-library/Types.js";
import { Helpers$$$mountById as Helpers$0024$0024$0024mountById } from "fable-repl-lib/src/Fable.React.Helpers";
export function Interop$$$reactElement(name, props, children) {
return React.createElement(name, props, ...children);
}
export function Interop$$$createObj(properties) {
const propsObj = {};
const propsList = properties;
iterate(function (forLoopVar) {
if (forLoopVar[0] !== "children") {
propsObj[forLoopVar[0]] = forLoopVar[1];
}
}, propsList);
return propsObj;
}
export function Interop$$$extract(pred, xs) {
const extract$0027 = function extract$0027($arg$$4, $arg$$5, $arg$$6, $arg$$7) {
var x, rest;
extract$0027: while (true) {
const pred$$1 = $arg$$4,
xs$$1 = $arg$$5,
elem = $arg$$6,
acc = $arg$$7;
const matchValue = [xs$$1, elem];
if (matchValue[0].tail != null) {
if (matchValue[1] == null) {
if (x = matchValue[0].head, (rest = matchValue[0].tail, pred$$1(x))) {
return [some(matchValue[0].head), append(acc, matchValue[0].tail)];
} else {
var $target$$8, rest$$2, x$$2;
if (matchValue[0].tail != null) {
if (matchValue[1] == null) {
$target$$8 = 0;
rest$$2 = matchValue[0].tail;
x$$2 = matchValue[0].head;
} else {
$target$$8 = 1;
}
} else {
$target$$8 = 1;
}
switch ($target$$8) {
case 0:
{
$arg$$4 = pred$$1;
$arg$$5 = rest$$2;
$arg$$6 = null;
$arg$$7 = append(new List(x$$2, new List()), acc);
continue extract$0027;
}
case 1:
{
throw new Error("The match cases were incomplete against type of 'List' at test.fs");
}
}
}
} else {
return [elem, append(matchValue[0], acc)];
}
} else if (matchValue[1] == null) {
return [null, acc];
} else {
return [elem, acc];
}
break;
}
};
return extract$0027(pred, xs, null, new List());
}
export function Interop$$$createElement(name$$1, properties$$1) {
const props$$1 = properties$$1;
const matchValue$$1 = Interop$$$extract(function (tupledArg) {
return tupledArg[0] === "children";
}, props$$1);
if (matchValue$$1[0] != null) {
const value$$2 = matchValue$$1[0][1];
const key$$2 = matchValue$$1[0][0];
const restProps$$1 = Interop$$$createObj(matchValue$$1[1]);
return Interop$$$reactElement(name$$1, restProps$$1, value$$2);
} else {
const restProps = Interop$$$createObj(matchValue$$1[1]);
return Interop$$$reactElement(name$$1, restProps, null);
}
}
export const attr = declare(function Fulma_Container_attr() {});
export function attr$reflection() {
return type("Fulma.Container.attr");
}
export function attr$$$$002Ector() {
return this instanceof attr ? attr.call(this) : new attr();
}
export function attr$$$children$$6E3A73D(elems) {
return ["children", ofList(elems, Array)];
}
export const view = Interop$$$createElement("div", ofArray([["className", "button"], attr$$$children$$6E3A73D(new List("Click me", new List()))]));
Helpers$0024$0024$0024mountById("elmish-app", view); If we use the // More info about Fulma at https://mangelmaxime.github.io/Fulma/
module Fulma.Container
open Fable.Core
open Fable.Core.JsInterop
open Fable.React
open Fable.React.Props
open Fulma
type IReactApi =
abstract createElement: comp: obj * props: obj -> ReactElement
abstract createElement: comp: obj * props: obj * [<ParamList>] children: ReactElement seq -> ReactElement
[<Global("React")>]
let reactApi : IReactApi = jsNative
type IReactAttribute = interface end
module Interop =
let reactElement (name: string) (props: IReactAttribute seq) : ReactElement = reactApi.createElement(name, keyValueList CaseRules.LowerFirst props)
let inline mkAttr (key: string) (value: obj) : IReactAttribute = unbox (key, value)
// [<Erase>]
type attr() =
static member inline className(value: string) = Interop.mkAttr "className" value
/// Alias for inline `attr.children [ Html.content value ]`
static member inline content(value: string) = attr.children [ unbox value ]
/// Alias for inline `attr.children [ Html.content value ]`
static member inline content(value: int) = attr.children [ unbox value ]
/// Alias for inline `attr.children [ Html.content value ]`
static member inline content(value: ReactElement) = attr.children [ value ]
static member children (elems: ReactElement list) = Interop.mkAttr "children" (Array.ofList elems)
[<Erase>]
type Html =
/// The `<div>` tag defines a division or a section in an HTML document
static member inline div xs = Interop.reactElement "div" xs
let view =
Html.div [
attr.className "button"
attr.content "Click me"
]
mountById "elmish-app" view It generates 20 lines of JavaScript (+ 41 lines coming from import { ofList } from "fable-library/Array.js";
import { type } from "fable-library/Reflection.js";
import { List, declare } from "fable-library/Types.js";
import { createObj } from "fable-library/Util.js";
import { Helpers$$$mountById as Helpers$0024$0024$0024mountById } from "fable-repl-lib/src/Fable.React.Helpers";
export function Interop$$$reactElement(name, props) {
return React.createElement(name, createObj(props, 1));
}
export const attr = declare(function Fulma_Container_attr() {});
export function attr$reflection() {
return type("Fulma.Container.attr");
}
export function attr$$$$002Ector() {
return this instanceof attr ? attr.call(this) : new attr();
}
export function attr$$$children$$6E3A73D(elems) {
return ["children", ofList(elems, Array)];
}
export const view = Interop$$$reactElement("div", [["className", "button"], attr$$$children$$6E3A73D(new List("Click me", new List()))]);
Helpers$0024$0024$0024mountById("elmish-app", view); And if we try to "optimize"/adapt the // Adapted type for using `inline children`
[<Erase>]
type attr =
static member inline className(value: string) = Interop.mkAttr "className" value
/// Alias for inline `attr.children [ Html.content value ]`
static member inline content(value: string) = attr.children [ unbox value ]
/// Alias for inline `attr.children [ Html.content value ]`
static member inline content(value: int) = attr.children [ unbox value ]
/// Alias for inline `attr.children [ Html.content value ]`
static member inline content(value: ReactElement) = attr.children [ value ]
static member inline children (elems: ReactElement list) = Interop.mkAttr "children" (Array.ofList elems) Generated JavaScript import { ofList } from "fable-library/Array.js";
import { List } from "fable-library/Types.js";
import { createObj } from "fable-library/Util.js";
import { Helpers$$$mountById as Helpers$0024$0024$0024mountById } from "fable-repl-lib/src/Fable.React.Helpers";
export function Interop$$$reactElement(name, props) {
return React.createElement(name, createObj(props, 1));
}
export const view = Interop$$$reactElement("div", [["className", "button"], ["children", ofList(new List("Click me", new List()), Array)]]);
Helpers$0024$0024$0024mountById("elmish-app", view); I added mention about |
I forgot to say something in my last message. I think that implementation proposition makes sense no matter if we choose Also, to speak go back about the With that said, I will trust the decision you made or will make :) |
Thank you so much @MangelMaxime for checking the generated bundle sizes and the docs, I didn't know about children being a prop as well and I will definitely incorporate it before the stable release, or maybe as soon as I get access to my computer :)
That's exactly what I think: it doesn't really matter 😄 maybe I could add a type alias called prop in this library and users can choose which to use but I still favor attr. The good news is that extending this type (whatever it is called) with more static functions is really easy for third party components. There is also something I want to check: I think in F# you could open a static class the same you do for F# modules so it could be as simple as open Feliz.attr but I am not sure it works |
Happy to share what I know and help improve this library :)
I am all for providing both in the library. Like that everyone can use what they prefer as long as we don't have 10 alias 😉
Indeed, and this can help people prototype their own typed CSS properties. Like using unit of measure, or specialized DSL for composing computation for SVG elements etc.
That actually reminds me of something but I do not think I have used it either |
After a quick search, I think that feature open static class is planned for F# 4.7 so we don't have it yet. |
Bundle size reduction implemented and simplified the interop story, however I kept the constructor of As for |
At minima, you can remove the You can also create a Like that they are still accessible via I mentioned |
Done and worked nicely! fixed in v0.8.0 |
On twitter, I mentioned that I would prefer using
prop
instead ofattr
so I would like to continue the discussion here.My point is that Feliz seems to come closer to the react philosophy where everything is a component and everything accepts properties. I don't know if that voluntary or not.
The original React DSL, is based on JSX (and so HTML) syntax which is a layer on top of react. In JSX, we are passing attributes/properties and have a separate way to pass children.
While in fact for react children are passed using
children
property. So for react, everything is aproperty
.I think that using
prop
instead ofattr
will favorize the adoption of Feliz. The other benefit is that if someone uses Feliz syntax for writing/binding components the termprop
is correct in that case too.And because HTML element (
div
,span
, etc.) should be considered the lowest-level components possible for me it makes sense to useprop
term.IHMO asking the user to create an alias in each of their module/projects is not a good alternative because they will be thinking in term of
prop
while the documentation could mentionattr
. Or for newcomers, it's not really friendly to use module alias, etc.What do you think?
The text was updated successfully, but these errors were encountered: