7 ms·
I now subscribe to the React-style "wait, why are you listening to anything?" mindset. However, if you're going to go down the mutable path, I think KVO was par
by tolmasky 11y ago
I now subscribe to the React-style "wait, why are you listening to anything?" mindset. However, if you're going to go down the mutable path, I think KVO was particularly well designed.
The main reason was that it made composition pretty sane and created clear places to handle changes. With KVO, you implement your KVO-compliant mutation methods, and all changes would then get funneled through there (most notable container ones). Compare the mess that is NSView (addSubview/insertSubview:positioned:relativeTo:/removeFromSuperview/etc) to the post-KVO NSTreeNode. NSTreeNode simply exposes .mutableChildNodes, which you treat as a normal array (addObject:/etc) and after adding a child, it then has parentNode set to the parent after. This was great for lots of reasons: 1. No need to re-create API, you just used NSMutableArray's existing API. So [someNode.mutableChildNodes removeObjectsWithPredicate:somePredicate] and all the parentNode relationships get handled for free. Compare this to pre-KVO methods where every time you wanted to take advantage of a neat NSMutableArray feature, you'd have to re-expose it yourself like removeSubviewsWithPredicate:, or wind up having to copy out the array, mutate, then re-assign with setSubviews: 2. Less room for error since all your cleanup code just happens in removeObjectFrom<Key>AtIndex:, one funnel.
Again, I believe this to be a great local maxima for this style of programming, one that was never that fully explored since the move to more KVO-ness was eclipsed by the attention to iOS. I think most the problems are better solved through React's top-down approach.
- haberman 11y agoI like the React model, but it does make one key assumption. It assumes that DOM mutation is so slow that re-rendering and diffing the entire virtual DOM for every update is basically free in comparison. I like the simplicity of React's approach, but this assumption makes me a little uncomfortable.
- Silhouette 11y agoAs an anecdotal data point, that assumption survived approximately no time at all during a recent exercise I was involved with evaluating how to build a new and reasonably large web app. With similar scepticism but trying to keep an open mind, we did an experiment allowing the entire DOM to rerender from a top-level React component when the underlying data model changed. It performed pretty much as badly as everyone thought it obviously would. We then switched to exploring more standard approaches like having individual components monitor the relevant model events and manage their own state, using React's rendering model at a much finer granularity. We haven't finished our investigations yet and there are some other concerns about React's performance under certain conditions, but so far, the finer-grained approach seems more promising and is our most likely way forward for this particular project.
- haberman 11y ago:( I was afraid of that. React docs oversell this point a bit: https://facebook.github.io/react/docs/multiple-components.html#a-note-on-performance https://facebook.github.io/react/docs/multiple-components.ht... "You may be thinking that it's expensive to change data if there are a large number of nodes under an owner. The good news is that JavaScript is fast and render() methods tend to be quite simple, so in most applications this is extremely fast. Additionally, the bottleneck is almost always the DOM mutation and not JS execution. React will optimize this for you by using batching and change detection. [...] Don't underestimate how fast JavaScript is relative to the DOM."
- Silhouette 11y agoIn my humble opinion, React docs oversell a lot of things. The point about the DOM mutation being a bottleneck is valid, but there is no magic.
- Akkuma 11y agoWas your data mutable or immutable? The former requires you to write shouldComponentUpdate logic that isn't just old == new. If you didn't write any of that logic and didn't use immutable data rendering everything again from the top down due to a single change is of course going to be terrible performance. If you wanted to be more adventurous you should look at alternative virtual dom implementations http://vdom-benchmark.github.io/vdom-benchmark/ http://vdom-benchmark.github.io/vdom-benchmark/. What you'll find is that React is actually the slowest one out there based on performance. For instance, on dbmonster InfernoJS sees 50-100% more fps and on my machine ~20x better benchmark performance. If you want the next popular implementations look at virtual-dom, which is leveraged in a variety of frameworks like https://github.com/ashaffer/vdux https://github.com/ashaffer/vdux, or Google's Incremental DOM, https://github.com/google/incremental-dom https://github.com/google/incremental-dom.
- Silhouette 11y agoWe've also found that some of the other virtual DOM implementations that are popping up can be somewhat faster than React's, though I confess I can't remember exactly which ones as I write this. But I don't think that really matters. Fundamentally, once we have a moderately complicated data model such as configuring a few thousand entities of perhaps 50-100 different types with assorted properties each and assorted constraints between them, even a relatively passive scan over the model to rerender the UI from the top following each transaction on the data model does not appear to be a viable architecture today. Fortunately, that architecture is also unnecessary, as a modest amount of finer-grained integration work presents a much more realistic alternative. The declarative style of specifying a UI still has considerable potential as a way of composing manageable components, we just need a scalable way of decomposing the overall design to match, and it seems for now that means smaller-scale components need to be aware of their own concerns in some cases.
- tlrobinson 11y ago"diffing the entire virtual DOM for every update is basically free in comparison" If you use something like Immutable.js you can avoid a lot (most? all?) of the diffing since comparing trees of objects becomes a simple object reference comparison. This is common in the Om world, and becoming more popular in JS too.
- haberman 11y agoIf you have a complicated, deeply interconnected model, updating immutable data structures properly doesn't seem much easier than properly implementing shouldComponentUpdate().
- chrisseaton 11y agoYou could use a lens to make the programming easier, and the logarithmic depth property of DOM-like trees makes it likely that allocation will also not be a problem.
- haberman 11y agoWhat is a lens in this context?
- audreyt 11y agohttps://www.npmjs.com/package/safety-lens https://www.npmjs.com/package/safety-lens https://www.fpcomplete.com/school/to-infinity-and-beyond/pick-of-the-week/a-little-lens-starter-tutorial https://www.fpcomplete.com/school/to-infinity-and-beyond/pic...
- wereHamster 11y agoIs it not easy because the syntax is cumbersome (eg. chained calls to setIn([...]) in immutable.js), or because it is fundamentally difficult? The former is something that's specific to Immutable.js and many other javascript frameworks. Other languages have solved that problem rather elegantly (cursors, lenses). If it's the later then I doubt shouldComponentUpdate will be much less complex than the state update function (in terms of complexity, lines of code, easy of understanding etc).
- deleted 11y ago[deleted]
- nostrademons 11y agoReact's assumption isn't actually true. DOM manipulation is fast - all modern browsers use a dirty-bit system where modifying the DOM is basically a few pointer swaps. Layout, however, is slow, and dirtying the DOM requires at least one layout as soon as control returns from JS. If you intersperse measure and modify calls, it's more. However, what is true is that: 1. Both JS and the DOM are fast enough that you can lay out most reasonable pages within the threshold of visual perception (~150-200ms). 2. If you need to layout the page during an animation frame, you're hosed anyway. You have a frame budget of 16.67ms and layout will take 100ms+ on mobile. And so the practical, qualitative conclusion is the same: you can use React for your basic page transformations & rendering, but you need to use CSS transforms and offload the work to the GPU if you want to have a prayer of making animations work on mobile. And React also protects you from completely fucking up performance (which is trivially easy with JQuery), so many real-world pages from inexperienced webdevs experience a very significant speedup.
- jesstaa 11y agoThat's an optimisation. It's not core to the React model. If DOM mutation was ever to be faster than the cost of the diffing then they'd just remove the diffing and your applications would still work the same with the same API.
- i_s 11y agoIf Cocoa KVO was well done, this would not need to exist: https://github.com/facebook/KVOController https://github.com/facebook/KVOController
- plorkyeran 11y agoKVO's API could be a lot better (even just making it target-action would be a huge improvement), but at the time it was created you couldn't do a block based API (since they didn't exist yet), and thread-safe automatic deregistration without zeroing weak pointers would be very complex and have a lot of overhead.
- arcticbull 11y agoThe key paths are still stringly-typed so maintainability is a nightmare :(
- arcticbull 11y agoI've interacted with KVO for years before iOS ever came out and I can honestly say it's just the worst. It's stringly typed with zero compile-time validation so as soon as your model key paths change your app breaks in the least expected places. Then throw bindings in the mix and your key path constants are in your nibs too! God have mercy. I'm not the least bit surprised they discarded bindings in iOS, then (effectively) discarded KVO in Swift. Next time, try playing with creating an observer protocol: @protocol MyThingObserver @optional - (void)myThing:(MyThing *)thing didChangeIntegerValue:(NSUInteger)newValue; - (void)numberOfItemsDidChangeInThing:(MyThing *)thing; @end Then you have strong typing, compile-time validation, additional context, and plays much nicer with the land of tomorrow (Swift). Coupled with immutable value types, you're going to make your own life way easier, I promise.
- tolmasky 11y agoI have no interest in returning to any programming model that fundamentally revolves around complex sync-up code (also, yes we "tried" this aplenty long before KVO/bindings). As I mentioned in my previous post, I felt KVO was a really interesting design path to be sure, but I agree with you it had its fair share of issues. That being said, observer patterns are just incremental improvements on this. Conceptually its still the same: figuring out ways of getting different parts of a system to eventually "settle" on the same value. Sure, strict typing and whatnot can make this safer, but its ultimately a lot of code that has no real connection to the actual problem your program is trying to solve. Personally I find immutable everything types (through persistent data structures) combined with immediate mode programming for rendering has more or less taken away the cognitive overhead I used to have around this set of "problems". You have a model, that can lead to a new model in the future. You then have a render function that is a function of this model. Its completely deterministic, there is only one path things can go down, you can grab the state of your program at any time and keep re-rendering it to figure out whats going on -- there's no "implicit" conglomeration of state through the value of all the free variables in your program at any particular point in time and their knowledge of each other. If I were to return to iOS programming today, I'd probably give React Native a stab. To me tomorrow land is being able to change code and see it live update without recompile or provisioning profiles or what have you.