Browse Source

feat: reducer 引入 immer 管理

master
suanmei 5 years ago
committed by 拾邑
parent
commit
adcbc04d35
  1. 5
      packages/json-schema-editor/package-lock.json
  2. 1
      packages/json-schema-editor/package.json
  3. 6
      packages/json-schema-editor/src/components/field-input.tsx
  4. 17
      packages/json-schema-editor/src/components/schema-item.tsx
  5. 210
      packages/json-schema-editor/src/reducer/schema.ts

5
packages/json-schema-editor/package-lock.json

@ -619,6 +619,11 @@
"@babel/runtime": "^7.3.1" "@babel/runtime": "^7.3.1"
} }
}, },
"immer": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/immer/-/immer-6.0.5.tgz",
"integrity": "sha512-Q2wd90qrgFieIpLzAO2q9NLEdmyp/sr76Ml4Vm5peUKgyTa2CQa3ey8zuzwSKOlKH7grCeGBGUcLLVCVW1aguA=="
},
"import-fresh": { "import-fresh": {
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",

1
packages/json-schema-editor/package.json

@ -59,6 +59,7 @@
"gitHead": "60b114ff262513544204f33a9bbcb6220d6c1c5d", "gitHead": "60b114ff262513544204f33a9bbcb6220d6c1c5d",
"dependencies": { "dependencies": {
"i18next": "^19.4.4", "i18next": "^19.4.4",
"immer": "^6.0.5",
"lodash-es": "^4.17.15", "lodash-es": "^4.17.15",
"react-i18next": "^11.4.0", "react-i18next": "^11.4.0",
"react-tracked": "^1.3.0" "react-tracked": "^1.3.0"

6
packages/json-schema-editor/src/components/field-input.tsx

@ -15,7 +15,8 @@ const CustomInput = styled(Input)`
} }
`; `;
function FieldInput({ value: initValue, changeField, addonAfter }: FieldInputProps): JSX.Element { function FieldInput(props: FieldInputProps, ref: React.Ref<Input>): JSX.Element {
const { value: initValue, changeField, addonAfter } = props;
const [value, setValue] = React.useState(initValue); const [value, setValue] = React.useState(initValue);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => { const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
@ -37,6 +38,7 @@ function FieldInput({ value: initValue, changeField, addonAfter }: FieldInputPro
return ( return (
<CustomInput <CustomInput
ref={ref}
value={value} value={value}
onChange={handleChange} onChange={handleChange}
onBlur={handleBlur} onBlur={handleBlur}
@ -46,4 +48,4 @@ function FieldInput({ value: initValue, changeField, addonAfter }: FieldInputPro
); );
} }
export default FieldInput; export default React.forwardRef(FieldInput);

17
packages/json-schema-editor/src/components/schema-item.tsx

@ -1,4 +1,5 @@
/** @jsx jsx */ /** @jsx jsx */
import { useRef } from 'react';
import { jsx } from '@emotion/core'; import { jsx } from '@emotion/core';
import { Row, Col, Tooltip, Input, Checkbox, Select } from 'antd'; import { Row, Col, Tooltip, Input, Checkbox, Select } from 'antd';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -34,6 +35,8 @@ function SchemaFormItem({
keyRoute, keyRoute,
children, children,
}: SchemaItemProps): JSX.Element { }: SchemaItemProps): JSX.Element {
const fieldRef = useRef<Input>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const schemaDispatch = useSchemaDispatch(); const schemaDispatch = useSchemaDispatch();
const setUIState = useSetUIState(); const setUIState = useSetUIState();
@ -46,10 +49,15 @@ function SchemaFormItem({
}; };
const toggleRequired = (): void => { const toggleRequired = (): void => {
if (!fieldRef.current) return;
const newField = fieldRef.current.state.value;
const fieldKeyRoute = keyRoute.slice(0, -1).concat(newField);
if (required) { if (required) {
schemaDispatch({ type: 'REMOVE_REQUIRED', keyRoute }); schemaDispatch({ type: 'REMOVE_REQUIRED', keyRoute: fieldKeyRoute });
} else { } else {
schemaDispatch({ type: 'ADD_REQUIRED', keyRoute }); schemaDispatch({ type: 'ADD_REQUIRED', keyRoute: fieldKeyRoute });
} }
}; };
@ -66,9 +74,7 @@ function SchemaFormItem({
}; };
const changeField = (value: string): void => { const changeField = (value: string): void => {
setTimeout(() => { schemaDispatch({ type: 'CHANGE_FIELD', keyRoute, field: value });
schemaDispatch({ type: 'CHANGE_FIELD', keyRoute, field: value });
}, 500);
}; };
const changeType = (value: SchemaType): void => { const changeType = (value: SchemaType): void => {
@ -94,6 +100,7 @@ function SchemaFormItem({
</Col> </Col>
<Col span="22"> <Col span="22">
<FieldInput <FieldInput
ref={fieldRef}
value={field} value={field}
changeField={changeField} changeField={changeField}
addonAfter={ addonAfter={

210
packages/json-schema-editor/src/reducer/schema.ts

@ -1,5 +1,6 @@
import produce, { Draft } from 'immer';
import { get, set, unset, remove, cloneDeep } from 'lodash-es'; import { get, set, unset, remove, cloneDeep } from 'lodash-es';
import { SchemaType, SchemaState, SchemaAction, KeyRoute } from '../model'; import { SchemaType, SchemaItem, SchemaState, SchemaAction, KeyRoute } from '../model';
import { defaultSchema, setAllFieldRequired } from '../utils/schema'; import { defaultSchema, setAllFieldRequired } from '../utils/schema';
let fieldNum = 0; let fieldNum = 0;
@ -8,169 +9,154 @@ const setSchema = (schema: SchemaState): SchemaState => {
return schema; return schema;
}; };
const addRequired = (state: SchemaState, keyRoute: KeyRoute): SchemaState => { const addRequired = (state: SchemaState, keyRoute: KeyRoute): void => {
const schema = cloneDeep(state); const field = keyRoute.slice(-1)[0];
const field = keyRoute[keyRoute.length - 1];
const requiredKeyRoute = keyRoute.slice(0, -2).concat('required'); const requiredKeyRoute = keyRoute.slice(0, -2).concat('required');
const required = get(schema, requiredKeyRoute) || []; const required = get(state, requiredKeyRoute) || [];
required.push(field); required.push(field);
set(schema, requiredKeyRoute, required); set(state, requiredKeyRoute, required);
return schema;
}; };
const removeRequired = (state: SchemaState, keyRoute: KeyRoute): SchemaState => { const removeRequired = (state: SchemaState, keyRoute: KeyRoute): void => {
const schema = cloneDeep(state); const field = keyRoute.slice(-1)[0];
const field = keyRoute[keyRoute.length - 1];
const requiredKeyRoute = keyRoute.slice(0, -2).concat('required'); const requiredKeyRoute = keyRoute.slice(0, -2).concat('required');
const required = get(schema, requiredKeyRoute) || []; const required = get(state, requiredKeyRoute) || [];
remove(required, (item) => item === field); remove(required, (item) => item === field);
set(schema, requiredKeyRoute, required); set(state, requiredKeyRoute, required);
return schema;
}; };
const toggleAllChecked = (state: SchemaState, checked: boolean): SchemaState => { const toggleAllChecked = (state: SchemaState, checked: boolean): void => {
const schema = cloneDeep(state); setAllFieldRequired(state, checked);
setAllFieldRequired(schema, checked);
return schema;
}; };
const addChildField = (state: SchemaState, keyRoute: KeyRoute): SchemaState => { const addChildField = (state: Draft<SchemaState>, keyRoute: KeyRoute): void => {
fieldNum += 1; fieldNum += 1;
const newField = `field_${fieldNum}`; const newField = `field_${fieldNum}`;
let schema = cloneDeep(state);
// add new field - string // add new field - string
const newKeyRoute = keyRoute.concat(newField); const newKeyRoute = keyRoute.concat(newField);
set(schema, newKeyRoute, defaultSchema.string); set(state, newKeyRoute, defaultSchema.string);
// add new filed to its parent's required // add new filed to its parent's required
schema = addRequired(schema, newKeyRoute); addRequired(state, newKeyRoute);
return schema;
}; };
const addSiblingField = (state: SchemaState, keyRoute: KeyRoute): SchemaState => { const addSiblingField = (state: SchemaState, keyRoute: KeyRoute): void => {
fieldNum += 1; fieldNum += 1;
const newField = `field_${fieldNum}`; const newField = `field_${fieldNum}`;
let schema = cloneDeep(state);
// add new field - string // add new field - string
const newKeyRoute = keyRoute.slice(0, -1).concat(newField); const newKeyRoute = keyRoute.slice(0, -1).concat(newField);
set(schema, newKeyRoute, defaultSchema.string); set(state, newKeyRoute, defaultSchema.string);
// add new filed to its parent's required // add new filed to its parent's required
schema = addRequired(schema, newKeyRoute); addRequired(state, newKeyRoute);
return schema;
}; };
const removeField = (state: SchemaState, keyRoute: KeyRoute): SchemaState => { const removeField = (state: SchemaState, keyRoute: KeyRoute): void => {
let schema = cloneDeep(state);
// remove field // remove field
unset(schema, keyRoute); unset(state, keyRoute);
// remove filed from its parent's required // remove filed from its parent's required
schema = removeRequired(schema, keyRoute); removeRequired(state, keyRoute);
return schema;
}; };
const changeField = (state: SchemaState, keyRoute: KeyRoute, field: string): SchemaState => { const changeField = (state: SchemaState, keyRoute: KeyRoute, newField: string): void => {
let schema = cloneDeep(state); const oldField = keyRoute.slice(-1)[0];
const parentKeyRoute = keyRoute.slice(0, -1);
const properties = get(state, parentKeyRoute);
const newProperties: { [key: string]: SchemaItem } = {};
// keep the order of fields
// eslint-disable-next-line no-restricted-syntax
for (const field in properties) {
if (field === oldField) {
newProperties[newField] = properties[field];
} else {
newProperties[field] = properties[field];
}
}
// temp old field's value and remove old field set(state, parentKeyRoute, newProperties);
const temp = get(schema, keyRoute);
unset(schema, keyRoute);
// remove old filed from its parent's required // remove old filed from its parent's required
schema = removeRequired(schema, keyRoute); removeRequired(state, keyRoute);
// point to new field and add temp to it
const fieldKeyRoute = keyRoute.slice(0, -1).concat(field);
set(schema, fieldKeyRoute, temp);
// add new filed to its parent's required // add new filed to its parent's required
schema = addRequired(schema, fieldKeyRoute); const fieldKeyRoute = parentKeyRoute.concat(newField);
addRequired(state, fieldKeyRoute);
return schema;
}; };
const changeType = (state: SchemaState, keyRoute: KeyRoute, fieldType: SchemaType): SchemaState => { const changeType = (state: SchemaState, keyRoute: KeyRoute, fieldType: SchemaType): void => {
const schema = cloneDeep(state);
// set field new value // set field new value
set(schema, keyRoute, defaultSchema[fieldType]); set(state, keyRoute, defaultSchema[fieldType]);
return schema;
}; };
const changeTitle = (state: SchemaState, keyRoute: KeyRoute, title: string): SchemaState => { const changeTitle = (state: SchemaState, keyRoute: KeyRoute, title: string): void => {
const schema = cloneDeep(state);
// point to title and set new value // point to title and set new value
const titleKeyRoute = keyRoute.concat('title'); const titleKeyRoute = keyRoute.concat('title');
set(schema, titleKeyRoute, title); set(state, titleKeyRoute, title);
return schema;
}; };
const changeDesc = (state: SchemaState, keyRoute: KeyRoute, desc: string): SchemaState => { const changeDesc = (state: SchemaState, keyRoute: KeyRoute, desc: string): void => {
const schema = cloneDeep(state);
// point to desc and set new value // point to desc and set new value
const descKeyRoute = keyRoute.concat('description'); const descKeyRoute = keyRoute.concat('description');
set(schema, descKeyRoute, desc); set(state, descKeyRoute, desc);
return schema;
}; };
const reducer = (state: SchemaState, action: SchemaAction): SchemaState => { const reducer = (state: SchemaState, action: SchemaAction): SchemaState =>
switch (action.type) { produce(state, (draft: Draft<SchemaState>) => {
case 'SET_SCHEMA': switch (action.type) {
return setSchema(action.schema); case 'SET_SCHEMA':
setSchema(action.schema);
case 'ADD_CHILD_FIELD': break;
return addChildField(state, action.keyRoute);
case 'ADD_CHILD_FIELD':
case 'ADD_SIBLING_FIELD': addChildField(draft, action.keyRoute);
return addSiblingField(state, action.keyRoute); break;
case 'REMOVE_FIELD': case 'ADD_SIBLING_FIELD':
return removeField(state, action.keyRoute); addSiblingField(draft, action.keyRoute);
break;
case 'TOGGLE_ALL_CHECKED':
return toggleAllChecked(state, action.checked); case 'REMOVE_FIELD':
removeField(draft, action.keyRoute);
case 'ADD_REQUIRED': break;
return addRequired(state, action.keyRoute);
case 'TOGGLE_ALL_CHECKED':
case 'REMOVE_REQUIRED': toggleAllChecked(draft, action.checked);
return removeRequired(state, action.keyRoute); break;
case 'CHANGE_FIELD': case 'ADD_REQUIRED':
return changeField(state, action.keyRoute, action.field); addRequired(draft, action.keyRoute);
break;
case 'CHANGE_TYPE':
return changeType(state, action.keyRoute, action.fieldType); case 'REMOVE_REQUIRED':
removeRequired(draft, action.keyRoute);
case 'CHANGE_TITLE': break;
return changeTitle(state, action.keyRoute, action.title);
case 'CHANGE_FIELD':
case 'CHANGE_DESC': changeField(draft, action.keyRoute, action.field);
return changeDesc(state, action.keyRoute, action.desc); break;
default: case 'CHANGE_TYPE':
return state; changeType(draft, action.keyRoute, action.fieldType);
} break;
};
case 'CHANGE_TITLE':
changeTitle(draft, action.keyRoute, action.title);
break;
case 'CHANGE_DESC':
changeDesc(draft, action.keyRoute, action.desc);
break;
default:
break;
}
});
export default reducer; export default reducer;

Loading…
Cancel
Save