React:在哪里扩展对象原型

IT技术 javascript reactjs prototype create-react-app
2021-05-12 17:24:55

我使用create-react-app创建了一个纯 React 应用程序我想扩展String该类并在一个或多个组件中使用它。例如:

String.prototype.someFunction = function () {
    //some code
}

(您可能需要查看此问题以了解有关扩展对象原型的更多信息。)

是的,我可以在组件旁边定义它并在其中使用它。但最好和最干净的方法是什么?

我应该把它写成 aclass method或 insidecomponentDidMount还是别的什么?

编辑:

在 React(或 JavaScript)中扩展对象原型甚至“可以”吗?

2个回答

TLDR 答案:无处可去!

——细致入微的回答——

我想要做的是扩展纯 JavaScript 类,比如 String 类,这是 JavaScript 中非常常见的任务

在 React(或 JavaScript)中扩展对象原型甚至“可以”吗?

在 JavaScript 中扩展/修改原生原型是一个有争议的话题,并且与您所说的相反,大多数专业开发人员都不会经常这样做。普遍的共识是,扩大本地JS的原型是要避免的编程反模式,因为它打破了封装和修改全局状态的原则。但是,与许多规则一样,它可能会有罕见的例外。例如:您正在从事一个不需要达到生产质量的玩具项目,您是唯一会接触该代码库的开发人员,或者您的代码永远不会成为其他任何人的依赖项。

如果你有一个很好的理由并且真的知道你在做什么并且完全意识到你对运行时环境和依赖项的本机数据类型/行为的修改的潜在后果,那么也许你会发现一些有效的用途这种做法的案例。但很可能不是,或者至少不是很频繁。就像几乎从来没有。

如果您只是追求便利/语法糖,那么最好引入实用函数(来自 lodash、下划线或 ramda 之类的函数)并学习练习函数组合。但是,如果您真的致力于面向对象的范式,那么您可能应该只是“子类化”本机数据类型而不是修改它们

因此,与其像这样改变一个类的原型:

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())

您将创建一个子类(仅适用于 ES6 类语法),如下所示:

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?')

这个子类在各方面都像内置的 String 一样工作,当然,除了您不能像 String 文字那样编写 MyString 文字。

我使用 create-react-app 创建了一个纯 React 应用程序。我想扩展 String 类并在一个或多个组件中使用它......是的,我可以在组件旁边定义它并在其中使用它。但最好和最干净的方法是什么?...我应该把它写成一个类方法还是在 componentDidMount 或其他东西?

因为修改内置原型(通过改变诸如 之类的东西String.prototype)会改变应用程序的全局状态,所以您只希望执行一次,并且几乎肯定会在任何其他代码执行之前执行(因为您正在设置全局状态字符串的行为适用于之后执行的所有代码)。因此,从 React 组件实例方法中更改内置原型没有多大意义。

如果您打算做肮脏的事,我建议为您要修改的每个本机类型创建一个单独的module,并将这些module保存在某个地方src/lib/extend-built-ins/,然后将import它们作为src/index.js. 你不需要导出任何东西。这样做import src/lib/extend-built-ins/String.js将执行代码,这将改变您的全局状态。这至少会提供体面的组织,并确保在应用程序的其余代码运行之前完全修改应用程序环境。这样您就可以在整个应用程序中使用您的扩展类型,而无需考虑从某处导入它们。

如果您打算走子类化路线 ( class MyThing extends NativeThing),那么我建议您类似地在单独的module中定义您的自定义类,例如src/lib/native-subclasses/. 但是在这种情况下,您必须将import类构造函数放入您想要使用它们的任何/每个module中。

但是,如果您想开发易于他人和您未来的自己理解的干净、可测试、可重构的代码,则不应该做这种事情。相反,考虑采用 React 及其生态系统的函数式编程原则。任何人都可以快速阅读和理解纯函数,因此使用它们来完成数据和状态转换,而不是依赖难以跟踪的技巧,例如修改全局对象。理解这个小技巧可能很可爱也很琐碎,但即使在项目中做一次也会促进和鼓励你自己和其他人使用额外的捷径和反模式。

对于 TypeScript 语言用户

使用 expo-cli 36 在基于 javascript 的 react-native 项目上进行测试,并在使用 Angular 7 时编写

我有错误将我带到这里......我使用的是基于 js 的 react-native。之前我写了一个 JS 库,后来因为一些需要,我把它改成了 TS,在切换到 Angular 之后,我不得不用它创建一个坚固的库才能在那个环境中工作......

不,我在这里复制了这些文件,我遇到了错误,我来到这个线程,但是一旦我第一次编译它,所有错误就消失了,并且它完全有效......

这是我如何使用它:

我有实用程序类Utility.ts

export class Utility {
  /**
   * Returns False If Incoming Variable Is Not Null, void or Undefined, Otherwise True
   */
  public static isNullOrUndefined(obj: any|void): boolean {
    // return obj == null // juggling-check
    return typeof obj === 'undefined' || obj === null; // strict-check
  }

  /**
   * Returns False If Incoming Variable Does Not Contains Data, Otherwise True
   */
  public static isNullOrUndefinedOrEmpty(obj: any|void): boolean {
    if (Utility.isNullOrUndefined(obj)) {
        return true;
    }

    // typeof for primitive string, instanceof for objective string
    if (typeof(obj) === 'string' || obj instanceof String) {
      return (obj as string).valueOf() === '';
    } else if (obj instanceof Array) {
      return (obj as Array<any>).length === 0;
    }
    throw new Error('Not Supported Exception');
  }
...
}

我有一个定义类index.d.ts我不确定,但我认为文件名在 Angular 时间非常重要,请随意测试...):

declare global {

  interface StringConstructor { // for static methods
    format: (str: string, ...args: any[]) => string;
  }

  interface String { // for instance methods
    /**
     * Support StringComparision Options
     * https://stackoverflow.com/questions/10636871/ordinal-string-compare-in-javascript
     * @param searchString {!string}
     * @param position {?number}
     * @param comparision {?StringComparison}
     * @returns {number}
     */
    indexOfString: (searchString: string, position?: number, comparision?: StringComparison) => number;
    insertAt: (index: number, strText: string) => string;
    removeAt: (index: number, count: number) => string;
    replaceAt: (index: number, count: number, strReplacement: string) => string;
    overrideAt: (index: number, strText: string) => string;
...
}

最后我有我的扩展文件Extensions.ts

import { Utility } from './Utility';

/**
 * Support StringComparision Options
 * https://stackoverflow.com/questions/10636871/ordinal-string-compare-in-javascript
 */
String.prototype.indexOfString = function(searchString: string, position?: number, comparision?: StringComparison): number {
  return Utility.indexOfString(this, searchString, position, comparision);
};

String.prototype.insertAt = function(index: number, strText: string): string {
  return this.substr(0, index) + strText + this.substr(index);
};

String.prototype.removeAt = function(index: number, count: number): string {
  if (Utility.isNullOrUndefined(count)) {
    count = this.length - index;
  }
  return this.substr(0, index) + this.substr(index + count);
};

在这里,我将所有这些文件放在一个名为的文件夹中,该文件夹util根本无关紧要,并且我可以从扩展直接访问实用程序,反之亦然(或者直到现在我没有任何问题。

现在虽然我仍然无法在 react 组件中使用我的扩展,但如果我添加一个简单的导入,我可以:import '../util/Extensions';

我是这样测试的:

import React from 'react';
import { Text, View } from 'react-native';

import '../util/Extensions'; //my import

const SurveyScreen = props => {
    const z = 'sdwqew';
    const x = z.insertAt(2,'hassan'); //function from my custom extensions
    return (
        <View>
            <Text>Hello World</Text>
            <Text>{x}</Text>
        </View>
    );
}

export default SurveyScreen;

请注意,此时我没有足够的时间来全面概述我的旧代码,但事情就是这样

第二个注意事项:如果我在实用程序中导入扩展,它会给我警告要求循环被允许,但可能导致未初始化的值。考虑重构以消除对循环的需要。 如果我没有在 VSCode 上看到一些错误,但它编译得很好,没有警告......