10 ms·
Show HN: 33 line React
- andrewstuart 7y agoReally not React in 33 lines at all.
- Jean-Philipe 7y agoCare to elaborate?
- anonytrary 7y agoIt's a very, very minimal implementation. The diff algorithm they use does not really have the necessary optimizations implemented.
- capableweb 7y agoNot all applications have a requirement around "necessary optimizations". Sometimes if the application is simple enough, something like this submission could be good enough. Or the constraints could be different where the bundle size vs performance implications could be worth trading. Depends on context :)
- anonytrary 7y agoYou seem to be talking about something else. We're talking about how this library is not React in 33 lines. It could be better labelled as a "VDOM framework in 33 lines".
- pgt 7y agohttps://github.com/leontrolski/leontrolski.github.io/blob/master/33-line-react.js https://github.com/leontrolski/leontrolski.github.io/blob/ma... captures the spirit of React. The "diff & patch" pattern is super common, e.g. Git and Incremental by Jane Street: https://github.com/janestreet/incremental https://github.com/janestreet/incremental
- emmelaich 7y agoAnd curses! > The programmer sets up the desired appearance of each window, then tells the curses package to update the screen. The library determines a minimal set of changes that are needed to update the display and then executes these using the terminal's specific capabilities and control sequences. https://en.wikipedia.org/wiki/Curses_(programming_library) https://en.wikipedia.org/wiki/Curses_(programming_library)
- dang 7y agoThe submitted title was "React in 33 lines". We've since changed it. This is a case where rewriting the title did the article a disservice. To my ear there is a subtle but significant difference between that and "33 line React", which is what the author actually said. "React in 33 lines" sounds reductionist. With the actual title, there's an implicit "a" (i.e. an indefinite article) at the start, i.e. "A 33-line React". In other words: a short exercise exploring the core idea of React. Not a claim of total equivalence. Submitters: please read the site guidelines. They include "Please use the original title, unless it is misleading or linkbait; don't editorialize." Much of the time, when people rewrite a title, they make it more misleading or linkbait, even if they didn't mean to. https://news.ycombinator.com/newsguidelines.html https://news.ycombinator.com/newsguidelines.html
- leontrolski 7y agoAuthor here, this was written as a way for me to explore the core idea of react in a minimal setting. Things it's missing - jsx - routing - performance (maybe?) - component state management stuff What else would you include?
- ngrilly 7y agoAgreed, except for routing which is not a part of React per se.
- pgt 7y agoExclude JSX and routing. You'll need some state. Routing != rendering and JSX is an abomination. For optimal performance, look into differential dataflow: https://github.com/TimelyDataflow/differential-dataflow/issues https://github.com/TimelyDataflow/differential-dataflow/issu...
- dahart 7y agoThis looks interesting! Not a working replacement for React, right? I would probably add that for optimal performance, don’t use functional differential rendering at all. Keep state and use direct DOM manipulation of only elements that change, right? The very idea of re-rendering by throwing a whole new page up and letting the system diff and update is guaranteed not to be optimal performance, it’s always doing more work than necessary. The tradeoff is that the optimally performant solutions are imperative and stateful and involve all the difficult and bug-prone techniques everyone’s trying to avoid. Engineering for functionality is a solved problem. Engineering for performance is not.
- petilon 7y agoConsider adding something like this for jsx (200 lines): https://github.com/wisercoder/uibuilder https://github.com/wisercoder/uibuilder Router (500 lines): https://github.com/Rajeev-K/mvc-router https://github.com/Rajeev-K/mvc-router
- dang 7y ago(This was originally a reply to https://news.ycombinator.com/item?id=22777156 https://news.ycombinator.com/item?id=22777156)
- tudorizer 7y agoThis also doesn't mix pseudo-HTML (JSX?) with JS code, so that's cool. As someone who has sort of lost contact with the front-end world, it's nice to see a coding TL;DR.
- frosted-flakes 7y agoWhy is it good? JSX is just a declarative syntax for creating DOM elements (usually), and in practice it works really well.
- s_y_n_t_a_x 7y agoJSX is syntactic sugar for nested function calls, it is JavaScript. That's why you can do anything (declarative) in them. You can loop, have conditionals, etc. without having to learn a template syntax.
- andreigaspar 7y agoI like this, it illustrates the concept nicely
- sfvisser 7y agoNice! Explorations like this are truly useful for understanding and maybe even for innovation. You might figure out new ways of expressing things that are unlike react, but solve particular problems better. As a next step I'd recommend trying to add component-local state connected to the component's lifetime. This is essential when building larger applications out of abstract building blocks that hide inner workings. I expect this to be a nontrivial addition, but I might be wrong!
- leontrolski 7y agoI'm not sure I can quite express what I'm thinking, but do you think this could be achieved orthoganally to the rendering library itself? (Just by "normal" js).
- jmchuster 7y agoIf you came to this comments section looking for an actual lightweight standalone version of react, preact+htm works wonders.
- lioeters 7y agoI searched for "preact" in the thread, since I'm a big fan. Its small size and compatibility with React API make it suitable for use in "embedded widget" type situations, where a dependency on React would be too heavy. It's also perfectly suited for building full apps, or static sites. I've been using Preact on various projects for years, I love it.
- anonytrary 7y agoThis library isn't meant to have full parity with React, but it's a great example that illustrates how simple a naive VDOM diff/patch is to implement. That said, this is missing a lot of essential optimizations that libraries like React make. For example, key and type matching, batching updates, scheduling updates, and other stuff that isn't immediately apparent without reading the code.
- leontrolski 7y agoI'm not sure if any of their content is still relevant, but mithril makes some bold claims for >React performance: https://mithril.js.org/framework-comparison.html#react https://mithril.js.org/framework-comparison.html#react "Generally speaking, React's approach to performance is to engineer relatively complex solutions. Mithril follows the less-is-more school of thought. It has a substantially smaller, aggressively optimized codebase. The rationale is that a small codebase is easier to audit and optimize, and ultimately results in less code being run." (Obviously, I'm not claiming the same for this toy example - just that batching updates etc, may not be necessary for performance).
- sdegutis 7y agoI wondered what happened to .prop() which looked like it had a lot of potential. Looks like that potential was (at least partially?) realized as it evolved into a "stream" library [1], which basically acts like cells in a spreadsheet. This seems like a really useful concept which could be used to create more kinds of UI frameworks than just declarative React style ones. [1]: https://mithril.js.org/stream.html https://mithril.js.org/stream.html
- modarts 7y agohttps://cycle.js.org/ https://cycle.js.org/
- chrisweekly 7y ago+1 for cyclejs! Strange to me that it hasn't caught on more
- manthideaal 7y agoIn the source code to perform el.classList = v.classes the author uses: for (const name of v.classes) if (!el.classList.contains(name)) el.classList.add(name) for (const name of el.classList) if (!v.classes.includes(name)) el.classList.remove(name) Why the standard decided to make classList read-only? Could ele.className=(v.classes).join(" ") be a valid and performant solution?, perhaps is to avoid the string to token traslation for performance reasons?, then why don't they include a classList.set method?
- leontrolski 7y agoAgreed, let me know if you find a better way! I feel if the browser APIs were slightly more declaratively inclined, this article could be "10 line React".
- deleted 7y ago[deleted]
- manthideaal 7y agoto merge the properties of two objects you could use let merged = {...obj1, ...obj2}; See (1) which has 2808 votes. (1) https://stackoverflow.com/questions/171251/how-can-i-merge-properties-of-two-javascript-objects-dynamically https://stackoverflow.com/questions/171251/how-can-i-merge-p...
- masklinn 7y ago> Why is classList read-only, I can think of any valid reason. Because it's a live DOMTokenList, not a computed property. That is if you keep a reference to a classList and mutate the className parameter, classList will reflect the changes. classList could also be a computed property which, when assigned to, clears the underlying token list and adds all elements but I'm guessing the developers of the API saw this as unnecessary complexity given you can do the same thing by setting className. The point of classList is the ability to more easily check for and toggle individual classes, if you don't need that capability you just don't use classList. > Perhaps ele.className=(v.classes).join(" ") is a valid solution? Yes.
- k__ 7y agoNice experiment. I've also "re-created React" multiple times before it was actually created. Diffing somehow always ended up being thr most natural way tondo things in the end. My selling point in the end were the components
- leontrolski 7y agoMy feeling is that if html and js had syntactically shared data structures at their conception, we wouldn't have quite so much frontend mess as we do now and declarative UI would have been the norm for a while. Having said that, I'm not sure how much precedent this has in UI frameworks outside of the web.
- k__ 7y agoYou think so? What would be different if Document.createElement('a') Would look like <a/> ? The rest would still be imperative code.
- capableweb 7y agoThe first one is imperative and locks the implementation to use the Document object and the createElement function. The second one is declarative and leaves it up to the receiver to handle however they seem fit. I'm not arguing one is better or worse, or that we would be better off if we we're using the JSX way with native support in the browsers, just explaining how I see the differences between the two.
- colejohnson66 7y agoIt’s been a bit since I’ve used JS (so I may have the latter syntax wrong), but I’d prefer the JSX style of var elem = <a href="#test">Test</a>; over var elem = document.createElement("a"); elem.href = "#test"; elem.innerText = "Test"; jQuery allows one to do something similar to JSX (which is nice): var elem = $("<a href='#test'>Test</a>");
- ken 7y agoI don't interpret "html and js had syntactically shared data structures" to mean what you wrote, unless you also replace all other JS data structures (arrays and dicts and ...) with <> syntax. You've made it more concise but it's still distinct. For an example of a system where the programming language and document generator share data structures, look at Hiccup (and Reagent). One of their very first examples is putting a loop inside a list. There's no special API for the attributes -- it's just an ordinary map. There's no special API for the children -- it's just an ordinary vector.
- codegladiator 7y agoLooks like mithril (the m object and m.render almost the same) ? https://mithril.js.org/ https://mithril.js.org/
- leontrolski 7y agoYeah, it's very mithril influenced (I love that library) - I namecheck them in the article at the top.
- codegladiator 7y agoOh I completely skipped that line. I like mithril a lot as well but haven't been able too use it in a real project.
- red_admiral 7y agoWhat I really like about this is it removes some of the feeling of magic around react and similar frameworks. Yes there's a lot more going on in real react, but the core idea is simple and you can build your own and really learn what's going on at the same time. Well done! Bookmarked!
- zserge 7y agoNice project! I've also recently made a React clone in <1KB, with JSX syntax and Hooks - https://github.com/zserge/o https://github.com/zserge/o
- pomber 7y agoNice article, short and to the point. If anyone wants to take this idea to another level, I wrote a post showing how to do the same but in 10 times more lines of code: https://pomb.us/build-your-own-react/ https://pomb.us/build-your-own-react/
- aenario 7y agoLove the format with "live-coding". Is it git-based? Did you open source it?
- pomber 7y agothe blog source: https://github.com/pomber/site https://github.com/pomber/site the post source: https://github.com/pomber/site/blob/master/posts/build-your-own-react/index.mdx https://github.com/pomber/site/blob/master/posts/build-your-...
- capableweb 7y agoVery cool, thanks for sharing that! Now all that's missing is a "eval" button that runs the result and drops you in a repl to experiment with the results.
- porsager 7y agoHey, that's what Flems is for. Check out https://github.com/porsager/flems https://github.com/porsager/flems - a single include embeddable sandbox (coincidentally written in mithril as the original post references). It's also used for https://flems.io https://flems.io
- capableweb 7y agoHm, you seem a little to eager to promote your own stuff. This thread was about the "live-coding" aspect where the code updates on the left as you scroll down the post, so you can follow along with how the code is being built. I fail to see how your project does that. Yours seem like the standard code playground, so very different from what was linked earlier in the thread. Or I misunderstand or simply missed where the "live-coding together with text" comes in.
- deleted 7y ago[deleted]
- joosters 7y agoThis is probably a naive question, coming from someone who has written very little JavaScript: Why do we need React? Why can’t the browser do the same job? Instead of passing a virtual dom to react, and letting it ‘diff/patch’ the real dom, why can’t code regenerate the dom directly, in a similar way? This would seem to save both time, memory and effort, since the browser can do the diff/patch itself, much more efficiently?
- quezzle 7y agoIt’s a framework. Similar questions are: Why not build rails into ruby? Why not build Django into python? Why not build qt into c++? It’s a programming flow and model, dom diffing is just one part of it.
- ken 7y agoThose examples are all programming languages. "Web browser" is not a programming language. It's practically a complete operating system, and they have not in the past shown much reluctance to add features to that big ball of mud, when it helps programmers.
- galaxyLogic 7y agoRight. I think the value of this "React in 33 lines" article is it shows us such an API would be easy to implement and add on by the browser-makers. Would it be as fast as React? Well it wouldn't need to be, but it well could easily be since it would be implemented in native code. It could be an extension to the Shadow-DOM API, right?
- masklinn 7y agoBecause the DOM is a very specific style of API which is both extremely procedural and extremely heavy, your average DOM element is full of cross-links to other elements, so generating a DOM is both a pain in the ass and quite expensive. It's also extremely stateful, so while you could generate a new DOM and replace the old one you'd lose all information in flight e.g. focus. Browsers might be able to provide a lighter vdom layer but… well first I'm not convinced the gain would be that big, and second what would the API be? It's not like there's a clear winner that has emerged, react is quite popular but hardly the only contender, and react has kept evolving its API so the sort of stability you'd want enshrined in a browser isn't exactly there.
- jonahx 7y agoVery nicely done. For a similar idea that can be used in production as a React replacement, check out the Meiosis pattern: http://meiosis.js.org/ http://meiosis.js.org/ It has most of React's benefits, while being extremely lightweight and fully grokkable (no hidden parts, no magic, no framework logic) to any developer.
- benbristow 7y agoWith none of the community support or third-party components that the React ecosystem has?
- keb_ 7y agoWhy use any new & upcoming tool when an older, more popular tool inevitably has more community support and third-party components?
- hencq 7y agoI'm not sure if you're the author, but in case you are I just want to say well done on the tutorial! I really like the way it's built up and not only shows the pattern, but also the why behind the pattern. I will definitely give this a try with Mithril.
- leetrout 7y agoCode like this is interesting to me whenever I see it: const g = [['', '', ''], ['', '', ''], ['', '', '']] // grid Why not just be descriptive and call it `const grid`? It's like we're all playing some game to be as terse and obscure as possible with variable names in all languages.
- jonplackett 7y agoSo many code examples have things like this and worse. One I saw the other day had about 10 variables created in one part and then these were all placed into another object that had the same names as all the variables. You also see a lot of examples with loads of other irrelevant code in them. Just keep it simple please!
- scelerat 7y ago"Code golf" https://en.wikipedia.org/wiki/Code_golf https://en.wikipedia.org/wiki/Code_golf
- rpunkfu 7y agocries in golang
- leetrout 7y agoFor small functions / blocks and descriptive types I’m ok with it in Go BUT I always use descriptive names anyway. So much easier to keep context in my head.
- libria 7y agoI thought the whole single-char-var thing was for very short-lived variables. I very much prefer for c in internalProductCategories p = fetchProductsInCategory(c.id) m = getAccountManagerName(c.id) results.append({categoryName: c.name, products: p, manager: m.name}) to for internalProductCategory in internalProductCategories products = fetchProductsInCategory(internalProductCategory.id) manager = getAccountManagerName(internalProductCategory.id) results.append({categoryName: internalProductCategory.name, products: products, manager: manager.name}) They're both clear, neither needs comments, but one is unnecessarily verbose.
- seanwilson 7y agoI'm guessing diffing and updating the DOM is really inefficient if there's a lot of DOM elements and a few frequently updating part of your UI? e.g. if you added a clock to the game in the article that updates every few fractions of a section to show something like "14.3 seconds taken so far". The solution to this would be to make the clock into its own tiny self-contained component and to make diffing smarter so that only the DOM parts that belong to the clock need to be diffed/checked each clock update?
- robertakarobin 7y agoYou would think that diffing is inefficient, but apparently modifying the DOM is significantly more inefficient. Mithril -- the framework that inspired this example -- has an interesting take: https://mithril.js.org/vnodes.html https://mithril.js.org/vnodes.html
- websitescenes 7y agoI think he means that this snippet of code doesn’t account for diffing like React does and would fail if scaled out. I think the OP is aware of that though.
- TheRealPomax 7y agoAs a 33 line implementation "just for fun" exercise, no need to guess: this was not meant to be actual React in fewer lines of code =)
- seanwilson 7y agoI don't know how React does it so it was a genuine question. I'm assuming they do some smart diffing to be more efficient. It'll be irrelevant for most apps but I'm curious how much less efficient all this is compared to (in CPU time, not developer time) updating the DOM directly e.g with: document.querySelector('.timer').textContent = "5 seconds"
- leontrolski 7y agoMithril's approach (as robertakarobin refers to) is that diffing VDOMs is cheap (where as updating the DOM itself is expensive), the "library" in this post takes the same approach. It's interesting, there's a lot of chat about performance with these frameworks, yet I'm not sure how much is relevant to the real world. When I do eg a document.querySelectorAll('*') on airbnb map view (I guess a pretty good example of a mid complexity SPA), there are 3407 DOM elements - doing a diff on the VDOM elements of these should quick. If I were doing something like a clock on a page (or an animation for example), I'd probably just do it out-of-band of my framework. These kind of things tend to be few and far between anyways.
- jxub 7y agoWeirdly triggered by the function arrows lacking spaces around them. Good work though
- diablo1 7y agoThis reminds my of Cash[0], a lightweight jQuery alternative for working with the DOM in a more accessible way [0] https://github.com/kenwheeler/cash https://github.com/kenwheeler/cash
- leeoniya 7y agoCash has some api incompatibilities with jQuery. https://umbrellajs.com/ https://umbrellajs.com/ is both smaller and better.
- fnord77 7y agoOT this is the first time I've seen the arrow function syntax in ES6. My god it is horrible. const Cell = (value, i, j)=>m('button.cell', {onclick: ()=>move(value, i, j)}, value ) I can see why people are using things like clojurescript instead.
- pier25 7y agoIt's not only a syntax for anonymous functions. It also binds the function to the context where it's defined. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe... Looks weird at first but you get used it pretty quickly. And I gotta say the auto binding thing is awesome.
- fulafel 7y agoRe the Clojure comparison, it's not much of a consolation as the function binding problem doesn't exist there.
- pgt 7y agoWait till you see what it does to scope!
- TheRealPomax 7y agoWe all hate new syntax that we feel isn't "samey" enough to what we already know. That doesn't mean it's gross, it just means you learned something about your own preconceptions of what you thought JS was supposed to look and feel like. Thankfully, most of the time we learn that's just us, and move past that. Arrow functions have been around for nearly five years now: they're fine. You learn that they're just part of JS, and you see them everywhere in modern code, just with normal indentation and sensible white spacing to keep them perfectly readable (because minification will solve the "the code as written is too verbose" part of the dev cycle). The important thing is what they do: bind declaration context as execution context, so `this` keeps meaning the same thing, which drastically simplifies a lot code. In this case, there is simply no reason to use arrow functions at all, because we don't need to preserve the declaration context (there is no `this` that needs to be preserved), so if this were real code you'd probably use a normal function, and your comment should probably be "why not this?" function makeCell(value, i, j) { function onclick() { playMove(value, i, j); } return build('button.cell', { onclick }, value); } Or even, "actually, why not this?" /** * ... */ function clickCell(evt) { const t = evt.target, d = t.dataset; playMove(t.textContent, d.i, d.j); } /** * ... */ function makeCell(value, i, j) { const e = build('button.cell', value); e.setData({ i, j }); e.click(clickCell); return e; } To which the answer of course is "this isn't production code, arrow functions are fine. I mean come on, it's just a fun little JS exercise, stop trying to turn fun into work".
- leontrolski 7y agoAuthor here, nice to see this got some interest! As a very small benchmark (uh-oh!), in a console on the linked page, I tried to make/recreate 10,000 divs: const root = document.getElementById('noughts') m.render(root, {children: [...Array(10000)].map(_=>m('', {class: ['hi']}, 'hi'))}) m.render(root, {children: [...Array(10000)].map(_=>m('', {class: ['bye']}, 'bye'))}) wrapped with: console.time('a'); ... console.timeEnd('a') On my laptop (admittedly a fairly new macbook), I got (approx): 130ms to make divs from scratch 80ms to switch from 'hi'->'bye' 50ms to re-render with no changes I'd be super interested as to what comparable figures would be for React. There's a fair bit of interesting discussion below surrounding performance, but with no datapoints.
- jsf01 7y agoThere are some fairly standard vdom benchmarks you could implement instead. https://github.com/krausest/js-framework-benchmark https://github.com/krausest/js-framework-benchmark https://localvoid.github.io/uibench/ https://localvoid.github.io/uibench/ Or even just something following mithril’s own benchmark suite: https://github.com/MithrilJS/mithril.js/blob/next/performance/test-perf.js https://github.com/MithrilJS/mithril.js/blob/next/performanc...
- leontrolski 7y agothanks!
- bastawhiz 7y ago50ms to rerender with no changes is quite slow. One frame is 16ms. Being able to rerender part of the render tree in ones of milliseconds is critical for most reasonable frameworks.
- retreatguru 7y agoI believe those numbers were for 10,000 divs.
- freefal 7y agoThe <marquee> tag is a throwback! Love it (in a toy project like this)
- CitrusFruits 7y agoAnyone who likes the idea of a minimal virtual DOM (like this) will probably appreciate MaquetteJS: https://maquettejs.org/ https://maquettejs.org/
- ppeetteerr 7y ago> const move = (value, i, j){...} > This function makes a move in the game, it takes 'x' or 'o' along with 2 integer coordinates. It will mutate all the state variables to reflect the new state of the game. After that, it calls renderNoughts(), this is a call to rerender the game - but we'll come back to that. Dear programmers, not related to the actual article but, when you have to describe your function with a paragraph of text, please consider improving the naming of your function and its parameters.
- bigmanwalter 7y agoIt seems perfectly well named to me! I love when code bases have more lines of comments than actual code.
- ricardobeat 7y agoSometimes the comments are only required because the code is confusing. In this case the function both mutates the state, and renders the app. If using a framework like React/Vue etc it might make perfect sense to call this function `move`, but here it has a lot more responsibility and would need a more representative name like update/setState/renderApp/etc. But I think it's pointless to debate as it breaks the single-responsibility principle and you're unlikely to find something like this in a large scale app anyway.
- bigmanwalter 7y agoIt could also be because the code is terse. In that case it makes sense to explain what's going on.
- uk_programmer 7y agoI just had a look at the dom implementation and it similar to what I have done in typescript. There is only so many ways you can skin a cat. If you are wishing to do something similar yourself it is worth looking at hyperscript source code: https://github.com/hyperhype/hyperscript https://github.com/hyperhype/hyperscript
- leontrolski 7y agoFurther musings here - https://news.ycombinator.com/item?id=22791473 https://news.ycombinator.com/item?id=22791473
- peey 7y agoIt seems like `m.update` function queries the current DOM state. I'm interested in the performance implications of this. From what I know, other vdom libraries maintain the current DOM state in-memory for efficient computation of what should be updated. No doubt that this is much simpler and ought to be at least more efficient than re-drawing everything. How much performance do you lose due to querying the DOM state?