mirror of
https://github.com/NetEase/tango.git
synced 2026-08-28 17:41:30 +08:00
215 lines
5.4 KiB
Plaintext
215 lines
5.4 KiB
Plaintext
# 管理前端应用状态
|
|
|
|
TangoBoot 采用了一个轻量级的使用 Reactive 的状态管理方案,开发者可以非常轻松的进行页面状态的管理。一个最基本的示例如下所示:
|
|
|
|
```js
|
|
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]` 来获取该引用
|
|
|
|
一个基本的例子如下:
|
|
|
|
```js
|
|
import { defineStore } from '@music163/tango-boot';
|
|
|
|
const user = defineStore({ name: 'Rick' });
|
|
// stores behave like normal JS objects
|
|
user.name = 'Bob';
|
|
```
|
|
|
|
### 复杂的数据结构
|
|
|
|
```js
|
|
// 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);
|
|
```
|
|
|
|
### 异步方法
|
|
|
|
```js
|
|
const userStore = defineStore({
|
|
user: {},
|
|
async fetchUser() {
|
|
userStore.user = await fetch('/user');
|
|
|
|
// or use this
|
|
// this.user = await fetch('/user');
|
|
},
|
|
});
|
|
```
|
|
|
|
### 多个 Store
|
|
|
|
useStore.js
|
|
|
|
```js
|
|
import { store } from '@music163/tango-boot';
|
|
|
|
const userStore = defineStore(
|
|
{
|
|
user: {},
|
|
async fetchUser() {
|
|
userStore.user = await fetch('/user');
|
|
},
|
|
},
|
|
'userStore',
|
|
);
|
|
|
|
export default userStore;
|
|
```
|
|
|
|
recipesStore.js
|
|
|
|
```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` 可以帮你自动监听状态的变化,并在变化时自动触发视图的重新渲染。
|
|
|
|
一个简单的例子如下:
|
|
|
|
```js
|
|
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](https://zh-hans.reactjs.org/docs/higher-order-components.html),可以借助它来实现视图与状态的双向绑定。
|
|
|
|
使用方法如下:
|
|
|
|
```js
|
|
withModel(options)(BaseComponent);
|
|
```
|
|
|
|
### 参数配置
|
|
|
|
其中可配置的 options 参数包括:
|
|
|
|
- `name` 用于配置包裹后组件的 displayName,可选
|
|
- `valuePropName` 绑定的 value 属性名,用于实现双向绑定的受控逻辑,默认为 `value`
|
|
- `trigger` 设置收集字段值变更的时机,默认为 `onChange`
|
|
- `getValueFromEvent` 设置从事件回调中获取 value 值的方法,默认为 `val => val`,直接返回 trigger 的第一个参数
|
|
|
|
嵌套 `whitModel` 后,组件将会获得两个新增的属性:
|
|
|
|
- `model` 用于绑定 Store 中的状态
|
|
- `innerRef` 用于获取内部组件的 [ref](https://zh-hans.reactjs.org/docs/refs-and-the-dom.html) 引用
|
|
|
|
### 双向绑定示例
|
|
|
|
基本用法如下:
|
|
|
|
```jsx
|
|
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>
|
|
);
|
|
});
|
|
```
|