stenciljs 学习六 组件开发样式指南
组件不是动作,最好使用名词而不是动词,
文件结构
- 每个文件一个组件。
- 每个目录一个组件。虽然将类似的组件分组到同一目录中可能是有意义的,但我们发现当每个组件都有自己的目录时,更容易记录组件。
- 实现(.tsx)和组件的样式应该位于同一目录中。
参考格式
├── card
│ ├── card.ios.scss
│ ├── card.md.scss
│ ├── card.scss
│ ├── card.tsx
│ └── test
│ └── basic
│ ├── e2e.js
│ └── index.html
├── card-content
│ ├── card-content.ios.scss
│ ├── card-content.md.scss
│ ├── card-content.scss
│ └── card-content.tsx
├── card-title
│ ├── card-title.ios.scss
│ ├── card-title.md.scss
│ ├── card-title.scss
命名
- html 标签
当创建要在不同项目中使用的组件集合(如@ ionic / core)时,前缀具有重要作用。Web组件没有作用域
,因为它们是在网页中全局声明的,这意味着需要“唯一”前缀来防止冲突。前缀还有助于快速识别组件的集合。
此外,Web组件需要在标记名称中包含“ - ”短划线,因此使用第一部分命名组件是很自然的。
不建议使用“stencil”作为前缀 - 命名
组件不是动作,它们在概念上是“事物”。最好使用名词而不是动词,例如“动画”而不是“动画”。
“输入”,“制表符”,“导航”,“菜单”是一些例子。 - 修饰符
当几个组件相关和/或耦合时,最好共享名称,然后添加不同的修饰符,例如:
<ion-card>
<ion-card-header>
<ion-card-content>
组件(ts 类)
由于类是作用域的,因此组件的ES6类的名称应该没有前缀。没有碰撞的风险。
@Component({
tag: 'ion-button'
})
export class Button { ... }
@Component({
tag: 'ion-menu'
})
export class Menu { ... }
typescript
- 遵循 tslint-ionic-rules
- 应该内联变量装饰器。
@Prop() name: string;
@Element() el: HTMLElement;
- 方法装饰器应该是多行的
@Listen('click')
onClick() {
...
}
- 尽可能使用私有变量和方法。
- 带有Method / Prop / Event / Component装饰器的代码应该有jsdocs
参考实例
@Component({
tag: 'ion-something',
styleUrl: 'something.scss',
styleUrls: {
ios: 'something.ios.scss',
md: 'something.md.scss',
wp: 'something.wp.scss'
},
host: {
theme: 'something'
}
})
export class Something {
/**
* 1. Own Properties
* Always set the type if a default value has not
* been set. If a default value is being set, then type
* is already inferred. List the own properties in
* alphabetical order. Note that because these properties
* do not have the @Prop() decorator, they will not be exposed
* publicly on the host element, but only used internally.
*/
num: number;
someText = 'default';
/**
* 2. Reference to host HTML element.
* Inlined decorator
*/
@Element() el: HTMLElement;
/**
* 3. State() variables
* Inlined decorator, alphabetical order.
*/
@State() isValidated: boolean;
@State() status = 0;
/**
* 4. Internal props (context and connect)
* Inlined decorator, alphabetical order.
*/
@Prop({ context: 'config' }) config: Config;
@Prop({ connect: 'ion-menu-controller' }) lazyMenuCtrl: Lazy<MenuController>;
/**
* 5. Public Property API
* Inlined decorator, alphabetical order. These are
* different than "own properties" in that public props
* are exposed as properties and attributes on the host element.
* Requires JSDocs for public API documentation.
*/
@Prop() content: string;
@Prop() enabled: boolean;
@Prop() menuId: string;
@Prop() type = 'overlay';
/**
* Prop lifecycle events SHOULD go just behind the Prop they listen to.
* This makes sense since both statements are strongly connected.
* - If renaming the instance variable name you must also update the name in @Watch()
* - Code is easier to follow and maintain.
*/
@Prop() swipeEnabled = true;
@Watch('swipeEnabled')
swipeEnabledChanged(newSwipeEnabled: boolean, oldSwipeEnabled: boolean) {
this.updateState();
}
/**
* 6. Events section
* Inlined decorator, alphabetical order.
* Requires JSDocs for public API documentation.
*/
@Event() ionClose: EventEmitter;
@Event() ionDrag: EventEmitter;
@Event() ionOpen: EventEmitter;
/**
* 7. Component lifecycle events
* Ordered by their natural call order, for example
* WillLoad should go before DidLoad.
*/
componentWillLoad() {}
componentDidLoad() {}
componentWillEnter() {}
componentDidEnter() {}
componentWillLeave() {}
componentDidLeave() {}
componentDidUnload() {}
/**
* 8. Listeners
* It is ok to place them in a different location
* if makes more sense in the context. Recommend
* starting a listener method with "on".
* Always use two lines.
*/
@Listen('click', { enabled: false })
onClick(ev: UIEvent) {
console.log('hi!')
}
/**
* 9. Public methods API
* These methods are exposed on the host element.
* Always use two lines.
* Requires JSDocs for public API documentation.
*/
@Method()
open() {
...
}
@Method()
close() {
...
}
/**
* 10. Local methods
* Internal business logic. These methods cannot be
* called from the host element.
*/
prepareAnimation(): Promise<void> {
...
}
updateState() {
...
}
/**
* 11. hostData() function
* Used to dynamically set host element attributes.
* Should be placed directly above render()
*/
hostData() {
return {
attribute: 'navigation',
side: this.isRightSide ? 'right' : 'left',
type: this.type,
class: {
'something-is-animating': this.isAnimating
}
};
}
/**
* 12. render() function
* Always the last one in the class.
*/
render() {
return (
<div class='menu-inner page-inner'>
<slot></slot>
</div>
);
}
}