State management
This topic provides information about the Goldfish state management APIs.
Overview
You can use Goldfish to develop the mini program DSL. Goldfish provides setup functions for the mini program view layer to create the execution environment for Goldfish Composition APIs.
Connectors
Goldfish provides three setup functions for the mini program: setupApp
, setupPage
, andsetupComponent
. You can use these functions to connect the Composition APIs with the mini program.
setupApp(setup)
- Type:
function setupApp(setup: SetupFunction): tinyapp.AppOptions
interface SetupFunction {
(): Record<string, any>;
}
// Global injection: tinyapp.AppOptions
// https://www.npmjs.com/package/mini-types
- Details:
Provides the App-related Composition API execution environment, and returns the configurations for App. The return value of setup()
will be mounted to globalData.
- Example:
//File path: src/app.ts
import { setupApp, useState } from '@goldfishjs/core';
App(setupApp(useBiz));
function useBiz() {
/* This is the Composition API execution environment
in the synchronization process of the function. */
const state = useState({});
// The return value will be mounted to globalData.
return {
state,
};
}
setupPage(setup)
- Type:
function setupPage(setup: SetupFunction): tinyapp.PageOptions
interface SetupFunction {
(): Record<string, any>;
}
// Global injection: tinyapp.PageOptions
// https://www.npmjs.com/package/mini-types
- Details:
Provides the Page-related Composition API execution environment, and returns the configurations for Page.
- Example:
//File path: src/pages/index/index.ts
import { setupPage, useState } from '@goldfishjs/core';
// setupPage connects useBiz with Page.
// You can read the return value of useBiz in the Page's AXML file.
Page(setupPage(useBiz));
function useBiz() {
/* This is the Composition API execution environment
in the synchronization process of the function.*/
const state = useState({ name: 'Jack Ma' });
function onTap() {
console.log('');
}
// Return the data and functions that are used in AXML file. For example:
// <button onTap="onTap">{{state.name}}</button>
return {
state,
onTap,
};
}
setupComponent(props?, setup)
- Type:
function setupComponent(setup: SetupFunction): tinyapp.ComponentOptions<any>;
function setupComponent(
defaultProps: Record<string, any>,
setup: SetupFunction
): tinyapp.ComponentOptions<any>;
interface SetupFunction {
(): Record<string, any>;
}
// Global injection: tinyapp.ComponentOptions
// https://www.npmjs.com/package/mini-types
- Details:
Provides the Component-related Composition API execution environment, and returns the configurations for Component.
- Example:
//File path: src/components/foo/index.ts
import { setupComponent, useState } from '@goldfishjs/core';
const defaultProps = {
city: 'ChengDu',
};
Component(setupComponent(defaultProps, useBiz));
function useBiz() {
// The props are reactive and readonly.
const props = useProps<typeof defaultProps>();
const state = useState({
name: 'Jack Ma',
get city4Display() {
// Read the data in props.
return { ChengDu: 'CD' }[props.city] || 'HZ';
},
});
function onTap() {
console.log('');
}
return {
state,
onTap,
};
}
Composition APIs
The following sections introduce a set of function-based APIs that allow flexible composition of logic.
useState(data)
- Type:
function useState<T extends Record<string, any>>(data: T): T;
- Details:
Converts the normal data to reactive data.
- Example:
//File path: src/pages/index/index.ts
import { useState, setupPage } from '@goldfishjs/core';
function useBiz() {
const normalPerson = {
name: 'Jack',
get fullName() {
return `${this.name}.ali`;
},
};
/* Convert `normalPerson` to `reactivePerson` with reactive capability.
fullName is the computed property:
- It will calculate the new value automatically and cache the value the first time fullName is read or when the name property is changed;
- otherwise, you will get the last cached value when reading the value of fullName.*/
const reactivePerson = useState(normalPerson);
setTimeout(() => {
// Change the name property, and when you read the value of fullName, it will be recalculated automatically.
reactivePerson.name = 'Jack Ma';
});
// Notice: keep the reactivity of the data.
// If the data lose the reactivity, the UI will not be changed with the data.
return {
// Return the whole person to keep the reactivity.
person: reactivePerson,
// Use getter to keep the reactivity.
get name() {
return reactivePerson.name;
},
};
}
Page(setupPage(useBiz));
Read data in AXML:
<!-- File path: src/pages/index/index.axml -->
<view>{{ person.name }} - {{ person.fullName }}</view>
useWatch()
- Type:
function useWatch(): Watch;
type Watch<R> = (
collector: WatchCollector<R>,
callback: WatchCallback<R>,
options?: WatchOptions
) => StopWatch;
type WatchCollector<R> = () => R;
type WatchCallback<N, O = N | undefined> = (newValue: N, oldValue?: O) => void;
interface WatchOptions {
deep?: boolean;
immediate?: boolean;
onError?: (error: unknown) => void;
}
type StopWatch = () => void;
- Details:
Get the watch()
function (corresponding to the type Watch<R>
), which is used to listen for changes to the reactive data. Listening will stop in the following cases:
- When the corresponding page or component is destroyed.
- When the
return
function ofwatch()
is manually called.
The first parameter of the watch()
function is used to collect the synchronously accessed reactive data, and then listen for their changes:
- Type:
{WatchCollector<R>} collector
The second parameter is a function that is triggered when the collected reactive data changes in the collector
function:
- Type:
{WatchCallback<N, O = N | undefined>} callback
. The types ofnewValue
andoldValue
are the same as the return value ofcollector
.
The third parameter is an object (corresponding to the type WatchOptions
), and supports the following configurations:
{boolean?} deep
: Whether to deeply listen to the reactive data returned in thecollector
function. The default value isfalse
.{boolean?} immediate
: Whether to execute thecallback
function immediately after creating the listening relations. The default value isfalse
.{Function?} onError
: Triggered when there is an exception in thecollector
orcallback
function.
- Example:
//File path: src/pages/index/index.ts
import { setupPage, useWatch } from '@goldfishjs/core';
function useBiz() {
const state = useState({
name: 'Jack Ma',
nickname: 'Ma Baba',
});
setTimeout(() => {
state.name = 'Jack Ma.ali';
});
const watch = useWatch();
// Listen for changes to the reactive data.
watch(
() => state.name,
(newName, oldName) => {
// Print:
// Jack Ma.ali Jack Ma
console.log(newName, oldName);
},
);
// Trigger the callback function immediately after creating the listening relations.
watch(
() => state.name,
(newName, oldName) => {
// Print:
// Jack Ma undefined
// Jack Ma.ali Jack Ma
console.log(newName, oldName);
},
{
immediate: true,
},
);
// Listen once to multiple reactive data (state.name, state.nickname).
watch(
() => `${state.name}(${state.nickname})`,
(newStr, oldStr) => {
// Print:
// Jack Ma.ali(Ma Baba) Jack Ma(Ma Baba)
console.log(newStr, oldStr);
},
{
immediate: true,
},
);
// Deeply listen to the reactive state object.
watch(
() => state,
(newState, oldState) => {
// Print:
// Jack Ma.ali Jack Ma
console.log(newState.name, oldState.name);
},
{
// The callback function is called when any data is changed in the state.
deep: true,
},
);
return {};
}
Page(setupPage(useBiz);
useAutorun()
- Type:
function useAutorun(): Autorun;
type Autorun = (
effect: AutorunEffect,
onError?: AutorunErrorCallback
) => StopAutorun;
type AutorunEffect = () => void;
type AutorunErrorCallback = (error: unknown) => void;
type StopAutorun = () => void;
- Details:
Gets the autorun()
function (corresponding to the type Autorun
), which is used to listen for changes to the reactive data. Listening will stop in the following cases:
- When the corresponding page or component is destroyed.
- When the
return
function ofautorun()
is manually called.
The first parameter of the autorun()
function is executed once the first time the function is called and will be executed again when the synchronously accessed reactive data changes in the effect
function:
- Type:
{AutorunEffect} effect
The second parameter is used to listen to the exception thrown when the effect
function is executed:
- Type:
{AutorunErrorCallback} onError
- Example:
// File path: src/pages/index/index.ts
import { setupPage, useAutorun } from '@goldfishjs/core';
function useBiz() {
const state = useState({ name: 'Jack' });
setTimeout(() => {
state.name = 'Jack Ma';
}, 1000);
const autorun = useAutorun();
// Listen to the synchronously accessed reactive data: state.name
autorun(() => {
// Print:
// Jack
// Jack Ma
console.log(state.name);
});
// Unable to listen to the asynchronously accessed reactive data.
// The effect function will not be executed again when state.name changes.
autorun(() => {
setTimeout(() => {
console.log(state.name);
});
Promise.resolve().then(() => {
console.log(state.name);
});
});
return {};
}
Page(setupPage(useBiz));
useContextType()
- Type:
function useContextType(): ContextType;
type ContextType = 'page' | 'component' | 'app';
- Details:
Gets the parameter of the current execution environment:
app
: The execution environment is in the synchronization process ofsetupApp()
.
page
: The execution environment is in the synchronization process ofsetupPage()
.component
: The execution environment is in the synchronization process ofsetupComponent()
.
useProps()
- Type:
function useProps<R extends Record<string, any>>(): Readonly<R>;
- Execution environment:
useSetup
, setupComponent
.
- Details:
Gets theprops
object of components and converts it into a reactive object.
- Example:
//File path: src/components/my-components/index.ts
import { setupComponent, useProps, useWatch } from '@goldfishjs/core';
const defaultProps = {
name: 'Jack Ma',
};
function useBiz() {
// props have the reactive capability.
const props = useProps<{ name: string }>();
const watch = useWatch();
// Use the watch function to listen for changes to props.name.
watch(
() => props.name,
() => {
console.log('name prop changed.');
},
);
return {};
}
Component(setupComponent(defaultProps, useBiz));
Note: Limited by JavaScript ES5 (ECMAScript 5) capabilities,
useProps()
can only get the data declared indefaultProps
. Writing the default props of components clearly is also a best practice for developing components, which can greatly improve the maintainability of components.
useGlobalData()
- Type:
function useGlobalData<G extends Record<string, any>>(): GlobalDataHandle<G>;
interface GlobalDataHandle<G extends Record<string, any>> {
get<T extends keyof G>(key: T): G[T];
set<T extends keyof G>(key: T, value: G[T]): void;
}
- Details:
Saves the global reactive data.
- Example:
//File path: src/MyComponent.ts
import React from 'react';
import { useSetup, useGlobalData, useWatch } from '@goldfishjs/core';
function useBiz() {
const globalData = useGlobalData<{ name: string }>();
const watch = useWatch();
// The global data obtained has the reactive capability.
watch(
() => globalData.get('name'),
(globalName) => {
console.log('Global name is changed.');
},
);
// Change global data
globalData.set('name', 'Jack Ma');
return {
state,
};
}
export default function MyComponent(props) {
const { connect } = useSetup(React, useBiz, props);
return connect(d => <div></div>);
}
useGlobalDestroy(callback)
- Type:
function useGlobalDestroy(callback: () => void): void;
- Details:
Executes custom logic when the App (mini program) is destroyed.
- Example:
//File path: src/MyComponent.ts
import React from 'react';
import { app, useSetup, useGlobalDestroy } from '@goldfishjs/core';
// Initialize the mini program
app.init({ data: {}, config: { } });
setTimeout(() => {
// Destroy the mini program
app.destroy();
}, 2000);
function useBiz() {
useGlobalDestroy(() => {
// Executed when the App is destroyed
console.log('destroy');
});
return {};
}
export default function MyComponent(props) {
const { connect } = useSetup(React, useBiz, props);
return connect(d => <div></div>);
}
useMount(callback)
- Type:
function useMount(callback: () => void): void;
- Details:
Executes custom logic when the Page or Component is mounted.
- Example:
//File path: src/MyComponent.ts
import React from 'react';
import { useSetup, useMount } from '@goldfishjs/core';
function useBiz() {
useMount(() => {
// Executed when the Component is mounted
console.log('mount');
});
return {};
}
export default function MyComponent(props) {
const { connect } = useSetup(React, useBiz, props);
return connect(d => <div></div>);
}
useUnmount(callback)
- Type:
function useUnmount(callback: () => void): void;
- Details:
Execute custom logic when the Page or Component is unmounted.
- Example:
//File path: src/MyComponent.ts
import React from 'react';
import { useSetup, useMount } from '@goldfishjs/core';
function useBiz() {
useUnmount(() => {
// Executed when the Component is unmounted
console.log('unmount');
});
return {};
}
export default function MyComponent(props) {
const { connect } = useSetup(React, useBiz, props);
return connect(d => <div></div>);
}
useAppLifeCycle(name, callback)
- Type:
function useAppLifeCycle<T extends keyof tinyapp.IAppOptionsMethods>(
name: T,
callback: Required<tinyapp.IAppOptionsMethods>[T]
): void;
// Global injection: tinyapp.IAppOptionsMethods and tinyapp.IAppOptionsMethods
// https://www.npmjs.com/package/mini-types
- Execution environment:
setupApp
- Details:
Executes the specified logic in a specific life cycle of the App.
- Example:
//File path: src/app.ts
import { setupApp, useAppLifeCycle } from '@goldfishjs/core';
function useBiz() {
useAppLifeCycle('onLaunch', () => {
// Executed when the App onLaunch function is invoked
});
return {};
}
App(setupApp(useBiz));
usePageLifeCycle(name, callback)
- Type:
function usePageLifeCycle<T extends keyof AllPageMethods>(
name: T,
callback: AllPageMethods[T]
): void;
type AllPageMethods = Required<tinyapp.IPageOptionsMethods>;
// Global injection: tinyapp.IPageOptionsMethods
// https://www.npmjs.com/package/mini-types
- Execution environment:
setupPage
- Details:
Executes the specified logic in a specific life cycle of the Page.
- Example:
//File path: src/pages/index/index.ts
import { setupPage, usePageLifeCycle } from '@goldfishjs/core';
function useBiz() {
usePageLifeCycle('onLoad', () => {
// Executed when the Page onLoad is invoked
});
return {};
}
Page(setupPage(useBiz));
useComponentLifeCycle(name, callback)
- Type:
function useComponentLifeCycle<T extends keyof AllComponentMethods>(
name: T,
callback: AllComponentMethods[T]
): void;
type AllComponentMethods = Required<tinyapp.IComponentLifeCycleMethods<any, any>>;
// Global injection: tinyapp.IComponentLifeCycleMethods
// https://www.npmjs.com/package/mini-types
- Execution environment:
setupComponent
- Details:
Executes the specified logic in a specific life cycle of the Component.
- Example:
//File path: src/components/my-component/index.ts
import { setupComponent, useComponentLifeCycle } from '@goldfishjs/core';
const defaultProps = {};
function useBiz() {
useComponentLifeCycle('onInit', () => {
// Executed when the Component onInit is invoked
});
return {};
}
Component(setupComponent(defaultProps, useBiz));
usePageQuery()
- Type:
function usePageQuery(): Promise<Record<string, any>>;
- Details:
Gets the query parameters in the Page and Component of the mini program. The query parameters are passed in through the onLoad
function.
- Example:
//File path: src/pages/index/index.ts
import { setupPage, usePageQuery } from '@goldfishjs/core';
function useBiz() {
const query = usePageQuery();
query.then(queryData => {
// Print the query object
console.log(queryData);
});
return {};
}
Page(setupPage(useBiz));
Class APIs
Goldfish provides three decorators: state
, computed
, and observable
to turn any class into a reactive one:
import { state, computed, observable } from '@goldfishjs/core';
@observable
export default class MyClass {
@state
public name = 'Jack';
@computed
public get fullName() {
return `${this.name} Ma`;
}
}
state
Marks the normal data member as a reactive data member.
computed
Marks the normal getter or setter as a reactive one.
observable
Turns marked reactive members into real reactive ones.