Skip to content

Latest commit

 

History

History
251 lines (195 loc) · 8.26 KB

trait.md

File metadata and controls

251 lines (195 loc) · 8.26 KB

Trait development documentation

In this chapter you can learn how to implement a custom Trait through an example of a Timer Trait.

Trait development and Component have many similarities.

Trait Component
Owned Sepc
Has properties, state, methods
Has Implementation
Implementation is a function

Trait is essentially a function. The role of Trait is to increase the capabilities of the Component. If the object-oriented analogy is still used, Trait is a decorator of Component. For example, adding styles, adding state, etc.

The main idea of ​​Trait is to accept a series of parameters, run some logic, and return TraitResult, thereby enhancing the capabilities of the Component. Obviously, this is the logic of a pure function. Therefore, we recommend implementing Trait as a pure function. Even if it is not a pure function, the implementation needs to consider the result when the Trait is repeatedly executed, and try to avoid unexpected side effects.

⚠️ Since Trait is a pure function, React hooks cannot be used in Trait.

Since Trait doesn't have its own id, Trait and Component share id. That is to say, all the States and Methods exposed by the Trait will be added to the Component, and accessing and calling them is no different from what the Component implements itself.

Trait development tutorial

Next, let's learn how to develop a Trait with an example. We want to implement a Timer Trait that has the following capabilities:

  • Specify a timer for a period of time, and an alert will pop up after the timer expires
  • Provides the function of clearing the timer
  • You can choose whether to start a timer immediately after the Component is mounted
  • Get timer status (waiting, finished, stopped)

Trait Spec

Trait Spec Example

First, let's write a Trait Spec and define some basic information for the Trait, such as version and name:

{
  version: 'custom/v1',
  metadata: {
    name: 'timer',
    description: 'Create a timer to alert, and could be clear.',
  },
  spec: {
    properties: PropsSpec, // see below for details
    state: StateSpec, // see below for details
    methods, // see below
  },
}

Trait Properties Spec

Next, we can start thinking about how to design our Timer Trait. First, we need to design the parameters that need to be passed into the Timer Trait, that is, the Spec of Properties.

First you need a time parameter to control the time of the timer, and a content parameter to display the content of the alert.

In addition, you need to control whether to trigger immediately after the Component is mounted, so there is also an immediate parameter.

So we can get our Props Spec:

const PropsSpec = Type.Object({
  time: Type.Number(),
  content: Type.String(),
  immediate: Type.Boolean(),
});

Trait's Props Spec is also defined by TypeBox.

Trait State Spec

Since Timer Trait also needs to expose state, we also need to add State Spec to define the State exposed by Timer Trait and its type.

const StateSpec = Type.Object({
  status: Type.KeyOf(
    Type.Object({
      waiting: Type.Boolean(),
      finished: Type.Boolean(),
      stopped: Type.Boolean(),
    })
  ),
});

Trait Methods Spec

We also need to provide Method to call externally to start or clear the timer, so we also need to define Method. Here we provide start and stop two methods.

const methods = [
  {
    name: 'start',
    parameters: Type.Object({}),
  },
  {
    name: 'clear',
    parameters: Type.Object({}),
  },
];

Trait Factory & Implementation

After defining the complete Trait Spec, we can start implementing the logic of Timer Trait. The first thing to do is to implement a factory function of a Trait, which returns the Implementation of the real Trait.

In the factory function, we can declare some variables to store data, such as Timer Trait Here we need to create a Map to record the Timer corresponding to each Component so that it can be used to clear the timer.

Since Trait is essentially a pure function and cannot have state, Trait needs a factory function. The factory function provides a closure to store state for the trait. It should be noted that for the same Trait, all Trait Implementation instances share the same js closure.

Let's take a look at the complete implementation code:

const TimerTraitFactory = () => {
  const map = new Map();

  return props => {
    const {
      time,
      content,
      immediate,
      services,
      componentId,
      subscribeMethods,
      mergeState,
    } = props;
    // implement clear method
    const clear = () => {
      const timer = map.get(componentId);

      if (timer) {
        clearTimeout(timer);
        mergeState({
          status: 'stopped',
        });
        map.delete(componentId);
      }
    };
    // implement start method
    const start = () => {
      clear();

      const timer = setTimeout(() => {
        // When the timer expires, an alert pops up
        alert(content);
        mergeState({
          status: 'finished',
        });
      }, time || 0);

      mergeState({
        status: 'waiting',
      });
      map.set(componentId, timer);
    };

    // register the method to the component
    subscribeMethods({
      start,
      clear,
    });

    // return Trait Result
    // Use the componentDidMount and componentDidUnmount lifecycles to start and clear timers
    return {
      props: {
        // start the timer after the Component hangs
        componentDidMount: [
          () => {
            if (immediate) {
              start();
            }
          },
        ],
        componentDidUnmount: [
          () => {
            clear();
          },
        ],
      },
    };
  };
};

Create Trait

Finally, generate the final Trait through the implementRuntimeTrait provided by runtime. So far, the complete implementation of Timer Trait is completed here.

export default implementRuntimeTrait({
  version: 'custom/v1',
  metadata: {
    name: 'timer',
    description: 'Create a timer to alert, and could be clear.',
  },
  spec: {
    properties: PropsSpec,
    state: StateSpec,
    methods,
  },
})(TimerTraitFactory);

Trait API Documentation

Trait Spec and Component are basically the same, with only a few differences.

| parameter name | type | description | | -------- | --------- | ------------------------------ -------- | | version | string | | | kind | "Trait" | Fixed, indicating that this is a Trait Spec. | | metadata | | See below for details | | spec | | See below for details |

Metadata of Trait Spec

The metadata content of Trait Spec is less than that of Component.

parameter name type remarks
name string Trait's name
annotations Record<string, any>

Spec field of Trait Spec

parameter name type remarks
properties JSONSchema
state JSONSchema
methods Record<KMethodName, JSONSchema>

Parameters of Trait Implementation

The parameters accepted by Trait Implementation are almost the same as Component, you can refer to the relevant chapter of Component.

TraitResult

TraitResult is the most important part of Trait. It is the return result of the Trait function and will be passed directly to the Component as a parameter. For more details, please refer to the relevant chapters of the parameters of Component Implementation.

TraitResult is an object with the following properties:

Parameter name Type Required Description
customStyle Record<string, string> No Style map passed to the Component. The value of the object should be a CSS string.
callbackMap Record<string, Function> No Map of callback functions for Component. Mainly used for Event Trait.
componentDidUnmount Function[] No Lifecycle hook. Will be executed after the Component is unloaded.
componentDidMount Function[] No Lifecycle hook. Will be executed after the Component is mounted.
componentDidUpdate Function[] No Lifecycle hook. Will be executed after the Component is updated.