docs/code/best_practices.md
MDC Web follows naming and documentation best practices to keep our code consistent, and our APIs user-friendly. We follow isolation best practices to keep our code loosely coupled. And we follow performance best practices to keep our components fast.
TODO: Add more notes about how to isolate subsystems from component specifics
requestAnimationFrameinitialize() and initSyncWithDom()) that are not contained within the constructor.! when you run into the error <PROPERTY_NAME> has no initializer and is not definitely assigned in the constructor.. ie.private progress!: number; // Assigned in init
init() {
this.progress = 0;
}
interface over type for defining types when possible.unknown over both any and {} types.any and {} defer to {}.@material/base defines convenience types (EventType and SpecificEventListener) for working with events and event listeners.EventType over string when you expect that the string will be a standard event name (e.g. click, keydown).SpecificEventListener over EventListener when you know what type of event is being listened for (e.g. SpecificEventHandler<'click'>).Node is more generic than Element, while Element is more generic than HTMLElement.Node is mainly used for the document or comments/text.Element should be used when the type in question could be HTMLElement, SVGElement, or others.HTMLElement only pertains to DOM Elements such as <a>, <li>, <div> just to name a few.import statements must not use re-exported modulesOnly the index.ts or component.ts files are allowed to reference from other component packages' index.ts.
This is because wrapping libraries only use foundation and adapter, so we should decouple the component.
// BAD
import {MDCFoundation} from '@material/base';
// GOOD
import {MDCFoundation} from '@material/base/foundation';
Each adapter must be defined within an adapter.ts file in the component's package directory.
All methods should contain a summary of what they should do. This summary should be
copied over to the adapter API documentation in our README. This will facilitate future endeavors
to potentially automate the generation of our adapter API docs.
Note that this replaces the inline comments present in the methods within defaultAdapter.
// adapter.ts
export interface MDCComponentAdapter {
/**
* Adds a class to the root element.
*/
addClass(className: string): void;
/**
* Removes a class from the root element.
*/
removeClass(className: string): void;
}
MDCFoundationFoundations must extend MDCFoundation parameterized by their respective adapter.
The defaultAdapter must return an object with the correct adapter shape.
// foundation.ts
import {MDCFoundation} from '@material/base/foundation';
import MDCComponentAdapter from './adapter';
export class MDCComponentFoundation extends MDCFoundation<MDCComponentAdapter> {
static get defaultAdapter(): MDCComponentAdapter {
return {
addClass: (className: string) => undefined,
removeClass: (className: string) => undefined,
};
}
}
MDCComponentComponents must extend MDCComponent parameterized by their respective foundation.
// index.ts
import {MDCComponent} from '@material/base/component';
import MDCComponentFoundation from './foundation';
export class MDCAwesomeComponent extends MDCComponent<MDCComponentFoundation> {
getDefaultFoundation(): MDCComponentFoundation {
return new MDCComponentFoundation({
addClass: (className: string) => this.root.classList.add(className),
removeClass: (className: string) => this.root.classList.remove(className),
});
}
}