我在TS中有一个项目,该项目需要一些类来实现以下接口:
interface IStylable {
readonly styles: {
[property: string]: string
};
addStyles (styles: { [property: string]: string }): void;
updateStyles (styles: { [property: string]: string }): void;
removeStyles (styles: Array<string>): void;
}
为了避免样板代码,我决定创建一个Mixin并将其应用于我需要的每个类中。 (我可以使用抽象类,但是我的问题需要多重继承解决方案,而TS并未提供这种解决方案。)以下是IStylable
接口的类实现:
export class StylableClass implements IStylable {
private readonly _styles: { [property: string]: string } = {};
// For each property provided in styles param, check if the property
// is not already present in this._styles and add it. This way we
// do not overide existing property values.
public addStyles (styles: { [property: string]: string }): void {
for (const [property, value] of Object.entries(styles)) {
if (!this._styles.hasOwnProperty(property)) {
this._styles[property] = value;
}
}
}
// For each property provided in styles param, check if the property
// is already present in this._styles and add it. This way we
// do add property values values that do not exist.
public updateStyles (styles: { [property: string]: string }): void {
for (const [property, value] of Object.entries(styles)) {
if (this._styles.hasOwnProperty(property)) {
this._styles[property] = value;
}
}
}
// For each property in styles param, check if it is present in this._styles
// and remove it.
public removeStyles (styles: Array<string>): void {
for (const property of styles) {
if (this._styles.hasOwnProperty(property)) {
delete this._styles[property];
}
}
}
public set styles (styles: { [property: string]: string }) {
this.addStyles(styles);
}
public get styles (): { [property: string]: string } {
return this._styles;
}
}
对于我真的很兴奋的事情,期待着ES6中装饰器规范的标准化。打字稿通过在experimentalDecorators
中设置tsconfig.json
标志来允许此实验功能。我希望将StylableClass
用作类装饰器(@Stylable
)以使代码更整洁,因此我创建了一个函数,该函数接受一个类并将其转换为装饰器:
export function makeDecorator (decorator: Function) {
return function (decorated: Function) {
const fieldCollector: { [key: string]: string } = {};
decorator.apply(fieldCollector);
Object.getOwnPropertyNames(fieldCollector).forEach((name) => {
decorated.prototype[name] = fieldCollector[name];
});
Object.getOwnPropertyNames(decorator.prototype).forEach((name) => {
decorated.prototype[name] = decorator.prototype[name];
});
};
}
并按如下方式使用它:
export const Stylable = () => makeDecorator(StylableClass);
现在该进行单元测试了。我创建了一个虚拟类来应用我的装饰器,并为addStyles()
方法编写了一个简单的测试。
@Stylable()
class StylableTest {
// Stylable
public addStyles!: (styles: {
[prop: string]: string;
}) => void;
public updateStyles!: (styles: {
[prop: string]: string;
}) => void;
public removeStyles!: (styles: string[]) => void;
public styles: { [property: string]: string } = {};
}
describe('Test Stylable mixin', () => {
it('should add styles', () => {
const styles1 = {
float: 'left',
color: '#000'
};
const styles2 = {
background: '#fff',
width: '100px'
};
// 1
const styles = new StylableTest();
expect(styles.styles).to.be.an('object').that.is.empty;
// 2
styles.addStyles(styles1);
expect(styles.styles).to.eql(styles1);
// 3
styles.addStyles(styles2);
expect(styles.styles).to.eql(Object.assign({}, styles1, styles2));
});
});
问题是第二个Expect语句失败。执行styles.addStyles(styles1);
后,styles.styles
数组应包含styles1
对象时仍为空。在调试代码时,我发现push
方法中的addStyles()
语句已按预期执行,因此循环没有问题,但是在方法执行结束后不会更新数组。您能给我提示或解决我错过的事情吗?我检查的第一件事是makeDecorator
函数可能出了问题,但是只要我能执行方法,就找不到其他要寻找的线索。
答案 0 :(得分:1)
StylableClass
混合声明了一个名为styles
的属性。但是StylableTest
创建一个字段名styles
并为其分配一个空对象,任何人都不会使用。您需要将属性描述从装饰器转移到目标类,并从= {}
中的styles
中删除StylableTest
:
function makeDecorator(decorator) {
return function (decorated) {
var fieldCollector = {};
decorator.apply(fieldCollector);
Object.getOwnPropertyNames(fieldCollector).forEach(function (name) {
decorated.prototype[name] = fieldCollector[name];
});
Object.getOwnPropertyNames(decorator.prototype).forEach(function (name) {
var descriptor = Object.getOwnPropertyDescriptor(decorator.prototype, name);
if (descriptor) {
Object.defineProperty(decorated.prototype, name, descriptor);
}
else {
decorated.prototype[name] = decorator.prototype[name];
}
});
};
}
我可以建议使用less error prone approach来混合打字稿中的mixins。这种必须重新声明所有mixin成员的方法以后会导致错误。至少避免使用类型查询来重述字段的类型:
@Stylable()
class StylableTest {
// Stylable
public addStyles!: IStylable['addStyles']
public updateStyles!: IStylable['updateStyles']
public removeStyles!: IStylable['removeStyles']
public styles!: IStylable['styles']
}