前言

了解或会 Vue 的朋友都知道,在 Vue 中我们可以通过 v-model 实现 受控组件的数据双向绑定,而在 React 中则需要通过 valueonChange 实现数据的双向绑定,单个还可以接受,如多个呢。

看个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const [nickName, setNickName] = useState('')
const [age, setAge] = useState(null)

const handleNickNameChange = useCallback((value) => {
setNickName(value)
}, [])

const handleAgeChange = useCallback((value) => {
setAge(value)
}, [])

return (
<>
<Input value={nickName} onChange={handleNickNameChange} />
<Input value={age} onChange={handleAgeChange} />
</>
)

根据上面得出结论,如果一个组件内有多个受控组件,那将会向上面一样写很多个。我们能不能封装一下只需要声明变量,不需要声明 set 方法呢。答案是OK的,可以看下面。

Tips:input 的 type 的值为 file 时为非受控组件,原因是因为 type 为 file 时的 value 处于可读状态。

说明

我这里使用的是 React + ts,使用js的话需要删除变量后面的类型声明。

withModel

我这里封装的方法组件为 withModel ,你们可以根据自身命名。

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
//withModel.tsx

import React, { forwardRef, useMemo, useCallback, useEffect } from 'react'

// 双向绑定工具方法

const withModel = (Component: any) => forwardRef((props, outerRef) => {

const p = {
models: [],
name: '',
value: '',
onChange: (event: any) => {},
...props,
}

const { models = [], name, value, onChange, ...other } = p;

const [modelValue, setModelValue] = useMemo(() => models, [models])

const handleChange = useCallback((event: any) => {
if (setModelValue) {
const setValue = setModelValue as Function;
setValue(event.target.value)
}

if(typeof onChange === 'function') onChange(event)
}, [onChange])

return (
<Component
{...other}
ref={outerRef}
name={name}
value={modelValue !== undefined ? modelValue : value}
onChange={handleChange}
/>
)
})

export default withModel

input.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
//input.tsx

import React, { forwardRef } from "react";

import withModel from '../utils/withModel'


type inputProps = React.DetailedHTMLProps<
React.InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
>

const Component = forwardRef<HTMLInputElement, inputProps>((props, outerRef) => {

const p = {...props};
let { type } = props;

if(!type) type = 'text';

let element = <input ref={outerRef} {...props} />

return (
<>
{!['checkbox', 'file', 'radio'].includes(type) && element}
</>
)
})


Component.displayName = 'Input'

export default withModel(Component );

页面使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 页面使用

import React, { useState } from "react";
import Input from "./Input"

export default () => {

const data = useState('我是输入的内容')

return (
<>
<Input models={data} placeholder="请输入内容" />
{/* */}
<p>{`我是输入的内容: ${data[0]}`}</p>
</>
)
}

总结

原理:使用 forwardRef 将当前受控组件的 ref 引用进行传递,通过 withModel 组件方法进行修改。

搞定收工。