Skip to content

Commit

Permalink
work on struct api docs
Browse files Browse the repository at this point in the history
  • Loading branch information
andrfra committed Apr 15, 2024
1 parent 7ce8c3f commit 4898088
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 90 deletions.
2 changes: 1 addition & 1 deletion packages/reactivity-core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*
* @groupDescription Structs
*
* Create simple class like objects having reactive properties.
* Create class like objects having reactive properties.
*/
export {
type ReadonlyReactive,
Expand Down
230 changes: 141 additions & 89 deletions packages/reactivity-core/struct/struct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import { computed, getValue, isWritableReactive, reactive } from "../ReactiveImpl";

/**
* A property of reactive struct.
* A property of a reactive struct.
*
* @group Structs
*/
export interface PropertyMemberType {
/**
Expand All @@ -26,7 +28,9 @@ export interface PropertyMemberType {
}

/**
* A method of reactive struct.
* A method of a reactive struct.
*
* @group Structs
*/
export interface MethodMemberType<T, Params extends any[], Ret> {
/**
Expand All @@ -41,6 +45,8 @@ export interface MethodMemberType<T, Params extends any[], Ret> {

/**
* A computed property of reactive struct.
*
* @group Structs
*/
export interface ComputedMemberType<T, V> {
/**
Expand All @@ -58,18 +64,18 @@ export interface ComputedMemberType<T, V> {
* All properties of T must be part of the definition.
*
* @param T The type of the struct.
* @group Structs
*/
export type ReactiveStructDefinition<T> = {
[key in keyof T]-?: GetMemberSchemaForProp<T, T[key]>;
};

/**
* A function type.
* Any function.
*/
type AnyFunc = (...args: any[]) => any;

/**
* @internal
* Computes which kind of property configuration are allowed for a given member,
* based on the original TypeScript interface.
*
Expand All @@ -87,14 +93,18 @@ type GetMemberSchemaForProp<T, V> = V extends AnyFunc
: PropertyMemberType | ComputedMemberType<T, V>;

/**
* The class returned by {@link reactiveStruct}.
* Constructor for reactive struct instances {@link reactiveStruct}.
*
* @param T The type of the struct.
* @param Def The definition of the struct.
* @group Structs
*/
export type ReactiveStructConstructor<T, Def> = new (
...args: ReactiveStructConstructorParams<T, Def>
) => T;
export interface ReactiveStructConstructor<T, Def> {
/**
* Creates a new reactive struct instance.
*/
new (...args: ReactiveStructConstructorParams<T, Def>) : T;
}

/**
* Computes the constructor parameters type.
Expand All @@ -108,13 +118,11 @@ type ReactiveStructConstructorParams<T, Def> = ConstructorArgs<

/**
* Makes the properties object optional if all properties are optional (i.e. if `{}` would be a valid value).
* @internal
*/
type ConstructorArgs<Props> = {} extends Props ? [initialValues?: Props] : [initialValues: Props];

/**
* Inspects `T` and `Def` and only picks those properties of T that are defined to be properties.
* @internal
*/
type PropertyMembers<T, Def> = {
[Key in keyof Def as Def[Key] extends PropertyMemberType ? Key : never]: Key extends keyof T
Expand All @@ -125,21 +133,21 @@ type PropertyMembers<T, Def> = {
/**
* This helper type inspects `Members` and makes all properties optional that may be undefined.
* All other properties are made required.
* @internal
*/
type ConstructorProps<Members> = Partial<PickOptionalProps<Members>> &
Required<Omit<Members, keyof PickOptionalProps<Members>>>;

/**
* Returns a type where only those properties of T remain that may be undefined.
* @internal
*/
type PickOptionalProps<T> = {
[Key in keyof T as undefined extends T[Key] ? Key : never]: T[Key];
};

/**
* Used to build reactive structs using a struct definition.
*
* @group Structs
*/
export interface ReactiveStructBuilder<T> {
/**
Expand All @@ -151,106 +159,147 @@ export interface ReactiveStructBuilder<T> {
}

/**
* Function used to obtain a builder for reactive structs.
* Call {@link ReactiveStructBuilder.define} on the builder to create a new reactive struct.
* Function used to create reactive structs with the help of the returned builder.
*
* A reactive struct is a class like object that can contain properties, computed properties and methods.
* By default all properties are reactive and writable.
*
* @param T The type of the struct.
* @returns A builder for reactive structs.
* #### Create a reactive struct
*
* To create a reactive struct proceed as follows:
*
* 1. Define the type of the struct.
* ```ts
* type PersonType = {
* firstName: string;
* lastName: string;
* }
* ```
*
* 2. Create a definition for the struct according to the type.
* ```ts
* const personDefinition: ReactiveStructDefinition<PersonType> = {
* firstName: {}, // default options (reactive and writable)
* lastName: {} // default options (reactive and writable)
* };
* ```
*
* @example Create a Person class with a first and last name
* 3. Create a new reactive struct class based on the provided definition.
* ```ts
* const PersonModel = reactiveStruct<PersonType>().define(personDefinition);
* ```
*
* 4. Create a new instance of the struct.
* ```ts
* const person = new PersonModel({
* firstName: "John",
* lastName: "Doe"
* });
* ```
* 5. Use the created instance.
*
* Now, you can use the person instance for example to compute the person's full name.
*
* ```ts
* const fullName = computed(() => `${person.firstName} ${person.lastName}`);
* console.log(fullName.value); // John Doe
* person.firstName = "Jane";
* console.log(fullName.value); // Jane Doe
* ```
*
* #### Options for simple properties
*
* The following options can be set for properties in the struct definition:
* - `writable`: if `true` the property is writable and it can be changed. If `false` the property is read-only.
* - `reactive`: if `true` the property is reactive. If `false` the property is not reactive.
*
* To define a read-only property set `writable` to `false`:
* ```ts
* const PersonClass = reactiveStruct<PersonType>().define({
* firstName: {},
* lastName: {},
* });
* const personDefinition: ReactiveStructDefinition<PersonType> = {
* firstName: {},
* lastName: { writable: false }
* };
* const PersonClass = reactiveStruct<PersonType>().define(personDefinition);
* const person = new PersonClass({
* firstName: "John",
* lastName: "Doe"
* firstName: "John",
* lastName: "Doe"
* });
* person.lastName = "Smith"; // throws an error
* ```
*
* To define a non reactive property set `reactive` to `false`:
* ```ts
* const personDefinition: ReactiveStructDefinition<PersonType> = {
* firstName: {},
* lastName: { reactive: false }
* };
* const PersonClass = reactiveStruct<PersonType>().define(personDefinition);
* const person = new PersonClass({
* firstName: "John",
* lastName: "Doe"
* });
* const fullName = computed(() => `${person.firstName} ${person.lastName}`);
* person.lastName = "Miller";
* console.log(fullName.value); // John Doe
* person.firstName = "Jane";
* console.log(fullName.value); // Jane Doe
* ```
*
* @example Create a Person class with a first, last name and computed property fullName
* #### Computed properties
*
* Computed properties are properties that are computed based on other properties.
* They can be defined in the struct definition as follows:
*
* ```ts
* const PersonClass = reactiveStruct<PersonType>().define({
* firstName: {},
* lastName: {},
* fullName: {
* compute() {
* type PersonType = {
* firstName: string;
* lastName: string;
* fullName: string;
* };
* const personDefinition: ReactiveStructDefinition<PersonType> = {
* firstName: {},
* lastName: {},
* fullName: {
* compute() {
* return `${this.firstName} ${this.lastName}`;
* }
* }
* });
* const person = new PersonClass({
* firstName: "John",
* lastName: "Doe"
* }
* }
* };
* const PersonModel = reactiveStruct<PersonType>().define(personDefinition);
* const person = new PersonModel({
* firstName: "John",
* lastName: "Doe"
* });
* console.log(person.fullName); // John Doe
* person.firstName = "Jane";
* console.log(person.fullName); // Jane Doe
* ```
*
* @example Create a Person class able to print its full name
* ```ts
* const PersonClass = reactiveStruct<PersonType>().define({
* firstName: {},
* lastName: {},
* fullName: {
* compute() {
* return `${this.firstName} ${this.lastName}`;
* }
* },
* printName: {
* method() {
* console.log(this.fullName);
* }
* }
* });
* const person = new PersonClass({
* firstName: "John",
* lastName: "Doe"
* });
* person.printName(); // John Doe
* person.firstName = "Jane";
* person.printName(); // Jane Doe
* ```
* #### Methods
*
* @example Create a Person with a readonly property
* ```ts
* const PersonClass = reactiveStruct<PersonType>().define({
* firstName: {},
* lastName: {
* writable: false
* }
* });
* const person = new PersonClass({
* firstName: "John",
* lastName: "Doe"
* });
* person.lastName = "Miller"; // throws an error (Cannot set property lastName)
* ```
* You can also define methods in the struct definition:
*
* @example Create a Person with a non reactive property
* ```ts
* const PersonClass = reactiveStruct<PersonType>().define({
* firstName: {},
* lastName: {
* reactive: false
* }
* type PersonType = {
* firstName: string;
* lastName: string;
* printName: () => void;
* };
* const personDefinition: ReactiveStructDefinition<PersonType> = {
* firstName: {},
* lastName: {},
* printName: {
* method() {
* return console.log(`${this.firstName} ${this.lastName}`);
* }
* }
* };
* const PersonModel = reactiveStruct<PersonType>().define(personDefinition);
* const person = new PersonModel({
* firstName: "John",
* lastName: "Doe"
* });
* const person = new PersonClass({
* firstName: "John",
* lastName: "Doe"
* });
* const fullName = computed(() => `${person.firstName} ${person.lastName}`);
* console.log(fullName.value); // John Doe
* person.lastName = "Miller";
* console.log(fullName.value); // John Doe
* person.printName(); // prints "John Doe"
* ```
* @group Structs
*/
export function reactiveStruct<T>(): ReactiveStructBuilder<T> {
return {
Expand Down Expand Up @@ -358,6 +407,9 @@ function preparePrototype<T>(
};
}

/**
* Configuration for a simple property (neither method nor computed).
*/
interface PropertyConfig {
propertyKey: PropertyKey;
reactive: boolean;
Expand Down

0 comments on commit 4898088

Please sign in to comment.