React as a UI Runtime
overreacted.io
overreacted.io
However, the entire mentality of the community is moving towards having behavior tied to specific portions of the tree; in essence it's common-practice to have stateful components. With Hooks this is even more the case.
Instead, the nature of UIs is that changing something in a part of a UI might and will effect something in a completely different location: when I click the button, open a dialog, show a spinner and reset the user profile screen.
The problem with writing stateful components is that the behavior (such as the one above) is tied to the UI representation (at which level of the tree should I write this logic so that it effects all the aforementioned components?).
We are essentially going back to tying logic and representation; something that we've been trying to avoid over the past decade or so.
I'm not saying React doesn't allow you to do differently; things like global-state (Redux, MobX, etc.) allow you to separate behavior and representation quite well. I'm more confused about the recent lack of best-practices surrounding this idea of separating logic-and-UI, and instead they're pushing towards: "Yeah now with Hooks you can even more easily bundle logic-and-UI together and have them tightly coupled". Which to me seems really confusing.
Among other wonderful features, Fulcro has built-in support for state machines [2].
[1]: https://github.com/fulcrologic/fulcro
[2]: https://github.com/fulcrologic/fulcro-incubator/blob/develop...
We are going to be incorporating xstate at a basic level in one of our next projects, but I’d eventually like to see it become entrenched in how we do all UI development.
Doing it in a way that best utilizes the strengths of both of those abstractions will happen eventually, but old habits die hard. As evidence look at all the convoluted ways people try to make all style information public, conflating the public aspects and implementation details.
There is just a ton of sloppy thinking and very little design pattern leadership. For example Airbnb used react for a long time and all it really gave back to the community was an extremely anal set of linting rules which incidentally differ from Facebook’s in annoying, nit picky ways.
One can imagine the amount of bike shedding that such minutiae creates across the entire react community.
A bit like a decade ago and earlier when we still used server-side rendering:
request parameters + backend state = some html
But I believe the GP is referring to interactive UIs, not static HTML. For example, you would never have a date picker widget render each month's calendar using a full server-side page refresh.
- either you're coupling the logic and the representation.
- or you have coupling between your different UI components and the global application.
You're talking about the first kind of coupling and say it's bad, but IMHO the second kind is even worse in some cases.
There is no silver bullet, it's all about tradeoffs.
But I think we are learning learned that complex state is graph shaped. There aren't any good answers here. You need a real graph "database" in the UI
I think it also shines light on why GraphQL has proven to be such a great fit for UI programming, more so, I feel, than inherently tree-oriented solutions to state management like Redux.
This quote from a blog post from the Apollo folks really internalized the essence of GraphQL for me:
"GraphQL allows us to extract trees from the app data graph."
https://blog.apollographql.com/the-concepts-of-graphql-bc68b...
Highly recommend taking a look at the article if you're interested at all in GraphQL. I think the first few sections at least serve as a great primer, though later sections go into Apollo specific implementation details that might not be as interesting for a newcomer.
The apples to apples comparison to Redux here would be approaches like Apollo's apollo-link-state and Relay's client schema extensions that allow you to manage local state with GraphQL (in addition to server state).
Although Redux also allows you to store state in a purely normalized form that's more amenable to graph traversal, and the prevailing wisdom in the community says that is the de-facto approach for managing state in complex apps, Redux certainly doesn't enforce it or make it effortless and foolproof. That is in contrast to most GraphQL based solutions where normalized state stores are the default, and you have to work to deviate from it.
In essence, with GraphQL-based solutions, you conceptualize your data as a graph, extracting trees out of it to use in your UI (which is fundamentally a tree) through queries/fragments. That Apollo/Relay stores the data in a normalized tree by default is an implementation detail that you normally wouldn't have to worry about (in fact there are alternative implementations of the Apollo cache that stores data in graph form to enable a different set of performance tradeoffs: https://github.com/convoyinc/apollo-cache-hermes). Whereas with Redux, that implementation detail is the user's responsibility, i.e. you have to manually implement the transformation of your data graph into tree form, normalized or not.
I can see that potentially changing if/when AR/VR takes off and we start building UIs that are 3D in nature, for instance, since the hierarchical structure of a tree no longer make any sense for objects in 3D space that have 6DOF.
A-frame might offer some early insights in this area since it's attempting to bridge the gap between the 2D document model of the web and 3D interfaces of AR/VR.
Soon however, I realized that in practice (at least in the context of a React-based application), there's little to be gained by storing state that is truly temporal/localized in a persistent/global store (which is necessary for enabling a truly pure mapping of state to UI), and that there's in fact much to lose, because:
1) By nature of being temporal, they're state that needs to be initialized when they need to be used and destroyed afterwards, otherwise we risk exposing stale state to the next usage in time.
The initialization/destruction process often needs to be synchronized with some lifecycle of a component. Animations on mount/unmount, and form input that needs to reset when closing a modal, etc, are great examples of this.
If we used component local state to begin with, that state lives and dies with the appropriate component automatically, with no additional ceremony.
On the other hand, if we were to store that temporal state in a persistent state store, we'd still end up having to create components that hook into component lifecycles to create/destroy that state at the appropriate time in the persistent store manually, which, even if we ignore that it's often a tedious, error prone process, means introducing impure components into our tree of pure components anyways, so we've really gained nothing over the component local state approach.
Not to mention that temporal state is often frequently updated (as an example of an extreme, FLIP animations (https://css-tricks.com/animating-layouts-with-the-flip-techn...) by nature require at least 60 state updates per second), and having a few of these in a persistent state store can wreak absolute havoc on application performance if we mess up even a little bit in our pure rendering optimizations, which is notoriously tricky to get right perfectly due to all the edge cases we have to be mindful of (not creating new event handlers functions with every render is especially tricky in a codebase that's committed to using only "pure" components; in fact, I'm not sure if that's even possible in the strictest definition, since all the techniques I've worked with involve creating an impure class component to memoize the function, and the new useCallback hook likely isn't pure in the strictest sense either).
2) By nature of being localized, they're state that no other component should ever be concerned with.
Making them accessible at all to other components, as we do by storing it in a global state store, is by definition a leak of implementation detail, which over the long term makes it harder to change the components that use localized state, because we have no easy way to guarantee its "localized" state isn't being depended on by something else. In fact, to suggest that they're using "localized" state at all at this point is more wishful thinking than anything else, because nothing exists to enforce this localization once we put it in the global store.
At best they're just extra noise we need to ignore/filter out when debugging/serializing the state of the store, and at their worst they can lead to extremely brittle and over-entangled component trees where changing one part of the tree can inexplicably break seemingly unrelated parts.
On the other hand, component local state is for all intents and purposes, truly localized. We cannot access component local state outside of the component itself unless we explicitly expose it to children as props or to parents by refactoring it up a few levels, at which point we're making the explicit decision to change the locality of that state to include more/different components, and that decision is completely self-evident.
Whenever we decide to use component local state, we can rest assured knowing that their locality is enforced by the React runtime, rather than some handwavy global store access convention that we'd otherwise have to resort to if we stored localized state in a global store, which involves an ongoing cost in terms of enforcement while offering little in the way of real safety.
To be perfectly honest, some/all of the points I mentioned could potentially be attributed to React not abstracting us far enough away from the stateful nature of the underlying platforms it builds upon (the DOM for web, native UI platforms for React Native). For the case of temporal state, I can imagine for instance a potential alternative/higher-level library where React's stateful lifecycle hooks are replaced with some elegant purely functional primitive for supporting the same use cases, perhaps something that models _time_ explicitly as part of state, like Datomic does with transactions (nothing similar comes to mind for handling localized state though, so perhaps encapsulation and true purity are just at odds on a fundamental level).
Though I have not yet seen anything that would enable a truly pure state -> UI mapping for building non-trivial applications while avoiding the aforementioned drawbacks, I'd of course happily re-evaluate my position on the feasibility of this approach when I encounter such a solution.
Perhaps, I mis-understood, but when I do .setState({data}, fnToCallAfter)
as a user of React, I do not really know where the state is stored, in other words, I do not store in global/persistent store.
---
I do use React.Context quite a bit to store my LoginState, my special purpose in memory store (using Immutable.js) that acts as cache for some often used/expensive to get backend data. When my LoginData, or CacheData get updated, react automagically calls 'render' (and static friends) on all the mounted components that have signed up to observers to the Context's changes.
This is very similar to newly release AndroidX jetpack Lifecycle-aware ViewModel, that calls the observer methods, only on activites/fragments whose lifecycle is 'active' (this reduces complexity of managing android activities during the 'rotation/config' changes.
---
I am just not clear where you run into a situation where you had to use the component-level state management in react, that required you to see the innerworkings (or implement) your mechanism to store component's state.
It turns out it is a lot easier to program dynamic 3D scenes using this method, rather than trying to do two-way binding like Angular, JQuery, etc do. And the performance is typically better too. Imagine trying to do two-way binding in a AAA game with 1,000s or even 100,000s of individual objects in a scene. It would be a programming nightmare! Instead much of the effort is spent trying to reduce the amount work the GPU has to do when rendering the entire scene such as occlusion culling, distance-based level of detail, etc. React's virtual DOM renderer is also trying to render the scene (DOM) as efficiently as possible. But admittedly it is much simpler than Unreal Engine's renderer, but the high-level concept is similar.
I think this is why React feels so natural to so many people when building dynamic web UIs compared to JQuery, Angular, and even Vue.
React has certain weaknesses that aren’t seen in other UI libraries/frameworks as well - forms are a complete mess to work with, even with formik, and animations are pretty problematic.
As with anything, there are tradeoffs to approaches. FB isn’t as form heavy in general, so optimizing how it works for the rendering inputs from external sources & dev usability makes sense for their use cases. It’s important not to conflate this by overpraising.
Could you elaborate on this? I've been creating form heavy react apps for a couple of years now and haven't really come across anything that would make it 'a mess'.
However, part of that complexity is the fault of React. The choice of HOC vs. Component... two ways to do the same thing. There is also FastForm vs. Form. As we move on over time, now we have hooks being added as well. The mental complexity just to put a simple input on a page is overwhelming.
I think what I'm looking for is React to take some ownership and provide guidance out of the box for these core browser features under the React umbrella.
So we need React on Rails? An opinionated collection of libraries that work on top on the React Runtime. I personally find this idea compelling.
What I'm talking about is something more akin to this statement: https://angular.io/guide/forms-overview "Handling user input with forms is the cornerstone of many common applications"
Styling is handled out-of-the-box. Reactiveness will be glitch free with proxies and it will have TS support.
(Disclaimer: I use React right now)
If you just want to put an input on the page, that’s just three lines with React. (Especially with Hooks.)
https://reactjs.org/docs/forms.html
Makes my point (happily) moot. You're right. Sorry for the trouble.
There is another component to React which I didn't mention that gets inspiration from the 3D world: the API. When I first saw React's API, the first thing I thought was "that looks kinda like OpenSceneGraph, cool!" Now this is the very subjective part, having spent 8-9 years doing 3D graphics React felt natural to me. Given its popularity, I must not be the only one either. I am glad you like the Vue API but it doesn't feel like a 3D engine. For whatever reason, many people seem to prefer the 3D engine feel. On purely technical merits, I think React and Vue are basically the same. But I think there is some intangible difference in how the tools feel. Some people like React and some like Vue and both are right. However more people seem to like React.
I would postulate that the subset of people who are both web developers and have also worked on 3D graphics is a relatively small slice, so I wouldn't be convinced that a 3D engine-like API is the reason for its popularity.
React on the other hand tries as hard as possible to avoid rendering, in fact it's whole design of immutability isn't because functional style is better, it's just convenient for it's requirements to avoid rendering.
For me react is more of an antichrist of UI tech, as much as I use it and rely on it, the day I can just mutate some state and that renders (i.e. .. just straight up dom), the better.. if I want to then layer on some immutability patterns then that's entirely in my apps domain.
3d engines often go to great lengths to avoid sending unnessesary data to the GPU. Frustum culling or more sophisticated occlusion culling are standard practices.
React feels more like "retained mode rendering engines" which were popular in the 90's and early 2000's.
Diffing and piecemeal updates are an implementation detail/optimization.
Even before react, I had webapps where I'd cobbled together helper objects that did things pretty similar to react, although obviously not as robust.
I've been doing GUI apps for long enough to have seen a few attempts in a variety of IDEs (and languages). I like React exactly for this reason - that it models what they've done with components, but in a way that is more robust and flexible. IDEs tend to come and go but JSX as a language seems to have grown beyond React. It's a relatively good way of describing UI that can be read and understood while leaving ample space for dynamic composability which has historically been an issue to represent in the IDE UI builders I've used. I've usually had to resort to mixing UI builders and own code, or giving up on the UI builder entirely and instantiate classes myself.
I like React because of the trade-offs. It's not perfect. It's just imperfect in some acceptable ways.
And Desktop UI libraries tend to be even more complex. And as usual, warts are on the outside.
https://www.bitquabit.com/post/the-more-things-change/
Prior discussion: https://news.ycombinator.com/item?id=10381015
Certainly when I first saw it my thought was, "hey someone did Cocoa for the Browser, nice".
https://blog.metaobject.com/2018/12/uis-are-not-pure-functio...
"One way data flow" is pretty much exactly how MVC works, except that MVC decouples a bit more: the model is only allowed to send #changed notifications, the view then pulls what it needs. The data still only flows in one direction.
"Actions" in Redux are very much the "Command" pattern as used in C++ GUI frameworks such as PowerPlant.
That's been my revised working assumption, but it's almost completely unclear from your writings, and also comes from a fairly deep (but understandable!) misunderstanding of GUI frameworks.
addSubview is not a defining feature of most GUI frameworks. drawRect is.
addSubview is used once in construction, and then you're done. And a lot (if not most) of the time it is hidden, because you just load a GUI definition. For example in Cocoa, you define your GUI in Interface Builder. IB saves a nib/xib, which is an serialised object graph with parameters. You then load that nib and voilà, there's your GUI!
The GUI is fully static/declarative. It reacts to dynamic content by drawing it in its "drawRect" method.
So where does the misunderstanding come from? It comes from recent changes in how developers (ab-)use GUI frameworks. I haven't full grokked how this came about, but it seems to be part (a) widget toolkits (b) horrible drawing APIs and (c) the iPhone with LayerKit/CoreAnimation.
This change has been that for example, when there is just some dynamic text to draw, people have been using text-label widgets instead of just drawing the damn text in drawRect. So suddenly you have people constructing, adding and removing views at runtime as a matter of course, rather than as something done in exceptional circumstances, which I gather is what you (rightly) object to.
However, this is not the "programming model" of GUI frameworks, it is an abuse of those GUI frameworks. Which is why your idea that the difference is about programming model, while understandable and somewhat defensible, is ultimately mistaken.
To put it succinctly, people are "drawing with widgets", instead of drawing in drawRect: like they're supposed to. So instead of drawRect, they are using addSubview to draw. However, widgets were not meant as a medium for drawing, they were meant as mechanism for constructing the (mostly) static UI that then draws itself, including the dynamic parts. As it is not really the supported way, it is cumbersome and error-prone.
If you were to actually adapt the framework APIs to a "drawing with widgets" model, every view would have a "drawSubviews" method in addition to or in lieu of the "drawRect" method.
See also: UIs Are Not Pure Functions of the Model - React.js and Cocoa Side by Side
https://blog.metaobject.com/2018/12/uis-are-not-pure-functio...
There is also a deeper pattern there, which goes back all the way to the beginnings of computer graphics: the back-and-forth between "object-oriented" graphics (see GKS[1], PHIGS[2]) and immediate-mode graphics. (Note that this is not OO in the computer language sense, but in the computer graphics sense).
Everybody, it seems has this idea that it would be nice to have a declarative tree of your graphics (and it also happened historically as a result of display-lists for vector graphics). It would also be nice to have a reusable library/API for this. Enter GKS/PHIGS. But then it turns out that things don't quite match up, so you end up having to express your domain graphics as complex (sub-)trees. So you need to imperatively edit the shape tree/database. Which is complex, painful and error-prone. In the end, it becomes easier to just drop the entire shape database and re-create it from scratch every time. At which point the whole idea of a shape database becomes somewhat moot.
Enter immediate mode graphics. See OpenGL, Postscript, Quartz, etc.
However, drawing everything procedurally is cumbersome. So you add back structure and composable elements. So let's have them be domain-/application-specific, draw themselves in immediate mode and also handle interaction. We might call them "Views". What's neat about Views is that they straddle the "object-oriented" and "immediate" graphics divide, and you can decide yourself where you want to be. You can shift more to the object/widget side and use object-composition, or you can shift towards the immediate-mode side and use procedural drawing. Best of both worlds, at least in theory.
And then things happen that make people shift towards the object-graphics side (sucky graphics APIs, phone UIs etc.) and lo-and-behold, we have the same problems that we used to have with GKS/PHIGS! And then we propose the same solution (modulo environmental and accidental differences).
And round and round we go.
drawRect is a primitive but once you start dealing with layout and text measurement I think it can get hairy and at that point you might end up with imperative subview soup again. Somehow people using React don’t fall into that.
drawRect is low level because it only specifies rendering. But UIs usually care about local state and when to destroy or create it. Especially in lists. That’s something I mention in the post which React has a solution for but I don’t think drawRect is sufficient for expressing this generally. See the ShoppingList reordering example.
And that's great. But then please argue/describe from practice, and not from some largely mythical fundamental differences in programming model that only confuse. That would be really helpful, thanks.
> layout and text measurement
Yup, as I mentioned, the text APIs in Cocoa/CocoaTouch/Quartz are so rancid that just slapping on a TextLabel is incredibly more convenient, despite the fact that you get horrible messy subview soup (I like that term, can I borrow it?).
The solution would probably be better text APIs. Which are actually not that hard to build.
https://github.com/mpw/DrawingContext
(Alas, the text stuff in particular is only partially complete, I had more important projects. The very rough idea is to be able to essentially printf() into a view)
> drawRect [..] only specifies rendering.
Yep.
> But UIs usually care about local state
Right, that's why drawRect is embedded into these things called Views, which have local state.
> Especially in lists.
Right. And you have easy-to-use Views like NSTableView that handle lists beautifully, without you having to worry about the active set of subviews. Essentially you give it a data source and it will ask the data source about what it needs, when it needs it. Meaning it can handle arbitrarily large/infinite lists without problems. There are layers of customizability, from just delivering data via specifying cells to customise drawing/interaction all the way to having the NSTableView use arbitrary subviews to represent rows/columns.
https://developer.apple.com/documentation/appkit/nstableview...
No new programming model required, just a view within the current programming model.
And of course if you create your own views, they handle both the drawing (drawRect) and the interaction.
https://developer.apple.com/documentation/appkit/nsview?lang...
Not sure when your "recent" (in "recent changes") refers to.
That's how GUI frameworks have worked at least since they've provided a widget hierarchy (with labels, containers, buttons, and so on). Delphi was like that, Swing was like that, QT was like that, GTK was like that, NeXT GUI lib was like that, Cocoa was like that, the old Mac OS 8 lib up to 8 was like that, and so on. Heck, even Athena was like that.
The GUI programmers and frameworks that have been "drawing the damn text in drawRect" are in the absolute minority, not since CoreAnimation, but since forever.
In fact, you even mention "I haven't full grokked how this came about, but it seems to be part (a) widget toolkits (b) horrible drawing APIs and (c) the iPhone with LayerKit/CoreAnimation.".
The first of those things, (widget toolkits) is 30+ year old, and has been synonymous with GUI development since forever, at least in the desktop application space.
>However, this is not the "programming model" of GUI frameworks, it is an abuse of those GUI frameworks.
Yeah, not really. Not only this is prevalent and common sense understanding of "GUI framework" in the last 3+ decades, but merely having some drawRect and co (without a Widget set) wouldn't even qualify as a "programming framework" at all, people call those "a graphics library".
"drawRect" has not been the main GUI programming tool since forever, except when a developer wanted to make their own custom widgets. Whole GUI apps never once call drawRect (or its equivalent in their lib) directly.
However, I am not sure where you got the idea that I denied the existence or use of widget toolkits, since they are central to the whole development. However, I don't buy your claim that the existence of widgets meant that nobody ever implemented drawRect::. That's just a false dichotomy.
For example, I just googled "open source Mac app", then went to the source for the first entry, Adium (https://github.com/adium/adium/tree/master/Source) and the first 3 implementation files I looked at all had an implementation of drawRect:: Second entry is Disk Inventory X. Includes a TreeMap framework, 5 classes, in is a view with a drawRect::.
In general, my experience is that you typically use a custom view for whatever your app is centrally about. For example, a drawing app has a custom Drawing view. A word processor has a view for the text, a spreadsheet for the central table. At the very least. Around the central view you would arrange tools and other chrome built out of the widgets of the toolkit.
The widgets are, however, not really part of the MVC pattern, they are tools you use to interact with the model, they rarely reflect the model itself (except maybe for being enabled/disabled).
In terms of horrible text drawing API, I don't know about other platforms, but for NeXTstep/Cocoa that happened with the transition away from DisplayPostscript. With DPS, text drawing was trivial and malleable. With the OSX/Quartz transition, text-drawing was delegated to ATS, with some of the most arcane, inconsistent and difficult to use APIs I've had the displeasure to use. And alas these were not built on top of the much saner Quartz APIs, which were bottom-most for everything else, but instead the Quartz text APIs were trivial and very limited convenience wrappers for underlying ATS calls. Sigh.
(And I realise that this is quite a while ago. (a) Yes, I'm old (b) I don't think the text APIs becoming horrible was a trigger, they already were when things changed)
The type of app that only used the widget set definitely also existed: these were the business/database apps or the like that just interfaced with textual/numerical data/tables. Those you often could build using just the widgets as-is, without ever creating a custom view. Apple concentrated a lot on those use-cases in their public communication, because NeXT's focus had been business apps and they made for great "look ma, no code!" demos.
Of course, these widgets aren't really connected to a wider model, they contain their own little model and MVC triad. In the case of Apple, they tried to fix that with bindings[1], but that was only a partial success. So the ViewControllers (which already existed, I think) jumped in and the "update view" part of MVC became "set the content object of this widget". This can actually work fairly well, if you really treat the ViewController as a View (this is entirely permissible, MVC describes roles, not objects) and really, really only do that update when you get a notification that the model has changed. Alas, that isn't enforced or even supported much, so you get arbitrary cross-view modification. Sigh. Slightly better support would probably help here, for example Notification Protocols[2].
So that leaves addSubview, adding and removing subviews for dealing with dynamic data. I'd still maintain that this is a fairly recent development as a major way of achieving UI dynamism, and I also think that its rise roughly coincides with the rise of the iPhone. And I also think that, even though this technique is now widely used, the basic widget sets aren't really well equipped to deal with that way of working, or with helping developers not make a hash of things. Because that's not how they were designed. They were designed to deal with fairly static hierarchies of views that manifest themselves and any dynamic content on the display using drawRect::.
[1] https://blog.metaobject.com/2014/03/the-siren-call-of-kvo-an...
[2] https://blog.metaobject.com/2018/04/notification-protocols.h...
Pages: 63
Keynote: 81
Numbers: 61
And are those uses because that's how they draw their overall UI -- e.g. do they use drawRect as the main paradigm, or do they merely create new widget looks and behaviors (that they then treat the same as Cocoa ready-made widgets, append to parent, etc)?
E.g. do they draw the UI or some large part of the UI that way, or is just drawRect used to have some custom looking derivative of Button, Label and so on?
You have a font render shader, and that renders to the texture, what's there to upload?
You are right in that changes to drawing induced by the original iPhone are responsible for at least part of the widgetization of CocoaTouch. The first iPhone(s) had a really, really slow CPU but somewhat decent GPU, so moving more rendering functions to the GPU made sense.
Originally, Cocoa as well as its NeXTstep predecessor did essentially all drawing on the CPU (some blotting on the NeXTdimension notwithstanding). And this was usually fast enough. At some point, window compositing was moved to the GPU (Quartz Compositor). With the phone, animations were both wanted for "haptics" and needed in order to cover for the slowness of the device (distract the monkey by animating things into place while we catch up... g ), and the CPU was also rather slow.
So instead of just compositing the contents of windows, CocoaTouch (via CoreAnimation) now could and would also composite the contents of views. But that's somewhat in conflict with the drawing model, and the conflict was never fully resolved.
> texture upload is too slow
First, you don't have to have separate textures for every bit of text. You can also just draw the text into a bigger view.
> redrawing your text each frame
Second, Cocoa does not redraw the entire screen each time, and does not have to redraw/reupload the texture each time (if it is using textures). It keeps track of damaged regions quite meticulously and only draws the parts that have changed, down to partial view precision (if the views co-operate). Views that intersect the damage get their drawRect:: method invoked, and that method gets a damage list so it can also optimise its drawing.
Now if you actually have a texture living in the GPU and you are incapable of drawing into that texture, then you must replace the texture wholesale and the rectangle/view based optimisations won't work. However, I know that they do work, at least to some extent, because we were able to optimise animations on an iOS app by switching from layer-based drawing to a view with drawRect:: and carefully computing and honouring the damage-rect. It went from using 100% CPU for 2-6 fps to 2% CPU at 60fps. (discussed in more detail with other examples in my book: iOS and macOS Performance Tuning: Cocoa, Cocoa Touch, Objective-C, and Swift, https://www.amazon.com/gp/product/0321842847/ref=as_li_tl?ie...)
Third, if your text does change, you have to redraw everything from scratch anyway.
Fourth, while the original phone was too slow for this and lots of other things, modern phones and computers are easily capable of doing that sort of drawing. The performance can sometimes be better using a pure texture approach and sometimes it is (much) better using a more drawing-centred approach (see above).
It’s not. I am familiar with Cocoa. When the model updates, the controller or the view notice and manually update the view state to match – update text fields, change colors, push new views, etc. This way the correspondence between model state and view state is hardwired into imperative, mutating code:
func updateView(model: Model) {
if (model.updating) {
statusLabel.text = "Updating…"
}
}
In React-like architectures, the view state is never mutated from the programmer’s perspective. The programmer just writes a function that takes the model state and produces the view state: func buildView(model: Model) -> View {
return Label(text: model.updating ? "Updating" : "Idle")
}
So the correspondence between the model state and the view state is declarative, pure code. That’s a huge conceptual difference, because it makes the result much more convenient, error-resistant and testable.You might want to check out Model View Controller, with things like drawRect:: and "setNeedsDisplay" or "setNeedsDisplayInRect:"
Widgets and Massive View Controller are built on top of the MVC framework, and can be more convenient in cases. However, that doesn't mean the underlying MVC framework has disappeared.
UPDATE: And yes, the terminological confusion is awful.
Concept Shadowing and Capture in MVC and its Successors
https://blog.metaobject.com/2017/03/concept-shadowing-and-ca...
Model Widget Controller (MWC) aka: Apple "MVC" is not MVC
https://blog.metaobject.com/2015/04/model-widget-controller-...
Label {
text: model.updating ? "Updating" : "Idle"
}
it's not implemented on top of JS but is a proper declarative DSL for reactive programmingHigher level, but much lower quality -- the feature set of GUI widgets as offered by desktop SDKs is unmatched in the HTML/DOM world. It's like actual objects vs play-doh.
I mean, for a start, no 2 MVC frameworks actually agree on how to pass data around.
React is just the V.
React is just one of paradigms. And UI is a multi-paradigm entity. When architecture of one component can benefit from React model, others - may not.
Consider basic <input> as a component. In order to update its value you just need to execute this:
input.value = "New value";
Wrapping such input into React.Component is a waste of resources.As for me Twitter's Flight ( https://flightjs.github.io/ ) is less constraining - more general if you wish.
It is just very simple infrastructure around PubSub idea. Loosely coupled components interact with each other by posting UI lifecycle events. Components subscribe themselves to such events and react on them in most optimal, component specific way.
Some of components may use React internally, some have better means to update themselves.
Apollo is also nice. It's best for display heavy apps as documentation and typechecking is at least enforced/built-in. But I feel it's too abstracted/magic for heavy state apps. I still need to find good flow with Typescript and Apollo though. (to not repeat types - haven't tried yet really).
Redux IMO is overkill and is/was abused. It's good if you want to store some global data/config and have clear view od it. But for very dynamic state MobX is much less overhead.
I would just like a good way to share components and simpler styling.
BTW for simplest state management I like pure class implementation and just calling update on main component after event handler finishes. It's easier to test and good enough for simple UIs.
Shadow DOM and custom tags make it possible to closely mimic the way we used to do UI development back in the Delphi/VB6 era. I like how eg. css properties only apply to a given component.
Polymer seemed like the most lightweight possible way to implement this because it uses the browser facilities. I understand that React also has components and custom tags (JSX) but it doesn't use browser facilities but rather implements them itself, which just sounds like the wrong way of approaching the problem.
Still my favorite one is "Commas in literal strings: Any comma occurring in a string literal must be escaped using a backslash (\)." here: https://polymer-library.polymer-project.org/3.0/docs/devguid...
React is Javascript through and through, and yes, it only uses browser facilities. Because JSX is not custom tags, it's function calls: https://reactjs.org/docs/react-without-jsx.html
It is true that polymer had quite a bit of limitations, but there are other solutions like stencil, lit-element, svelte or vue (yes it can output WC's) that do not have those problems.
Lit-html in latest version I think is only outperformed by inferno from popular solutions.
This is not true, there is no string conversion. There is even separate syntax for setting props vs attrs.
Check out the readme here.
https://github.com/Polymer/lit-element
I also normally use redux in my WC applications.
- Write codes in ES6/7 or TypeScript (transpiling using babel).
So the syntax is exactly like what you're writing on the web (not React-like syntax)
- Bundling using webpack (with a sensible default, zero-configuration FTW)
- Use any front-end library (redux, lodash, rxjs, redux-observable etc.)
- Supports Hot Module Reloading (sub-second reloading)
- Supports react-devtools & redux-devtools (Time Travel Debugging possible)
- Super easy to test & debug in multiple platforms
The problem we're facing now is that it required both React & QML knowledge. In the new version, I'm working on completely new APIs to support developing desktop application using only React. Would love to hear your opinions on this approach.[1]: https://github.com/longseespace/react-qml/tree/next/packages...
This project looks really interesting, definitely something I'll be following.
Do you have any plans to expand on documentation a bit more? At present your HN comment tells me more than the README does.
https://reactjs.org/docs/hooks-faq.html#what-is-the-prior-ar...
So in a nutshell: "React as a UI runtime" considers React as implementing part of your UI's execution model. In other words: instead of your code having to call appendChild, setAttribute, etc. to modify the UI, React does that.
A ) Dependency Tree (eg dom or virtual dom) is the interface user interacts with.
B ) Each interactable element in the tree can have its own state.
C ) Each interactable element gets data or update data from/to its own state.
It can also get data from 'properties' that a parent passed on (but the interactable cannot update those properties itself, instead it must call its parent's function to do that)
D ) After the above A, B, C are setup -- the code interacts with the state, not with UI elements
E) The UI runtime is a collection of mechanism that supports A, B, C.
This includes :
1) Primitives allowing Interactable to be attached and interact with the dependency tree, ability to receive data from parents
2) Well defined (from both APIs, callbacks, and Data) primitives -- that enable state management within the Interactable, and across their dependency tree
This also includes mechanism deciding efficiently, which state changes affect which Interactable)
3) Developer friendly run time helpers to debug and manage the above
----
Cleary A) --> can be a dependency tree of Excel cells, or Matrices, or robot's extremities, for that matter.
Clearly B) (the state) is widely appreciated these day ViewModel (as it is called in Android's new JetPack LiveData components)
The state is also the thing that can be validated, even with TLA+ (which is what am thinking about, in a downtime).
And it is much much easier, in my view to map interaction business rules to state, rather than to UI.
---
I am looking forward to try this with my console UIs project.
---
I also have a strange feeling, that this model will work when I have to manage complex backend interdependencies,
where updates to distributed caches affect the algorithms running across a server farm.
---
components know about their state only.
the UI runtime is the one that understands 'state of the tree'
Ok, now that author feels better, let's move on..? Or what is the purpose of such 'disclaimers' and 'warnings'? Let the reader see whether the article is at their appropriate level, don't patronize...
I really dislike these. I can skim the text, and within 30s, I can see whether it might be my level or not. This feels, however, that the author is purposely driving me away for no other reason than 'you don't make a living with react, so shoo, this is for the big boys'.
I think this is reasonable for this author to want to emphasize. It's quite common for a beginner to make one of several reasoning mistakes that might push them away from trying something or push them away from something they were already using. The beginner who hasn't used a thing yet might think 'I need to understand how this thing works in detail before I can use it' in which case they might think reading an article like this is an appropriate way to get started. The beginner who has already started using a thing might think 'here is a good article about how the thing I've been using works in detail which I can't understand -- therefore this thing I've been using must be too hard for me to use and I should look for something else'.
I assume for popularity retention reasons, the author doesn't want to encourage either of these kinds of mistakes.
I did feel that the yellow warning sign was a bit much. There was also a line about it not being for designers. As somebody with both design and engineering backgrounds, I felt less enthusiastic right away — mostly because I dislike buckets being enforced. It encourages people to put themselves into one over the long run, which in turn has a tendency to stunt their progress. It’s also odd when any educational resource (higher ed in the US being a great example) introduces itself by trying to be inaccessible. It feels counter to the professed goal of the institution / article.
Anyways, I guess I felt the comment above is a valid thing to discuss if it could improve other people’s efforts to write similar pieces.
As an aside: ironically, the article is fascinating from a design perspective. Just not the particular bucket of design the author was thinking of when he wrote that line.
Another unintended effect can be contributing to the perception that React is only for people with a strong programming background rather than, say, a11y expertise. I don’t want that either and that’s why I mentioned there are multiple ways to look at React.
Yet another problem is that random things from my posts are sometimes used in interview questions. :-/
I trust a curious reader to ignore the disclaimers and dive in anyway.
But I also wish I didn’t need to put it there and I emptathize with your frustration.
Anyways, thank you for writing the article, it’s really an enjoyable read after a day of wrestling a closed source framework and wishing I could understand how that particular architecture was reasoned about.
Do you think that without the warning, a beginner would deep into the article, and 50 minutes in, he/she would be like 'heeeey, wait a minute! This is not for me! Why didn't he tell me?'
If not, then such a disclaimer is meaningless.
I really don't understand your issue here.
Your complaint about it makes it look like you believe the post was written for you personally, with no regard for the wider audience. That's idiotic.
How do I know? Because I have disclaimers here and there that are obvious for most people and still help to reduce the support burden.
Of course, he didn’t consider the massive inconvenience he was causing to god level programmers like you when writing his disclaimer. Shame on him!!!
To tell people what to expect going in, so they don't have to lose their time if they're just starting out and looking for a quick high level tutorial etc.
edit: disregard that...
Why should Electron users have all the fun?
Some Ux in regedit?
Run dialog not permitting ctrl-arrows?
Really, we need to wake up and realise the fact that the ui experience in windows is not good enough at all.