问题
Let's say I have some application state, maintained on some backend system. It looks like this
data MyState = State1 MyState1 | State2 MyState2
data MyState1 = MyState1 { ms1_text :: Text, ms1_int :: Int }
data MyState2 = MyState2 { ms2_bool :: Bool, ms2_maybe_char :: Maybe Char }
I also have a function to get the latest state from the backend system
getLatestState :: IO MyState
I'm pretty sure I can figure out how to package that up into a Dynamic by repeatedly querying the backend, so that I have
dynMyState :: MonadWidget t m => Dynamic t MyState
I want to render this to html. I want each part of the datastructure to render to a div. However, things that don't exist shouldn't be rendered at all - so, when ms2_maybe_char
is Nothing
, there should be no div for it, and when MyState
is a State1
, there should be no div for the State2
.
a couple examples for clarity:
State1 (MyState1 "foo" 3)
becomes
<div class=MyState1>
<div>foo</div>
<div>3</div>
</div>
and
State2 (MyState2 False Nothing)
becomes
<div class=MyState2>
<div>False</div>
</div>
Ideally, each part of the DOM should only be modified if necessary - so if ms2_maybe_char
changes from Nothing
to Just 'a'
, then a new div needs to be created. Or if ms1_text
changes from "foo"
to "bar"
, then we need to change that string in the DOM. However, changing ms1_text
should never cause the sibling or parent nodes to be redrawn.
How should I structure my code? Is this even possible, given the getLatestState
api as a building block? Am I entirely missing the point of Reflex by trying to build off of a single Dynamic
value, and I need to rethink my approach?
In particular, the very first stumbling block is that I can't easily inspect the Dynamic to know if it contains a State1 or a State2. I could potentially use dyn
or widgetHold
here, and fmap
a function over dynMyState
which can treat the state as a simple value and generate a m ()
action that draws the whole thing. But, then I lose all sharing - the entire UI will be redrawn from scratch on every single state change.
Note: this is a more detailed followup question to How can I branch on the value inside a Reflex Dynamic?. What's different/clearer about this question is the additional desire to not lose efficient updates of everything inside the inspected value. Thanks to everyone who helped on that question as well!
回答1:
The answer depends on exactly what your goals and requirements are. If you want the best possible dom sharing, derived from two separate functions renderState1
and renderState2
, I think that calls for virtual-dom
.
But it actually sounds like you want to have some precise control over what gets added to the DOM when.
Something simple you can do, if you have modified versions of renderState1
and renderState2
in hand that each take a Maybe State1
or Maybe State2
argument is to build a pair of these dynamic maybes and use css attributes to hide one or the other:
let mState1 = (\c -> case c of
s@(State1 _ _) -> Just s
_ -> Nothing
) <$> dynMyState
mState2 = (\c -> case c of
s@(State2 _ _ _) -> Just s
_ -> Nothing
nothingHider a m =
let atr = bool mempty ("style" =: "displayNone") . isJust <$> a
in elDynAttr "div" atr (m a)
nothingHider mState1 renderMaybeState1
nothingHider mState2 renderMaybeState2
If you derive Prisms then a lot of the awkwardness can be gotten rid of.
来源:https://stackoverflow.com/questions/40497229/how-can-i-efficiently-branch-on-the-value-inside-a-reflex-dynamic