7 ms·
Overzealous Destructuring
- dmitryminkovsky 4y agoGlad to see this here. Couldn't agree more... Destructuring adds lots of noise and ceremony and makes it hard to see where a name came from. "Did this variable come from state or props? Is it derived?" The result is you have to keep looking up in the code, over and over again. It's lose-lose. `props` and `state` are already pretty short words. Unless you really keep using the same reference over and over and over again, it's usually not worth introducing another name for that reference into the local scope.
- deleted 4y ago[deleted]
- username_exists 4y agoI definitely get the point. I often have the urge to use destructuring for saving space, but want to kick myself when revisiting the code a month or two later. I like your rules, thanks for sharing.
- moogly 4y agoI definitely agree with the spirit of the article: you can take this too far. The real benefit of destructuring isn't really brevity, per se, IMO, but easy extensibility. You all of a sudden need two or three properties from the same object at the same level? You just reference the additional properties on the left side without any fuss.
- fredrikholm 4y agoJesus that loaded quickly. Hats off to the author for creating such a snappy site.
- danielvaughn 4y agoI probably wouldn't have even noticed had you not said anything. But yeah you're right, that was instant. Very nice work.
- deleted 4y ago[deleted]
- RedShift1 4y agoA quick look at the network tab in the dev tools: the page is less than 10 KB (fits in TCP fast start window), little external resources (10 in total: 1 HTML, 1 CSS, 4 fonts, 2 images and 2 JS files), tiny amount of JS code which doesn't add extra requests, all served by Google's CDN which probably has a mirror geographically close to you.
- Arrath 4y agoI'm almost taken aback at how quick that loaded. It reminds me of finally upgrading from dialup. The modern web experience really is subpar.
- markdog12 4y agoExtremely sad the comment is warranted these days, but yep, nice and fast.
- jakub_g 4y agoSemi-related: "there-must-be-an-eslint-rule"-fetishism. Some developers seemingly can't live in a world where certain constructs can be used sometimes in the code, and others at other times, depending what is more appropriate in a given context. If they think construct B is superior over A, they will put in place an eslint rule that forbids A and always forces you to use B. In some cases it happens that B is clearly superior because A is known to be broken for reasons x,y,z, then I'm okay with this. But sometimes those kind of rules are taken out of nowhere for no good reason, just a personal preference, or with a fake reason like this blog shows (certain construct is only better at certain times, but not always).
- legulere 4y agoA common thing seems to be that the reasoning behind the rule is missing. I have personally seen single return recommended for C# code, when the original reasoning for it is manual memory management.
- deckard1 4y agobeen there. Worst offender I've seen is an eslint rule that forces you to not mutate the object you're creating with reduce() because mutation is bad. So now you're doing object spreading and went from an O(n) algorithm to an O(n^2) algorithm. Wonderful. There is a lot of unquestioned dogma going around in this industry.
- jvalencia 4y agolol, and then you add an exception for spreading: /* eslint-disable react/jsx-props-no-spreading */
- LAC-Tech 4y agois that an airbnb eslint rule? no wonder their website is so slow!
- cercatrova 4y agoWhy not just use a for loop?
- ramesh31 4y agoNah. Destructure all the things. The alternative is writing unsafe code or using a library in place of standard language features.
- klyrs 4y agoThat's funny, a good portion of the article is about the unsafety of destructuring...
- ramesh31 4y ago>That's funny, a good portion of the article is about the unsafety of destructuring... Destructuring is guaranteed to be 100% safe if you do it properly. That's the whole point. const envelope = {data:{edge:{node:{value:'test'}}}} const {data: {edge: {node: {value = ''} = {} } = {} } = {} } = envelope || {} console.log(value) Is completely safe. Versus the alternative of using an accessor library or null checking at each level. You can also do it with arrays to avoid out of range errors: const arrayVals = ['test'] const {0: firstEl = ''} = arrayVals || [] console.log(firstEl) 100% safe and avoids mutating the array
- capableweb 4y agoIf you're like me (and many others) and can't get enough of destructuring, Clojure has it on steroids compared to JS and is everywhere in a normal/average Clojure codebase: https://clojure.org/guides/destructuring https://clojure.org/guides/destructuring Destructuring in Clojure doesn't suffer from the same problems as JavaScript, so lot of the things in the article doesn't apply. I was always slightly disappointed when destructuring finally came to JavaScript but it was so neutered compared to what it could have been. But on the other hand, I'm happy JavaScript is not becoming more complicated just for the sake of new features. But on the other foot, we already got `class` and a bunch of other complicated, syntactic sugar, so why not another?
- __ryan__ 4y agoI’m not super familiar with Clojure, but I tried to read through the link you shared. I certainly could be missing something, what can Clojure’s destructuring do that JavaScript cannot?
- deleted 4y ago[deleted]
- joshlemer 4y agoI'm not sure what are all the differences but one I have come across that you can't do in JS is to simultaneously destructure an object/array, and at the same time bind a name to the entire object/array. For instance here in Clojure we can extract the a,b,c keys from the passed map, and at the same time bind the whole map to the variable bar. (defn foo [{:keys [a b c] :as bar}] [bar a b c]) (foo {:a 1 :b 2 :c 3}) ;; => [{:a 1, :b 2, :c 3} 1 2 3] in JS you can do const f = ({a,b,c}) => [???, a, b, c] but how do you bind to the whole object as well?
- joshlemer 4y agoOh I thought of an other one. In Clojure "objects" are actually maps, which can have non-string keys as well. That means you can destructure maps which have non-string (or, technically, keyword) keys as well: (defn foo [{a 1 b 2 :as bar}] [bar a b]) (foo {1 "hi" 2 "world" 3 "!"}) ;; => [{1 "hi", 2 "world", 3 "!"} "hi" "world"] In JS of course you can have Maps with non-string keys, but destructuring doesn't work on them. Plus you have other issues like arrays in JS having equality defined by reference rather than value, making maps less useful overall.
- myhf 4y agothe curly braces in that font are TOO CURLY
- bluepnume 4y ago> it’s important to remember that TypeScript does not provide any assurances about a variable’s runtime type, especially if the data is coming from an external source (like an API). In those cases, you often need to use type assertions or guards anyway to assert the returned type because TypeScript can’t infer the type of dynamically fetched data. So destructuring the data is more dangerous than defensive coding (like using the optional chaining operator or good-old if statements). Optional chaining or null checks should not be used "defensively". They should just be used whenever TypeScript tells you to. Otherwise you just exacerbate the problem and end up with more unanticipated null values. If you're dealing with an API with untrustworthy types or lots of null values, the solution is to: a) Write mostly optional type definitions for that API, and/or b) Use zod to verify the API data before you use it, to make sure it matches your expectations. https://github.com/colinhacks/zod https://github.com/colinhacks/zod
- themanmaran 4y ago> Optional chaining or null checks should not be used "defensively" Agreed. I often find the `Uncaught TypeError: Cannot read properties of null` error annoying. But I would rather see the error than let unknown nulls sneak through my codebase.
- bluepnume 4y agoQuite often the best thing to do is: 1. Wait until TS complains about a possible null 2. If you have a good default value, use that instead 3. If not, throw a meaningful error Either way, don't propagate the nulls further than they need to go.
- lootsauce 4y agoI see destructuring as a great way to make function signatures more self documenting. Without the complexity of typescript types we can use destructuring of function arguments to define exactly what shape that input requires to fulfill the needs of that function. Call it overkill I call it lightweight types.
- julian37 4y agoAll of the examples show a single use but the value of destructuring becomes more obvious when you look at an example with multiple uses. Take this variant of one example: const Component = (props) => { funcA(props.a.deeply.nested.variable); funcB(); funcC(props.a.deeply.nested.variable); }; Depending on what the functions are doing, neither the VM nor tsc might be able to infer that variable's value stays the same or that props, props.a, props.a.deeply, or props.a.deeply.nested will stay the same for that matter. The VM will likely have to generate code to dereference the chain all over for the second use, and the compiler might lose narrowing information. Both of these can easily be avoided with destructuring. (You could use "const variable = props.a.deeply.nested.variable", but then you have many of the same issues the article complains about.)
- _ZeD_ 4y agothat thing about forests and tree. IMHO the problem here is not the destructuring, it's passing a gigantic object to a function when the only important stuff are a pair of leaf nodes. const Component = (props) => { console.log(props.a.deeply.nested.variable); }; In the article the author said that this is a good approach (and there is a second one filled with `?.` between each level This is a symptom of a bigger problem: what has happened to the Law of Demeter? I think we drop a lot of good practices, when we switched from OOP and "functions + data structures".
- swah 4y agoBut passing the full object is so easy... and works so well most of the time. And with Typescript we know what is there... in theory.
- capableweb 4y agoBut it doesn't signal the original intent and is also reusable in less contexts... Imagine we have the two following objects: const person = {name: "John"} const book = {title: "Bible"} Only one of the following functions tell the user (developer who uses the function) A) what is actually needed for the function to work by just looking at the function signature and B) works for both objects, regardless of where `name` comes from. function sayHello(person) { console.log(`Hello ${person.name}`) } function sayHello(name) { console.log(`Hello ${name}`) } Not to mention, passing smaller things leads to less memory usage, which is generally also a good idea.
- ziml77 4y ago> Not to mention, passing smaller things leads to less memory usage, which is generally also a good idea. It's not using any more memory. Whether you pass in the person object or the string, you're putting a reference on the stack. There will be a slight speedup with the more generic one if you are accessing the name more than once in the function, but you can avoid that by storing the name in a local variable. And picking between the two isn't as straightforward as you make it out. Sure, you make it so any string can go into sayHello, but now you've introduced repetition at the call sites as they each have to pick the correct property to pass in. Also it means you can't easily change the sayHello function to do something like meeting a new requirement that it needs to greet the user more formally. If I take the whole person object, the change can be made in a single spot instead of having to modify the callers: function sayHello(person) { console.log(`Hello ${person.title} ${person.lastName}`); }