Closed nighca closed 2 years ago
By introducing a new state class ProxyState
, we wrap original state (FieldState
or FormState
) with transform logic, and get a new state instance. For example:
// type of business value
type Uid = number | undefined
// type of view value
type UidInput = string
// transform from view value to business value
function parseUid(uidStr: UidInput): Uid {
const num = parseInt(uidStr, 10)
return Number.isNaN(num) ? undefined : num
}
// transform from business value to view value
function stringifyUid(uid: Uid): UidInput {
return uid == null ? '' : (uid + '')
}
export function createUidState() {
const rawState = new FieldState('') // the `rawState` is for view-binding, we will access it with `state.$`
const state = new ProxyState(rawState, parseUid, stringifyUid) // the `state` is for business usage (parent input, the form...)
return state
}
function createFormState() {
return new FormState({
uid: createUidState()
})
}
type FormState = ReturnType<typeof createFormState>
function handleSubmit(formState: FormState) {
const formValue = formState.value // { uid: Uid }
}
Additionally, we can do this based on FormState
instance, too.
考虑 https://github.com/qbox/www/blob/43970e15ee6e64565fbecaa5131e1667014f63ce/front/2020/components/pages/activity/detail/Banner/Modal/PhoneInfoInput.tsx 这样的场景,给外边的 value 只是部分信息,转回来的时候可能导致信息丢失
closed by #56
考虑 https://github.com/qbox/www/blob/43970e15ee6e64565fbecaa5131e1667014f63ce/front/2020/components/pages/activity/detail/Banner/Modal/PhoneInfoInput.tsx 这样的场景,给外边的 value 只是部分信息,转回来的时候可能导致信息丢失
For information which exists in view value, while does not exist in business value. To avoid losing it, we can read it from the view state when transforming from business value to view value:
export function createFullNameState() {
const state = new FormState({
first: new FieldState('').validators(required),
mid: new FieldState(''),
last: new FieldState('').validators(required)
})
return new ProxyState(
state,
({ first, last }) => `${first} ${last}`, // `mid` is not included in the final full name
fullName => {
const mid = state.$.mid.value // so we read from the state `mid` to keep it
const [first, last] = fullName.split(' ')
return { first, mid, last }
}
)
}
Related solutions:
Background
In many cases, the business value of an input is different from the view value. For example:
The above code shows a typical practice pattern we follow: export
createState
,getValue
& the input component (UidInput
) together. Then the usage code may look like this:It works well in most cases. However there are a few drawbacks:
UidInput
providesgetValue
, inputs based onUidInput
have to providegetValue
to get correct business valueProposal
Consider to integrategetValue
(and the opposite) logic in form state (FieldState
/FormState
), so that we can get business value directly fromstate.value
:Check the updated proposal here.