Building a Custom React Renderer | Sophie Alpert

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Sophie I used to work at Facebook and manage the react team but then I abandon them in January now I'm an engineer at a tech startup called humu where we're trying to help people build work environments that are more fulfilling and empowering and inclusive and you can find me as Sophie bits just about anywhere on the Internet so today I want to go under the hood of react a little bit since you're here I assume most of you use react but I'm guessing you won't know most of the things I'm going to talk about in this talk and hopefully you'll learn something you probably read the title of the talk on the schedule so you know where this is going building a custom react renderer but I want to start with some of the basics so when you sit down and write down some of the different parts of react you might end up with a list something like this let's say you have the built-in components those are like div and span and image things like that you have function components you have class components you have props and State and effects and life cycles key and breadth and context you might know react top Luisi and boundaries and then the new stuff which is concurrent mode and suspense and you've probably also heard of react and of react native and in react native everything on this list behaves exactly the same except for this first line these built-in components instead on react native you have components like view and text and image things like that and they take a different set of props and event handlers and so the way we act is implemented the code for everything in this bottom list is completely shared and on the react team we call the code that implements this the reconciler and it's set up to plug into these different platforms like react Dom and reactive which are officially maintained by the react and react native teams of Facebook and some people in the community have also connected react to other platforms like 2d drawing libraries and 3d graphics libraries and even rendering for for command line applications that run in a terminal and so we call each of these platforms a host environment that's because it's conceptually hosting your copy of react and providing the base level primitives that react runs on top of and I wrote built-in components here but usually on the react team are you know we we say built-in components colloquially but our formal name for these is host components because they come from the host environment and so this reconciler this bottom list is part of the code of react dom and react native it's part of what you're getting when you install each one of them but it's also available as its own package on NPM called react reconciler it's not nearly as stable as the main api's and it doesn't have a ton of documentation but you can build your own renderer using that package and that's what I'm going to show you how to do today the reconciler package has two different modes depending on what kind of host environment you're trying to integrate with the first one is called mutation mode you use it whenever you're integrating with an environment where you have views that are mutable in some way this one will probably be a little more intuitive to most of you and so usually what this looks like is the host environment you have some sort of create view call maybe this returns a view and then when you want to change the appearance you mutate this view somehow so maybe you call like update view which takes some new props so maybe you're setting the color to red and that will mutate this view in place and I'm using sort of a generic API here if you're if you're thinking about an actual Dom API this would look more like document dot create create element this will return a div and then to update the color you might do div dot style dot color equals red something like that and so react Dom is built using this mutation mode because mutation is how the Dom itself works the other mode is called persistent mode and I'm not going to talk too much about this today I do want to just mention it it's used for if your target is an environment where the API treats treats the tree of views as persistent immutable objects we're instead of mutating of you in place you create a new version of it each time you want to update it and so that looks something more like create view and then instead of update view and changing it in place maybe a call like clone view where again you pass it some new props but instead of changing this view this returns a new view like a new version of that same view and then you need to update the parents to point to that new copy the next generation version of react native that the team's been working on for a while uses this mode because it's more efficient in several cases especially with concurrent mode and in react native we have complete control over the environment so we can make it work like this so the one that I'm really going to dig into today though is mutation mode whenever react does a render or a rerender including for a state update it calls in to all of the relevant components in the application it does a sort of death and then it comes up with basically a list of commands for what needs to happen in the hosts environment commands like update few acts to be read or it might say create a new view or might say add that view you just created as a child of view why something like that and so our job in bridging react to this host environment is to implement how each of these commands works I suspect that many of you are familiar with the basic Dom API is like document create element and div style color so to make this a little bit easier to follow what I'm going to build today is a simple version of react Dom this might not be quite as exciting as building against a flashy new platform but you can take what I'm going to show you here and apply it to any platform not just the Dom so let's get started my base here is going to be a create react app app which most of you may be familiar with and if I open index is you can see that it's using the real version of react Dom it's importing from that react Dom package and what I'm going to do is just take out we got Dom and replace it with our own version so I'm going to make a new file I'm going to call it react Dom mini and then I'm going to import that file and then instead of react Dom dot render I'm gonna call react Dom mini dot render and it crashes because I haven't actually implemented anything in this file yet but in my render implantation here this react Dom mini is I want to start by importing the reconciler module from npm so this is import reconciler from the react reconciler package which i installed in my project that's really the only change I made from a plain new installation of create react app and so this is the package I mentioned that has all of the guts of react that are fair across different platforms and so the basic structure here is I'm going to instantiate react reconciler and this takes a sort of config object which is configuration for how to talk to the host environment or we sometimes call this the host config and this returns a reconciler and then the API for this reconciler object is slightly different than the top-level API of react Dom so in order to make the dot render method available that we're calling here and the index file I need to define Oh No I need to define an object which I'll just give one method and render it should take two arguments the first one is the what we're rendering and the second one is the container we're putting in so this is what to render and then if we're gonna put it in and export this when the render method is called we're gonna want to call into the reconciler somehow so that this thing we want to render actually gets passed to react and so if I reload now I'll see a white screen because my render method doesn't do anything yet and in this host config there are actually a whole bunch of methods that you need to implement to get a working render and so I'm gonna I'm gonna paste in a little shell here with just some stubs for methods you can see that I am starting with supports mutation colon truth that means I'm going to use that mutation mode I talked about and then I have a bunch of methods here where all the implementations are empty but that'll just save me a little time typing later and don't worry too much about reading all the names I'll tell you about the important ones as we fill them in I'm also gonna fill in this render method with a couple lines where the reconciler has its own notion of a container so I'm gonna first reconciler to create container passes this div a couple false arguments which I believe are for concurrent mode and server-side hydration neither of which we're using today and so this returns a container and then I call reconcile add a container with what to render and a reference to that container and a couple of and I don't need to return anything that's all I need to do so the first method in this host config and I'm gonna implement is called create instance this gets called whenever there is a new host component that we want to put on the screen it has a bunch of different arguments but the ones we really care about our type and props right here so I'm gonna start by just logging type and props and if you look at the dev tools here on the right side you can see that type is just going to be a string that is the HTML tag that of the element and then props is going to be a props object for what is being passed that element and I want you to notice that already all we have to worry about dealing with is these low-level whose components even though there is this app component that we're rendering you don't actually see the app component in these logs here because react is taking care of it before this method even gets called and so what do we want to do well to create a new node in the DOM we call document that create an element I'm going to pass it the type which is again the tag name that we're using I store that in a variable called yell and then I want to deal with the props as well I'm going to start with just a class name so if prop stopped class name is present I'm gonna do class name equals class name and if props dot source I'm also gonna do source because there's an image and it won't look great if we don't have the source gonna do that the same way and then we're gonna return a reference to this element it's worth noting that react itself is not designed to be tied to the Dom so it doesn't actually have any idea what a Dom element is or how to interact with it so you might be wondering why we even need to return it here from create instance the answer is that react reconciler isn't going to call any methods or set any properties on this object but what it is going to do is pass it back to us in some of these other methods so that we can manipulate it a later so react doesn't actually care at all what the shape of this return value is so if I want it to return maybe some other data with it you know I could write I could return an object that has some other data here and react doesn't care but for our purposes it will work for me to just return the element I also didn't tell you yet about a second type of host component that we also need to implement and that's a text component or more specifically a host text component you know when you write some code like this where you say div and maybe it says click here you have a button you can see two components already you have the div component and the button component but there's actually a third component here which is for the click here text so whenever you have a string or a number in your component hierarchy react is going to create one of these text instances and the implantation here is pretty simple we get to just call document create text node and return that the browser here is going to return to us a dom text node which if you're not familiar with them is pretty similar to add on element it's just a different type of dom node that can live in a dom tree and when the browser renders it you can see some text this is also how any type of text in a page works like when your browser is just parsing some HTML if it sees a piece of text it creates one of these Dom text nodes so we're just doing the same thing so now we're creating all of these instances but we don't see any of them yet and the reason for that is that we're not attaching the node anywhere you need to add an element to some parent in order for it to actually show up in the dom and in fact we actually already be calling these next three methods for append child we're just not doing anything in them there are these three different methods append child to container append child append initial child and they're called in slightly different situations the differences aren't that interesting right house so we actually want to do the same thing and every single one of them this child value here is going to be one of the instances that was returned from either create instance or create text instance so it's going to be either a Dom element or a Dom node and container here is also going to be a and so I can call the doms appendchild API that's built into browsers so I'm gonna call continue append child I'm gonna do the same thing here so now each time react wants to add a new element to the hierarchy it'll call one of these three methods which will add up which will end up calling the doms append child api and so if i see hopefully you can see it works and so you can see we've only written about like ten lines of actual code but already we have something on the screen that's pretty cool right I also before we get into updates I want to fill in a couple more props here I did class name and source but there's actually six different props that are used here and that's why I like this learn react thing that is supposed to be a link is not underlined and I can't click it and so let me fill those in those are alt class name H ref rel source and target are the ones that are used in this app and so for each of those I'm gonna say if props has that key then just copy it to the element I don't need that anymore and you can see now I have a link which opens cool so we don't handle any state updates yet which I guess kind of defeats the purpose of using react so with a spinning logo by by the way is done completely in CSS there's no js' updates so that's why that works but let's let's change that and add some state so here's our app component and for this demo I'm gonna make it so that you can when we click here it'll toggle whether the logo is visible so I'm gonna add a state variable new state and that's gonna default to true and then otherwise and then I want an onclick handler so on click I want to call set show logo and I'm gonna set that to whatever the opposite of what it was before is and I made a typo now I'm missing a brace here that I hear I'm using the functional update form of set state so I got the old value here and then I'm supposed to return the new value and if I reload and try to click nothing happens and the reason for that is we didn't ever say what on-click means the only props were handling are these ones that we wrote down and create instance remember react doesn't know anything about any Dom specific properties it doesn't know what a click is and so if we want on click to do something we need to handle that ourselves so if I log the props dot on click property then you'll see on the right side that is that function that we just set up and so the logic to actually call this when I click it's pretty short I'm gonna say if props dot on click is present then I'm gonna call add event listener on the element give it a click event and pass props on as a function and so if I did that right now when I click on the page it's gonna call my function which is supposed to hide the logo do you think it'll work and so if I click if I click and I get an error and I think the event listener for it actually did we can verify this by adding an alert here I'm just gonna say alert hi if I click I do get that alert so the event listener is working it's actually the rear-ending that's throwing an error and so the reason it's crashing now is that I didn't actually implement the methods that were used for removing things from the Dom which is these remove child methods and the implementation here is also going to just be one line I can call the built-in browser remove child method on the container element or on the parent element while I'm at it I'm also gonna fill in these implementations for insert before which is used when we're adding an element in the middle of a list instead of at the end of this and luckily we can just also use the built-in Dom API insert before because it has basically the same API so child is the same and then before is another node that you want to insert it right next to and so I can do this I call and so now when I click just the logo actually disappear it does and when I click again it comes back so you can see that in our renderer we didn't have to write any logic specific to state updates or how they're handled or knowing when to re-render but we were able to use state within our component again that's because react state works exactly the same on every platform and so it's implemented inside this shared react reconciler module and now we can handle components being added and removed the final big piece I'm missing is updating properties of components that are already on the screen let me make it so this text here on the right side is a background color that just changes over time so I'm going to have variable start with red I'm going to add an effect in this effect I'm gonna have a list of colors in a cycle between red and green and blue I'm gonna have a counter and I'm going to call set interval so every one second I will increment I and I'll set color with the next item in the list so that's column I mod 3 will loop around this will return an interval and that and to clean up I'm going to return and let me set this as the background color of this paragraph tag here now up to this point I've been matching the api's that react Dom itself uses and so this component works is actually the same if you were to run it under react Dom but I want to remind you that we're actually building our own environment here and so we can kind of do whatever we want let's say I don't like the CSS inline style API and I want to go back to the days of HTML 3 where we could just put like a BG color attribute on any element and so I'm gonna do that I'm gonna write BG color equals color here and then to make this work I'm gonna have to handle it in create instance ok if drop stuff BG color and here I do need to use the style API that browsers have and I'm just gonna set it to props dot peachy color and that works so if you tried to use this prop in react Dom it wouldn't work but we make the rules here so the red background color does work the effect I wrote is probably working but the code the color isn't changing and let's make that happen now when react does any state update it calls all of the components and tries to figure out what changed between the old and new trees and it does all of this before actually modifying any of actual host components these render calls and the diffing happen in what we call the render phase and then once we've prepared the whole tree that's when we go to the commit phase which is when we actually change things in the host environment and mutate the Dom so to make updates work we have to implement two more methods prepare update and commit update here and there divided according to these two phases prepare update is called in the render phase commit update in the commit phase and so it gets a whole bunch of arguments here including the old and new props and it's supposed to return what we call an update payload which is basically just a diff between the old and new props so I'm gonna make a payload variable and I'm feeling a little lazy so I'm only going to compare the bgcolor props so I'm just gonna say oh if old props dot bgcolor it's not equal to new props dot Petri color then I'm gonna set the payload and this can really be whatever we want react again doesn't really care what this is it's just going to get passed to our next function so I'm going to say new PG color is going to be a new prop style BG color that payload and then in commit update we got the component instance which again is the Dom element itself and the update payload that we just returned from prepare update so here all we need to do is we need to say if update payload has a new BG color property then I'm gonna background color equals and that should do it so you'll notice that just like for state we didn't have to do anything special for the effect to work react takes care of all that it'll just calls us when it's like oh now it's time to change this host component and what it looks like and so now we have a renderer that can render this page do some basic event handlers and handle changes in the component tree both adding and removing components as well as updating components that are already on screen and I think that covers the main things that we expect react to do and this file is still only about a hundred lines and that's all you need in order to write a basic interface with a new host environment so I feel pretty good about this the real react on implantation implements these exact same methods this is how the real one works it probably wouldn't surprise you though to hear that the real react Dom is quite a bit more than hundred lines and at this point you might be wondering what the real one does that I didn't build here and I don't have an exhaustive list but I wrote down a few of the major differences in this toy version we're only handling a few properties on initial render and on for updates we don't have any special support for form controls there's actually quite a bit of logic you need to handle controlled and uncontrolled components we didn't build anything in 4s and so although you can so if you try to take Raph to one of these host components instead of getting the Dom node back like you do in react Dom you just don't have access to it and there's no way to actually get the The Associated Dom node in this render we built there's a lot of attribute and event normalization for cross browser compatibility that I left out and we don't support SVG event bubbling through portals is another one I didn't actually test portals at all so I have no idea if they work and probably a lot of other education I forgot but I think these are the biggest differences and that's all I have for you today I don't know if this talk was super practical for most of you I imagine it's rare you'll find the occasion to build your own react renderer from scratch but I hope you found this fun and I hope you learned a little more about how react works and more than you knew before thank you [Applause]
Info
Channel: React Conf
Views: 37,365
Rating: 4.8172379 out of 5
Keywords:
Id: CGpMlWVcHok
Channel Id: undefined
Length: 28min 21sec (1701 seconds)
Published: Wed Oct 30 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.