React: Where To Extend Object Prototype

后端 未结 2 476
抹茶落季
抹茶落季 2021-02-13 03:00

I created a pure React application using create-react-app. I would like to extend the String class and use it in one or more components. For example:



        
2条回答
  •  Happy的楠姐
    2021-02-13 03:11

    TLDR Answer: Nowhere!

    -- Nuanced Answer --

    What I'm trying to do is extend pure JavaScript classes, like String class, which is a very common task in javascript

    Is it even "OK" to extend object prototype in React (or in JavaScript) at all?

    Extending/modifying native prototypes in JavaScript is a controversial topic, and, contrary to what you said, not something most professional developers do very often. The general consensus is that extending the native JS prototypes is a programming anti-pattern to be avoided, because it breaks the principle of encapsulation and modifies global state. However, as with many rules, there may be rare exceptions to it. For instance: you're working on a toy project that doesn't need to be production quality, you're the only dev who will ever touch that code base, or your code will never be a dependency for anyone else.

    If you have a really good reason and really know what you're doing and are fully aware of the potential consequences of your modifications to the native data types/behaviors for your run-time environment and dependencies, then perhaps you will find some valid use case for this practice. But most likely not, or at least not very often. Like almost never.

    If you're just after convenience / syntactic sugar, you're better off pulling in utility functions (from the likes of lodash, underscore, or ramda) and learning to practice functional composition. But if you're really committed to the Object Oriented paradigm, then you should probably just be "subclassing" the native data types rather than modifying them.

    So rather than mutating a class's prototype like this:

    String.prototype.somethingStupid = function () {
      return [].map.call(this, function(letter) {
        if ((Math.random() * 1) > .5) return letter.toUpperCase()
        else return letter.toLowerCase()
      }).join('')
    }
    
    console.log('This is a silly string'.somethingStupid())

    You would create a sub-class (only works with ES6 class syntax), like so:

    class MyString extends String {
      constructor(x = '') {
        super(x)
        this.otherInstanceProp = ':)'
      }
      
      somethingStupid() {
        return [].map.call(this, function(letter) {
          if ((Math.random() * 1) > .5) return letter.toUpperCase()
          else return letter.toLowerCase()
        }).join('')
      }
    }
    
    const myStr = new MyString('This is a silly string')
    console.log(myStr)
    console.log(myStr.valueOf())
    console.log(myStr.somethingStupid() + ', don\'t you think?')

    This subclass would work like a built-in String in every way, except of course that you wouldn't be able to write MyString literals like String literals.

    I created a pure React application using create-react-app. I would like to extend the String class and use it in one or more components ... Yes I can define it beside a component and use it within. But what is the best and cleanest way? ... Should I write it as a class method or inside componentDidMount or something else?

    Because modifying built-in prototypes (by mutating things like String.prototype) alters the global state of your application, it is something that you will want to execute only once and almost certainly before any other code executes (because you're setting the global state of how Strings behave for all code that executes after). So altering built-in prototypes from within a React component instance method wouldn't make much sense.

    If you're going to do the dirty deed, I'd recommend creating a separate module for each native type you want to modify, and keep those modules somewhere like src/lib/extend-built-ins/ or something, and then import them as the very first thing in src/index.js. You wouldn't need to export anything. Doing import src/lib/extend-built-ins/String.js will execute the code, which will mutate your global state. That would provide at least decent organization and ensure that your application environment is fully modified before the rest of your app's code runs. That way you can just use your extended types throughout your application without thinking about importing them from somewhere.

    If you're going to go the subclassing route (class MyThing extends NativeThing), then I would recommend you similarly define your custom classes in separate modules somewhere like src/lib/native-subclasses/. But in this case, you would have to import your class constructors into any/every module where you want to use them.

    However, if you want to develop clean, testable, refactorable code that will be easy for others and your future self to understand, you shouldn't do this sort of thing. Instead, think about adopting the functional programming principles of React and its ecosystem. Anyone can quickly read and understand a pure function, so use them to accomplish your data and state transformations rather than relying on hard-to-track hacks like modifying global objects. It may be cute and trivial to understand this one little hack, but doing it even once in a project promotes and encourages yourself and others to use additional shortcuts and anti-patterns.

提交回复
热议问题