This repository has been archived by the owner on Dec 13, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 143
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
256 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
In certain situations, such as when building highly reusable and customizable components, props may be composed of Roact objects, such as an element or a component class. | ||
|
||
To facilitate safer development for these kinds of situations, Roact exposes the `Roact.typeOf` function to inspect Roact objects and return a value from the `Roact.Type` enumeration. | ||
|
||
## Without Object Type Inspection | ||
|
||
Suppose we want to write a Header component with a prop for the title child element: | ||
```lua | ||
local Header = Component:extend("Header") | ||
function Header:render() | ||
local titleClass = props.titleClass | ||
return Roact.createElement("Frame", { | ||
-- Props for Frame... | ||
}, { | ||
Title = Roact.createElement(titleClass, { | ||
-- Props for Title... | ||
}) | ||
}) | ||
end | ||
``` | ||
|
||
Now suppose we want to validate that titleClass is actually a class using [validateProps](../../api-reference/#validateprops). Unfortunately, the best we can do is query Header to see if it contains characteristics of a Component class: | ||
```lua | ||
local Header = Component:extend("Header") | ||
Header.validateProps = function() | ||
local titleClass = props.titleClass | ||
if type(titleClass.render) == "function" then | ||
return true | ||
end | ||
return false, tostring(Header) .. " prop titleClass cannot render" | ||
end | ||
``` | ||
|
||
## With Object Type Inspection | ||
|
||
With `Roact.typeOf`, we can be certain we have a Component class: | ||
```lua | ||
Header.validateProps = function() | ||
local titleClass = props.titleClass | ||
if Roact.typeOf(titleClass) == Roact.Type.StatefulComponentClass then | ||
return true | ||
end | ||
return false, tostring(Header) .. " prop titleClass is not a component class" | ||
end | ||
``` | ||
|
||
We can even provide props which can be of multiple different Roact object types to give the consumer more flexibility: | ||
```lua | ||
local Header = Component:extend("Header") | ||
Header.validateProps = function() | ||
local title = props.title -- Type.Element | Type.StatefulComponentClass | ||
local isElement = Roact.typeOf(title) == Roact.Type.Element | ||
local isClass = Roact.typeOf(title) == Roact.Type.StatefulComponentClass | ||
if isElement or isClass then | ||
return true | ||
end | ||
return false, tostring(Header) .. " prop title must be a class or element" | ||
end | ||
function Header:render() | ||
local title = props.title | ||
local isElement = Roact.typeOf(title) == Roact.Type.Element | ||
local isClass = Roact.typeOf(title) == Roact.Type.StatefulComponentClass | ||
return Roact.createElement("Frame", { | ||
-- Props for Frame... | ||
}, { | ||
Title = isElement and title or isClass and Roact.createElement(title, { | ||
-- Props for Title... | ||
}) | ||
}) | ||
end | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
--[[ | ||
Mirrors a subset of values from Type.lua for external use, allowing | ||
type checking on Roact objects without exposing internal Type symbols | ||
TypeMirror: { | ||
Type: Roact.Type, | ||
typeOf: function(value: table) -> Roact.Type | nil | ||
} | ||
]] | ||
|
||
local Type = require(script.Parent.Type) | ||
local Symbol = require(script.Parent.Symbol) | ||
local strict = require(script.Parent.strict) | ||
|
||
local ALLOWED_TYPES = { | ||
Type.Binding, | ||
Type.Element, | ||
Type.HostChangeEvent, | ||
Type.HostEvent, | ||
Type.StatefulComponentClass, | ||
Type.StatefulComponentInstance, | ||
Type.VirtualTree | ||
} | ||
|
||
local MirroredType = {} | ||
for _, type in ipairs(ALLOWED_TYPES) do | ||
local name = Type.nameOf(type) | ||
MirroredType[name] = Symbol.named("Roact" .. name) | ||
end | ||
|
||
setmetatable(MirroredType, { | ||
__tostring = function() | ||
return "RoactType" | ||
end | ||
}) | ||
|
||
strict(MirroredType, "Type") | ||
|
||
local Mirror = { | ||
typeList = ALLOWED_TYPES, | ||
Type = MirroredType, | ||
typeOf = function(value) | ||
local name = Type.nameOf(Type.of(value)) | ||
if not name then | ||
return nil | ||
end | ||
return MirroredType[name] | ||
end, | ||
} | ||
|
||
setmetatable(Mirror, { | ||
__tostring = function() | ||
return "TypeMirror" | ||
end | ||
}) | ||
|
||
strict(Mirror, "TypeMirror") | ||
|
||
return Mirror |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
return function() | ||
local Type = require(script.Parent.Type) | ||
local Mirror = require(script.Parent.TypeMirror) | ||
|
||
describe("Type", function() | ||
it("should return a mirror of an internal type", function() | ||
local name = Type.nameOf(Type.Element) | ||
local mirroredType = Mirror.Type[name] | ||
expect(mirroredType).to.equal(Mirror.Type.Element) | ||
end) | ||
|
||
it("should not return the actual internal type", function() | ||
local name = Type.nameOf(Type.Element) | ||
local mirroredType = Mirror.Type[name] | ||
expect(mirroredType).to.never.equal(Type.Element) | ||
end) | ||
|
||
it("should include all allowed types", function() | ||
for _, type in ipairs(Mirror.typeList) do | ||
local name = Type.nameOf(type) | ||
local mirroredType = Mirror.Type[name] | ||
expect(mirroredType).to.be.ok() | ||
end | ||
end) | ||
|
||
it("should not include any other types", function() | ||
local name = Type.nameOf(Type.VirtualNode) | ||
local success = pcall(function() | ||
local _ = Mirror.Type[name] | ||
end) | ||
expect(success).to.equal(false) | ||
end) | ||
end) | ||
|
||
describe("typeOf", function() | ||
it("should throw if the value is not a valid type", function() | ||
local typeOfCheck = function(value) | ||
local _ = Mirror.typeOf(value) | ||
end | ||
expect(pcall(typeOfCheck, 1)).to.equal(false) | ||
expect(pcall(typeOfCheck, true)).to.equal(false) | ||
expect(pcall(typeOfCheck, "test")).to.equal(false) | ||
expect(pcall(typeOfCheck, print)).to.equal(false) | ||
expect(pcall(typeOfCheck, {})).to.equal(false) | ||
expect(pcall(typeOfCheck, newproxy(true))).to.equal(false) | ||
end) | ||
|
||
it("should return the assigned type", function() | ||
local test = { | ||
[Type] = Type.Element | ||
} | ||
|
||
expect(Mirror.typeOf(test)).to.equal(Mirror.Type.Element) | ||
end) | ||
end) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters