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:
copy
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:
copy
//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:
copy
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:
copy
//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:
copy
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:
copy
//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:
copy
function useState<T extends Record<string, any>>(data: T): T;
  • Details:

Converts the normal data to reactive data.

  • Example:
copy
//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:

copy
<!-- File path: src/pages/index/index.axml -->
<view>{{ person.name }} - {{ person.fullName }}</view>

useWatch()

  • Type:
copy
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 of watch()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 of newValueand oldValue are the same as the return value of collector.

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 the collector function. The default value is false.
  • {boolean?} immediate: Whether to execute the callback function immediately after creating the listening relations. The default value is false.
  • {Function?} onError: Triggered when there is an exception in the collector or callback function.
  • Example:
copy
//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:
copy
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 of autorun() 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:
copy
// 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:
copy
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 of setupApp().
  • page: The execution environment is in the synchronization process of setupPage().
  • component: The execution environment is in the synchronization process of setupComponent().

useProps()

  • Type:
copy
function useProps<R extends Record<string, any>>(): Readonly<R>;
  • Execution environment:

useSetup, setupComponent.

  • Details:
    Gets the props object of components and converts it into a reactive object.
  • Example:
copy
//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 in defaultProps. 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:
copy
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:
copy
//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:
copy
function useGlobalDestroy(callback: () => void): void;
  • Details:

Executes custom logic when the App (mini program) is destroyed.

  • Example:
copy
//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:
copy
function useMount(callback: () => void): void;
  • Details:

Executes custom logic when the Page or Component is mounted.

  • Example:
copy
//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:
copy
function useUnmount(callback: () => void): void;
  • Details:

Execute custom logic when the Page or Component is unmounted.

  • Example:
copy
//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:
copy
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:
copy
//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:
copy
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:
copy
//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:
copy
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:
copy
//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:
copy
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:
copy
//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:

copy
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.