# 数据的获取

> 一个应用程序如果需要通过API，或者其他渠道获取用于渲染的数据的话，就需要使用到container组件的一个扩展功能 ——— DataProvider
 
## 基本概念
有状态组件不仅能够持有自己的私有数据模型，也可以通过dataProvider配置，来指定通过什么样的方式来获取新的数据，再合并到原有的数据模型中。

dataProvider支持多种获取数据的方法，不同获取数据方式会通过mode属性来进行区分，不同类型的获取数据方式最终都会把获取到的数据合并到原有的数据模型中。

接下来，我们就以ajax获取数据为例子，来展开介绍。

## 基本配置

container组件的dataProvider属性是一个数组类型，数组中每一个元素都是一个对象，用来表述这是一个数据源。

一个container组件支持指定多个数据源来使用。

例如：

```_raw_json
{
    "type": "container",
    "model": "DEMO",
    "dataProvider": [{
        "mode": "ajax",
        "namespace": "firstRequest",
        "config": {
            "url": "https://api.github.com/search/repositories",
            "method": "GET",
            "data": {
                "q": "#ES{$data.name}"
            }
        }
    }],
    "children": []
}
```

每一个dataProvider数据源配置，都需要含有mode,config属性。

### mode属性

mode属性标识这个数据源配置的类型，如果通过ajax来获取数据的话，那么mode的值应该为"ajax",
如果是通过localStorage来获取数据的话，那么mode的值应该为"localStorage"

### config属性

config属性描述这个数据源配置所需要的一些参数和配置项。比如ajax这个功能就需要URL，method这些参数，不同的数据源配置的config都会不一样。

### namespace属性

默认情况下，数据源运行结束之后，会将返回的所有数据都合并到现有的数据模型中，如果同时有2个以上的数据源配置，就很有可能出现数据被覆盖的情况。

因此需要指定给每一个数据源配置都指定一个命名空间，这样写入的数据都会写入到命名空间名称的字段内。


### 传递参数
在发起请求的时候，很多请求都需要带上不同的参数。而参数的值往往都需要从container组件的数据模型中获取。

对于ajax这个数据源类型，它支持在data属性中嵌入ExpressionString来实现每一次发送请求时候，去读取container组件中的值。

在上面例子中有如下的代码。配置中的data属性就表述请求带上的参数值。其中嵌入的`#ES{$data.name}`会在每一次发起请求的时候，计算出当前container组件的nane属性的值，并附在请求的参数上。

```_raw_json
"config": {
    "url": "https://api.github.com/search/repositories",
    "method": "GET",
    "data": {
        "q": "#ES{$data.name}"
    }
}
```


### 获取当前获取数据的状态
大部分的数据源获取操作都是异步的行为，如果开发者想实现加载动画这样的功能，就需要得到在不同加载状态下值。

如果在container组件上使用了带有异步行为的dataProvider数据源配置，数据模型中会自动写入`$loading`这个值。

当请求发起的时候，`$loading`会自动变成true， 当请求结束，`$loading`也会自动变成false。

开发者只需要根据`$loading`这个值来做出不同的判断逻辑即可。

例如：

```json
{
    "body": [
        {
            "type": "container",
            "model": "loading",
            "data": {
                "q": "apollo"
            },
            "dataProvider": [
                {
                    "mode": "ajax",
                    "config": {
                        "url": "https://api.github.com/search/repositories",
                        "method": "GET",
                        "data": {
                            "q": "#ES{$data.q}"
                        }
                    }
                }
            ],
            "children": [
                {
                    "type": "text",
                    "hidden": "#ES{$data.$loading === false}",
                    "text": "loading..."
                },
                {
                    "type": "text",
                    "hidden": "#ES{$data.$loading === true}",
                    "text": "total count: #ES{$data.total_count}"
                },
                {
                    "type": "text",
                    "text": "刷新页面即能看到加载中"
                }
            ]
        }
    ]
}
```
                                                                    
### 返回值验证
凡是获取外部的数据，就必然存在获取失败的情况。我们一样也可以使用ExpressionString来动态验证接口是否出现了问题。

一般来说，获取失败有两种情况。

1. 接口调用失败，直接判定为错误
2. 接口调用成功，但是返回值为错误数据

对于第一种情况，container组件会自动处理，并抛出错误信息。

而对于第二种情况，就需要开发者自行判断了。

每一个dataProvider的数据源配置中都提供了`retCheckPattern`这个属性。 开发者可以使用ExpressionString来动态判断返回的值是否失败。

`retCheckPattern`中含有一个特殊的内置变量——$output

它是数据源返回值的引用，开发者可以通过这个$output来判定返回值是否存在问题。

例如：

一个接口的返回值中，正常情况下，errno为0，不正常情况是不为0.

所以通过添加
```_raw_json
{
    "mode": "ajax",
    "retCheckPattern": "#ES{$output.errno === 0}"
    //...
}
```
来让container得知返回值是否为正确的返回值。

### 使用接口作为组件的默认值
如果需要让某个组件的默认值是来自于接口的一些字段，只需要将接口返回的值，映射成和组件的name一样的名称即可。

使用`responseRewrite`就能对接口的返回值进行映射。

responseRewrite种的每一个key都是将会写入到数据模型的Key，将它和组件的name设置成一样，就能和组件进行绑定。
而每一个key所对应的value既可以是一个常量，也可以是ExpressionString。在ExpressionString通过$output来获取接口返回的值的对象。

**注意：使用responseRewrite之后，写入到数据模型的值，只会是通过responseRewrite解析之后的值**

```json
{
    "body": [{
        "type": "container",
        "model": "basicDataProviderDemo",
        "dataProvider": [
            {
                "mode": "ajax",
                "config": {
                    "url": "/api/mock/dateStartTime",
                    "method": "GET"
                },
                "responseRewrite": {
                    "name": "111",
                    "time": "#ES{$output.data.startTime}"
                },
                "debug": true
            }
        ],
        "children": [
            {
                "type": "input",
                "name": "name",
                "placeholder": "please input name"
            },
            {
                "type": "input",
                "name": "time"
            }
        ]
    }]
}
```


### 自定义错误信息

如果发生错误，默认情况下，dataProvider会生成一个默认的错误信息。但是为了提升用户体验，开发者还需要自定义错误信息。

错误信息的来源一般有2种情况。

1. 错误信息是一个固定值，可以直接在JSON中配置
2. 错误信息来自接口内的数据，不同情况的错误信息都不一样

设置自定义错误信息使用`retErrMsg`属性。

如果直接传入一个字符串，
```_raw_json
{
    "mode": "ajax",
    "retErrMsg": "发生错误"
}
```

这个字符串会在发生错误时候通过弹框弹出。

当然，对于第二种情况的话，可以传入一个ExpressionString。

如果错误信息出自接口数据的`errmsg`字段的话。

那么可以将`retErrMsg`设置为
```_raw_json
{
    "retErrMsg": "#ES{$output.errmsg}"
}
```

## 数据刷新的条件
container组件私有数据源上任意一个字段的更改，并不会触发所有的dataProvider数据源更新。每一个dataProvider数据源，都会判断当前请求的数据配置与上一次请求的配置是否完全相同。只有在不相同的情况下，才会调起数据源更新。不过在一些特殊场合，需要强制进行数据刷新，如果有需要的读者，请看[事件](./guide/event)

## 满足指定条件才请求

如果一个接口发送的前提是所需的数据字段都要存在，则可以使用requiredParams来指定。

如果还要判断每个字段的值隐式转换之后依然为true。则可以设置strictRequired为true。

以下几种数据在隐式转换之后会变为true：

+ 长度大于0的字符串
+ true
+ 不为0的数字
+ 对象
+ 数组

```json
{
    "body": [{
         "type": "container",
         "model": "requiredParam",
         "dataProvider": [{
             "mode": "ajax",
             "config": {
                 "url": "/api/mock/table",
                 "method": "GET",
                 "data": {
                     "flag1": "#ES{$data.flag1}",
                     "text": "#ES{$data.text}"
                 }
             },
             "requiredParams": ["flag1", "text"],
             "strictRequired": true
         }],
         "children": [
            {
                "type": "row",
                "children": [
                    {
                        "type": "checkbox",
                        "name": "flag1",
                        "text": "Flag1",
                        "gridCount": 2
                    },
                    {
                        "type": "input",
                        "name": "text",
                        "gridCount": 5
                    }       
                ]
            }
         ]
     }]
}
```

## 从LocalStorage获取数据
设置mode为localStorage可以从localStorage中读取数据。

`config`需要按照数组的形成来配置，数组中每一个元素都会当做是key，去localStorage中读取。

```javascript
function getDataFromLocalStorage(key) {
    let result = localStorage.getItem(key);
    try {
        result = JSON.parse(result);
    } catch(e) {}
    return result;
}
if (!window.RCRE.filter.hasFilter('getDataFromLocalStorage')) {
    window.RCRE.filter.setFilter('getDataFromLocalStorage', getDataFromLocalStorage);
}
```

```json
{
    "body": [{
        "type": "container",
        "model": "localStorageContainer",
        "data": {
            "LS": "#ES{getDataFromLocalStorage('keyList')}"
        },
        "children": [
            {
                "type": "text",
                "text": "动态根据key来读取数据"
            },
            {
                "type": "row",
                "children": [{
                    "type": "select",
                    "gridWidth": 120,
                    "name": "keyList",
                    "options": [{
                        "key": "key1",
                        "value": "key1"
                    }, {
                        "key": "key2",
                        "value": "key2"
                    }]
                }]
            },   
            {
                "type": "text",
                "text": "值为： #ES{$data.LS}"
            }
        ]
    }]
}
```
