# fx-schema-form-core

schemaForm的核心组件。用于解析JsonSchema，为SchemaForm提供支持。

## 依赖项

* ajv
* jsonschema

## 重要的概念

jsonschema；用于描述json的结构。
uischema；用于描述界面的表现形式，是jsonschema的增强属性。

## todolist

1. 去掉ajv的依赖

## Schema DEMO

```js
 let schema = {
    type: "object",
    $id: "dnd-oneof",
    title: "oneof测试schema",
    default: {},
    required: ["value"],
    properties: {
        type: {
            type: "number",
            enum: [1, 2, 3, 4],
            title: "类型选择",
            description: "1:数字,2:字符串,3:bool,4:object"
        },
        value: {
            oneOf: [{
                $id: "dnd-oneof-number",
                type: "number",
                title: "这是一个数字类型"
            }, {
                $id: "dnd-oneof-string",
                type: "string",
                title: "这是一个字符串类型"
            }, {
                $id: "dnd-oneof-boolean",
                type: "boolean",
                title: "这是一个bool类型"
            }, {
                $id: "dnd-oneof-object",
                type: "object",
                title: "这是一个object类型",
                default: {},
                required: ["a", "b"],
                properties: {
                    a: {
                        type: "string",
                        default: "nick"
                    },
                    b: {
                        type: "boolean",
                        default: true
                    }
                }
            }]
        }
    }
};
```

## API

### schemaKeysFactory

存储了所有key与schemaKey的对应关系

> example: 'dnd-oneof-object/a': 'dnd-oneof-object#/properties/a',

### schemaFieldFactory

存储了schemaKey对应的schema

example:

```js
{
    'dnd-oneof-object#/properties/a': {
        type: 'string',
        default: 'nick',
        keys: ['value','a'],
        schemaPath: 'dnd-oneof-object#/properties/a'
    }
}
```

### schemaKeyWordFactory

> 所有的keyword处理方式。目前有ref和oneOf；

* ref: 处理schema中的$ref关键字
* oneOf: 处理schema中的oneOf关键字
* anyOf: 处理schema中的anyOf关键字
* definitions: 处理schema中的definitions关键字

### schemaTypeFactory

> schema类型的处理方式

* array     schema中数组类型
* object    schema中的对象类型
* undefined schema中的其他简单类型(string,number,integer,any,boolean)

```js
schemaTypeFactory.add("array", array);
schemaTypeFactory.add("string", none);
schemaTypeFactory.add("undefined", none);
schemaTypeFactory.add("number", none);
schemaTypeFactory.add("null", none);
schemaTypeFactory.add("any", none);
schemaTypeFactory.add("integer", none);
schemaTypeFactory.add("boolean", none);
schemaTypeFactory.add("object", object);
```

### ResolveLib

> 解析schema中的所有字段，存储到【schemaFieldFactory】中

``` typescript
/**
 * 构造函数
 * @param ajv      Ajv的一个实例
 * @param schema   jsonschema
 * @param $id      schema的$id字段，用于找到schema
 */
constructor(private ajv: Ajv, schema: JSONSchema6, public readonly $id = "")
```

### MergeLib

> 解析uiSchama，与对应的schema合并

``` typescript
/**
 * 构造函数
 * @param ajv        Ajv的一个实例
 * @param schema     schemaPath
 * @param parent     父亲schema
 * @param uiSchemas  uiSchemas
 */
constructor(ajv: Ajv, private schemaPath: string, public parent: UiSchema | null = null, private uiSchemas: Array<UiSchema | string> = ["*"]) {
```

## 基础使用

> 定义Schema

``` js
const schema: JSONSchema6 = {
    type: "object",
    $id: "design",
    required: ["name", "dsModelIds"],
    properties: {
        name: {
            type: "string",
            title: "面板名称"
        },
        description: {
            type: "string",
            title: "面板详情"
        },
        appType: {
            type: "string",
            title: "应用类型"
        },
        dsModelIds: {
            type: "array",
            items: {
                type: "number"
            }
        },
        dsModelData: {
            type: "object",
            properties: {
                data: {
                    type: "object"
                },
                ids: {
                    type: "object"
                }
            }
        },
        infoOptions: {
            type: "array",
            items: {
                type: "object",
                properties: {
                    label: {
                        type: "string"
                    },
                    data: {
                        type: "object"
                    },
                    infoOptions: {
                        $ref: "design#/properties/infoOptions"
                    }
                }
            }
        }
    }
};
```

> 解析上一步中定义的schema,curAjv是一个ajv的实例。

```js
let someResolve = new ResolveLib(curAjv, schema);
```

> 最后与定义的uiSchema做合并。

```js
/**
 * 参数说明
 * @param $1 ajv的一个实例
 * @param $2 jsonschema的当前路径
 * @param $3 父亲schema
 * @param $4 uiSchema
 * @return Array类型
 */
let merge = new MergeLib(curAjv, "design", null, ["infoOptions/-"]);
```

返回值：

```json
[{
    "type": "object",
    "properties": {
        "label": {
            "type": "string",
            "keys": ["infoOptions", "-", "label"]
        },
        "data": {
            "type": "object",
            "keys": ["infoOptions", "-", "data"]
        },
        "infoOptions": {
            "$ref": "design#/properties/infoOptions",
            "keys": ["infoOptions", "-", "infoOptions"]
        }
    },
    "keys": ["infoOptions", "-"],
    "schemaPath": "design#/properties/infoOptions/items",
    "key": "design/infoOptions/-"
}]
```

## 如何使用ajv的功能动态获取jsonschema

```json
{
    "type": "object",
    "$id": "echart",
    "title": "echarts参数设置",
    "default": {},
    "properties": {
        "title": {
            "$ref": "echart-title#"
        },
        "toolbox": {
            "$ref": "echart-toolbox#"
        },
        "tooltip": {
            "$ref": "echart-tooltip#"
        },
        "legend": {
            "$ref": "echart-legend#"
        },
        "series": {
            "$ref": "echart-series#"
        },
        "xAxis": {
            "$ref": "echart-axis#"
        },
        "yAxis": {
            "$ref": "echart-axis#"
        }
    }
}
```

在做echart的配置界面的时候，由于参数众多，而且很多参数都重复，不得不把echart的jsonschema拆分成若干个json文件来复用。上面的json文件使用$ref来连接其他的schema，使得schema的依赖过于繁琐。如果我们要使用这个schema，就必须先解析其依赖的schema，这样就有问题了。我们能不能不关系依赖呢？

解决方案：

* 这个方法用于加载我们需要的schema文件。

```jsx
    /**
     * 动态加载schema方法
     * 可以在组件中去使用
    */
    const initSchema = (schemaId: string) => {
        // getSchema是一个接口方法，用来获取json的数据
        return getSchema.get(null, {
            params: {
                id: schemaId + ".json"
            }
        }).then((schema: JSONSchema6) => {
            // 这里使用compileAsync来获取schema的验证信息
            // 这个方法可以解析依赖关系，然后使用ajv中的loadSchema来加载依赖的jsonschema
            curAjv.compileAsync(schema).then(() => {
                // 解析获取的schema，这里已经解析完了所有的依赖
                let resolve = new ResolveLib(curAjv, schema);
            });
        }).catch(() => {
            console.error("fetch schema [" + uri + "] error!");
        });
    }
```

* 配置ajv中的loadSchema方法，用于加载$ref中使用的schema文件。

```jsx
// 初始化ajv实例
export const curAjv: ajv.Ajv = ajvErrors(new ajv({
    allErrors: true,
    jsonPointers: true,
    useDefaults: true,
    format: "full",
    $data: true,
    errorDataPath: "property",
    removeAdditional: true,
    // 关键参数
    loadSchema: initSchema
}));
```

* react示例

```jsx
    import { ResolveLib, schemaKeysFactory } from "fx-schema-form-core";

    class ComponentHoc extends React.PureComponent<any, any> {

        /**
         * 加载完毕后，update当前组件
         */
        private initSchema(schemaId: string){
            initSchema(schemaId).then(this.forceUpdate);
        }

        /**
        * 如果当前的schema不存在，则远程拉取
        */
        public render(): JSX.Element | null {
            const { schemaId, ...extraProps } = this.props;

            if (!schemaKeysFactory.has(schemaId)) {
                this.initSchema(schemaId);
                return null;
            }

            return <span>schema已经解析好了</span>;
        }
    }
```

## 命令

* ```npm test``` 测试命令
* ```npm run build``` 打包命令

## License

[MIT](LICENSE.md)