6 ms·
I enjoyed reading this blog post, and the perspective that the author brings. The notion that a cache is a big ask is novel to me, but I think it is a good poin
by lilactown 6y ago
I enjoyed reading this blog post, and the perspective that the author brings. The notion that a cache is a big ask is novel to me, but I think it is a good point that it does complicate things more for simpler cases.
However, I would like to hear the author's thoughts on Concurrent Mode in general, and how Crank.js addresses the same issues. There's no mention here of time slicing, which is what Concurrent Mode really unlocks. IIUC Suspense was discovered, not invented, after implementing React Fiber and exploring the knock-on effects of being able to restart rendering from a place in the component tree.
React's insistence on rendering being "pure" is not dogma; it is an attribute that unlocks the ability to pause and resume rendering at any node in the component tree... like if an important update from a keyboard press comes in that you need to handle right now. This means that when CPU is scarce, jank can be alleviated by prioritizing user input and other high priority updates.
There's a tweet out there by sebmarkbage that gives a slightly cryptic response to using generators, but the gist that I remember is that because generators are mutable, you don't get the properties you need to arbitrarily pause and resume a component just like it was when you paused rendering it.
The author claims that this is just dogma, but I believe (and the React team believes too) that time slicing provides a measurable benefit to the user experience. This point is contentious still; Rich Harris of Svelte fame claims that _doing less work_ is more important than prioritizing it. But the author here doesn't address this at all, instead claiming architectural simplicity is its own goal.
I'm excited to see alternatives to React come out and generate excitement, and I'm very interested to see where Crank.js continues to evolve. My hope is that in 5 years we'll know way more about all of these questions than we do now, and we'll be able to put those into practice in better frameworks, delivering better applications.
- lhorie 6y agoAs an outsider, I honestly feel that there's a strong difference in opinions between Dan and Sebastian. One doesn't need to reason too hard about the purity thing: Dan himself admits hooks were criticized (accurately) for being impure. On the other end of the discussion, the claim about impurity preventing prioritization is very weird, because simply going async will shuffle the rest of your rendering further out in the microtask queue, giving the opportunity for event handlers to fire half way through a vdom render pass, effectively prioritizing user input.
- acemarke 6y agoThe React team has been pretty clear that Concurrent Mode needs rendering to be pure, as render methods may get called multiple times but have the results thrown away if the render pass isn't completed and committed to the DOM. The case of user input is particularly relevant here. Say React starts a render pass for a relatively low-priority update. It walks through the component tree asking components to render themselves, runs out of time on this tick, and pauses the half-calculated render pass. While paused, the user types a key in a text input or clicks a button. That will be treated as a much higher priority update, so React sets aside the half-calculated render tree, does the complete render pass based on the input event, and commits it to the DOM. It can then "rebase" the half-completed low-pri render pass on top of the new tree that resulted from the input update. If components go kicking off side effects while rendering, whether it be as simple as mutating a React ref object or more complex like an async request, those side effects will likely result in unwanted behavior because they might happen multiple times or happen at the wrong time.
- lhorie 6y agoYes, I'm aware of the concurrent mode contract. But do you see the Stockholm syndrome here? If I were to tell you that in order to use a hypothetical Floober framework, your code must be tail call optimizable or else the framework has undefined behavior, surely you would think the author of Floober framework was insane. It seems strange, for example, to dismiss async functions on grounds of function coloring when the alternative is coloring functions with a conceptual requirement of "purity". Or to ignore the opportunity for a paradigm shift to something more naturally reactive (e.g. a la s.js) considering how there was already an API shift when going from OOP components to functional + hooks.
- bikeshaving 6y agoYou’ll notice that in a lot of the Crank examples, I freely mix side-effects with the actual yielding/returning of elements. This is because Crank doesn’t treat side-effects as special. Given that rendering is a huge side-effect in and of itself, I always thought is was kind of unfair that React could peform them and the user couldn’t. The reason this is possible is because Crank is executionally transparent. If you render a component, and that component calls refresh 4 times, the developer can know that the component has been updated 5 times. You get no such guarantees with React, and it will even go out of its way to make sure that you don’t put side-effects in your code by calling your components multiple times in development, which I think is crazy.
- bikeshaving 6y agoHi, author here. Glad to hear that you enjoyed the blog post. > I would like to hear the author's thoughts on Concurrent Mode in general, and how Crank.js addresses the same issues. There's no mention here of time slicing, which is what Concurrent Mode really unlocks I think Concurrent Mode is a really interesting project, but I also think that the developer experience of using CM as the React team has defined it is really lacking. There’s the issue of querying when a specific component (or the whole tree) has finished rendering. With React, rendering is treated as a black box, and this was mostly okay when rendering was synchronous, but people often ask with concurrent mode, how do I know when rendering has finished, locally or globally. The answer the React maintainers would give would be, probably, useEffect hooks, but that’s not enough for me. Promises are like the Planck constant of asynchrony in javascript; they’re the smallest units of time possible for executing code later and their callbacks have the highest priority. To not have a promise-backed API for CM seems like a step backwards. Even if I were to implement time slicing, I would want it to be promise based. Also, React fiber time slicing comes at a cost, in the sense that you have to check the clock for every unit of work to make sure you don’t exceed your budget. I would want any sort of priority-based it to be done on an opt-in, limited basis. I definitely don’t think every component should be a possible asynchronous breakpoint, that sounds like a nightmare to debug. I dunno, I need to write a longer, better structured blog post about this stuff, but here’s something to think about: if you want concurrent mode or time slicing or scheduling priorities, the tools to implement it in user space are probably already there with Crank.
- lilactown 6y agoI haven't used concurrent mode in anger yet, but I have used frameworks built on top of React that introduce their own async scheduler for rendering - Reagent, re-frame in ClojureScript-land. You end up with two issues IME with async tasks (rendering being one of them): - Making everything async - without prioritizations - means that at some point you have simply moved the problem into the scheduler. Just like taking a sequence of sync operations and wrapping them in a promise chain doesn't speed them up, likewise making every render async by itself doesn't speed things up at all (on the contrary it can make things slower). - Using prioritized async tasks only allows you to prioritize the things which participate. If a component renders, or any other computation occurs, and isn't sent to the scheduler, it can steal work from higher priority things because the scheduler doesn't control it. This is why IME a la carte async async rendering isn't super useful. It is a lot of mental overhead (and potentially laborious) to manage your application's schedule while also trying to build features. It would be interesting to have tools to tune it when necessary (which I assume React will provide in the form of ways to schedule computations at different priorities), but having it opt-in means that the default case is everything will steal work from the things that you are actually marking as high priority!