定义组件

通过OakComponent接口可以定义一个组件对象。在前几章,我们已经看到部分重要的定义配置项,完整的配置项及格式可以参见oak-frontend-base/src/types/Page.ts文件。在编写代码时,typescript也会给出有效的提示。在本章节我们将归纳并进一步介绍部分配置项,以及组件本身可引用的数据项和方法。

配置项

配置项类型作用
entitystring | () => string组件关联的Entity
isListboolean组件是否为列表页
projectionobject | () => object组件所取的Entity属性
filtersarray组件取Entity的过滤条件(仅对list组件有效)
sortersarray组件取Entity的排序(仅对list组件有效)
paginationobject组件取Entity的分页设置(仅对list组件有效)
getTotalnumber | object组件取Entity时,取满足条件的行总数设置(仅对list组件有效)
propertiesRecord<string, any>组件接受的props
dataRecord<string, any>组件自身的data(React中的state)
featuresarray组件需要监听哪些features的变化
actionsarray组件需要检查哪些actions的许可
zombieboolean组件是否保留数据
formData(params) => Record<string, any>组件处理取到的Entity行数据逻辑
lifetimesRecord<string, function>组件的生命周期方法
listenersRecord<string, function>组件监听属性事件
methodsRecord<string, function>组件自定义方法

entity

本组件关联的对象,如果不设置,则这个组件就是Virtual组件。

projection

组件需要取的对象的属性,结构就是此Entityprojection。可以通过级联取其相关的对象数据。

filters

list组件需要取的对象的过滤条件,结构就是此Entityfilter。由于一个列表页可能会更新不同的过滤条件,因此这里用的是数组结构。您可以在组件的方法中调用addNamedFilterremoveNamedFilter等接口对过滤条件进行增删。

sorters

list组件需要取对象的排序条件,结构就是此Entitysorter。和filters类似,这里用的也是数组结构。

pagination

list组件取数据的分页设置。

  1. currentPage:起始页号,默认0,
  2. pageSize:页面数据条数,默认20,
  3. randomRange:随机取数据范围,用于一些特殊场合。在randomRange标识的范围内随机取pageSize条数据。 pagination还支持数组形式,用一个deviceWidth来标定宽窄屏下不同的配置。

getTotal

list组件是否取当前条件下的行总数量 有时列表页想取得满足当前条件的行数量,此时可以在getTotal上配置一个最大值,框架就会去取得不大于这个数值的总数量。如果满足条件的行的数量大于这个数值,则系统返回此数值(比方说有1亿行数据,此时系统不会去查询这么大量的数据返回1亿,而是只返回getTotal的值),这种设计的用意是当行数量极大时节省系统性能。

和pagination一样,getTotal也支持分设备配置。

properties

组件接受的外部属性值(相当于React中的props)。注意,在小程序环境下,会根据properties的类型向小程序环境注册其属性类型。因此即使属性默认值是null或者undefined,在这里也要用该属性类型来声明初始值,并在组件方法中处理。建议的各种类型的初始值可以是:

  1. number: 0;
  2. string: ''(空字符串);
  3. object: null;
  4. array: [];
  5. function: () => undefined as void;

data

组件的自身内部属性值(相当于React中的state)。

features

组件要监听所配置feature的变化。例如,如果一个页面与用户是否登录状态有关,就应当关联在oak-general-businesstoken上,这样当用户登录或退出时,本页面就会被通知。关于features的介绍请参见todo。

对于每个feature发生变化可以定义监听后的行为:

  1. refresh:组件刷新数据
  2. reRender:组件重新渲染
  3. 自定义回调函数

有些系统级别的feature会被组件默认jtkf,如所有非Virtaul的组件都会默认监听cache的变化,而所有的组件都会监听locales的变化。

actions

组件需要对取得的数据行进行某些action的判定,其值为EntityAction

在Oak框架中,所有的action的行为判定都应该写成checker,加上相关权限判定。组件在取得行数据后,会自动判定当前用户是否可以在这些行上执行这些actions,判定的结果在FormData中作为参数的一部分返回。

zombie

声明组件在析构后是否保留其数据。注意,这个声明只能在顶层组件也就是Page上声明

zombie意味着这个页面在析构时,其在框架中执行的结果——如增加的filters/sorters,更新但尚未提交的数据——是不会丢失的,当用户回到这个页面时,可以恢复到上次的状态。

比方说您在创建某个特别复杂的对象中途,因为某些原因切换到了其它页面上,等回到这个页面时,如果不希望填写了一半的数据消失,就可以使用这个声明。但是要注意,如果在应用中可能某个页面有多个不同的场景,需要仔细设计避免不正确的重用。

formData

当框架被某个feature通知数据有更新时,组件就可能进入重新渲染(reRender) 逻辑。重新渲染会先调用formData函数,并将返回的结果也置在组件的data域中,再调用相应平台下的渲染代码。

formData函数接受一个对象输入参数,其形式声明如下:

options: {
    data: IsList extends true ? RowWithActions<ED, T>[] : RowWithActions<ED, T>;
    origin?: IsList extends true ? RowWithActions<ED, T>[] : RowWithActions<ED, T>;
    features: BasicFeatures<ED> & FD;
    props: TProperty;
    legalActions?: ActionDef<ED, T>[];
    originLegalActions?: ActionDef<ED, T>[];
    dirty: boolean;
    modified: boolean;
}
  • data: 取到的行数据,组件为list时是数组,非list时是对象。在行的oakLegalActions当中返回了声明的Actions中通过的(该行上的)合法操作。
  • origin: 行数据的前项。如果在组件上对行数据有更新但未提交,这里返回的是更新前的原数据。
  • features:所有的features。
  • props:组件的props。
  • legalActions:当组件不是list时,返回该行通过的Actions;如果组件是list,只有当Actions中声明了create时,这里返回的是该用户是否有创建新数据的权限(只有create动作和具体行无关,每行数据相关的action动作检查结果都在该行的oakLegalActions上)。
  • originLegalActions:和legalActions一样,只不过检查的是origin数据上的动作权限。
  • dirty:行上是否有更新,调用组件的update/updateItem方法可以更新行上的属性。
  • modified:行是否真的被更新。和dirty的区别在于:如果将行上的某原值为1的属性改为2,再改为1,此时dirty为true,而modified为false。

formData需要返回一个对象,此对象会被合并到组件的内部数据项(this.state)上,供渲染层和方法层使用。

lifetimes

组件的生命周期函数。组件有以下几个生命周期:

  1. created
  2. attached
  3. ready
  4. detached
  5. moved
  6. error
  7. show
  8. hide
  9. resize

其中5~9的生命周期函数和小程序同构,在其它端暂不支持。重要的是前面4个生命周期:

  • created: 当组件构建时调用(此时组件上的方法和数据都可能未完成准备)
  • attached: 当组件mount时调用(此时组件上的数据已经可用,但部分方法还没有准备完成)
  • ready:组件完全初始化完成,且声明的对象数据已经取得之后调用。此时所有的数据和方法都可用
  • detached:组件准备析构时调用

listeners

组件监听的自身数据(包括props和data)的变化,和小程序的方法声明类似,函数名就是监听的数据项名,其调用参数如下:

(prev: Record<string, any>, next: Record<string, any>) => void

第一个参数传入前项的自身数据字典,第二个参数传入当前的自身数据字典。不过要注意的是,在小程序环境下,props上的变化是无法监听到前项的

methods

组件自定义方法

组件的数据项

组件中有两种数据项: state和props

state

继承了React的语义,表达组件内部的自身数据项,并可以通过setState方法来设置这些项(和类语法下的React完全一样)。

this.state中初始会包括在data配置项中声明的数据,以及formData方法返回的数据。当然,在调用setState方法时,可以不限于只更新data配置项中声明的数据项,但是我们不推荐这样做。

要注意的是,调用setState来更新数据项,会触发渲染函数调用进行重渲染,但并不会触发调用formData。如果您希望执行框架层面上的重渲染,可以在setState的回调方法参数里调用reRender方法。

props

继承了React的语义,表达组件外部传入的属性。为了在小程序环境下能正确处理传入的属性,需要在properties配置项中进行准确声明(上面已经叙述)。

如何引用数据

在组件的各种方法(formData/lifetimes/methods)中,可以通过this关键字访问两种数据项:

const { oakId } = this.props;
const { name, oakFullpath } = this.state;

在props和state上都包含了一些框架的内置数据项,见下节介绍。state上还包含了从formData返回的数据项。

在小程序的渲染xml代码中,直接引用数据项名称就可以访问数据项(props和state上的所有数据项)。

在web和native环境下的渲染函数中,可以通过参数props中的data来引用所有的数据项。在使用前要通过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是内置方法
}

框架内置数据项

框架有一些内置的数据项供组件使用。这些数据项都是以oak开头来命名的。用户应该避免使用相同的关键字来命名数据项。

在props上,有以下内置数据项:

数据项类型意义
oakIdstring非list页面的对象主键
oakDisablePulldownRefreshboolean页面是否禁用下拉刷新(与index.json中所配置的enablePulldownRefresh保持相反)
oakZombieboolean组件在析构时数据是否保留,和配置项中的zombie类似,可用来控制单个组件中的数据保留(对于非页面组件必须在props上配置才能生效)
oakActionsstring[]以参数的形式传入Actions,覆盖组件配置项中的Actions
oakFiltersNamedFilters[]以参数的形式传入Filters,覆盖组件配置项中的Filters

在state上,有以下内置数据项:

数据项类型意义
oakExecutableboolean | Exception组件上的更新是否可以执行。如果更新不能执行,可能会以Exception的方式告知异常
oakExecutingboolean组件是否正在提交更新
oakDirtyboolean组件上是否有更新数据(等同于formData参数上的isDirty
oakModifiedboolean组件上的数据是否真被更新(等同于formData参数上的isModified
oakLoadingboolean是否正在刷新数据
oakLoadingMoreboolean是否正在加载更多数据
oakPullDownRefreshLoadingboolean是否正在下拉刷新数据(只对窄屏有效)
oakEntityboolean组件关联的entity
oakFullpathstring组件在页面“组件树”中的路径
oakLegalActionsstring[]组件上当前合法的Actions(等同于formData参数上的oakLegalActions

善用这些数据项会有效节省您的开发工作量,提升组件的交互性。例如,您可以用oakLoading来判断是否在刷新页面并在渲染中处理这种情况,或者用oakDirty加上oakExecutable来判断页面上是否有可以提交的更新。

组件上的方法

OakComponent配置项的各个函数中,均可以通过this来引用组件上的方法。这些方法可以分为以下三类:

  • methods项中用户自定义的组件上的私有方法
  • 组件的公共方法
  • 从features暴露到组件上的方法

组件的公共方法

组件上的公共方法目前只有以下两个:

方法名参数作用
setStateRecord<string, any>, callback设置组件上的data(同react)
reRender-重新调用formData

未来会考虑加入动画等相关的api

从features暴露到组件上的方法

features提供了前端公用的能力,比较重要的底层feature包括:

  • cache: 前端数据缓存
  • locale: 语言国际化(i18n)
  • runningTree:组件树结构
  • navigator: 页面路由访问
  • localStorage: 前端数据持久存储 等。这些底层的feature都存放在oak-frontend-base/src/features目录下,如果组件需要调用某个feature上的指定接口,可以像这样写:
this.features.cache.refresh(...);

同时为了方便起见,框架也将最常用的一批接口暴露到了组件上,这样组件就可以很方便的通过this关键字来直接调用。这批接口包括:

feature 方法名 作用
localStorage save 存储持久化数据
load 读取持久化数据
clear 清除持久化数据
message setMessage 发消息通知
consumeMessage 消费消息通知
navigator navigateTo 跳转页面
redirectTo 重定向页面
navigateBack 向回跳转
switchTab 切换Tab(小程序专用)
locale t 翻译
cache checkOperation 检查操作可行性
select 查询数据
aggregate 聚合查询
operate 操作数据
subscriber subDataEvents 订阅数据变化
runningTree refresh 刷新数据
execute 提交更新
create 创建(非list页面)
update 更新(非list页面)
remove 删除(非list页面)
setId 设置id(非list页面)
getId 获取id(非list页面)
isCreation 是否创建(非list页面)
loadMore 取下一页(list页面)
getFilters 获取当前过滤条件(list页面)
setFilters 设置当前过滤条件(list页面)
getFilterByName 获取指定命名的过滤条件(list页面)
addNamedFilter 增加命名过滤条件(list页面)
setNamedFilters 设置命名过滤条件(list页面)
removeNamedFilter 删除命名过滤条件(list页面)
removeNamedFilterByName 根据命名删除命名过滤条件(list页面)
setNamedSorters 设置命名排序条件(list页面)
getSorters 获取过滤条件(list页面)
getSorterByName 获取命名排序条件(list页面)
addNamedSorter 增加命名过滤条件(list页面)
removeNamedSorter 移除命名排序条件(list页面)
removeNamedSorterByName 根据命名移除过滤条件(list页面)
getPagination 获取分页设置(list页面)
setPageSize 设置分页长度(list页面)
setCurrentPage 设置当前分页(list页面)

如何使用方法

如上所述,在formData/生命周期函数/自定义方法中,都可以通过this关键字来调用这些方法,或者通过this.features调用更多features上的方法。

  • 要注意,在生命周期的create/attach函数中,runningTree上的许多方法是不能调用的。因为runningTree是在组件开始构建后才进行初始化的,更多细节可以参见todo。

在小程序环境下,可以直接在xml文件中调用自定义方法。

在react/react-native环境下,可以在props.methods中直接调用所有的自定义方法,同时也包括一部分公共方法。所提供的公共方法通过typescript声明可以获得,而自定义方法需要在props的声明中显式加以定义(参看上面「如何使用数据」小节的例子)。