Preface
friends who know or know Vue
all know that in Vue
, we can achieve bidirectional data binding of controlled components through v-model
, while in React
, we need to achieve bidirectional data binding through value
and onChange
. A single can also be accepted, such as multiple.
look at an example.
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} />
</>
)
according to the conclusion above, if there are multiple controlled components in a component, many will be written as above. Can we encapsulate that we only need to declare variables instead of set
methods? The answer is OK, you can see below.
Tips:input is an uncontrolled component when the value of type is file, because the value is readable when type is file.
description
what I use here is React
+ ts
. If you use js
, you need to delete the type declaration after the variable.
withModel
the method component I encapsulated here is withModel
, which you can name after yourself.
//withModel.tsx
import React, { forwardRef, useMemo, useCallback, useEffect } from 'react'
// two-way binding tool method
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
//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 );
pages use
// Page usage
import React, { useState } from "react";
import Input from "./Input"
export default () => {
const data = useState("I am the input.")
return (
<>
<Input models={data} placeholder="Please enter content" />
{/* */}
<p>{`I am the input: ${data [0]} `}</p>
</>
)
}
Summary
principle: use forwardRef
to pass the ref
reference of the current controlled component, and modify it through the withModel
component method.