# 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}}"
/>
# 相关规则
# [建议] 当元素有多个属性时,应该按照统一的顺序书写
优先级顺序:
- 条件渲染(元素是否渲染/显示)
- s-if
- s-else-if / s-elif
- s-else
- 列表渲染(创建多个变化的相同元素)
- s-for
- 动态组件
- s-is
- 全局感知(需要超越组件的知识)
- id
- 唯一的特性(需要唯一值的特性)
- s-ref
- slot
- 未绑定的属性
- 绑定的属性
- 事件(组件事件监听器)
- on-
- 内容(覆写元素的内容)
# 相关规则
# [强制] 不能有重复的属性,包括class
和 style
// 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-if
和 s-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 其它
# [建议] 组件中使用 fire
或dispatch
事件时携带的参数,个数不应该超过 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>