我使用create-react-app创建了一个纯React应用程序。我想扩展Ubuntu
类并在一个或多个组件中使用它。例如:
String
(您可能需要查看this question以了解有关扩展对象原型的更多信息。)
是的我可以在组件旁边定义它并在其中使用它。但最好和最干净的方法是什么?
我应该将其写为String.prototype.someFunction = function () {
//some code
}
还是class method
或其他内容?
修改
是否均匀"好的"在React(或JavaScript)中扩展对象原型?
答案 0 :(得分:10)
- 细致入微的答案 -
我要做的是扩展纯JavaScript类,比如String类,这是javascript中非常常见的任务
在React(或JavaScript)中扩展对象原型是否“OK”?
在JavaScript中扩展/修改原生原型是controversial topic,与你所说的相反,并不是大多数专业开发人员经常做的事情。 general consensus是extending the native JS prototypes is a programming anti-pattern to be avoided,因为它打破了封装原则并修改了全局状态。但是,与许多规则一样,可能会有罕见的例外情况。例如:你正在开发一个不需要生产质量的玩具项目,你是唯一一个接触过该代码库的开发者,或者你的代码永远不会成为其他任何人的依赖。
如果您有充分的理由并且真正了解自己正在做什么,并且完全了解修改运行时环境和依赖项的本机数据类型/行为的潜在后果,那么您可能会发现这种做法的一些有效用例。但很可能不是,或者至少不是经常。几乎从来没有。
如果你只是在方便/语法糖之后,你最好不要使用实用功能(来自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一样工作,当然除了你不能写像字符串文字这样的MyString文字。
我使用create-react-app创建了一个纯粹的React应用程序。我想扩展String类并在一个或多个组件中使用它...是的我可以在组件旁边定义它并在其中使用它。但最好和最干净的方法是什么? ...我应该将其作为类方法还是在componentDidMount或其他内容中编写?
因为修改内置原型(通过改变诸如String.prototype
之类的东西)会改变应用程序的全局状态,所以你只想执行一次,几乎可以肯定在任何其他代码执行之前(因为你'重新设置Strings对之后执行的所有代码的行为的全局状态)。因此,在React组件实例方法中更改内置原型并没有多大意义。
如果你要做肮脏的行为,我建议为你想要修改的每个本机类型创建一个单独的模块,并将这些模块保存在src/lib/extend-built-ins/
之类的某个地方,然后{{1它们是import
中的第一件事。你不需要出口任何东西。执行src/index.js
将执行代码,这将改变您的全局状态。这将提供至少合适的组织,并确保在应用程序的其余代码运行之前完全修改您的应用程序环境。这样,您可以在整个应用程序中使用扩展类型,而无需考虑从某处导入它们。
如果您打算使用子类路由(import src/lib/extend-built-ins/String.js
),那么我建议您在class MyThing extends NativeThing
之类的单独模块中类似地定义自定义类。但在这种情况下,您必须将src/lib/native-subclasses/
类构造函数放入您想要使用它们的任何/每个模块中。
但是,如果您想开发干净,可测试,可重构的代码,这些代码对其他人和您未来的自我都很容易理解,那么您就不应该这样做。相反,考虑采用React及其生态系统的函数式编程原理。任何人都可以快速阅读和理解纯函数,因此使用它们来完成数据和状态转换,而不是依赖于修改全局对象等难以跟踪的黑客攻击。理解这个小小的黑客可能是可爱而微不足道的,但在项目中甚至一次这样做可以促进并鼓励自己和他人使用额外的快捷方式和反模式。
答案 1 :(得分:1)
使用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
的文件夹中,并且我可以直接从Extensions访问Utility,反之亦然(或者到目前为止,我还没有任何问题。)
现在,尽管我仍然不能在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;
请注意,此时我没有足够的时间来全面概述我的旧代码,但这是事情
第二个注意事项:如果我在Utility中导入Extensions,它会警告我“允许循环”,但可能会导致未初始化的值。考虑重构以消除循环的需要。 如果我没有看到VSCode上的一些错误,但是它可以正常编译,没有警告...