完成了2-5.md,修订了1-1.md的部分内容

This commit is contained in:
Xu Chang 2024-07-26 20:36:18 +08:00
parent c3ceeff80b
commit 1d8ae7544b
3 changed files with 358 additions and 10 deletions

View File

@ -8,7 +8,7 @@
- [部署](./chapter1/deploy.md)
- [编写应用](./chapter2/def.md)
- [编写对象](./chapter2/1.md)
- [对象之间的关系](./chapter2/1-1.md)
- [查询和操作对象](./chapter2/1-1.md)
- [编写组件/页面](./chapter2/2.md)
- [三层架构](./chapter2/2-1.md)
- [目录文件结构](./chapter2/2-2.md)
@ -17,4 +17,4 @@
- [编写更新组件](./chapter2/2-3-2.md)
- [编写列表组件](./chapter2/2-3-3.md)
- [组织组件](./chapter2/2-4.md)
- [定义组件对象](./chapter2/2-5.md)
- [定义组件](./chapter2/2-5.md)

View File

@ -1 +1,216 @@
# 对象之间的关系
# 查询和操作对象
## 编译
在编写或更新了*Entity*定义后,都需要执行命令来编译完整的**对象数据字典**
```script
npm run make:domain
```
编译出来的数据字典声明在*src/oak-app-domain*目录下,同时也会编译出来一个数据的存储格式供框架引用,可以在代码中像这样去引用它们:
```typescript
import { EntityDict, StorageSchema } from '@project/oak-app-domain';
```
数据字典和存储格式是整个Oak框架最核心的内容贯穿于使用框架的各个层面因此需要深刻理解。本章节将使用上小节的*Address*和*Area*对象,介绍一些查询和操作的核心概念。
## 查询相关概念
### Projection
当查询对象时通过projection可以定义要查询对象的哪些属性projection的命名本身就借鉴了数据库中的“投影”概念。例如对上一节中所定义的*Address*对象,查询时可以指定投影为:
```typescript
{
detail: 1,
name: 1,
phone: 1,
}
```
可以根据对象之间的关系将*projection*扩展到多对一的父对象上,实现**级联查询**
```typescript
{
detail: 1,
name: 1,
phone: 1,
area: { // 查询相关联的Area
id: 1,
name: 1,
parent: { // 查询Area所相关的更高层Area
id: 1,
name: 1,
},
},
}
```
也可以扩展到一对多的子对象上的级联查询,例如我们查询*Area*对象时,可以将之关联的*Address*一并查找出来:
```typescript
{
id: 1,
name: 1,
address$area: { // 查询和area相关的所有address对象
$entity: 'area',
data: {
id: 1,
name: 1,
phone: 1,
},
},
}
```
通过这样的扩展,可以很方便的通过一次查询获得一个对象相关联的所有数据,查询结果会是一个复杂的数据对象(*Schema*
Oak框架还支持表达式查询和函数计算例如如果想返回一个name和phone连接的字符串可以这样写
```typescript
{
id: 1,
$expr: {
$concat: [
"姓名:",
{
'#attr': "name",
},
"手机号:",
{
'#attr': "phone",
}
]
}
}
```
### Schema
Schema是查询某对象后可能获得的数据格式。例如在上面的例子里查询到的*Address*数据结果就是:
```typescript
{
id: 'xxx',
name: 'xxxxx',
phone: '139xxxxxxxx',
areaId: 'xxxx',
area: {
id: '310100',
name: '杭州市',
parentId: '330000',
parent: {
id: '330000',
name: '浙江省',
},
},
}
```
而查询到的*Area*数据结果就是:
```typescript
{
id: '310100',
name: '杭州市',
address$area: [
{
id: 'xxx',
name: 'xxxxx',
phone: '139xxxxxxxx',
},
{
id: 'xxx',
name: 'xxxxx',
phone: '139xxxxxxxx',
}
]
}
```
### Filter
Filter代表查询某个对象的条件例如我们要查询*Area*为杭州市手机号码以139开头的*Address*,就可以这样写:
```typescript
{
areaId: '310100',
phone: {
$startsWith: '139',
},
}
```
可见Filter的算子语法比较接近MongoDBOak框架在此基础上做了大量的扩展例如如果不知道杭州市的areaId我们也可以这样写
```typescript
{
area: { // 把filter扩展到Area父对象上
name: '杭州市
},
phone: {
$startsWith: '139',
},
}
```
同样的Filter也可以被扩展到子对象上比方说我们查询*Area*条件是“该Area上至少有一条相关的Address其手机号码以139开头”不用考虑这个查询是否合理
```typescript
{
address$area: {
phone: {
$startsWith: '139',
},
}
}
```
甚至我们可以查询“该Area上不能有任何一条手机号以139开头的ddress”
```typescript
{
address$area: {
phone: {
'#sqp': 'not in',
$startsWith: '139',
},
}
}
```
具体的Filter算子的语法比较丰富我们罗列在下方部分复杂的语法用户可以查阅*oak-domain/src/types/Demand.ts*。
算子 | 参数类型 | 作用
------------|-----------------------|---------
$gt | number \| string | 大于
$gte | number \| string | 大于等于
$lt | number \| string | 小于
$lte | number \| string | 小于等于
$eq | number \| string \| boolean | 等于
$ne | number \| string \| boolean | 不等于
$in | (number \| string)[] | 在……中
$nin | (number \| string)[] | 不在……中
$mod | [number, number] | 取模
$between | [number, number] | 在……之间(包括等于)
$startsWith | string | 以……开头
$endsWith | string | 以……结尾
$includes | string | 包含……
$exists | boolean | 是否为空
$search<br>$language | string <br> 'zh_CN'\|'en_US' | 全文检索(对象上必须声明了全文索引)
$and | Filter[] | 与
$or | Filter[] | 或
此外Filter还能支持表达式计算和非结构化数据查询。todo
### Sorter
Sorter表示查询时的排序。例如我们查询*Address*时要求结果按手机号排序可以这样写sorter
```typescript
[
{
$attr: {
phone: 1,
},
$direction: 'ASC',
},
]
```
写成数组的形式意味着我们可以按序支持多个sort条件同时也支持将排序的属性扩展到多对一的父对象上
```typescript
[
// 先按phone升序排再按area.name降序排
{
$attr: {
phone: 1,
},
$direction: 'ASC',
},
{
$attr: {
area: {
name: 1,
},
},
$direction: 'DESC',
}
]
```
在查询中,如果不指定任何排序条件,则框架会自动添加一个$$createAt$$属性上的降序排序条件。

View File

@ -1,4 +1,4 @@
# 定义组件对象
# 定义组件
通过OakComponent接口可以定义一个组件对象。在前几章我们已经看到部分重要的定义配置项完整的配置项及格式可以参见```oak-frontend-base/src/types/Page.ts```文件。在编写代码时typescript也会给出有效的提示。在本章节我们将归纳并进一步介绍部分配置项以及组件本身可引用的数据项和方法。
@ -160,12 +160,34 @@ options: {
```typescript
const { oakId } = this.props;
const { name } = this.state;
const { name, oakFullpath } = this.state;
```
在props和state上都包含了一些框架的内置数据项见下节介绍。state上还包含了从formData返回的数据项。
在小程序的渲染xml代码中直接引用数据项名称就可以访问数据项props和state上的所有数据项
在web和native环境下的渲染函数中可以通过参数props中的data来引用所有的数据项。在使用前要通过typescript来声明这些数据项
```typescript
export default function Render(
props: WebComponentProps<
EntityDict,
'system', // 对象
false, // 是否list
{
name: string;
description: string;
},
{
setName: (name: string) => void;
}
>
) {
const { name, description, oakId } = props.data; // oakId是内置数据项
const { setName, execute } = props.methods; // execute是内置方法
}
```
在web和native环境下的渲染函数中可以通过参数props中的data来引用所有的数据项在前面的[编写详情组件](2-3-1.md)中已经有相关示例代码。
在小程序的渲染xml代码中直接引用数据项名称就可以访问该数据项。
#### 框架内置数据项
框架有一些内置的数据项供组件使用。这些数据项都是以*oak*开头来命名的。用户应该避免使用相同的关键字来命名数据项。
@ -228,7 +250,7 @@ features提供了前端公用的能力比较重要的底层feature包括
this.features.cache.refresh(...);
```
同时为了方便起见,框架也将最常用的一批接口暴露到了组件上,这样组件就可以很方便的通过*this*关键字来调用。这批接口包括:
同时为了方便起见,框架也将最常用的一批接口暴露到了组件上,这样组件就可以很方便的通过*this*关键字来直接调用。这批接口包括:
<table style="width: 80%">
<tr style="background-color: var(--table-header-bg)">
<td>feature</td>
@ -301,4 +323,115 @@ this.features.cache.refresh(...);
<td>subDataEvents</td>
<td>订阅数据变化</td>
</tr>
</table>
<tr>
<td rowspan="25">runningTree</td>
<td>refresh</td>
<td>刷新数据</td>
</tr>
<tr>
<td>execute</td>
<td>提交更新</td>
</tr>
<tr>
<td>create</td>
<td>创建非list页面</td>
</tr>
<tr>
<td>update</td>
<td>更新非list页面</td>
</tr>
<tr>
<td>remove</td>
<td>删除非list页面</td>
</tr>
<tr>
<td>setId</td>
<td>设置id非list页面</td>
</tr>
<tr>
<td>getId</td>
<td>获取id非list页面</td>
</tr>
<tr>
<td>isCreation</td>
<td>是否创建非list页面</td>
</tr>
<tr>
<td>loadMore</td>
<td>取下一页list页面</td>
</tr>
<tr>
<td>getFilters</td>
<td>获取当前过滤条件list页面</td>
</tr>
<tr>
<td>setFilters</td>
<td>设置当前过滤条件list页面</td>
</tr>
<tr>
<td>getFilterByName</td>
<td>获取指定命名的过滤条件list页面</td>
</tr>
<tr>
<td>addNamedFilter</td>
<td>增加命名过滤条件list页面</td>
</tr>
<tr>
<td>setNamedFilters</td>
<td>设置命名过滤条件list页面</td>
</tr>
<tr>
<td>removeNamedFilter</td>
<td>删除命名过滤条件list页面</td>
</tr>
<tr>
<td>removeNamedFilterByName</td>
<td>根据命名删除命名过滤条件list页面</td>
</tr>
<tr>
<td>setNamedSorters</td>
<td>设置命名排序条件list页面</td>
</tr>
<tr>
<td>getSorters</td>
<td>获取过滤条件list页面</td>
</tr>
<tr>
<td>getSorterByName</td>
<td>获取命名排序条件list页面</td>
</tr>
<tr>
<td>addNamedSorter</td>
<td>增加命名过滤条件list页面</td>
</tr>
<tr>
<td>removeNamedSorter</td>
<td>移除命名排序条件list页面</td>
</tr>
<tr>
<td>removeNamedSorterByName</td>
<td>根据命名移除过滤条件list页面</td>
</tr>
<tr>
<td>getPagination</td>
<td>获取分页设置list页面</td>
</tr>
<tr>
<td>setPageSize</td>
<td>设置分页长度list页面</td>
</tr>
<tr>
<td>setCurrentPage</td>
<td>设置当前分页list页面</td>
</tr>
</table>
#### 如何使用方法
如上所述,在*formData/生命周期函数/自定义方法*中,都可以通过*this*关键字来调用这些方法,或者通过*this.features*调用更多features上的方法。
- 要注意,在生命周期的*create/attach*函数中runningTree上的许多方法是不能调用的。因为runningTree是在组件开始构建后才进行初始化的更多细节可以参见todo。
在小程序环境下可以直接在xml文件中调用自定义方法。
在react/react-native环境下可以在props.methods中直接调用所有的自定义方法同时也包括一部分公共方法。所提供的公共方法通过typescript声明可以获得而自定义方法需要在props的声明中显式加以定义参看上面「如何使用数据」小节的例子