# San 代码规范

代码规范关注下面两个关键指标:

  • 代码风格:可读性检测
  • 语法规则:可执行,正确性检测

# 1 前言

任何问题或建议,欢迎跟我们讨论: discussions (opens new window)

# 2 代码风格

# 2.1 模块书写顺序

# [建议] template -> script -> style

# 相关规则

# 3 template 部分

# 3.1 根节点

# [强制] template 根节点只允许包含一个直接子节点,以下情况都是不允许的:

  • 根结点为空;
  • 根结点是文字;
  • 根结点有多个元素;
  • 在根结点使用循环;
  • 在根结点使用 template 和 slot;
// bad
<template></template> // san/valid-template-root
<template>hello</template> // san/valid-template-root
<template><div>one</div><div>two</div></template> // san/no-multiple-template-root
<template><div s-for="x in list"></div></template> // san/no-multiple-template-root
<template><template>hello</template></template> // san/no-multiple-template-root

// good
<template><div>one</div></template>
# 相关规则

# 3.2 标签

# [强制] 自定义组件的标签名不得使用 HTML 中预留的标签(reserved HTML elements);并且符合 kebab-case

  • 解释: 避免和 HTML 保留字段冲突导致错误。
  • 注意⚠️:components 中的自定义组件名称必须是 kebab-case,否则无法正常渲染
// bad 由于与原生标签同名导致自定义组件无法渲染 san/no-unused-components
<template>
    <sub/>
</template>

<script>
import OtherComponent from './OtherComponent.san';

export default {
    components: {
        sub: OtherComponent
    }
}
</script>

// good
<template>
    <other-component/>
</template>

<script>
import OtherComponent from './OtherComponent.san';

export default {
    components: {
        'other-component': OtherComponent
    }
}
</script>

// bad
<mycomponent/>
<myComponent/> // san/valid-components-name
<MyComponent/> // san/valid-components-name

// good
<my-component/>

预留的 html 标签包括: html,body,base,head,link,meta,style,title,address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,menuitem,summary,content,element,shadow,template,blockquote,iframe,tfoot

预留的 SVG 标签包括: svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view

# 相关规则

# [强制] HTML Void Element (opens new window) 不需要闭合,其它类型标签都需要闭合

// bad san/html-end-tags
<input></input>
<br></br>

// good
<input>
<br>
# 相关规则

# [强制] fragment标签或者非根结点的 template 标签,必须有一个以上的子结点

// bad
<ul>
    <template>
        <li></li>
    </template>
</ul>

// good
<ul>
    <li></li>
</ul>

// good
<ul>
    <template>
        <li></li>
        <li></li>
    </template>
</ul>

# [强制] 如果自定义标签中没有内容,需要以自闭合标签形式出现

// bad san/html-self-closing
<c-title url="{{url}}" label-type="{{type}}"></c-title>

// good
<c-title url="{{url}}" label-type="{{type}}"/>
# 相关规则

# [强制] 标签右括号 > 的位置:

  • 元素只有一行时,右括号与元素保持在同一行。
  • 多行元素(元素最后一个属性与左括号 < 不在同一行)时,右括号 > 需要另起一行,缩进与左括号 < 保持对齐。
// bad san/html-closing-bracket-newline
<div id="foo" class="bar"
></div>

// good
<div id="foo" class="bar"></div>

// bad san/html-closing-bracket-newline
<div
    id="foo"
    class="bar">
</div>

// good
<div
    id="foo"
    class="bar"
>
    some message
</div>

// bad san/html-closing-bracket-spacing
<c-title
    text="{{text}}"
    url="{{url}}"
    label-type="{{type}}"/>

// good
<c-title
    text="{{text}}"
    url="{{url}}"
    label-type="{{type}}"
/>
# 相关规则

# 3.3 属性

# [强制] 属性值必须用双引号包围

// bad san/html-quotes
<div class='c-color'></div>

// good
<div class="c-color"></div>
# 相关规则

# [强制] 模板中的属性命名需要符合 kebab-case

// bad san/attribute-hyphenation
<my-component greetingText="hi"/>

// good
<my-component greeting-text="hi"/>
# 相关规则

# [强制] class / style 属性值不能设置空字符串

// bad
<div class=""></div>
<div style=""></div>

// good
<div></div>

# [建议] 布尔类型的属性值为 true 时,建议不添加属性值

// bad san/boolean-value
<c-title text="带箭头标题" arrow="{{true}}"/>

// good
<input type="text" disabled>
<c-title text="带箭头标题" arrow/>
<c-title text="带箭头标题" arrow="{{false}}"/>
# 相关规则

# [建议] 当组件的属性多于 2 个时,建议分成多行,每行写一个属性;只有属性个数小于或等于 2 个时,可以写在一行内

// bad san/max-attributes-per-line
<c-title text="{{text}}" url="{{url}}" label-type="{{type}}"/>

// good
<c-title text="{{text}}" url="{{url}}"/>
<c-title
    text="{{text}}"
    url="{{url}}"
    label-type="{{type}}"
/>
# 相关规则

# [建议] 当元素有多个属性时,应该按照统一的顺序书写

优先级顺序:

  1. 条件渲染(元素是否渲染/显示)
    • s-if
    • s-else-if / s-elif
    • s-else
  2. 列表渲染(创建多个变化的相同元素)
    • s-for
  3. 动态组件
    • s-is
  4. 全局感知(需要超越组件的知识)
    • id
  5. 唯一的特性(需要唯一值的特性)
    • s-ref
    • slot
  6. 未绑定的属性
  7. 绑定的属性
  8. 事件(组件事件监听器)
    • on-
  9. 内容(覆写元素的内容)
# 相关规则

# [强制] 不能有重复的属性,包括classstyle

// bad san/no-duplicate-attributes
<c-title foo="abc" foo="{{def}}"/>
<c-title foo="def" foo="abc"/>
<c-title class="def" class="abc"/>
<c-title style="def" style="abc"/>
<c-title
    class="c-color"
    class="{{selected ? 'c-selected' : ''}}"
/>

// good
<c-title foo="{{def}}"/>
<c-title foo="abc"/>
<c-title class="c-color {{selected ? 'c-selected' : ''}}"/>
<c-title
    style="color: #000; width: 100px"
/>
# 相关规则

# [建议] slot 命名采用 kebab-case

<slot name="header-left"></slot>

<div slot="header-left"></div>

# [建议] ref 命名采用 camelCase

<div s-ref="userInfo"></div>

# 3.4 指令

# [建议] 不要把 s-ifs-for 同时用在同一个元素上

解释:

引入这个规则是为了避免引起困惑。

// bad san/no-use-s-if-with-s-for
<ul>
    <li
        s-for="user in users"
        s-if="user.isActive"
    >
        {{ user.name }}
    </li>
</ul>

// good
<template>
    <ul>
        <li
            s-for="user in activeUsers"
        >
            {{ user.name }}
        </li>
    </ul>
</template>
<script>
export default {
    computed: {
        activeUsers: function () {
            return this.data.get('users').filter(function (user) {
                return user.isActive;
            });
        }
    }
}
</script>
# 相关规则

# [强制] 不能有相同的条件

解释:

引入这个规则是为了避免引起困惑。

// bad san/no-dupe-s-else-if
<template>
  <!-- ✗ BAD -->
  <div s-if="isSomething(x)" />
  <div s-else-if="isSomething(x)" />

  <div s-if="a" />
  <div s-else-if="c && d" />
  <div s-else-if="c && d" />
</template>
# 相关规则

# [强制] s-else-if 必须有表达式

// bad san/valid-s-else-if
<template>
  <div s-if="a" />
  <div s-else-if/>
</template>
# 相关规则

# [强制] s-else 不能有表达式

// bad san/valid-s-else
<template>
  <div s-if="a" />
  <div s-else="foo"/>
  <div s-else="{{ {aaa: bar} }}"/>
  <div s-else="{{ {bbb: bar} }}"/>
</template>
# 相关规则

# [强制] s-for 必须有合法的表达式

// bad san/valid-s-for
<template>
  <div>
    <div s-for />
    <div s-for="" />
    <div s-for="." />
    <div s-for="a, in list" />
    <div s-for="a of list" />
    <div s-for=",a in list" />
    <div s-for="(a, b) in list" />
    <div s-for="a, b in list trackby {}" />
  </div>
</template>
# 相关规则

# [强制] s-show 不能没有表达式且表达式不能为空

// bad san/valid-s-show
<template>
  <div>
    <div s-show />
    <div s-show="" />
  </div>
</template>
# 相关规则

# 3.5 插值(Mustache)

# [建议] 插值左右添加一个空格

// bad san/mustache-interpolation-spacing
<div>{{   text   }}</div>
<div>{{text}}</div>

// good
<div>{{ text }}</div>
# 相关规则

# 3.6 空格

# [强制] 不能有多余空格

// bad san/no-multi-spaces
<div     class="foo"
      style="{{bar}}"         >   </div>

// good
<div class="foo" style="{{bar}}"></div>
# 相关规则

# [强制] = 两边不能有多余空格

// bad san/no-spaces-around-equal-signs-in-attribute
<div class = "item"></div>
# 相关规则

# 3.7 变量

# [强制] 不能有多余的变量

// bad san/no-unused-vars
<ol><!-- "i" is defined but never used. -->
    <li s-for="i in 5">item</li>
</ol>

// good
<ol>
    <li s-for="i in 5">{{ i }}</li>
</ol>
# 相关规则

# [强制] 模版中不能有重名的变量

// bad san/no-template-shadow
<template>
  <div>
    <div s-for="k in 5">
      <div s-for="k in 10"></div>
    </div>
  </div>
</template>
# 相关规则

# [强制] 禁止在插值中使用 this

// bad san/this-in-template
<a href="{{this.url}}">
    {{ this.text }}
</a>

// good
<a href="{{url}}">
    {{ text }}
</a>
# 相关规则

# 4 javascript 部分

# 4.1 dataTypes

# [强制] 在 dataTypes 中声明的属性,其属性名应该始终符合 camelCase

// bad
<script>
export default {
    dataTypes: {
        'greeting-text': DataTypes.string
    }
};
</script>

// good
<script>
export default {
    dataTypes: {
        greetingText: DataTypes.string
    }
};
</script>

# [建议] 指定 dataTypes 类型

// good
<script>
export default {
    dataTypes: {
        status: DataTypes.string
    }
};

# 4.2 data

# [强制] initData 必须是一个函数

// bad san/initdata-in-component
<script>
export default {
    initData: {
        b: 1
    }
}
</script>

// good
<script>
export default {
    initData() {
        return {
            b: 1
        };
    }
}
</script>
# 相关规则

# [强制] initData, computed 中不能有重复的 key

// bad san/no-dupe-keys
<script>
export default {
    dataTypes: {
        foo: DataTypes.string
    },

    initData() {
        return {
            foo: null
        };
    },

    computed: {
        foo() {
            return 'foo';
        }
    }
}
</script>

// good
<script>
export default {
    dataTypes: {
        foo: DataTypes.string
    },

    data() {
        return {
            bar: null
        };
    },

    computed: {
        baz() {
            return foo + bar;
        }
    }
}
</script>
# 相关规则

# [强制] computed 不能存在异步逻辑

export default {
  computed: {
    /* ✗ BAD */
    pro () {
        return Promise.all([new Promise((resolve, reject) => {})])
    },
    foo1: async function () {
        return await someFunc()
    }
  }
}
# 相关规则

# [强制] computed 不能存在更新 data 的操作

// bad no-side-effects-in-computed-properties
export default {
  computed: {
    fullName () {
      this.data.set('firstName', 'lorem') // <- side effect
      return `${this.data.get('firstName')} ${this.data.get('lastName')}`
    },
    reversedArray () {
      return this.data.get('array').reverse() // <- side effect - orginal array is being mutated
    }
  }
}
# 相关规则

# [建议] computed 属性需要有返回值

// bad return-in-computed-property
export default {
  computed: {
    /* ✗ BAD */
    baz () {
      const baf = this.data.get('baf')
      if (baf) {
        return baf
      }
    },
    baf: function () {}
  }
}
# 相关规则

# 4.3 变量

# [强制] 不能使用 San 中的保留字段命名变量

// bad san/no-reserved-keys
<script>
export default {
    dataTypes: {
        el: DataTypes.string
    },

    data() {
        return {
            _foo: null
        };
    },

    computed: {
        fire() {
            return 2;
        }
    },

    nextTick() {
    }
}
</script>
# 相关规则

# 4.4 其它

# [建议] 组件中使用 firedispatch 事件时携带的参数,个数不应该超过 1 个。建议将数据参数以 Object 形式传递

// bad
onClick(event) {
    const {value1, value2} = this.data.get();
    this.fire('click', value1, value2, event);
}

// good
onClick(event) {
    const {value1, value2} = this.data.get();
    this.fire(
        'click',
        {
           value1,
           value2,
           event
        }
    );
}

// good
onClick(event) {
   this.fire('click', event);
}

# [建议] 组件中使用 fire 事件时事件的名称应该为 kebab-case

onClick () {
    /* ✓ GOOD */
    this.fire('my-event')
    this.fire('my-event', params1)

    /* ✗ BAD */
    this.fire('myEvent')
}
# 相关规则

# 5 style 部分

# [建议] 为组件样式设置作用域

<style scoped>
.button {
    border: none;
    border-radius: 2px;
}
</style>

# 6 参考

Last Updated: 11/5/2021, 10:15:10 AM