code_guideline.md 7.5 KB

代码规范 - 命名

变量名和函数

使用驼峰(camelCase)命名变量和函数名

let fooVar: string;
function barFunc(): void { }

使用帕斯卡(PascalCase)命名类名

class Foo { }

使用驼峰(camelCase)命名类成员变量和方法

class Foo {
    bar: number;
    baz() { }
}

接口

与类相似

interface Foo {
    foo: number;
    baz(): void;
}

类型

与接口类似

type T = Foo | Bar;

命名空间

使用帕斯卡(PascalCase)命名

枚举

使用帕斯卡(PascalCase)命名枚举类型和成员

enum Color {
    Red,
    Blue
}

数组

声明数组时使用 let foos:Foo[]; 而不是 let foos:Array<Foo>;,便于阅读。

常量

模块级常,采用大写字母加下划线方式,如:

export const UNITY_RES_BASE = "res/unity/LayaScene_prefabs/Conventional";
export const BASE_PLAYER_SCALE = 0.4;
export const BASE_WEAPON_SCALE = 0.6;

函数内部自用常量,同变量使用驼峰(camelCase)命名。(目的:防止自己不小心改变指向造成bug)

代码风格及注意事项

类型

能声明出类型的都要使用明确的类型,除非极特殊情况用any

工具中特殊的函数,如function shallowCompare(obj1: any, obj2: any),否则能用函数优先考虑泛型。

修饰符

变量使用let声明,不使用var; 不允许改变指向的变量用const;只读类型用Readonly/DeepReadonly。 对于系统预定义常量对象使用constReadonly结合,如:

export const FLICK_ARR: Readonly<string[]> = ["break", "freeze"];

风格

  1. 使用箭头函数代替匿名函数表达式。
  2. 只在需要的时候才把箭头函数的参数括起来。比如 typescript x => x + x (x, y) => x + y <T>(x: T, y: T) => x === y
  3. 总是使用{}把循环体和条件语句括起来。
  4. 每个变量声明语句只声明一个变量。不要let x = 1, y = 2;,而是let x = 1; let y = 2;
  5. 回调函数总是把参数和返回值函数声明出来,不要使用Function
  6. 代码使用vs code工具格式化。
  7. 一行过长的时候,要拆成多行。
  8. 一个函数过长,考虑将其中某些代码块抽成函数。
  9. 类的成员变量和函数优先考虑private,其次protected,谨慎public。

函数里的this

尽量不使用bind函数,因ts无法识别里面的this。使用箭头函数绑定this。

this和箭头函数

JavaScript里,this的值在函数被调用的时候才会指定。 这是个既强大又灵活的特点,但是你需要花点时间弄清楚函数调用的上下文是什么。 但众所周知,这不是一件很简单的事,尤其是在返回一个函数或将函数当做参数传递的时候。

let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        return function() {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

console.log("card: " + pickedCard.card + " of " + pickedCard.suit);

为了解决这个问题,我们可以在函数被返回时就绑好正确的this。 这样的话,无论之后怎么使用它,都会引用绑定的‘deck’对象。 我们需要改变函数表达式来使用ECMAScript 6箭头语法。 箭头函数能保存函数创建时的this值,而不是调用时的值:

let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        // NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

console.log("card: " + pickedCard.card + " of " + pickedCard.suit);

不幸的是,this.suits[pickedSuit]的类型依旧为any。 这是因为this来自对象字面量里的函数表达式。 修改的方法是,提供一个显式的this参数。 this参数是个假的参数,它出现在参数列表的最前面

interface Card {
    suit: string;
    card: number;
}
interface Deck {
    suits: string[];
    cards: number[];
    createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    // NOTE: The function now explicitly specifies that its callee must be of type Deck
    createCardPicker: function(this: Deck) {
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

console.log("card: " + pickedCard.card + " of " + pickedCard.suit);

回调函数里的this

当你将一个函数传递到某个库函数里在稍后被调用时,你可能也见到过回调函数里的this会报错。 因为当回调函数被调用时,它会被当成一个普通函数调用,this将为undefined。 稍做改动,你就可以通过this参数来避免错误。

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void;
}
class Handler {
    info: string;
    onClickBad(this: Handler, e: Event) {
        // oops, used this here. using this callback would crash at runtime
        this.info = e.message;
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // error!
class Handler {
    info: string;
    onClickGood(this: void, e: Event) {
        // can't use this here because it's of type void!
        console.log('clicked!');
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);
class Handler {
    info: string;
    onClickGood = (e: Event) => { this.info = e.message }
}

事件机制

function onMouseDown() { }

element.on("mousedown", onMouseDown);

element.off("mousedown", onMouseDown);

element.emit("mousedown", mouseEvent);
const emitter = new Emitter();
export namespace CameraEvents {
    export const CHENG_STATE = emitter.createEvent<(state: ECameraState) => void>();
    export const SET_OFFSET = emitter.createEvent<(factor: number, rotateX: number, position: Laya.Vector3) => void>();
    export const SET_FACTOR = emitter.createEvent<(factor: number) => void>();
}

class XXX {
    private disposable = new CompositeDisposable;

    regiterEvents() {
        this.disposable.add(CameraEvents.CHENG_STATE.on(() => {}));
        this.disposable.add(CameraEvents.SET_OFFSET.on(() => {}));
    }

    unregisterEvents() {
        this.disposable.dispose();
    }

    setCameraFactor() {
        CameraEvents.SET_FACTOR.emit(2);
    }
}

模块循环依赖

如果只是作为类型引用,严格来说不算依赖。

设计上应单向依赖,避免循环依赖。

  • 使用事件机制解耦
  • 耦合太深的写到一个模块
  • 类型/模型单独放到模块中,不依赖任何其它模块