This repository was archived by the owner on Dec 13, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 140
Add Type Checking API #230
Open
MisterUncloaked
wants to merge
16
commits into
Roblox:master
Choose a base branch
from
MisterUncloaked:type-checking-api
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
c5ccab0
Addresses https://github.com/Roblox/roact/issues/227
MisterUncloaked ca0b9e6
Added comments for TypeMirror.lua and updated docs/changelog with typ…
MisterUncloaked 999d6a2
Polishing Type checking docs
MisterUncloaked e5ed458
Remove Type Checking as a term in docs
MisterUncloaked 1044fd7
Lua fixes in Type documentation
MisterUncloaked 3ddc18b
Slightly simpler syntax for Lua code in Type docs
MisterUncloaked e4962cc
TypeMirror improvements:
MisterUncloaked 45a3920
Added Roact.isValidElementType to see if a value has the right type t…
MisterUncloaked 0993ed2
Update documentation for type validation
MisterUncloaked 57be113
Add API reference for Roact.isValidElementType
MisterUncloaked c5be781
Polish type-validation.md
MisterUncloaked 9fc38e8
Rename isValidElementType to isComponent
MisterUncloaked 0fc8b00
Updated changelog to reflect Roact.isComponent
MisterUncloaked 4ea1247
Simplify test case of typeOf
MisterUncloaked 0d52578
Fix name for skipped test in isComponent.spec.lua to reflect its nature
MisterUncloaked 666d7a9
- typeOf no longer throws
MisterUncloaked File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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,62 @@ | ||
| In certain situations, such as when building reusable and customizable components, props may be composed of Roact objects, such as an element or a component. | ||
|
|
||
| To facilitate safer development for these kinds of situations, Roact provides the `Roact.typeOf` and `Roact.isComponent` functions to help validate these objects. | ||
|
|
||
| ## Roact Object Type Validation | ||
|
|
||
| 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 title = props.title | ||
|
|
||
| return Roact.createElement("Frame", { | ||
| -- Props for Frame... | ||
| }, { | ||
| Title = title | ||
| }) | ||
| end | ||
| ``` | ||
|
|
||
| Now suppose we want to validate that `title` is actually an element using [validateProps](../../api-reference/#validateprops). With `Roact.typeOf` we can be certain we have a Roact Element: | ||
| ```lua | ||
| Header.validateProps = function() | ||
| local title = props.title | ||
|
|
||
| if Roact.typeOf(title) == Roact.Type.Element then | ||
| return true | ||
| end | ||
|
|
||
| return false, "prop title is not an element" | ||
| end | ||
| ``` | ||
|
|
||
| ## Component Type Validation | ||
|
|
||
| In some cases, a component will be more preferable as a prop than an element. `Roact.isComponent` can be used to see if a value is a plausible component and thus can be passed to `Roact.createElement`. | ||
|
|
||
| ```lua | ||
| local Header = Component:extend("Header") | ||
|
|
||
| Header.validateProps = function() | ||
| local title = props.title | ||
|
|
||
| if Roact.isComponent(title) then | ||
| return true | ||
| end | ||
|
|
||
| return false, "prop title can not be an element" | ||
| end | ||
|
|
||
| function Header:render() | ||
| local title = props.title | ||
| return Roact.createElement("Frame", { | ||
| -- Props for Frame... | ||
| }, { | ||
| Title = Roact.isComponent(title) and Roact.createElement(title, { | ||
| -- Props for Title... | ||
| }) | ||
| }) | ||
| end | ||
| ``` |
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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,53 @@ | ||
| --[[ | ||
| 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, | ||
| } | ||
|
|
||
| strict(Mirror, "TypeMirror") | ||
|
|
||
| return Mirror | ||
|
MisterUncloaked marked this conversation as resolved.
|
||
This file contains hidden or 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,53 @@ | ||
| 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 return nil if the value is not a valid type", function() | ||
| expect(Mirror.typeOf(1)).to.equal(nil) | ||
| expect(Mirror.typeOf(true)).to.equal(nil) | ||
| expect(Mirror.typeOf"test").to.equal(nil) | ||
| expect(Mirror.typeOf(print)).to.equal(nil) | ||
| expect(Mirror.typeOf({})).to.equal(nil) | ||
| expect(Mirror.typeOf(newproxy(true))).to.equal(nil) | ||
| end) | ||
|
MisterUncloaked marked this conversation as resolved.
|
||
|
|
||
| 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 hidden or 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 hidden or 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 hidden or 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,14 @@ | ||
| local Portal = require(script.Parent.Portal) | ||
| local Type = require(script.Parent.Type) | ||
|
|
||
| -- Returns true if the provided object can be used by Roact.createElement | ||
| return function(value) | ||
| local valueType = type(value) | ||
|
|
||
| local isComponentClass = Type.of(value) == Type.StatefulComponentClass | ||
| local isValidFunctionComponentType = valueType == "function" | ||
| local isValidHostType = valueType == "string" | ||
| local isPortal = value == Portal | ||
|
|
||
| return isComponentClass or isValidFunctionComponentType or isValidHostType or isPortal | ||
| end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.