Inside Fiber: the in-depth overview you wanted a TLDR for


I want to provide an in-depth overview of the important concepts behind reconciliation. We'll then explore how React uses the algorithm and go through a few magic words we hear a lot, like coroutines, continuations, fibers, generators, algebraic effects and see how they all relate to React.js.



♪♪♪ Hello everyone, it's great to be here. Hello Amsterdam, hello everyone who's online. Yeah, I'm Mateusz, and this session is inside Fiber, the TLDR you wanted for. This is me, you can find me everywhere as Ydecominator, and I'm a senior front-end engineer at Medaglia, and I also mentor front-end people in TechLabs Berlin. By the way, we're hiding. Before we start, I have a few disclaimers. The first one is that, as you probably can imagine, React source code is really complex, and some of the thoughts I have here are a bit speculative. And the second thing is that maybe it's going to be not 100% what you call a TLDR, but whenever you find a huge payload of content, you'll see this icon, and it means we can discuss that in the after party or in the Q&A sessions, etc. So don't worry. Before I start, I'd like to provide you a bit of context of why I wanted to present this and why I wanted to talk about this stuff. It all started with this friend of mine called Bruno. He was preparing this proposal to bring algebraic effects into JavaScript. It was just a regular TC39 proposal, and he asked me for some feedback. When I was reading his proposal, I saw a huge amount of topics I had never heard about any of them. And there was this huge cloud in my mind, like algebraic effects, coroutines, continuations, fibers, threads, generators, etc. And what I wanted to do here was to put those in a way that it makes sense to go through them and also to show how they all piece together in React. So starting with fibers and a bit of an overview, the way I like to see fibers is, let's think about a regular JavaScript function. So we have this add function. It has two parameters. I just summed them. If you think of what a stack frame for that would look like, we would get something like this. We have a return, we have the function itself, we have a few parameters, and the local variables being the numbers or results. If we think about a React component, we can get to something similar. So we can think of a fiber in this way, where we have our component instead of a function, our props instead of our parameters, and then our component state being our local variables. So to start, we can think of the fiber architecture as this React-specific call stack model that basically gives full control over scheduling of what should be done. And a fiber itself is basically a stack frame for a given React component. Okay, we got to this definition. We got to this parallel with regular stack frames. Now we can start seeing fibers as units of work. So let's think what happens with our components. So once our template goes through the JSX compiler, we end up with a bunch of elements. That's what you know. What happens next is, during reconciliation, the data from all of these elements is merged into three of the fiber nodes. We'll talk a bit more about them. And then, of course, depending on the type of what it is, for example, it can be a function component, a class component, a suspense component, or whatever. So depending on the type of the thing, React itself needs to perform different types of work. So it's going to take these. And then each element is converted into this fiber node that we're talking about. This is going to describe for React what kind of work that needs to be done. So this leads us to this unit of work thing. And because fibers are units of work, it makes that convenient to track, schedule, pause, and abort specific types of work. Now that we can abstract those in terms of units of work, we can also think about visualizing those units. And for this, I'd like to propose this first experiment that is inspecting elements. So let's take this simple app. I have just a few components with some local state, and I have this button. I'm incrementing and decrementing this local state, and that's about it. If we start logging our fibers in the console, we are going to see this, a bunch of metadata about our components. And we'll see metadata about props, about state, about et cetera. And I know this is really tiny, but don't bother trying to read that. But I just wanted to see that there's a bunch of information there. And we can use, for example, this kind of information. So here's a piece of code where I'm using only five of these properties to iterate on those fibers. And what I'm going to get is now I just copy and pasted the code into developer tools, and now I have Twitter open with a red summit profile open. And I was iterating and storing those in a regular JavaScript map. And what I have now is that, for example, I can inspect a given element of that. So because those are in a map, I can click that and find those in the map. And I get a lot of metadata about components. For example, I can figure out that Twitter uses a lot of class components. So what I would recommend you to explore those is this part of the reconciler. It's called React internal types. So there you can see literally all the properties of the fiber and what kind of data they store. And another really interesting part is those work tags. Because you will see that, for example, React has 25 different types of tags that can be used to tag what needs to be done. It can be, for example, an error boundary, a suspense component, a profile component, a regular class or function component. And you will see that this keeps growing. Like last time, a few years ago when I checked it, it was like five. So the more React adds features, the more you see those growing. Now that we can visualize those, we can also talk about manipulating them. So I'd like to be a little bit of a raise your hands here. So who here has ever heard about homo-iconicity? Woo! So this nice word is you'll find like in Wikipedia or some other sites that basically this is a property of programming languages in which the code used to express the program is written using the data structures of that language. This is really easier to visualize in languages like Clojure, for example, or Lisp. But for now, let's take as your code is data and your data within the program is the code. We just saw that React elements are just data as well. So we can start thinking of React and homo-iconicity. And just like Lisp and those languages that have this property, we can manipulate an element's children or their properties in the way we want. And this allows us to do interesting things. The second experiment is parent matching in React. So I have this example of code where basically I have a match component and I have with and otherwise. And basically, and this is typed, but what this code is doing is I'm trying to match a response of an API. And if it's an error, I'm going to render my error component. If it's okay but it's an image, then I'm going to render a component. And then if it's a text, I'm going to render a different component. So that's parent matching. And this is built by checking elements internal metadata. By the way, the code for that is on GitHub. Now we can move to the next thing, coroutines. So I started reading about coroutines after I saw this tweet by Andrew Clark. He used to be in coroutine in React. And basically he said that React suspense would trick people into learning about coroutines. And he was right, like I was tricking them. And then I started digging definitions out there for that. And I saw many of those, being the first one that a coroutine is a generator or a producer that can also consume values. So if you think about this, the generators we have in JavaScript, they can also consume that. So by these definitions, those would be coroutines. Another definition I found is that those are generators that can resolve async values, just like we have with async await, for example. And this definition is also really common in JavaScript because, for example, you can even remember the code or the code coroutine before promises were really a thing. Those were basically promise implementations based on generator. And even bubble under the hood, it also does that. And the last one is like the one with more buzzwords, is that it's a generator that can use with a stack for continuation. And I know it sounds complicated, but we can think of this one as we had deep await. If you're familiar with React suspense, for example, you can think that in suspense you can pause reconciliation at any depth of your components tree. So to sum up, we saw fibers, and in fibers, to summarize, we have control passed to a scheduler, for example, React scheduler. And this scheduler is going to determine what runs next. And it can be, for example, in node. It will be something similar. Whereas in coroutines, basically you have control passed to the caller and handled by your code, so by developer code. So that's the key difference for these two. Now we can talk about coroutines in React. So they first appeared when work on fiber was going as a specific component type. So remember we saw that tags where you have all the different component types. You have two others that were coroutine component and yield component. And the idea there, as opposed to fibers, was to give you, the developer, the full control of pausing and resumping that. And by the way, there's this really interesting pull request that determines the idea and how they meant to implement that and the whole thing. But the thing is that coroutines per se in React no longer exist. For the face-to-face challenges, for example, optimizing those and memoizing, et cetera, they no longer exist. But I admit it's going to be really interesting to see in which form this was going to come up in the future. But what we can do is talk about how coroutines as an abstraction might have influenced concurrent React. That is one of the most trending things we've talked about recently. So if you remember this talk by Dan Abramov a few years ago, he was proposing this experiment where he had this really CPU-bound operation happening in the component, and the component was lagging, and et cetera. I want to propose a similar experiment. So let's say we have this resourceful operation, and that's the regular JavaScript function that basically iterates up to a million, and then it sums the values and returns a string. Just think of it as any CPU-bound operation. It can be, for example, cracking some password. And then you have the resourceful component that's just using that and rendering the result. So if we try to simulate that, you'll see that you can start typing, but it's going to start lagging to a point that your input field is going to be unresponsive, and then you just crack the whole main track. And that's a really bad user experience. So for the next experiment, I wanted to recreate the same example as we had a coroutines-based scheduler. So this is the code we have. I'm going to modify that a bit, and the key parts you will see is that now I have a generator function, my resourceful operations. Now a generator, and I added this while true. I know it sounds hacky, but I just wanted to use in the very beginning of the execution. And now I created this instance of a new scheduler, and I'm passing my heavy operation. Let's see how this looks like, the actual code. So I can start typing, and you see, of course, it's suspensing because this scheduler is suspense-ready. And you see, it suspends while it's performing, but it doesn't lag at all. You see no issues with the user experience. And this is the whole source code of this scheduler I just shared. So it's not even 40 lines. I would highlight this part. That is basically where I'm switching in three different states, so it can be idle, and then it can be pending, and then it can be done. You will see I'm throwing some promises because I want it to be suspense-ready so that you would see suspense execution. And what you can see is that, wait, did we just did something that sounds like useTransition? So let's go back to the original code, and let's now do this startTransition that's out there in React 18, as you probably saw. So let's see how this works. You see that, yeah, it did lag a bit in the beginning, but I have a really similar experience. So, yes, we did using some coroutines magic. And when you start to think about in React, there's no use of web workers or WASM or anything for parallelism. So how does React feel like this? So what we have is this multitasking comparative model where you have, yes, you have one single rendering thread, but it's interruptible, and rendering can be interleaved with other tasks, and also other tasks could be other rendering tasks. So in our example, it's like this. We had this original render task, and then we had this user input that, of course, has high priority so that the UI is responsive. And then you have the right priority to handle tasks, but then you can resume the original one. And the cool thing is that any update can happen in the background, so it wouldn't block the response to the new input. And the scheduler isn't smart enough to switch to the more urgent one, then after it finishes, resume the other one you had. And my favorite fact about that is that it yields the execution back to the main thread every five milliseconds. And the first time I figured this out, I was like, okay, but why five is this, like, those magical CSS numbers we use sometimes? And it's not. Actually, the thing is that it's the smaller thing you can fit in a single frame, even on 120 FPS devices. So that's why it feels like it doesn't block animations. And to be honest, the second thing is that most of the individual components, they don't take longer than that. It's not like we, on a daily basis, have iterate up to a million in our components or anything like this. So in practice, yes, rendering is effectively interruptible. And all that's in one of my favorite parts, effect handlers. So an overview for that is I was Googling that when I first saw it, I saw that basically effects are this thing that asks the call environment to handle a particular task. And when an effect is used, the nearest effect handler is called, and then it allows you to run some code in response to this. Then Abramov has this post that's called Algebraic Effects for the Rest of Us that's really interesting. And the code sample he uses, this is not JavaScript code. This is, like, imaginary JavaScript code. And what you're doing here is just we have a regular component, we have a regular function, we are getting this name, and we will see this perform keyword. And then this handle effect, so we are performing some effect and then we are handling. And if we think a bit, this sounds like throw, try, catch. But instead of throw, we have perform, and instead of catch, we are handling that effect. And then this resume with basically lets us jump back to where we perform the effect. So this could be, for example, fetching something from a database or logging something. So effect handlers in React, they appear a lot throughout the whole history. So it all started with the layouting algorithm. So years ago, they were – yeah, you can check this out. It's the whole thing. But to summarize, they were experimenting with that, with constructions based on effect handlers to redo the whole way that React does layouting. Fast forward in time, when they were rebuilding the context API. So if you think about when React 16.3 was out, we had a brand-new context API. When they were experimenting to create that, they also used the idea of effect handlers to build those. But again, issues with memorization and other optimizations prevented them from shipping that with effect handlers. But still, it's really interesting to check this out. And again, with handling side effects inside a component. So this one is a proposal by Sebastien Macbach, another guy from the core team. And a really similar example, and again, where you have the same pattern, but instead of throw, raise, and then instead of catch, and catch, and effect. Now my favorite part about that. So who here has ever built a suspense-ready API? If you're building an SDK and you want people to use that with suspense. Oh! So I would recommend you to check this one, for example. That's React Cache. But it doesn't have to be React Cache. Just check any suspense-ready API you use. For example, React Fire or whatever. You will see the same pattern. That is, a component is able to suspend the rendering by throwing a promise, which is caught and handled by the framework. And this sounds hacky, but actually this was the way they had to mimic the effect handlers. And how? Because you have the same throw, handle, resume pattern, but in the form of React components. A few conclusions on top of that. So I guess the first one is, yeah, React Fiber was a rewrite of React, the whole React core focused on giving more control for low-level execution. Where we have fibers as this cooperative way to handle execution in low-level. And we have algebraic effects as a way to handle effects where they and their behavior are independent. The second thing we can see is that React tries a lot to address the lack of those resources at language level, because JavaScript doesn't help them, by implementing alternative solutions that the first time we checked them, we thought, okay, this sounds like a hacky solution or a workaround. But no, it's because the language is still evolving and it lacks resources, and those are creative ways of having them. By consequence, we can see that understanding those internals and the rationales behind them allows us to come up with our own abstractions, for example, the pattern matching thing or the coroutines-based scheduler that not only was responsible, but improved a lot the performance of our component in the example. For the next conclusions, I'd like to mention this talk by Rich Harris from years ago, it's called Rethinking Reactivity, and in this talk he explains a whole thing of why React is not reactive. And I think he's right, it's not reactive, but it feels more and more concurrent, and that's amazing. For one of my last ones, I love this tweet, it's like years and years ago, it was by Gisela Mohos, and he said that React is such a good idea that we would spend the next decade exploring its implications and applications. And I couldn't agree more, because the fact that we are here, a lot of people discussing all of those concepts, because they all somehow relate to React, shows that React is this democratic agent of spreading that kind of knowledge that's not so popular in the front-end world. So I really love that. And for my last one, I have this picture, and people say that a picture is worth a thousand words. So this is me, eight years ago, in an iOS meetup, telling iOS developers that Ionic or Angular would be the whole feature of development. So I guess the last conclusion is, don't always trust all of my speculations and future predictions. These slides are going to be available on my speaker deck profile. And that's all. Thank you so much for being here. And it feels great to have those events back. Thank you, thank you so much. Now before people rush off to lunch, I have this massive cup filled with React coins. Remember, if you collect the most React coins, you can get a prize. Stick around, you'll find out how to get these off of me. But thank you so much for your talk. I really enjoyed it. Let's jump into some of those questions. So, when throwing a promise for suspense, can you also use React hooks that store data under fiber in the same stack frame? Read them off. I'm not sure if I get this one. It first has to rephrase, maybe. We'll come back to that one. But let's jump on to the next question about try-catch and being an expensive operation. As far as I'm aware of, no. But to be honest, I'd never checked benchmarking on that, to be honest. But as far as I'm aware, no. Probably this question was because of the things I mentioned, the difficulty of memorizing stuff and etc. But it relates more to the whole way you abstract those than to trying to catch something itself. And for example, this was the reason that the proposal for a Jibraik effect didn't move forward a lot. Because we got to the same thing that it was difficult to optimize that, but not because of the whole nature of try-catch. Nice. Now, we don't have any more questions, but I just want to learn. I personally have not actually learned a lot about fiber. So I was listening to your talk, but there's so much that I would like to go and research and learn more about. Where should I start looking? One of my favorite ways of doing that is actually talking during discussions to all the RFCs. And they've been doing, since 2018, a lot of really good work with the RFCs, Reaper, etc. But go through the PRs' discussions, especially the old ones, because you'll see how they evolved to the point it is now. You will understand the context. That's one thing. And go through other repos, like React Basic and the RFCs repos. They are repos outside of the main one, but they have a lot of content that helps you understand the historical context and how they got to this point. That's one thing. And the other thing is check inspiration from other languages. For example, there's a language called F, and it implements out of the box this whole idea of effect handlers. It's called F for a reason. So check how those other languages abstract on top of that. That's interesting. For example, for the thing of homo-iconicity, Closure or other Lisp languages. So try to see how other languages did a lot of previous work on that abstract on top of this, and then connect it to what you see of the previous work on the React team and try to piece them all together. Nice, nice. Now this next one looks more like a comment than a question about Drupal throwing values instead of exceptions. And I guess we don't need to feel too bad about that, React. Yeah, I didn't know, to be honest. I mean, I've done PHP in the past, but I didn't get to that point. But that's interesting. And by the way, I'm not sure if that was a post or only a comment in a PR discussion, but then Abramov was kind of mentioning that it's a pity that today we see only suspense as throwing promises, because it's way much more than that. There's a lot of interesting work on caching, for example, that pieces together with suspense. So I myself was one that was making jokes about throwing promises for a while. But yeah, it's interesting to see that. Nice. Thank you so much. Now, before you go, folks, I have literally maybe a couple hundred of React coins. I'm going to give them over to Matthias. Wow. Because if you go over to Matthias, you can grab some of these React coins from him. However, you need to ask him a question. So Matthias, they can ask you a random question. Any question, I'm throwing you under the bus here. It could be about Fiber, it could be just about your time speaking. And go and talk to Matthias over at the Q&A booth. Sure. And see him before you go to lunch. Not only this, but I didn't get to put on the slides, but I did trade stickers, as you saw. Trade stickers, yes. Me and him traded stickers at the booth. So definitely check him out for cool stickers. Give him a round of applause and go see him for React coins. Thank you.
27 min
17 Jun, 2022

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic