qiniu / formstate-x

Manage state of form with ease.
https://qiniu.github.io/formstate-x
MIT License
34 stars 10 forks source link

`FormState` `onChange()` support #40

Closed nighca closed 2 years ago

nighca commented 3 years ago

A method like FieldState's onChange, which activates the state, while without debounce, may be needed for FormState( & ArrayFormState)

With form state's onChange, if we look forward, maybe we should consider remove $, which makes fields manipulation available for users.

For object form state, fields manipulation (remove / add / replace) should be considered unnecessary.

For array form state, we can achieve fields manipulation with value operation (onChange and maybe other related methods such as append, remove, ...):

state.onChange(v => [...v, 'foo'])
// or
state.append('foo')
// instead of 
state.$.push(createFieldState('foo'))

If we avoid manipulation through $, the manipulation of state will be much more limited. So there will be more space for us to add features and introduce optimizations. #18 will partly benefit from this: fields of array form state will always be correctly disposed as we manipulate them inside of formstate-x only.

nighca commented 3 years ago

Better read #37 frist.


Proposal

We prevent write operation of form state via $, by mark $ readonly:

1. For object form state:

state.$ // { readonly [key: string]: Field }

It's almost the same with current behavior, which means, no problem.

2. For array from state:

state.$ // readonly Field[]

Users can not do push / pop / splice operation on state.$ any more. So we provide two other ways to archieve this:

  1. form-level operation API
  2. field-level operation API

Suppose we've got a array form state as follow:

const state = new ArrayFormState(
  ['a', 'b', 'c', 'd'],
  v => new FieldState(v)
)

1. form-level operation

This fits situations when we want to change value in whole.

state.onChange(['a', 'b', 'c']) // [a, b, c, d] => [a, b, c]
state.set(['a', 'b', 'c']) // [a, b, c, d] => [a, b, c]

2. field-level operation

This fits situations when the change is caused by field-level user interaction.

state.remove(from, num) // remove items from specific index
state.remove(1, 2) // [a, b, c, d] => [a, d]

state.insert(from, ...items) // insert items from specific index
state.insert(0, 'e', 'f') // [a, b, c, d] => [e, f, a, b, c, d]

state.append(...items) // insert items at the end of current field list, shorthand for `state.insert(state.$.length, ...items)`
state.append('e', 'f') // [a, b, c, d] => [a, b, c, d, e, f]

state.move(from, to) // move item, with specific from index and target index
state.move(0, 2) // [a, b, c, d] => [b, c, a, d]

Note that field-level operation will preserve state (initialValue, _activated, validation, etc) of unaffected fields.

By now, we ensure that all fields' changes are made by us(formstate-x), so we can dispose fields properly when they are removed.

All users need to do is to ensure that the root form state disposed properly.

lzfee0227 commented 3 years ago

-> #17

lzfee0227 commented 3 years ago
state.move(0, 2) // [a, b, c, d] => [b, c, a, d]

or

state.move(0, 0) // -
state.move(0, 1) // -
state.move(0, 2) // [b, a, c, d]

?

nighca commented 3 years ago

@lzfee0227

state // [a, b, c, d]
state.move(0, 0) // -
state.move(0, 1) // [b, a, c, d]
state.move(0, 2) // [b, c, a, d]
state.move(0, 3) // [b, c, d, a]