220 lines
7.3 KiB
TypeScript
220 lines
7.3 KiB
TypeScript
|
|
import React from 'react';
|
|
import { ContentPage } from '../components/ContentPage';
|
|
import { CodeBlock } from '../components/CodeBlock';
|
|
|
|
export const IntroductionPage: React.FC = () => {
|
|
const intro = (
|
|
<>
|
|
<p className="mb-4">
|
|
Chemical is a Luau library designed for building applications with a reactive programming paradigm, primarily within environments like Roblox. It provides tools for managing state, creating derived data, handling side effects, and composing user interfaces in a declarative way.
|
|
</p>
|
|
<p className="mb-4">
|
|
Inspired by modern frontend frameworks, Chemical aims to simplify complex state interactions and UI logic by providing a structured and predictable way to handle data flow.
|
|
</p>
|
|
</>
|
|
);
|
|
|
|
const sections = [
|
|
{
|
|
title: "Key Features",
|
|
entries: [
|
|
{
|
|
signature: "Reactive Primitives",
|
|
description: "Includes `Value`, `Computed`, and `Effect` for fine-grained reactive control.",
|
|
example: `
|
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
|
local Value = Chemical.Value
|
|
local Computed = Chemical.Computed
|
|
local Effect = Chemical.Effect
|
|
|
|
-- Create a reactive value
|
|
local count = Value(0)
|
|
|
|
-- Create a computed value derived from count
|
|
local doubled = Computed(function()
|
|
return count:get() * 2
|
|
end)
|
|
|
|
-- Create an effect that runs when count changes
|
|
Effect(function()
|
|
print("Count changed to:", count:get())
|
|
print("Doubled is:", doubled:get())
|
|
end)
|
|
|
|
count:set(5) -- This will trigger the effect
|
|
-- Output:
|
|
-- Count changed to: 5
|
|
-- Doubled is: 10
|
|
`,
|
|
},
|
|
{
|
|
signature: "Stateful Collections",
|
|
description: "Manage lists and dictionaries with `Table` and `Map`, which propagate changes reactively.",
|
|
example: `
|
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
|
local Table = Chemical.Table
|
|
local Effect = Chemical.Effect
|
|
|
|
local items = Table({"apple", "banana"})
|
|
Effect(function()
|
|
print("Items:", table.concat(items:get(), ", "))
|
|
end)
|
|
|
|
items:insert("cherry")
|
|
-- Output:
|
|
-- Items: apple, banana
|
|
-- (After scheduler runs)
|
|
-- Items: apple, banana, cherry
|
|
`,
|
|
},
|
|
{
|
|
signature: "Declarative UI Composition",
|
|
description: "Use the `Compose` API to build and update UI elements based on reactive state.",
|
|
example: `
|
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
|
local Value = Chemical.Value
|
|
local Computed = Chemical.Computed
|
|
local Compose = Chemical.Compose
|
|
local Children = Chemical.Symbols.Children -- Alias for Children key
|
|
-- Assuming playerGui is defined: local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
|
|
|
|
|
|
local name = Value("Player")
|
|
local score = Value(100)
|
|
|
|
local MyScreenGui = Compose("ScreenGui")({
|
|
Parent = playerGui,
|
|
[Children] = { -- Use the aliased Children symbol
|
|
Compose("TextLabel")({
|
|
Name = "PlayerName",
|
|
Position = UDim2.new(0.5, 0, 0.1, 0),
|
|
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
Text = Computed(function()
|
|
return "Name: " .. name:get()
|
|
end),
|
|
TextColor3 = Color3.new(1,1,1),
|
|
BackgroundTransparency = 1,
|
|
Size = UDim2.new(0, 200, 0, 30)
|
|
}),
|
|
Compose("TextLabel")({
|
|
Name = "PlayerScore",
|
|
Position = UDim2.new(0.5, 0, 0.2, 0),
|
|
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
Text = Computed(function()
|
|
return "Score: " .. score:get()
|
|
end),
|
|
TextColor3 = Color3.new(1,1,1),
|
|
BackgroundTransparency = 1,
|
|
Size = UDim2.new(0, 200, 0, 30)
|
|
})
|
|
}
|
|
})
|
|
|
|
-- Later, when state changes, the UI updates automatically
|
|
name:set("Hero")
|
|
score:set(150)
|
|
`,
|
|
},
|
|
{
|
|
signature: "Networked State with Reactors",
|
|
description: "Synchronize state across server and clients using `Reactor` and `Reaction` objects for multiplayer experiences.",
|
|
example: `
|
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
|
local Value = Chemical.Value
|
|
local Reactor = Chemical.Reactor
|
|
local Effect = Chemical.Effect
|
|
|
|
-- Server-side
|
|
local PlayerDataReactor = Reactor({ Name = "PlayerData" }, function(key_playerIdString, initialData)
|
|
-- 'key_playerIdString' is the key, typically not stored in this returned table.
|
|
initialData = initialData or {}
|
|
return {
|
|
Health = Value(initialData.Health or 100),
|
|
Mana = Value(initialData.Mana or 50)
|
|
}
|
|
end)
|
|
|
|
local player1DataReaction = PlayerDataReactor:create("player1_id", { Health = 90 })
|
|
-- Access properties directly on the reaction:
|
|
player1DataReaction.Health:set(80) -- Change will replicate
|
|
|
|
-- Client-side
|
|
local player1DataClient = PlayerDataReactor:await("player1_id")
|
|
-- player1DataClient is now the Reaction object
|
|
if player1DataClient then
|
|
Effect(function()
|
|
print("Player 1 Health:", player1DataClient.Health:get())
|
|
end)
|
|
end
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: "Philosophy",
|
|
entries: [
|
|
{
|
|
signature: "Data Flow",
|
|
description: "Chemical promotes a unidirectional data flow. State changes trigger computations, which in turn can trigger effects (like UI updates or network calls). This makes applications easier to reason about.",
|
|
example: `
|
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
|
local Value = Chemical.Value
|
|
local Computed = Chemical.Computed
|
|
local Effect = Chemical.Effect
|
|
|
|
-- State (Value) -> Derived State (Computed) -> Side Effect (Effect)
|
|
local function UpdateDisplay(text) print("Display: " .. text) end
|
|
|
|
local price = Value(10)
|
|
local quantity = Value(2)
|
|
|
|
local totalCost = Computed(function()
|
|
return price:get() * quantity:get()
|
|
end)
|
|
|
|
Effect(function()
|
|
UpdateDisplay("Total Cost: $" .. totalCost:get())
|
|
end)
|
|
|
|
price:set(12) -- Triggers totalCost recomputation, then updates display
|
|
quantity:set(3) -- Triggers totalCost recomputation, then updates display
|
|
`
|
|
},
|
|
{
|
|
signature: "Modularity",
|
|
description: "The library is structured with clear separation of concerns: core reactivity, state management, UI composition, and networking are distinct but interoperable parts.",
|
|
example: `
|
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
|
-- Assuming modules are set up in ReplicatedStorage.Chemical.Modules
|
|
-- local PlayerProfileReactor = require(game.ReplicatedStorage.Chemical.Modules.PlayerProfileReactor) -- Uses Reactor
|
|
-- local HUD = require(game.ReplicatedStorage.Chemical.Modules.HUDComponent) -- Uses Compose
|
|
|
|
-- Mock for example purposes
|
|
local PlayerProfileReactor = {
|
|
await = function(playerId)
|
|
print("Mock Reactor: Awaiting profile for", playerId)
|
|
-- Simulate awaiting a profile by returning it directly for example simplicity
|
|
-- In a real scenario, this would yield until the data is available.
|
|
return {
|
|
UserId = playerId,
|
|
Name = Chemical.Value("MockPlayer-" .. playerId:sub(1,4))
|
|
}
|
|
end
|
|
}
|
|
local HUD = { Render = function(profile) print("Rendering HUD for", profile.Name:get()) end }
|
|
-- End Mock
|
|
|
|
local player = game.Players.LocalPlayer
|
|
local profileReaction = PlayerProfileReactor:await(tostring(player.UserId))
|
|
if profileReaction then
|
|
HUD.Render(profileReaction) -- HUD component consumes reactive profile data
|
|
end
|
|
`
|
|
}
|
|
]
|
|
}
|
|
];
|
|
|
|
return <ContentPage title="Introduction to Chemical" introduction={intro} sections={sections} />;
|
|
};
|