Because the reactive instances may be shared and held, especially those "handle" / "root" instances. Mutate, so components get updated. Replace, easily resulting components hold old instance.
UI element get updated when component updates, which can be triggered by any other source that affects the component. The update is not "per element" but "whole component". Cannot control the update for elements differently. Also the update is reaction triggered, implicit. Therefore it's difficult to reason.
Todo List
"Todo":
state
id
dueIn: number
title: string
done: boolean
integrity rule:
dueIn must be positive
title must starts with "I will" if not done, or "I have" if done
when updating dueIn, can only decrease not increase (integrity depends on history)
Front End:
input -> data store (state & logiic) -> rest API
Back End:
restAPI -> handler (state & logic) -> persistence API
Codes at https://github.com/bingtimren/vue3-store-solution-tests
DRY - don't repeat yourself
Orthogonal - things not related conceptually should not be tightly coupled
Users can make whatever input they like. UI needs to hold their inputs - even at the moment being invalid. Also wish to give immediate response.
BAD: @input, has to put together the item in elements, using DOM api. this already loses meaning of using Vue.
In this example, item has "title" and "completed", both held in input elements
it could be that both inputs has changed, both input state not in sync with store (because it's still not valid) but only held in elements
upon input, to put together an item from input elements, cannot just use $event.target - because need to gather state from both elements
needs to use DOM API, and depends on DOM structure - whenever DOM structure changes, code changes
one-way binding does not work well:
since state held in element (.value, .checked etc.) is not consistent with store
yet rendering is controlled by reactivity - not explicit
difficult to control when UI is rendering and the scope (my case when error status is rendering, the whole item also renders)
In my experiment: when title in input element is "I will do it" and "complete" is unchecked, (correct state, persisted), and I select all, type "I", component method try to update, validation failed, store puts a "false" to validation result (reactive), triggers UI rendering, then checkbox becomes checked (sync with state, correct), but title also returned to "I will do it", although I wish to hold the invalid state "I" in it. Reason of this: not just the single element is updated, the whole <tr>, perhaps the whole <table> is updated. The rendering / reactivity logic is internal to Vue. This solution depends on it.
BAD: cannot use v-model
Very bad decision I'd say. The whole point of Vue is (1) binding (2) manipulate data, leave rendering (when to do it) to Vue. However this solution requires control of rendering, because HTML element holds a state and wants to keep it. Should not do this.
Failed attempt can be found here: https://github.com/bingtimren/vue3-store-solution-tests/releases/tag/oneway-bind-invalid-state-in-elements
Works, mostly satisfactory
Con:
store needs to track invalid / unsaved items, but it's ok
cannot use v-model, meaning still needs to write update methods in component; though can mostly combine methods
See code here: https://github.com/bingtimren/vue3-store-solution-tests/releases/tag/one-way-bind-to-store-hold-invalid-state-in-store
State: TodoItem[]
Validation State: true / false, or TodoValidation[]
Validator (todoItem) => Validation State
Front-end Store:
retrieves State from backend
exposes readonly State
exposes Validation State
exposes mutation actions (create, update, remove)
inside actions, validate requested update, update Validation State
if to use validator whole-some, needs to construct whole "invalid" item - writes merge code
if to validate only changes - cannot reuse validator, repeat validation logic and result in fragmented validation codes
update State (if valid, or if allowing invalid)
persist immediately, or tracks "dirty" items
exposes persistence method
validate (if allows invalid / partial state), and calls RestAPI
or persist upon every mutation action
not allowing "dirty state"
calls backend API
Front-end UI components:
one-way bind to readonly model
on change, invoke store mutation actions with changed values
bind error UI with Validation State in store
ask store to persist if user click button, or automatically
Back-end:
receive request, extract to-be-updated items
validate to-be-updated items
if valid, persist to storage
if not valid, return error
Discussion:
DRY - state and validator can be reused
Front-end UI Components
cannot two-way bind input element to model, only one-way bind
UI invoking mutation actions - element specific (input.value, checkbox.checked...)
Front-end UI Store
either not allowing partial / invalid state / deferred persistence - pushing that responsibility to Components
or allowing partial / invalid state / deferred persistence - tracking dirty / invalid state nodes
Backend
simple and straight forward: receive request, use core logic to validate, call storage or return error
Front-end Store:
retrieves State from backend
exposes read/write State
exposes Validation State
deep watches state, triggers validation, or exposes validation method
exposes persistence method
validate
either no tracking and replace everything - not practical in many scenario
track / compare => changes
call backend API
or persist upon every mutation action
not allowing "dirty state"
calls backend API
Front-end UI components:
two-way bind to model
bind error UI with Validation State in store
ask store to validate upon change
ask store to persist - upon every change or upon user trigger (click "save")
Discussion:
exposes state to arbitrary change
without deep-watching, cannot track changes
therefore relys on validation, able to reuse validator (if validator supports duel modes), components need to call validation upon every change - becomes repeative
needs to track changed items to call backend API to persist them - still needs deep watch / deep state tracking