最近使用React Hooks結合zarm組件庫,基于js對象配置方式開發了大量的h5表單頁面。大家都知道h5表單功能無非就是表單數據的收集,驗證,提交,回顯編輯,通常排列方式也是自上向下一行一列的方式顯示 , 所以一開始就考慮封裝一個配置化的頁面生成方案,目前已經有多個項目基于此方式配置開發上線,思路和實現分享一下。
使用場景
任意包含表單的h5頁面(使用zarm庫,或自行適配自己的庫)
目標
- 代碼實現簡單和簡潔
- 基于配置
- 新手上手快,無學習成本
- 老手易擴展和維護
寫之前參考了市面上的一些方案 ,大多都是通過定義json schema方式定義一套格式, 比如阿里的form-render ,通過 JSON Schema定義表單, 有可視化編輯器配置,導出json文件,動態渲染,凡此種種,無出其外。 這里就以阿里form-render為代表,談談它的一些缺陷 (優點自行參考官網宣傳)
form-render或思路一些缺陷
1.我的目標是h5, form-remder目前僅僅是pc后臺表單配置方案,提供了antd和fusion主題,默認不支持h5
2.form-render 支持擴展組件,內部維護了一個映射表 mapping, 映射了數據類型和組件的映射關系,比如antd主題各數據類型和antd組件的映射關系如下,如果我有一套自己的/第三方公司統一使用的h5組件庫,需要自己去配置擴展,比較麻煩。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// 例如在antd下映射如下: export const mapping = { default : 'input' , string: 'input' , array: 'list' , boolean: 'checkbox' , integer: 'number' , number: 'number' , object: 'map' , html: 'html' , 'string:upload' : 'upload' , 'string:date' : 'date' , 'string:dateTime' : 'date' , 'string:time' : 'date' , 'string:textarea' : 'textarea' , 'string:color' : 'color' , 'string:image' : 'input' , 'string:email' : 'input' , 'string:url' : 'url' , 'range:date' : 'dateRange' , 'range:dateTime' : 'dateRange' , '*?enum' : 'select' , 'array?enum' : 'checkboxes' , }; |
如果用過/開發過 form-render 等基于json schema的工具, 有一個需求處理比較麻煩, 比如表單字段聯動處理, form-render 提供了有限的幾個聯動屬性,ui:options,ui:disabled,ui:hidden等, 這樣算下來,除了需要掌握form-render定義的數據類型,數據類型主題組件映射,及組件關聯的各種屬性,還要背下額外的聯動屬性,不外乎多學了一門新編程的復雜程度,所以需要可視化界面輔助編輯。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
import FormRender from 'form-render/lib/antd' ; const schema = { type: 'object' , properties: { select: { title: '單選' , type: 'string' , enum: [ 'a' , 'b' ], enumNames: () => [ '顯示輸入框' , '隱藏輸入框' ], 'ui:disabled' : (formData, rootValue) => rootValue.input1.length > 5, 'ui:widget' : 'radio' , }, input1: { title: '輸入框' , description: '嘗試輸入超過5個字符' , type: 'string' , 'ui:hidden' : (formData, rootValue) => formData.select === 'b' , }, }, }; const Demo1 = () => { const [formData, setFormData] = useState({}); return ( <FormRender schema={schema} formData={formData} onChange={setFormData} /> ); }; export default Demo1; |
4.這種配置的json適合非開發人員快速懟出表單功能, 但是不適合開發人員開發擴展,比如我要在兩個輸入框中間放個圖文混排的東西. (另行開發一個自定義組件掛載?)
javascript object 方案
所以以阿里form-render 等以json配置實現動態渲染的方案不能滿足 簡單,快速,突破,共贏的碼農生存準則, 如果把json換成javascript object , 那配置和擴展的能力就不一樣了, 這里我們還是借用form-render的總體思路,但是將type從string,number,boolean,array,object,html等數據類型換成Function,一個Function就是一個React組件,比如antd-mobile ,zarm 的Input組件,因為本文基于zarm, 后面都基于zarm展開(對antd-mobile ,vue等第三方移動端/pc端同樣適用) ,例如下面的配置
- import React, { useState, useEffect } from 'react';
- import FormRenderer from 'zarm-form-render';
- import { Input, Cell, Radio, Select, DateSelect, Button, Toast, Panel } from 'zarm';
- export default function App() {
- const [data, setData] = useState({});
- const layoutData = [
- {
- type: Input,
- label: '被保人姓名',
- placeholder: '請填寫',
- name: 'name',
- },
- {
- type: Radio.Group,
- label: '性別',
- name: 'gender',
- elProps: {
- type: 'button',
- ghost: true,
- },
- items: [
- { label: '男', value: 'male' },
- { label: '女', value: 'female' },
- ],
- },
- {
- render() {
- if (!data.gender) return null;
- return <Cell title="你是" description={data.gender === 'male' ? '男生' : '女生'}></Cell>;
- },
- },
- {
- type: Select,
- label: '愛吃的水果',
- name: 'favfood',
- elProps: {
- dataSource: [
- { label: 'apple', value: 'apple' },
- { label: 'banana', value: 'banana' },
- ],
- },
- },
- {
- type: DateSelect,
- label: '出生日期',
- title: '被保人出生日期',
- placeholder: '請選擇',
- name: 'birthday',
- min: '1900-01-01',
- },
- {
- type: Input,
- label: '手機號',
- placeholder: '請填寫',
- name: 'mobile',
- },
- {
- render() {
- return <div style={{ margin: '30px 6px',}}></div>;
- },
- },
- {
- render() {
- return (
- <Panel title="你錄入的內容">
- <div style={{ margin: '10px 6px' }}>{JSON.stringify(data)}</div>
- </Panel>
- );
- },
- },
- ];
- return (
- <div>
- <FormRenderer layoutData={layoutData} data={data} setData={setData} />
- <Button block theme="primary" onClick={() => Toast.show(JSON.stringify(data))}>
- 確定
- </Button>
- </div>
- );
- }
一個表單由一個數組定義,對象type為要渲染的組件, name為收集數據的key,其他屬性定義組件其他的props, 基于哪個庫,則參考相關庫的組件API文檔配置即可,對于一些特殊的/不存在的組件我們定義render函數動態渲染,然后就沒有其他的了,全部是你熟悉的React, 最終我們可以定義一個自己的FormRender 接受一個一維數組用于從上往下排列表單項(用render不限于表單項,你可以render任意內容), 一個data用于數據收集,一個setData用于數據更新 (源自React Hooks useState), 就這么簡單,源碼如下
- import React from 'react';
- import { Cell, Radio, DateSelect, Select } from 'zarm';
- // 對于無法配置(比如自定義組件,需要根據條件顯示的組件等)的情況, 請使用render方法,
- // getJSON() 動態返回json
- // render() 自定義render
- export default function FormRenderer({ layoutData, data, setData }) {
- const onFiledChange = (name, value) => {
- let v = value;
- // for Select ctrl
- if (Array.isArray(value)) {
- v = value.map((item) => item.value)[0];
- }
- setData({ ...data, [name]: v });
- };
- const onChangeFactory = (name) => (value) => onFiledChange(name, value);
- return (
- <div className="renderer">
- {layoutData.map((item, idx) => {
- if (typeof item.getJSON === 'function') {
- item = item.getJSON();
- }
- if (typeof item !== 'object' || !item) return null;
- const {
- name,
- type,
- description,
- items,
- elProps = {},
- cellProps = {},
- render,
- ...props
- } = item;
- if (typeof render === 'function') {
- return render();
- }
- let children = [];
- if (Array.isArray(items) && type === Radio.Group) {
- children = items.map((it, idx1) => (
- <Radio value={it.value} key={idx1}>
- {it.label}
- </Radio>
- ));
- }
- props.value = data[name];
- props.onChange = onChangeFactory(name);
- if (type === Select) {
- props.dataSource = items;
- }
- if (type === DateSelect || type === Select) {
- props.onOk = props.onChange;
- delete props.onChange;
- props.onChange = elProps.onChange;
- }
- return (
- <Cell key={idx} title={item.label} description={description} {...cellProps} name={name}>
- {React.createElement(type, { ...props, ...elProps }, ...children)}
- </Cell>
- );
- })}
- </div>
- );
- }
配置說明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import * as React from 'react' ; export interface Item { type: React.Component; // 組件類型, 比如Input 等 name: string; // key items?: Array<any>; // dataSource description: string; // Cell description label?: string; // Cell title render?: () => React.ReactNode; //自定義 render getJSON?: () => object | null ; // 動態返回Item配置 elProps?: object; // 組件的props配置 , 比如type為Input, elProps則會配置到Input cellProps?: object; // cell props配置 } export interface Props { layoutData: Array<Item>; // 表單布局配置 data: object; // 數據存儲,name作為key,內容為value setData: () => void; // 數據更新 } interface FormRenderer extends React.FC<Props> {} declare const FormRenderer: FormRenderer; export default FormRenderer; |
上述代碼效果如下
這種方式唯一缺點是無法像json一樣持久化存儲在數據庫 ,優點是可以統一移動和pc端表單配置式開發,少了大量的樣板代碼和嵌套,可以統一處理數據的存取和驗證,表單項的排列方式 。
到此這篇關于基于react hooks,zarm組件庫配置開發h5表單頁面的文章就介紹到這了,更多相關react hooks,zarm組件庫內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://www.cnblogs.com/leonwang/archive/2021/04/05/14618280.html