fewkz / froact Goto Github PK
View Code? Open in Web Editor NEWWrapper around Roact & Roact Hooks to simplify UI development and add full, correct Luau typing
License: MIT License
Wrapper around Roact & Roact Hooks to simplify UI development and add full, correct Luau typing
License: MIT License
What do we want? Fully typed bindings! When do we want it? NOW!
Anyways, here's what it would look like:
Beautiful, right? Absolutely glorious and game-changing.
Anyways, I figured out how to implement the types, so we'll be seeing it soon(er or later).
Instead of doing
local newBinding = Roact.joinBindings({binding1, binding2}):map(function(vals)
local v1, v2 = unpack(vals)
return '' + v1 + ' ' + v2
end)
you get the luxury of being able to simply do:
local newBinding = froact.join(binding1, binding2).map(function(v1, v2)
return '' + v1 + ' ' + v2
end)
Absolutely beautiful, if I do say so myself, and with full luau typing support! Everything is typed, which is not possible with the way Roact bindings are implemented.
Currently, there is no way to "name" pure-function based components like you can for froact.c
components. This encourages a semi-bad practice of making every component that could be a function into an actual component with it's extra overhead.
A way we could solve this is via a froact.name()
function, that sets the "name" of the next element created by froact.
local function CoolButton(props: { text: string })
froact.name("Cool Button") -- the next textbutton will be called "CoolButton" instead of "TextButton" in the explorer
return froact.TextButton({
Text = props.text,
})
end
You would be able to use froact.name in lists, like so:
froact.list({}, {
froact.name("Open Button")
CoolButton({ text = "open" })
froact.name("Close Button")
CoolButton({ text = "close" })
})
if multiple names are set in a row, only the oldest name will apply until the name is consumed by creating a froact element, to allow the patterns such as above, where the name "Open Button" overwrites "Cool Button"
This would probably deprecate the key
feature in the list's config. I think the key
feature isn't very valuable, since it's not able to be derived from multiple props. By having froact.name()
be a function, components could name themselves programmatically according to their props, such as froact.name(`Player-{props.userId}`)
This could potentially allow us to replace all children fields with a froact list, which would make writing code a lot simpler, since you won't have to name every single child, and won't run into the issue where children are named like "1", "2", "3", "4", etc, which is terrible for debugging in the explorer. Another solution to the 1 2 3 4 error is proposed here: #10
Make a froact.createRef()
function that returns { current: Instance }
with the code:
type Ref = { current: Instance }
local function createRef(): Ref
return froact.Roact.createRef()
end
As it turns out, you can do .current on Roact refs. We can type the ref
property on Roblox instance components as { current: Instance }
so that you'll never fuck up the type of refs ever!
We get the beautiful, absolutely amazing extravagance of branch-based luau type inference! This is absolutely invaluable.
oh, and while we're add it, add froact.useRef(hooks)
, instead of doing hooks.useMemo(froact.createRef(), {})
Right now, the children type is set to {any}
, this means that froact is OK with you not giving each child in the children a string key. By changing this to {[string]: any}
this would catch a lot of issues where you accidentally forget to write the name of the child you're going to make, which causes the children to be named with sequential numbers, which is super annoying to read in the explorer. However, having to write the name of every children sucks a lot and a lot of existing froact code would probably turn into a lot of errors without a clear reason why. This is semi-related to #9
We should probably do the same thing for templates, by passing a second arg to the onUpdate callback functions with the children
Originally posted by @fewkz in #4 (comment)
Currently when using templates you have to handle the cleanup function yourself in a verbose way:
local Button = froact.template({}, function(name, parent, onUpdate)
local rbx = ReplicatedStorage.ButtonTemplate:Clone()
rbx.Name = name
rbx.Parent = parent
local cleanup
onUpdate(function(props)
if cleanup then
cleanup()
end
local conn = rbx.OnActivated:Connect(props.onActivated)
cleanup = function()
conn:Disconnect()
end
end)
return function()
if cleanup then
cleanup()
end
rbx:Destroy()
end
})
We should instead allow you to return a cleanup function in onUpdate, like so:
local Button = froact.template({}, function(name, parent, onUpdate)
local rbx = ReplicatedStorage.ButtonTemplate:Clone()
rbx.Name = name
rbx.Parent = parent
onUpdate(function(props)
local conn = rbx.OnActivated:Connect(props.onActivated)
return function()
conn:Disconnect()
end
end)
return function()
rbx:Destroy()
end
})
The faulty code is
Lines 174 to 176 in c9790a0
It sets the children to be blank after the first render, which is bad. We can fix this by removing the line that sets the props[roact.Children] to nil, but we don't really want children to be in props. Haven't quite figured out a better solution yet.
For now, to fix, you have to do this following code: local children = hooks.useMemo(function() return children end, {props})
, since the children will get set correct whenever props change.
We should implement our own wrapper around context that would be fully typed.
froact.provide, makes the type for Context smaller, by not including Provider:
local context = froact.createContext({ hello = "world" }) -- infered type: Context<{ hello = "world" }>
local bah = froact.c({}, function(props, hooks)
local value = hooks.useContext(context)
return TextLabel({ text = `hello is {value.hello}` })
end
local element = froact.provide(context, { hello = "foo" }, {
bah({})
})
similar to react
local context = froact.createContext({ hello = "world" } -- infered type: Context<{ hello = "world" }>
local bah = froact.c({}, function(props, hooks)
local value = hooks.useContext(context)
return TextLabel({ text = `hello is {value.hello}` })
end
local element = context.Provider({
value = { hello = "foo" }
}, {
bah({})
})
Context<T>
should be { default: T, Provider: ({ value: T }) -> Element }, Consumer: any
(too lazy to define consumer, you shouldn't use consumer, use the hook instead)
Rather than having to do:
local height, setHeight = hooks.useState(0)
froact.TextLabel({
Size = UDim2.new(1, 0, 0, height),
Text = "Text",
onChanged = function(rbx, property)
if property == "TextBounds" then
setHeight(rbx.TextBounds.Y)
end
end,
})
There should be a "bind" field that does the same:
local height, setHeight = hooks.useState(0)
froact.TextLabel({
Size = UDim2.new(1, 0, 0, height),
Text = "Text",
bindTextBounds = setHeight,
})
In the background, this will create a callback for the Changed
event.
Bind fields will only be generated for properties prefixed with Absolute
, or TextBounds
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.