ReactWheels11-01form组件

Form 组件

form基本思路

form.example.tsx

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
import * as React from "react";
import Form from "./form";
import {useState, Fragment} from "react";

const FormExample:React.FunctionComponent = ()=>{
const [formData] = useState({
username:'',
password:''
})
const [fields] = useState([
{name:'username',label:'用户名',input:{type:'text'}},
{name:'password',label:'密码',input:{type:'password'}},
])
return (
<div>
<Form value={formData} fields={fields}
buttons={
<Fragment>
<button>提交</button>
<button>返回</button>
</Fragment>
}
/>
</div>
)
}

export default FormExample;

form.tsx

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
import * as React from "react";
import {ReactFragment} from "react";

interface Props {
value: { [K: string]: any };
fields: Array<{name:string,label:string,input:{type:string}}>;
buttons: ReactFragment;
}

const Form:React.FunctionComponent<Props> = (props)=>{
// key 是为了消除警告
return (
<div>
{props.fields.map( f =>
<div key={f.name}>
{f.label}
<input type={f.input.type}/>
</div>
)}
<div>
{props.buttons}
</div>
</div>
)
}

export default Form;

受控组件和非受控组件

1
2
3
4
5
6
const FormExample:React.FunctionComponent = ()=>{
const [name,setName] = useState('hjx')
return (
<input value={name} />
)
}

页面显示一个 input 然后 value 为 “hjx”

  • 但是无法编辑
  • 而且有警告
1
2
3
4
5
6
7
8
9
10
11
12
checkPropTypes.js:20 Warning: Failed prop type: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.
in input (created by FormExample)
in FormExample (created by Context.Consumer)
in Route
in div (created by Content)
in Content
in div (created by Layout)
in Layout
in div (created by Layout)
in Layout
in Router (created by HashRouter)
in HashRouter

想改怎么办?

  • 添加 onChange 事件
1
2
3
4
5
6
const FormExample:React.FunctionComponent = ()=>{
const [name,setName] = useState('hjx')
return (
<input value={name} onChange={(e)=> setName(e.target.value)} />
)
}

困惑

为什么以前什么都不加就可以写,而现在在React要这样写

关系到React的设计哲学

  • UI = f(state) 只要 状态 state 没变 UI就不该变
    1
    2
    3
    fn(1) //返回 2
    fn(1) //返回 2
    // 再次调用时,应该还是返回2 ,这样才符合函数式

其他方式

1
2
3
4
5
6
7
8
9
10
11
12
13
const FormExample:React.FunctionComponent = ()=>{
const [name,setName] = useState('hjx')
return (
<div>
<input value={name} onChange={(e)=> setName(e.target.value)} />
<input defaultValue={name} type="text"/>
</div>
)
}

第一个可以修改 但要添加 onChange

第二个也可以修改 但是拿不到 value的值此时你只能使用 Ref

Ref

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const FormExample:React.FunctionComponent = ()=>{
const [name,setName] = useState('hjx')
// 泛型告诉 refInput.current 就是 input元素
const refInput = useRef<HTMLInputElement>(null);
const x = ()=>{
// refInput.current 就是 input
// 断言有值
console.log(refInput.current!.value)
}
return (
<div>
<input value={name} onChange={(e)=> setName(e.target.value)} />

<input defaultValue={name} ref={refInput} type="text" onBlur={x}/>
</div>
)
}
  • 第一种叫做受控组件(React推荐使用)
  • 第二种叫做 非受控组件(它只是比较方便,如只是显示一个值)

onChange输出最新的值

form.example.tsx

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
32
33
34
35
36
37
import * as React from "react";
import Form, {FormValue} from "./form";
import {useState, Fragment} from "react";


const FormExample:React.FunctionComponent = ()=>{
const [formData,setFormData] = useState<FormValue>({
username:'aaa',
password:'111'
})
const [fields] = useState([
{name:'username',label:'用户名',input:{type:'text'}},
{name:'password',label:'密码',input:{type:'password'}},
])

const onSubmit = (e:React.FormEvent<HTMLFormElement>)=>{
console.log(formData)
}

return (
<div>
<Form
value={formData} fields={fields}
buttons={
<Fragment>
<button>提交</button>
<button>返回</button>
</Fragment>
}
onChange={(newValue) => setFormData(newValue)}
onSubmit={onSubmit}
/>
</div>
)
}

export default FormExample;

form.tsx

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import * as React from "react";
import {ReactFragment} from "react";

// FormValue 就是把整个 value传递回去 有性能问题 但是你有10000条数据的表单吗? 最多提交20条
export interface FormValue{
[K: string]: any
}

interface Props {
value: FormValue;
fields: Array<{name:string,label:string,input:{type:string}}>;
buttons: ReactFragment;
onSubmit: React.FormEventHandler<HTMLFormElement>;
onChange: (value: FormValue)=> void
}

const Form:React.FunctionComponent<Props> = (props)=>{
const formData = props.value;
const onSubmit: React.FormEventHandler<HTMLFormElement> = (e)=>{
e.preventDefault();
props.onSubmit(e);
}
const onInputChange = (name:string,value:string)=>{
const newFormValue = {...formData, [name]:value};
props.onChange(newFormValue)
console.log(name,value)
}
// key 是为了消除警告
return (
<form onSubmit={onSubmit}>
{props.fields.map( f =>
<div key={f.name}>
{f.label}
<input type={f.input.type} value={formData[f.name]}
onChange={(e) => onInputChange(f.name, e.target.value)}
//还可以用bind onChange={onInputChange.bind(null,f.name)}
/>
</div>
)}
<div>
{props.buttons}
</div>
</form>
)
}

export default Form;

表单验证思路

Validator.tsx

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import {FormValue} from "./form";

interface FormRule {
key: string;
required?: boolean;
minLength?: number;
maxLength?: number;
pattern?: RegExp
}

type FormRules = Array<FormRule>

interface FormErrors {
[K: string]: string[]
}

function isEmpty(value: any){
if(value === undefined || value === null || value === ''){
return true;
}
return false;
}

const Validator = (formValue: FormValue, rules: FormRules): FormErrors =>{
let errors: any = {};
const addError = (key: string, message: string) => {
if(errors[key] === undefined){
errors[key] = [];
}
errors[key].push(message);
}
rules.map(rule => {
console.log(rule);
const value = formValue[rule.key];
if (rule.required && isEmpty(value)) {
addError(rule.key, '必填');
}
if (rule.minLength && !isEmpty(value) && value.length < rule.minLength) {
addError(rule.key, '太短');
}
if (rule.maxLength && !isEmpty(value) && value.length > rule.maxLength) {
addError(rule.key, '太长');
}
if( rule.pattern ){
if(!(rule.pattern.test(value))){
addError(rule.key,'格式不正确')
}
}
})
return errors;
}

export default Validator;

form.example.tsx

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import * as React from "react";
import Form, {FormValue} from "./form";
import {useState, Fragment} from "react";
import Validator from "./validator";


const FormExample:React.FunctionComponent = ()=>{
const [formData,setFormData] = useState<FormValue>({
username:'aaa',
password:'111'
})
const [fields] = useState([
{name:'username',label:'用户名',input:{type:'text'}},
{name:'password',label:'密码',input:{type:'password'}},
])

const onSubmit = (e:React.FormEvent<HTMLFormElement>)=>{
console.log(formData)
const rules = [
{key: 'username',required: true },
{key: 'username',minLength: 3 , maxLength:16 },
{key: 'username',pattern: /^[A-Za-z0-9]+$/},
]
const errors = Validator(formData,rules);
console.log(errors)
}

return (
<div>
<Form
value={formData} fields={fields}
buttons={
<Fragment>
<button>提交</button>
<button>返回</button>
</Fragment>
}
onChange={(newValue) => setFormData(newValue)}
onSubmit={onSubmit}
/>
</div>
)
}

export default FormExample;

UNIX 编程艺术

  • 有些代码,没有明显的BUG
  • 另外一些代码,明显没有BUG (我们应该写这种)