# 管理前端应用状态 | 使用 Tango 构建你的低代码生产力工具
TangoBoot 采用了一个轻量级的使用 Reactive 的状态管理方案，开发者可以非常轻松的进行页面状态的管理。一个最基本的示例如下所示：

跳到主要内容
🏗 当前版本为 alpha 版本，相关文档正在编写之中，敬请期待！

# 管理前端应用状态

TangoBoot 采用了一个轻量级的使用 Reactive 的状态管理方案，开发者可以非常轻松的进行页面状态的管理。一个最基本的示例如下所示：

    import React from 'react';
    import { defineStore, definePage } from '@music163/tango-boot';

    const counter = defineStore({
      num: 0,
      increment: () => counter.num++,
    });

    // 当状态变化的时候，视图会自动刷新
    export default definePage(() => <button onClick={counter.increment}>{counter.num}</button>);

值得注意的是，开发者必须借助 `defineStore` 来定义状态，它会帮助你创建一个 observable 的状态对象。借助 `definePage` 来定义视图，它会帮你订一个 reactive 的视图组件，并且在自动监听状态的变化，按需进行 UI 更新。

## 创建状态模型 Stores​

基本语法为 `defineStore(storeObject: object, namespace?: string)`

- `storeObject` 为原始的状态对象
- `namespace` 为该 Store 对应的命名空间，如果你定义了，可以借助 `tango.stores[namespace]` 来获取该引用

一个基本的例子如下：

    import { defineStore } from '@music163/tango-boot';

    const user = defineStore({ name: 'Rick' });
    // stores behave like normal JS objects
    user.name = 'Bob';

### 复杂的数据结构​

    // stores can include any valid JS structure
    // including nested data, arrays, Maps, Sets, getters, setters, inheritance, ...
    const user = defineStore({
      profile: {
        firstName: 'Bob',
        lastName: 'Smith',
        get name() {
          return `${user.profile.firstName} ${user.profile.lastName}`;
        },
      },
      hobbies: ['programming', 'sports'],
      friends: new Map(),
    });

    // stores may be mutated in any syntactically valid way
    user.profile.firstName = 'Bob';
    delete user.profile.lastName;
    user.hobbies.push('reading');
    user.friends.set('id', otherUser);

### 异步方法​

    const userStore = defineStore({
      user: {},
      async fetchUser() {
        userStore.user = await fetch('/user');

        // or use this
        // this.user = await fetch('/user');
      },
    });

### 多个 Store​

useStore.js

    import { store } from '@music163/tango-boot';

    const userStore = defineStore(
      {
        user: {},
        async fetchUser() {
          userStore.user = await fetch('/user');
        },
      },
      'userStore',
    );

    export default userStore;

recipesStore.js

    import tango, { store } from '@music163/tango-boot';
    import userStore from './userStore';

    const recipesStore = defineStore(
      {
        recipes: [],

        // 方式1：直接用调用
        async fetchRecipes() {
          recipesStore.recipes = await fetch(`/recipes?user=${userStore.user.id}`);
        },

        // 方法2：使用 this 调用，仅支持 method property，不支持箭头函数
        async fetchRecipes2() {
          this.recipes = await fetch(`/recipes?user=${userStore.user.id}`);
        },

        // 方法3：使用 tango 全局变量调用，你也可以使用这种方式引入其他的 store
        async fetchRecipes3() {
          tango.stores.recipesStore.recipes = await fetch(
            `/recipes?user=${tango.stores.userStore.user.id}`,
          );
        },
      },
      'recipesStore',
    );

    export default recipesStore;

## 创建 Reactive 视图​

你可以借助 `definePage` 创建视图，也可以直接包裹您的原始组件。基本用法为 `definePage(Component)`，借助 `definePage` 可以帮你自动监听状态的变化，并在变化时自动触发视图的重新渲染。

一个简单的例子如下：

    import React from 'react';
    import { view, store } from '@music163/tango-boot';

    // this is a global state store
    const user = defineStore({ name: 'Bob' });

    // this is re-rendered whenever user.name changes
    export default definePage(() => (
      <div>
        <input value={user.name} onChange={(ev) => (user.name = ev.target.value)} />
        <div>Hello {user.name}!</div>
      </div>
    ));

## 实现视图与状态双向绑定​

TangoBoot 提供了一个名为 `withModel` 的 HOC，可以借助它来实现视图与状态的双向绑定。

使用方法如下：

    withModel(options)(BaseComponent);

### 参数配置​

其中可配置的 options 参数包括：

- `name` 用于配置包裹后组件的 displayName，可选
- `valuePropName` 绑定的 value 属性名，用于实现双向绑定的受控逻辑，默认为 `value`
- `trigger` 设置收集字段值变更的时机，默认为 `onChange`
- `getValueFromEvent` 设置从事件回调中获取 value 值的方法，默认为 `val => val`，直接返回 trigger 的第一个参数

嵌套 `whitModel` 后，组件将会获得两个新增的属性：

- `model` 用于绑定 Store 中的状态
- `innerRef` 用于获取内部组件的 ref 引用

### 双向绑定示例​

基本用法如下：

    import tango, { withModel, defineView, defineStore } from '@music163/tango-boot';

    // 定义一个基本的 Input 组件
    class Input extends React.Component {
      foo() {}

      render() {
        return <input {...this.props} />;
      }
    }

    // 实现双向绑定
    const ModelInput = withModel({
      getValueFromEvent(e) {
        return e.target.value;
      },
    })(Input);

    // 定义一个 Store
    defineStore(
      {
        name: 'alice',
      },
      'user',
    );

    // 双向绑定验证
    const ModelApp = defineView((props) => {
      return (
        <div>
          <ModelInput model="user.name" />
          <div>{tango.stores.user.name}</div>
        </div>
      );
    });