254 lines
9.3 KiB
TypeScript
254 lines
9.3 KiB
TypeScript
|
|
import React from 'react';
|
|
import { ContentPage } from '../components/ContentPage';
|
|
import { Link } from 'react-router-dom';
|
|
import { ApiSection } from '../types'; // Import ApiSection
|
|
|
|
export const CoreConceptsPage: React.FC = () => {
|
|
const intro = (
|
|
<p>
|
|
Chemical is built upon a few core concepts that work together to create reactive and manageable applications. Understanding these concepts is key to effectively using the library.
|
|
</p>
|
|
);
|
|
|
|
const sections: ApiSection[] = [
|
|
{
|
|
title: "Reactivity",
|
|
entries: [
|
|
{
|
|
signature: "Stateful Objects (Values, Tables, Maps)",
|
|
description: (
|
|
<>
|
|
At the heart of Chemical are stateful objects. These are containers for your application's data.
|
|
The primary stateful object is a <Link to="/api/value" className="text-purple-400 hover:underline">Value</Link>.
|
|
For collections, Chemical provides <Link to="/api/table" className="text-purple-400 hover:underline">Table</Link> (for arrays)
|
|
and <Link to="/api/map" className="text-purple-400 hover:underline">Map</Link> (for dictionaries).
|
|
When the data within these objects changes, Chemical's reactive system is notified.
|
|
</>
|
|
),
|
|
example: `
|
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
|
local Value = Chemical.Value
|
|
local Table = Chemical.Table
|
|
local Map = Chemical.Map
|
|
|
|
local name = Value("Guest")
|
|
local scores = Table({10, 20, 30})
|
|
local settings = Map({ theme = "dark", volume = 0.5 })
|
|
|
|
name:set("PlayerOne") -- Triggers reactivity
|
|
scores:insert(40) -- Triggers reactivity
|
|
settings:key("volume", 0.7) -- Triggers reactivity
|
|
`,
|
|
},
|
|
{
|
|
signature: "Computed Values",
|
|
description: (
|
|
<>
|
|
<Link to="/api/computed" className="text-purple-400 hover:underline">Computed</Link> values
|
|
are derived from one or more stateful objects. They automatically update whenever their underlying dependencies change.
|
|
This allows you to create complex data relationships without manual synchronization.
|
|
</>
|
|
),
|
|
example: `
|
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
|
local Value = Chemical.Value
|
|
local Computed = Chemical.Computed
|
|
|
|
local firstName = Value("John")
|
|
local lastName = Value("Doe")
|
|
|
|
local fullName = Computed(function()
|
|
return firstName:get() .. " " .. lastName:get()
|
|
end)
|
|
|
|
print(fullName:get()) -- Output: John Doe
|
|
|
|
firstName:set("Jane")
|
|
-- fullName:get() will be "Jane Doe" after scheduler runs
|
|
print("Full name after set:", fullName:get()) -- Might print old value here due to async update
|
|
`,
|
|
},
|
|
{
|
|
signature: "Effects",
|
|
description: (
|
|
<>
|
|
<Link to="/api/effect" className="text-purple-400 hover:underline">Effects</Link> are used to perform side effects in response to state changes.
|
|
This could include updating the UI, making network requests, or logging. Effects automatically re-run when their dependencies change.
|
|
They can also return a cleanup function that runs before the next execution or when the effect is destroyed.
|
|
</>
|
|
),
|
|
example: `
|
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
|
local Value = Chemical.Value
|
|
local Effect = Chemical.Effect
|
|
|
|
local count = Value(0)
|
|
|
|
Effect(function()
|
|
print("Current count is:", count:get())
|
|
|
|
-- Optional cleanup function
|
|
return function()
|
|
print("Cleaning up effect for count (previous value was):", count:get())
|
|
end
|
|
end)
|
|
|
|
count:set(1) -- Effect runs
|
|
count:set(2) -- Cleanup for count 1 runs, then effect for count 2 runs
|
|
`,
|
|
},
|
|
{
|
|
signature: "Observers and Watch",
|
|
description: (
|
|
<>
|
|
<Link to="/api/observer" className="text-purple-400 hover:underline">Observers</Link> provide a way to listen to changes in a specific stateful object and run callbacks.
|
|
<Link to="/api/watch" className="text-purple-400 hover:underline">Watch</Link> is a higher-level utility built on Observers for simpler change tracking.
|
|
</>
|
|
),
|
|
example: `
|
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
|
local Value = Chemical.Value
|
|
local Observer = Chemical.Observer
|
|
local Watch = Chemical.Watch
|
|
|
|
local name = Value("Alex")
|
|
|
|
-- Using Observer
|
|
local obs = Observer(name)
|
|
obs:onChange(function(newValue, oldValue)
|
|
print("Observer: Name changed from", oldValue, "to", newValue)
|
|
end)
|
|
|
|
-- Using Watch
|
|
local watchHandle = Watch(name, function(newValue, oldValue)
|
|
print("Watch: Name changed from", oldValue, "to", newValue)
|
|
end)
|
|
|
|
name:set("Jordan")
|
|
-- Both Observer and Watch callbacks will fire.
|
|
|
|
obs:destroy()
|
|
watchHandle:destroy()
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: "UI Composition with `Compose`",
|
|
entries: [
|
|
{
|
|
signature: "Declarative UI",
|
|
description: (
|
|
<>
|
|
Chemical's <Link to="/api/compose" className="text-purple-400 hover:underline">Compose</Link> API allows you to define UI structures declaratively.
|
|
Properties of UI elements can be bound to stateful objects (Values or Computeds), and the UI will automatically update when the state changes.
|
|
</>
|
|
),
|
|
example: `
|
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
|
local Value = Chemical.Value
|
|
local Compose = Chemical.Compose
|
|
local OnEvent = Chemical.OnEvent
|
|
local Children = Chemical.Symbols.Children
|
|
-- Assuming playerGui is defined: local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
|
|
|
|
local isVisible = Value(true)
|
|
local buttonText = Value("Click Me")
|
|
|
|
local MyScreen = Compose("ScreenGui")({
|
|
Parent = playerGui,
|
|
[Children] = {
|
|
Compose("TextButton")({
|
|
Text = buttonText, -- Bound to the buttonText Value
|
|
Visible = isVisible, -- Bound to the isVisible Value
|
|
Size = UDim2.new(0, 100, 0, 50),
|
|
-- Parent is implicitly MyScreen due to [Children] structure
|
|
[OnEvent("Activated")] = function()
|
|
print("Button clicked!")
|
|
isVisible:set(false) -- Change state, UI updates
|
|
end
|
|
})
|
|
}
|
|
})
|
|
|
|
|
|
buttonText:set("Submit") -- Button's text updates automatically
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: "Networking with Reactors",
|
|
entries: [
|
|
{
|
|
signature: "Reactions and Reactors",
|
|
description: (
|
|
<>
|
|
For networked applications, Chemical provides <Link to="/api/reactor" className="text-purple-400 hover:underline">Reactors</Link> and <Link to="/api/reaction" className="text-purple-400 hover:underline">Reactions</Link>.
|
|
A Reactor is a server-side factory for creating Reactions. Reactions are stateful objects whose state can be replicated from the server to clients.
|
|
Changes made to a Reaction on the server are automatically propagated to subscribed clients. Reaction properties are accessed directly.
|
|
</>
|
|
),
|
|
example: `
|
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
|
local Value = Chemical.Value
|
|
local Reactor = Chemical.Reactor
|
|
local Effect = Chemical.Effect
|
|
|
|
-- Server-side: Define a Reactor for player scores
|
|
local ScoreReactor = Reactor({ Name = "PlayerScore" }, function(playerIdString)
|
|
-- 'playerIdString' (the key) is available but typically not stored in the returned table.
|
|
return {
|
|
currentScore = Value(0)
|
|
}
|
|
end)
|
|
|
|
-- Create a Reaction for a specific player
|
|
local player1ScoreReaction = ScoreReactor:create("player123")
|
|
-- Access Reaction properties directly:
|
|
player1ScoreReaction.currentScore:set(100) -- This change will be sent to clients
|
|
|
|
-- Client-side: Await and use the Reaction
|
|
local player1ScoreClientReaction = ScoreReactor:await("player123") -- Yields
|
|
if player1ScoreClientReaction then
|
|
Effect(function()
|
|
-- Access Reaction properties directly:
|
|
print("Player 123 score on client:", player1ScoreClientReaction.currentScore:get())
|
|
end)
|
|
end
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: "Underlying ECS",
|
|
entries: [
|
|
{
|
|
signature: "Entity-Component-System",
|
|
description: (
|
|
<>
|
|
Chemical internally uses an Entity-Component-System (ECS) architecture (specifically `JECS`) to manage its reactive objects and their relationships.
|
|
While direct interaction with the ECS is usually not required for typical use cases, understanding this can be helpful for advanced scenarios or debugging.
|
|
Each reactive object created by Chemical (Value, Computed, Effect, etc.) is an "entity" with associated "components" (like its current value, previous value, or callback functions) and "tags" (like `IsStateful`, `IsDirty`).
|
|
</>
|
|
),
|
|
example: `
|
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
|
local Value = Chemical.Value
|
|
-- This is a conceptual example, direct ECS manipulation is advanced.
|
|
|
|
local myValue = Value(10)
|
|
-- myValue.entity is the ECS entity ID.
|
|
-- Chemical.World:get(myValue.entity, Chemical.ECS.Components.Value) would return 10.
|
|
-- When myValue:set(20) is called, Chemical.World:set(...) updates the component
|
|
-- and adds the Chemical.ECS.Tags.IsDirty tag.
|
|
-- The Scheduler then processes entities with IsDirty.
|
|
`,
|
|
},
|
|
],
|
|
}
|
|
];
|
|
|
|
return <ContentPage title="Core Concepts" introduction={intro} sections={sections} />;
|
|
};
|