Skip to content

Commit f2d0f27

Browse files
committed
update
1 parent c6e1874 commit f2d0f27

File tree

14 files changed

+138
-78
lines changed

14 files changed

+138
-78
lines changed

docs/demos/form/formStore.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from "react"
2+
import { createStore, useForm } from "@autostorejs/react";
3+
import { Button, CheckBox, Input, JsonView, Layout, TextArea } from "x-react-components";
4+
5+
const store = createStore({
6+
users:[
7+
{
8+
name:"张三",
9+
age:18,
10+
address:"北京",
11+
phone:"1234567890",
12+
13+
vip:false,
14+
resume:"非常优秀的员工"
15+
}
16+
]
17+
})
18+
19+
20+
21+
export default ()=>{
22+
23+
const { Form,reset } = useForm<typeof store.state.users[0]>(store,{entry:'users.0'})
24+
const [ state ] = store.useReactive()
25+
return (
26+
<Layout>
27+
<div>
28+
<Form>
29+
<Input name="user.name" label="Name"/>
30+
<Input name="user.age" label="Age" type="number"/>
31+
<Input name="user.address" label="Address"/>
32+
<Input name="user.phone" label="Phone"/>
33+
<Input name="user.email" label="Email" type="email"/>
34+
<TextArea name="user.resume" label="Resume"/>
35+
<CheckBox name='user.vip' label="VIP"/>
36+
</Form>
37+
<Button onClick={()=>reset()}>Reset</Button>
38+
</div>
39+
<div>
40+
<JsonView data={state}/>
41+
</div>
42+
</Layout>
43+
)
44+
}

docs/demos/watch/useWatchDirty.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import { createStore } from '@autostorejs/react';
33
import { ColorBlock,Button,Input, Layout } from "x-react-components"
44

5-
const { state,useFields,batchUpdate,$,useWatch } = createStore({
5+
const { reset,useFields,$,useWatch } = createStore({
66
user:{
77
firstName:"Zhang",
88
lastName:"Fisher",
@@ -15,24 +15,18 @@ import { ColorBlock,Button,Input, Layout } from "x-react-components"
1515
export default ()=>{
1616
const bindings = useFields({entry:'user'})
1717

18-
const [ dirty,setDirty ] = useWatch<boolean>(({path})=>{
18+
const [ dirty ] = useWatch<boolean>(({path})=>{
1919
if(['firstName','lastName'].includes(path[path.length-1])){
2020
return true
2121
}
2222
},{initial:false})
23-
24-
2523
return (
2624
<Layout>
2725
<div>
2826
<Input label="FirstName" {...bindings.firstName}/>
2927
<Input label="lastName" {...bindings.lastName}/>
3028
<Button onClick={()=>{
31-
batchUpdate(state=>{
32-
state.user.firstName = "Zhang"
33-
state.user.lastName = "Fisher"
34-
})
35-
setDirty(false)
29+
reset()
3630
}}>Reset</Button>
3731
</div>
3832
<div>

docs/guide/form/useform/basic.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11

22
# 基本用法
33

4-
`useForm`返回一个`Form`组件,该组件是对标准`form`元素的封装。
4+
`useForm`返回`Form``Field`等组件,该组件是对标准`form`元素的封装。
5+
6+
## 全新表单
7+
8+
可以通过`useForm`创建一个全新的表单,如下:
59

610
```tsx
711
import { useForm } from "@autostorejs/react"
@@ -26,10 +30,17 @@ const { Form } = useForm({
2630

2731
**下面是一个简单的示例:**
2832

29-
<demo react="form/formBase.tsx"/>
33+
demo react="form/formBase.tsx"
34+
35+
:::warning 提示
36+
`useForm`内部会创建一个`Store`实例,用于存储表单数据。
37+
:::
38+
39+
40+
## 基于现有Store
41+
42+
`useForm`还可以基于现有的`Store`实例创建表单,如下:
3043

44+
<demo react="form/formStore.tsx" />
3145

3246

33-
:::info 提示
34-
配置`input`元素的`name=<状态数据路径>`即可。
35-
:::

docs/guide/watch/use-watch.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,16 @@ export interface UseWatchType<State extends Dict> {
3737

3838
## 基本用法
3939

40-
同样的`diray`功能实现如下:
40+
同样的`diray`检测功能实现如下:
4141

4242
<demo react="watch/useWatchDirty.tsx"/>
4343

4444

45+
- `useWatch`创建的侦听会在组件销毁时自动取消订阅,不需要手动取消订阅。
46+
- `useWatch`不会创建`WatchObject`.
47+
- `useWatch``getter`函数可以返回一个值作为`React`状态,该状态会在`selector`所指向的状态变化后自动更新。
48+
- 如果没有指定`selector`,则会侦听`store`对象的所有变化。
49+
4550

4651
:::warning 提示
4752
`useWatch`会在组件销毁时自动取消订阅,不需要手动取消订阅。

packages/core/src/events/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { WatchObject } from "../watch/watchObject";
55
export type StoreEvents = {
66
'load' : AutoStore<any>; // 响应对象创建后
77
'unload' : AutoStore<any> // 响应对象销毁后
8+
'reset' : AutoStore<any> // 对象重置后
89
'computed:created' : ComputedObject // 当计算对象创建时
910
'computed:done' : {id:string,path:string[],value:any,computedObject:ComputedObject} // 当计算函数执行成功后
1011
'computed:error' : {id:string,path:string[],error:any,computedObject:ComputedObject} // 当计算函数执行出错时

packages/core/src/store/store.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export class AutoStore<State extends Dict> extends EventEmitter<StoreEvents>{
144144
this._updatedState![pathKey] = oldValue
145145
}
146146
},{operates:'write'})
147+
147148
}else{
148149
if(this._updatedWatcher){
149150
this._updatedWatcher.off()
@@ -171,9 +172,10 @@ export class AutoStore<State extends Dict> extends EventEmitter<StoreEvents>{
171172
})
172173
}finally{
173174
this._updatedState = {}
175+
this.emit("reset",this)
174176
}
175177
}else{
176-
throw new Error("resetable option is not enabled")
178+
this.log("resetable option is not enabled","warn")
177179
}
178180
}
179181

packages/core/src/types.ts

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -76,34 +76,10 @@ declare global {
7676

7777
export type Primitive = string | number | boolean | null | undefined | symbol | bigint;
7878

79-
export type Dict<T=any> = Record<string,T>
80-
81-
79+
export type Dict<T=any> = T extends (...args:any[])=>any ? never : Record<string,T>
80+
8281

8382
export type ObjectKeyPaths<T> =Exclude<Paths<T,{maxRecursionDepth:30}>,number>
8483

85-
export type GetTypeByPath<State extends Dict,Path extends string> = Get<State,Path>
86-
87-
88-
89-
// type Project = {
90-
// filename: string;
91-
// listA: string[];
92-
// listB: [{filename: string}];
93-
// folder: {
94-
// subfolder: {
95-
// filename: string;
96-
// };
97-
// };
98-
// };
99-
100-
// type ProjectPaths = Paths<Project>;
84+
export type GetTypeByPath<State extends Dict,Path extends string> = Path extends '' | undefined ? State : Get<State,Path>
10185

102-
// type G<
103-
// State extends Dict,
104-
// Name extends Exclude<Paths<State>,number>,
105-
// VALUE extends GetTypeByPath<State,Name> // string
106-
// >={
107-
// name:Name,
108-
// value:VALUE
109-
// }

packages/core/src/utils/getVal.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
import { PATH_DELIMITER } from "../consts";
12
import { getMapVal } from "./getMapVal";
23
import { isMap } from "./isMap";
34

4-
export function getVal(obj: any, keyPath: string[] | undefined,defaultValue?:any): any {
5+
export function getVal(obj: any, keyPath: string | string[] | undefined,defaultValue?:any): any {
56
if(!keyPath) return obj
67
if(keyPath.length === 0) return obj
8+
let paths:string[] = Array.isArray(keyPath) ? keyPath : keyPath.split(PATH_DELIMITER);
79
let val;
810
let parent = obj;
911

10-
for (let i = 0; i < keyPath.length; i++) {
11-
const key = keyPath[i];
12+
for (let i = 0; i < paths.length; i++) {
13+
const key = paths[i];
1214
if (isMap(parent)) {
1315
val = getMapVal(parent, key);
1416
} else {
@@ -18,7 +20,7 @@ export function getVal(obj: any, keyPath: string[] | undefined,defaultValue?:any
1820
if(defaultValue!==undefined){
1921
return defaultValue
2022
}else{
21-
throw new Error(`invalid state path: ${keyPath.join(".")}`);
23+
throw new Error(`invalid state path: ${paths.join(".")}`);
2224
}
2325
}
2426
}

packages/react/src/form/Field.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,6 @@ export function createAutoFieldComponent<State extends Dict>(store: ReactAutoSto
258258
error : validate?.error?.message,
259259
onChange
260260
})
261-
// formCtx.current?.validator!.updateInvalids(name, isValid)
262261
}
263262

264263
const getFieldPropObj = useCallback((path:string[]):[string | undefined,ComputedObject<any> | undefined]=>{
@@ -273,6 +272,7 @@ export function createAutoFieldComponent<State extends Dict>(store: ReactAutoSto
273272
useEffect(()=>{
274273
const watchers:Watcher[] =[]
275274
let count:number = 0
275+
// 当validate字段校验失败时触发错误导致计算出错,此处可以捕获进行更新
276276
watchers.push(store.on("computed:error",({path,error})=>{
277277
const [propKey,propObj] = getFieldPropObj(path)
278278
if(propObj && propKey){
@@ -281,12 +281,13 @@ export function createAutoFieldComponent<State extends Dict>(store: ReactAutoSto
281281
setRefresh(++count)
282282
}
283283
}))
284+
// 侦听字段的变化,当变化时,重新渲染字段
284285
watchers.push(store.watch(name,({value}:any)=>{
285286
Object.assign(renderProps.current!,{value})
286287
Object.assign(renderProps.current!.bind,{value})
287288
setRefresh(++count)
288289
}))
289-
// 侦听所有字段计算属性的变化,当变化时,重新渲染字段
290+
// 侦听所有字段相关的计算属性(validate,visible等)的变化,当变化时,重新渲染字段
290291
watchers.push(store.watch(`#${name}.*`,({path,value})=>{
291292
const [propKey,propObj] = getFieldPropObj(path)
292293
if(propObj && propKey){
@@ -305,7 +306,12 @@ export function createAutoFieldComponent<State extends Dict>(store: ReactAutoSto
305306
Object.assign(renderProps.current!,updated)
306307
setRefresh(++count)
307308
}
308-
}))
309+
}))
310+
// 侦听Field字段的validate属性的变化,当变化里需要更新校验结果
311+
// Field字段的validate属性是一个计算属性,其path="#name.validate"形式,具有以#开头,以validate结尾的特点
312+
watchers.push(store.watch(`#${name}.validate`,({value})=>{
313+
314+
}))
309315
return ()=>watchers.forEach(w=>w.off())
310316
},[])
311317
return <>{props.render(renderProps.current as any)}</>

packages/react/src/form/Form.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { UseFormOptions } from "./types";
55
import { Validator } from './validator';
66
import { findAutoFields } from "./utils/findAutoFields";
77
import React from "react";
8-
import { isEmpty } from "../utils/isEmpty";
98
import { fromStateToField } from "./utils/fromStateToField";
109
import { fromFieldToState } from "./utils";
1110
import { isFalse } from "./utils/isFalse";
@@ -51,16 +50,13 @@ export function createAutoFormComponent<State extends Dict>(store: ReactAutoStor
5150
const options:UseFormOptions<State> = formCtx.current!.options
5251
return (props:AutoFormProps<State>)=>{
5352
const initial = useRef<boolean>(false);
53+
5454
// 仅在初始化时执行一次
5555
const initForm = useCallback(()=>{
5656
const form = options.ref!.current;
5757
if (!form) return;
5858
let initValid:boolean = true
59-
ctx.fields = findAutoFields(form,options.findFields);
60-
if(isEmpty(ctx.fields)){
61-
store.log('No fields found in the autoform', 'warn')
62-
return
63-
}
59+
ctx.fields = findAutoFields(form,options.findFields);
6460
ctx.validator.attach()
6561
// 初始化表单控件的值: 从state读取数据更新到表单控件
6662
initFormFields(store,ctx.fields,options)
@@ -76,15 +72,16 @@ export function createAutoFormComponent<State extends Dict>(store: ReactAutoStor
7672
useEffect(() => {
7773
const form = options.ref!.current;
7874
if (!form) return;
79-
const { entry = [] } = options;
75+
const { entry =[] } = options;
76+
const entryPath = Array.isArray(entry) ? entry : entry.split(PATH_DELIMITER)
8077
// 1. 初始化表单控件
8178
if (!initial.current && form) {
8279
initForm()
8380
}
8481
// 2. 侦听来自变更
8582
const watcher = store.watch(({path, value }) => {
8683
// 2.1 如果变更的路径不是表单的路径,则忽略
87-
if (!pathStartsWith(entry, path)) return;
84+
if (!pathStartsWith(entryPath, path)) return;
8885
// 2.2 更新到表单的输入控件
8986
const spath = path.join(PATH_DELIMITER);
9087
if (spath in ctx.fields) {
@@ -133,8 +130,6 @@ export function createAutoFormComponent<State extends Dict>(store: ReactAutoStor
133130
};
134131

135132
},[]);
136-
137-
138133

139134
return <form {...props}
140135
ref={options.ref}

0 commit comments

Comments
 (0)