├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── deploy.sh ├── docs ├── .vuepress │ └── config.js ├── README.md └── guide │ ├── README.md │ ├── css │ └── README.md │ ├── foreword │ └── README.md │ ├── git │ └── README.md │ ├── js │ └── README.md │ ├── naming │ └── README.md │ ├── react │ └── README.md │ ├── ts │ └── README.md │ └── vue │ └── README.md ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - lts/* 4 | install: 5 | - yarn install # npm ci 6 | script: 7 | - yarn build # npm run docs:build 8 | deploy: 9 | provider: pages 10 | skip_cleanup: true 11 | local_dir: docs/.vuepress/dist 12 | github_token: $GITHUB_TOKEN 13 | keep_history: true 14 | on: 15 | branch: master -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 NCUHOME 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NCUHOME Front End Style Guide 2 | 3 | [![Build Status](https://travis-ci.org/ncuhome/frontend-guide.svg?branch=master)](https://travis-ci.org/ncuhome/frontend-guide) 4 | 5 | Write clean JavaScript Code, Make the world a better place. 6 | 7 | ## Get Started 8 | 9 | **Install Dependencies** 10 | 11 | ```bash 12 | npm install 13 | ``` 14 | 15 | **Start Editing** 16 | 17 | ```bash 18 | npm run dev 19 | ``` 20 | 21 | **Depoly** 22 | 23 | Just push your changes, Travis CI will handle this. 24 | 25 | ## Todos 26 | 27 | - [x] JavaScript Code Style Guide 28 | - [x] React Code Style Guide 29 | - [x] TypeScript Code Style Guide 30 | - [ ] Vue Code Style Guide 31 | - [ ] Naming Rules 32 | 33 | ## License 34 | 35 | NCUHOME Front End Style Guide is under MIT License 36 | 37 | Copyright (c) 2019-present NCUHOME 38 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 确保脚本抛出遇到的错误 4 | set -e 5 | 6 | # 生成静态文件 7 | npm run build 8 | 9 | # 进入生成的文件夹 10 | cd docs/.vuepress/dist 11 | 12 | git init 13 | git add -A 14 | git commit -m 'deploy' 15 | 16 | # 发布到 https://.github.io/ 17 | git push -f git@github.com:ncuhome/frontend-guide.git master:gh-pages 18 | 19 | cd - 20 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'NCUHOME FE Guide', 3 | // description: '', 4 | head: [ 5 | ['link', { rel: 'icon', href: 'https://ncustatic.ncuos.com/index/1570754776734.logo.png' }] 6 | ], 7 | base: '/frontend-guide/', 8 | themeConfig: { 9 | nav: [ 10 | { text: 'Home', link: '/' }, 11 | { text: 'Guide', link: '/guide/' }, 12 | { text: 'About us', link: 'https://team.ncuos.com' }, 13 | ], 14 | sidebar: [ 15 | { 16 | title: '写在前面', 17 | path: '/guide/foreword/' 18 | }, 19 | { 20 | title: 'GIT 使用规范', 21 | path: '/guide/git/' 22 | }, 23 | { 24 | title: '命名规范', 25 | path: '/guide/naming/' 26 | }, 27 | { 28 | title: 'CSS 代码风格', 29 | path: '/guide/css/' 30 | }, 31 | { 32 | title: 'JavaScript 代码风格', 33 | path: '/guide/js/' 34 | }, 35 | { 36 | title: 'TypeScript 代码风格', 37 | path: '/guide/ts/' 38 | }, 39 | { 40 | title: 'React 代码风格', 41 | path: '/guide/react/' 42 | }, 43 | // { 44 | // title: 'Vue 代码风格', 45 | // path: '/guide/vue/' 46 | // }, 47 | ], 48 | editLinkText: 'Edit on GitHub', 49 | plugins: ['@vuepress/back-to-top'], 50 | lastUpdated: 'Last Updated', 51 | repo: 'ncuhome/frontend-guide', 52 | repoLabel: 'GitHub', 53 | docsRepo: 'ncuhome/frontend-guide', 54 | docsDir: 'docs', 55 | docsBranch: 'master', 56 | editLinks: true, 57 | smoothScroll: true 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroImage: https://ncustatic.ncuos.com/index/1570754776734.logo.png 4 | heroText: NCUHOME 前端风格指南 5 | tagline: For readability, robustness and high maintenance 6 | actionText: 开始阅读 → 7 | actionLink: /guide/ 8 | features: 9 | - title: 最佳原则 10 | details: 坚持制定好的代码规范。无论团队人数多少,代码应该同出一门。 11 | - title: 实用高于完美 12 | details: 尽量遵循标准和语义,但是不应该以浪费实用性作为代价;任何时候都要用尽量小的复杂度和尽量少的算法来解决问题。 13 | - title: 配套 lint 工具 14 | details: 根据此规范配置了对应的 lint 工具,保证规范的执行与落实。 15 | footer: MIT Licensed | Copyright © 2019-present NCUHOME 16 | --- 17 | -------------------------------------------------------------------------------- /docs/guide/README.md: -------------------------------------------------------------------------------- 1 | # NCUHOME 前端风格指南 ✍ 2 | 3 | > *系统恒久远,代码永流传! —— 鲁肃 - 蚂蚁金服 CTO* 4 | 5 | **团队愿景:成为国内大学中一流的开发团队!** 6 | 7 | ## 为什么要有团队代码规范? 8 | 虽然这些细节是小事,不会有体验或者性能上的优化,但是却体现了一个 coder 和团队的专业程度。 9 | 同时统一的代码规范,有利于后期的维护,增加阅读他人代码的便利性等。 10 | 11 | 所以不管团队有多少人,代码风格都应该师出同门! 12 | 13 | ## 一起改进? 14 | 15 | 欢迎正在阅读本指南的朋友给我们提 `Pull Request`。:D 16 | -------------------------------------------------------------------------------- /docs/guide/css/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarDepth: 2 3 | --- 4 | 5 | # CSS 代码风格 6 | 7 | ## 缩进 8 | 9 | 使用soft tab(2个空格)。 10 | 11 | ```css 12 | .element { 13 | position: absolute; 14 | top: 10px; 15 | left: 10px; 16 | border-radius: 10px; 17 | width: 50px; 18 | height: 50px; 19 | } 20 | ``` 21 | 22 | ## 分号 23 | 24 | 每个属性声明末尾都要加分号。 25 | 26 | ```css 27 | .element { 28 | width: 20px; 29 | height: 20px; 30 | background-color: red; 31 | } 32 | ``` 33 | 34 | ## 空格 35 | 36 | 以下几种情况不需要空格: 37 | 38 | + 属性名后 39 | + 多个规则的分隔符`,`前 40 | + !important `!`后 41 | + 属性值中`(`后和`)`前 42 | + 行末不要有多余的空格 43 | 44 | 以下几种情况需要空格: 45 | 46 | + 属性值前 47 | + 选择器`>`, `+`, `~`前后 48 | + `{`前 49 | + !important `!`前 50 | + @else 前后 51 | + 属性值中的`,`后 52 | + 注释`/*`后和`*/`前 53 | 54 | ```css 55 | /* not good */ 56 | .element { 57 | color :red! important; 58 | background-color: rgba(0,0,0,.5); 59 | } 60 | 61 | /* good */ 62 | .element { 63 | color: red !important; 64 | background-color: rgba(0, 0, 0, .5); 65 | } 66 | 67 | /* not good */ 68 | .element , 69 | .dialog{ 70 | ... 71 | } 72 | 73 | /* good */ 74 | .element, 75 | .dialog { 76 | 77 | } 78 | 79 | /* not good */ 80 | .element>.dialog{ 81 | ... 82 | } 83 | 84 | /* good */ 85 | .element > .dialog{ 86 | ... 87 | } 88 | 89 | /* not good */ 90 | .element{ 91 | ... 92 | } 93 | 94 | /* good */ 95 | .element { 96 | ... 97 | } 98 | 99 | /* not good */ 100 | @if{ 101 | ... 102 | }@else{ 103 | ... 104 | } 105 | 106 | /* good */ 107 | @if { 108 | ... 109 | } @else { 110 | ... 111 | } 112 | ``` 113 | 114 | ## 空行 115 | 116 | 以下几种情况需要空行: 117 | 118 | + 文件最后保留一个空行 119 | + `}`后最好跟一个空行,包括scss中嵌套的规则 120 | + 属性之间需要适当的空行,具体见属性声明顺序 121 | 122 | ```scss 123 | /* not good */ 124 | .element { 125 | ... 126 | } 127 | .dialog { 128 | color: red; 129 | &:after { 130 | ... 131 | } 132 | } 133 | 134 | /* good */ 135 | .element { 136 | ... 137 | } 138 | 139 | .dialog { 140 | color: red; 141 | 142 | &:after { 143 | ... 144 | } 145 | } 146 | ``` 147 | 148 | ## 换行 149 | 150 | 以下几种情况不需要换行: 151 | 152 | + `{`前 153 | 154 | 以下几种情况需要换行: 155 | 156 | + `{`后和`}`前 157 | + 每个属性独占一行 158 | + 多个规则的分隔符`,`后 159 | 160 | ```css 161 | /* not good */ 162 | .element { 163 | color: red; 164 | background-color: black; 165 | } 166 | 167 | /* good */ 168 | .element { 169 | color: red; 170 | background-color: black; 171 | } 172 | 173 | /* not good */ 174 | .element, .dialog { 175 | ... 176 | } 177 | 178 | /* good */ 179 | .element, 180 | .dialog { 181 | ... 182 | } 183 | ``` 184 | 185 | ## 注释 186 | 187 | 注释统一用`/* */`(scss中也不要用`//`),具体参照右边的写法; 188 | 189 | 缩进与下一行代码保持一致; 190 | 191 | 可位于一个代码行的末尾,与代码间隔一个空格。 192 | 193 | ```css 194 | /* Modal header */ 195 | .modal-header { 196 | ... 197 | } 198 | 199 | /* 200 | * Modal header 201 | */ 202 | .modal-header { 203 | ... 204 | } 205 | 206 | .modal-header { 207 | /* 50px */ 208 | width: 50px; 209 | color: red; /* color red */ 210 | } 211 | ``` 212 | 213 | ## 引号 214 | 215 | 最外层统一使用双引号; 216 | 217 | url的内容要用引号; 218 | 219 | 属性选择器中的属性值需要引号。 220 | 221 | ```css 222 | .element:after { 223 | content: ""; 224 | background-image: url("logo.png"); 225 | } 226 | 227 | li[data-type="single"] { 228 | ... 229 | } 230 | ``` 231 | 232 | ## 命名 233 | 234 | + 类名使用小写字母,以中划线分隔 235 | + id采用驼峰式命名 236 | + scss中的变量、函数、混合、placeholder采用驼峰式命名 237 | 238 | ```css 239 | /* class */ 240 | .element-content { 241 | ... 242 | } 243 | 244 | /* id */ 245 | #myDialog { 246 | ... 247 | } 248 | 249 | /* 变量 */ 250 | $colorBlack: #000; 251 | 252 | /* 函数 */ 253 | @function pxToRem($px) { 254 | ... 255 | } 256 | 257 | /* 混合 */ 258 | @mixin centerBlock { 259 | ... 260 | } 261 | 262 | /* placeholder */ 263 | %myDialog { 264 | ... 265 | } 266 | ``` 267 | 268 | ## 属性声明顺序 269 | 270 | 相关的属性声明按右边的顺序做分组处理,组之间需要有一个空行。 271 | 272 | ```css 273 | .declaration-order { 274 | display: block; 275 | float: right; 276 | 277 | position: absolute; 278 | top: 0; 279 | right: 0; 280 | bottom: 0; 281 | left: 0; 282 | z-index: 100; 283 | 284 | border: 1px solid #e5e5e5; 285 | border-radius: 3px; 286 | width: 100px; 287 | height: 100px; 288 | 289 | font: normal 13px "Helvetica Neue", sans-serif; 290 | line-height: 1.5; 291 | text-align: center; 292 | 293 | color: #333; 294 | background-color: #f5f5f5; 295 | 296 | opacity: 1; 297 | } 298 | // 下面是推荐的属性的顺序 299 | [ 300 | [ 301 | "display", 302 | "visibility", 303 | "float", 304 | "clear", 305 | "overflow", 306 | "overflow-x", 307 | "overflow-y", 308 | "clip", 309 | "zoom" 310 | ], 311 | [ 312 | "table-layout", 313 | "empty-cells", 314 | "caption-side", 315 | "border-spacing", 316 | "border-collapse", 317 | "list-style", 318 | "list-style-position", 319 | "list-style-type", 320 | "list-style-image" 321 | ], 322 | [ 323 | "-webkit-box-orient", 324 | "-webkit-box-direction", 325 | "-webkit-box-decoration-break", 326 | "-webkit-box-pack", 327 | "-webkit-box-align", 328 | "-webkit-box-flex" 329 | ], 330 | [ 331 | "position", 332 | "top", 333 | "right", 334 | "bottom", 335 | "left", 336 | "z-index" 337 | ], 338 | [ 339 | "margin", 340 | "margin-top", 341 | "margin-right", 342 | "margin-bottom", 343 | "margin-left", 344 | "-webkit-box-sizing", 345 | "-moz-box-sizing", 346 | "box-sizing", 347 | "border", 348 | "border-width", 349 | "border-style", 350 | "border-color", 351 | "border-top", 352 | "border-top-width", 353 | "border-top-style", 354 | "border-top-color", 355 | "border-right", 356 | "border-right-width", 357 | "border-right-style", 358 | "border-right-color", 359 | "border-bottom", 360 | "border-bottom-width", 361 | "border-bottom-style", 362 | "border-bottom-color", 363 | "border-left", 364 | "border-left-width", 365 | "border-left-style", 366 | "border-left-color", 367 | "-webkit-border-radius", 368 | "-moz-border-radius", 369 | "border-radius", 370 | "-webkit-border-top-left-radius", 371 | "-moz-border-radius-topleft", 372 | "border-top-left-radius", 373 | "-webkit-border-top-right-radius", 374 | "-moz-border-radius-topright", 375 | "border-top-right-radius", 376 | "-webkit-border-bottom-right-radius", 377 | "-moz-border-radius-bottomright", 378 | "border-bottom-right-radius", 379 | "-webkit-border-bottom-left-radius", 380 | "-moz-border-radius-bottomleft", 381 | "border-bottom-left-radius", 382 | "-webkit-border-image", 383 | "-moz-border-image", 384 | "-o-border-image", 385 | "border-image", 386 | "-webkit-border-image-source", 387 | "-moz-border-image-source", 388 | "-o-border-image-source", 389 | "border-image-source", 390 | "-webkit-border-image-slice", 391 | "-moz-border-image-slice", 392 | "-o-border-image-slice", 393 | "border-image-slice", 394 | "-webkit-border-image-width", 395 | "-moz-border-image-width", 396 | "-o-border-image-width", 397 | "border-image-width", 398 | "-webkit-border-image-outset", 399 | "-moz-border-image-outset", 400 | "-o-border-image-outset", 401 | "border-image-outset", 402 | "-webkit-border-image-repeat", 403 | "-moz-border-image-repeat", 404 | "-o-border-image-repeat", 405 | "border-image-repeat", 406 | "padding", 407 | "padding-top", 408 | "padding-right", 409 | "padding-bottom", 410 | "padding-left", 411 | "width", 412 | "min-width", 413 | "max-width", 414 | "height", 415 | "min-height", 416 | "max-height" 417 | ], 418 | [ 419 | "font", 420 | "font-family", 421 | "font-size", 422 | "font-weight", 423 | "font-style", 424 | "font-variant", 425 | "font-size-adjust", 426 | "font-stretch", 427 | "font-effect", 428 | "font-emphasize", 429 | "font-emphasize-position", 430 | "font-emphasize-style", 431 | "font-smooth", 432 | "line-height", 433 | "text-align", 434 | "-webkit-text-align-last", 435 | "-moz-text-align-last", 436 | "-ms-text-align-last", 437 | "text-align-last", 438 | "vertical-align", 439 | "white-space", 440 | "text-decoration", 441 | "text-emphasis", 442 | "text-emphasis-color", 443 | "text-emphasis-style", 444 | "text-emphasis-position", 445 | "text-indent", 446 | "-ms-text-justify", 447 | "text-justify", 448 | "letter-spacing", 449 | "word-spacing", 450 | "-ms-writing-mode", 451 | "text-outline", 452 | "text-transform", 453 | "text-wrap", 454 | "-ms-text-overflow", 455 | "text-overflow", 456 | "text-overflow-ellipsis", 457 | "text-overflow-mode", 458 | "-ms-word-wrap", 459 | "word-wrap", 460 | "-ms-word-break", 461 | "word-break" 462 | ], 463 | [ 464 | "color", 465 | "background", 466 | "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader", 467 | "background-color", 468 | "background-image", 469 | "background-repeat", 470 | "background-attachment", 471 | "background-position", 472 | "-ms-background-position-x", 473 | "background-position-x", 474 | "-ms-background-position-y", 475 | "background-position-y", 476 | "-webkit-background-clip", 477 | "-moz-background-clip", 478 | "background-clip", 479 | "background-origin", 480 | "-webkit-background-size", 481 | "-moz-background-size", 482 | "-o-background-size", 483 | "background-size" 484 | ], 485 | [ 486 | "outline", 487 | "outline-width", 488 | "outline-style", 489 | "outline-color", 490 | "outline-offset", 491 | "opacity", 492 | "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity", 493 | "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha", 494 | "-ms-interpolation-mode", 495 | "-webkit-box-shadow", 496 | "-moz-box-shadow", 497 | "box-shadow", 498 | "filter:progid:DXImageTransform.Microsoft.gradient", 499 | "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient", 500 | "text-shadow" 501 | ], 502 | [ 503 | "-webkit-transition", 504 | "-moz-transition", 505 | "-ms-transition", 506 | "-o-transition", 507 | "transition", 508 | "-webkit-transition-delay", 509 | "-moz-transition-delay", 510 | "-ms-transition-delay", 511 | "-o-transition-delay", 512 | "transition-delay", 513 | "-webkit-transition-timing-function", 514 | "-moz-transition-timing-function", 515 | "-ms-transition-timing-function", 516 | "-o-transition-timing-function", 517 | "transition-timing-function", 518 | "-webkit-transition-duration", 519 | "-moz-transition-duration", 520 | "-ms-transition-duration", 521 | "-o-transition-duration", 522 | "transition-duration", 523 | "-webkit-transition-property", 524 | "-moz-transition-property", 525 | "-ms-transition-property", 526 | "-o-transition-property", 527 | "transition-property", 528 | "-webkit-transform", 529 | "-moz-transform", 530 | "-ms-transform", 531 | "-o-transform", 532 | "transform", 533 | "-webkit-transform-origin", 534 | "-moz-transform-origin", 535 | "-ms-transform-origin", 536 | "-o-transform-origin", 537 | "transform-origin", 538 | "-webkit-animation", 539 | "-moz-animation", 540 | "-ms-animation", 541 | "-o-animation", 542 | "animation", 543 | "-webkit-animation-name", 544 | "-moz-animation-name", 545 | "-ms-animation-name", 546 | "-o-animation-name", 547 | "animation-name", 548 | "-webkit-animation-duration", 549 | "-moz-animation-duration", 550 | "-ms-animation-duration", 551 | "-o-animation-duration", 552 | "animation-duration", 553 | "-webkit-animation-play-state", 554 | "-moz-animation-play-state", 555 | "-ms-animation-play-state", 556 | "-o-animation-play-state", 557 | "animation-play-state", 558 | "-webkit-animation-timing-function", 559 | "-moz-animation-timing-function", 560 | "-ms-animation-timing-function", 561 | "-o-animation-timing-function", 562 | "animation-timing-function", 563 | "-webkit-animation-delay", 564 | "-moz-animation-delay", 565 | "-ms-animation-delay", 566 | "-o-animation-delay", 567 | "animation-delay", 568 | "-webkit-animation-iteration-count", 569 | "-moz-animation-iteration-count", 570 | "-ms-animation-iteration-count", 571 | "-o-animation-iteration-count", 572 | "animation-iteration-count", 573 | "-webkit-animation-direction", 574 | "-moz-animation-direction", 575 | "-ms-animation-direction", 576 | "-o-animation-direction", 577 | "animation-direction" 578 | ], 579 | [ 580 | "content", 581 | "quotes", 582 | "counter-reset", 583 | "counter-increment", 584 | "resize", 585 | "cursor", 586 | "-webkit-user-select", 587 | "-moz-user-select", 588 | "-ms-user-select", 589 | "user-select", 590 | "nav-index", 591 | "nav-up", 592 | "nav-right", 593 | "nav-down", 594 | "nav-left", 595 | "-moz-tab-size", 596 | "-o-tab-size", 597 | "tab-size", 598 | "-webkit-hyphens", 599 | "-moz-hyphens", 600 | "hyphens", 601 | "pointer-events" 602 | ] 603 | ] 604 | ``` 605 | 606 | ## 颜色 607 | 608 | 颜色16进制用小写字母; 609 | 610 | 颜色16进制尽量用简写。 611 | 612 | ```css 613 | /* not good */ 614 | .element { 615 | color: #ABCDEF; 616 | background-color: #001122; 617 | } 618 | 619 | /* good */ 620 | .element { 621 | color: #abcdef; 622 | background-color: #012; 623 | } 624 | ``` 625 | 626 | ## 属性简写 627 | 628 | 属性简写需要你非常清楚属性值的正确顺序,而且在大多数情况下并不需要设置属性简写中包含的所有值,所以建议尽量分开声明会更加清晰; 629 | 630 | margin 和 padding 相反,需要使用简写; 631 | 632 | 常见的属性简写包括: 633 | 634 | + font 635 | + background 636 | + transition 637 | + animation 638 | 639 | ```css 640 | /* not good */ 641 | .element { 642 | transition: opacity 1s linear 2s; 643 | } 644 | 645 | /* good */ 646 | .element { 647 | transition-delay: 2s; 648 | transition-timing-function: linear; 649 | transition-duration: 1s; 650 | transition-property: opacity; 651 | } 652 | ``` 653 | 654 | ## 媒体查询 655 | 656 | 尽量将媒体查询的规则靠近与他们相关的规则,不要将他们一起放到一个独立的样式文件中,或者丢在文档的最底部,这样做只会让大家以后更容易忘记他们。 657 | 658 | ```css 659 | .element { 660 | ... 661 | } 662 | 663 | .element-avatar{ 664 | ... 665 | } 666 | 667 | @media (min-width: 480px) { 668 | .element { 669 | ... 670 | } 671 | 672 | .element-avatar { 673 | ... 674 | } 675 | } 676 | ``` 677 | 678 | ## SCSS相关 679 | 680 | 提交的代码中不要有 `@debug`; 681 | 682 | 声明顺序: 683 | 684 | + `@extend` 685 | + 不包含 `@content` 的 `@include` 686 | + 包含 `@content` 的 `@include` 687 | + 自身属性 688 | + 嵌套规则 689 | 690 | `@import` 引入的文件不需要开头的'_'和结尾的'.scss'; 691 | 692 | 嵌套最多不能超过5层; 693 | 694 | `@extend` 中使用placeholder选择器; 695 | 696 | 去掉不必要的父级引用符号'&'。 697 | 698 | ```scss 699 | /* not good */ 700 | @import "_dialog.scss"; 701 | 702 | /* good */ 703 | @import "dialog"; 704 | 705 | /* not good */ 706 | .fatal { 707 | @extend .error; 708 | } 709 | 710 | /* good */ 711 | .fatal { 712 | @extend %error; 713 | } 714 | 715 | /* not good */ 716 | .element { 717 | & > .dialog { 718 | ... 719 | } 720 | } 721 | 722 | /* good */ 723 | .element { 724 | > .dialog { 725 | ... 726 | } 727 | } 728 | ``` 729 | 730 | ## 杂项 731 | 732 | 不允许有空的规则; 733 | 734 | 元素选择器用小写字母; 735 | 736 | 去掉小数点前面的`0`; 737 | 738 | 去掉数字中不必要的小数点和末尾的0; 739 | 740 | 属性值`0`后面不要加单位; 741 | 742 | 同个属性不同前缀的写法需要在垂直方向保持对齐,具体参照右边的写法; 743 | 744 | 无前缀的标准属性应该写在有前缀的属性后面; 745 | 746 | 不要在同个规则里出现重复的属性,如果重复的属性是连续的则没关系; 747 | 748 | 不要在一个文件里出现两个相同的规则; 749 | 750 | 用 `border: 0;` 代替 `border: none;`; 751 | 752 | 选择器不要超过4层(在scss中如果超过4层应该考虑用嵌套的方式来写); 753 | 754 | 发布的代码中不要有 `@import`; 755 | 756 | 尽量少用`*`选择器。 757 | 758 | ```css 759 | /* not good */ 760 | .element { 761 | } 762 | 763 | /* not good */ 764 | LI { 765 | ... 766 | } 767 | 768 | /* good */ 769 | li { 770 | ... 771 | } 772 | 773 | /* not good */ 774 | .element { 775 | color: rgba(0, 0, 0, 0.5); 776 | } 777 | 778 | /* good */ 779 | .element { 780 | color: rgba(0, 0, 0, .5); 781 | } 782 | 783 | /* not good */ 784 | .element { 785 | width: 50.0px; 786 | } 787 | 788 | /* good */ 789 | .element { 790 | width: 50px; 791 | } 792 | 793 | /* not good */ 794 | .element { 795 | width: 0px; 796 | } 797 | 798 | /* good */ 799 | .element { 800 | width: 0; 801 | } 802 | 803 | /* not good */ 804 | .element { 805 | border-radius: 3px; 806 | -webkit-border-radius: 3px; 807 | -moz-border-radius: 3px; 808 | 809 | background: linear-gradient(to bottom, #fff 0, #eee 100%); 810 | background: -webkit-linear-gradient(top, #fff 0, #eee 100%); 811 | background: -moz-linear-gradient(top, #fff 0, #eee 100%); 812 | } 813 | 814 | /* good */ 815 | .element { 816 | -webkit-border-radius: 3px; 817 | -moz-border-radius: 3px; 818 | border-radius: 3px; 819 | 820 | background: -webkit-linear-gradient(top, #fff 0, #eee 100%); 821 | background: -moz-linear-gradient(top, #fff 0, #eee 100%); 822 | background: linear-gradient(to bottom, #fff 0, #eee 100%); 823 | } 824 | 825 | /* not good */ 826 | .element { 827 | color: rgb(0, 0, 0); 828 | width: 50px; 829 | color: rgba(0, 0, 0, .5); 830 | } 831 | 832 | /* good */ 833 | .element { 834 | color: rgb(0, 0, 0); 835 | color: rgba(0, 0, 0, .5); 836 | } 837 | ``` 838 | -------------------------------------------------------------------------------- /docs/guide/foreword/README.md: -------------------------------------------------------------------------------- 1 | # 写在前面 2 | 3 | > ✍️Writing code is like liberty art. 4 | 5 | 阅读团队的代码规范,并且遵守它,的确能够统一团队代码风格。 6 | 7 | 但是,要想真正地编写出可维护、可拓展、高灵活性的,也就是我们口中的「好代码」。仅靠此代码规范,还远远不够。 8 | 9 | 你需要学习体验设计模式、重构思想、各种软件工程的概念与原则。亲身地去体会,感受编写「优雅的」代码给我们带来的好处。 10 | 11 | ## 📚读书推荐 12 | 13 | 在这里推荐一些书籍 14 | 15 | + [《重构:改善既有的代码设计》](https://book.douban.com/subject/30468597/) 16 | + [《大话设计模式》](https://book.douban.com/subject/2334288/) 17 | + [《代码整洁之道》](https://book.douban.com/subject/4199741/) 18 | + [《设计模式 - 可复用面向对象软件的基础》](https://book.douban.com/subject/1052241/) 19 | -------------------------------------------------------------------------------- /docs/guide/git/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarDepth: 2 3 | --- 4 | 5 | # GIT 使用规范 6 | 7 | 规范制订背景: 8 | 9 | * 家园项目日渐增多,每个项目使用的 Git 开发方式都不一样,给跨项目合作与维护带来了一定程度上的困扰。 10 | * 统一 Git Flow,提高开发效率,向正式互联网公司的开发模式看齐。 11 | * 统一且规范的 Commits 信息还有助于配合相关工具自动生成 Changelogs。 12 | 13 | ## **分支** 14 | 15 | ### `main`分支(原 master 分支) 16 | 17 | * 代码主分支 18 | * 不允许直接推送代码到 main 分支,所有新功能与 BUG 修复均从 main 分支 checkout 新分支,完成后提 PR 并合入 19 | * 确保 main 分支质量,不允许出现 main 分支无法部署的低级问题 20 | 21 | ### `feature` 分支 22 | 23 | * 用于开发新功能,完成后合并回 main 分支 24 | * 命名以 `feature/[新功能名字]` 为准,不使用任何无意义的单词 25 | > 示例:feature/login feature/topic-editor 26 | 27 | * 一个分支只完成一个功能 28 | * 合并前确保能正常编译或者部署 29 | 30 | ### BUG 修复分支 31 | 32 | * 用于修复产品 bug,完成后合并回 main 分支 33 | * 命名以 `fix/[bug名称]` 为准,不使用任何无意义的单词 34 | > 示例:fix/topic-editor fix/login-502 35 | 36 | * 一个分支可修复多个BUG 37 | * 合并前确保能正常编译或者部署 38 | 39 | ## `commit` 信息 40 | 完整的 commit 信息包含头部信息及提交信息,其中头部信息为必填,在必要时(有较大的变动或其它需要备注的信息)选填提交信息。 41 | 头部信息需要包含两个部分``: `` 42 | 43 | * `type` 可以选用 `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore` 44 | * feat: 新功能(feature) 45 | * fix: 修复Bug 46 | * docs: 文档 47 | * style: 格式化代码(在不影响代码功能的前提) 48 | * refactor: 重构代码 49 | * test: 添加测试用例 50 | * chore: 构建过程或者辅助工具变动 51 | * `description` 可以是任何文字,但不能包含空格 52 | 53 | ## 文档 54 | 55 | * 项目文档统一放置于 docs 文件夹 56 | * 后端部分,需要包含部署、API 等文档 57 | * 前端部分需要包含编译构建流程文档 58 | 59 | ## Tag 版本标签 60 | 61 | * 适用与当前项目*按明确版本对外发布*的,如 App 62 | * 发布正式版本时,需打上 Tag 版本号,并推送到远端 63 | 64 | ## 其它事项 65 | 66 | * 项目中必须有 `.gitigore`,并屏蔽编辑器自定义配置/编译文件/依赖文件等 67 | * **不允许**提交无意义的 commit message,如 aaa,bbb 等信息 68 | * 勤 commit,修复一个 bug,完成部分新功能即 commit 一次,**不要一次提交包含多个功能或者十几个改动等** 69 | 70 | ## 如何落实 71 | 72 | * 规范 Commit 信息,推荐使用 [commitlint](https://github.com/conventional-changelog/commitlint) 工具,配合工作室定制的规则。 73 | * 使用自动化 Release 工具,简化打 Tag、生成 Release 与 Changelogs 的流程。一般情况下可以使用 [release-it](https://github.com/release-it/release-it),monorepo 场景下可以使用 [changesets](https://github.com/atlassian/changesets),配合工作室定制的规则使用。 74 | * 在提交 commit 之前,需要确保工作室已经完成了相关的工作,如果没有完成,需要先完成。 75 | -------------------------------------------------------------------------------- /docs/guide/js/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarDepth: 2 3 | --- 4 | 5 | # NCUHOME JavaScript 代码风格指南 6 | 7 | ## 目录 8 | 1. [介绍](#介绍) 9 | 2. [变量](#变量) 10 | 3. [函数](#函数) 11 | 4. [对象和数据结构](#objects-and-data-structures) 12 | 5. [类](#类) 13 | 6. [测试](#测试) 14 | 7. [并发](#并发) 15 | 8. [错误处理](#错误处理) 16 | 9. [格式化](#格式化) 17 | 10. [注释](#注释) 18 | 19 | ## **变量** 20 | ### 使用有意义,可读性好的变量名 21 | 22 | **反例**: 23 | ```javascript 24 | var yyyymmdstr = moment().format('YYYY/MM/DD'); 25 | ``` 26 | 27 | **正例**: 28 | ```javascript 29 | var yearMonthDay = moment().format('YYYY/MM/DD'); 30 | ``` 31 | 32 | ### 使用 ES6 的 const 定义常量 33 | 反例中使用"var"定义的"常量"是可变的。 34 | 35 | 在声明一个常量时,该常量在整个程序中都应该是不可变的。 36 | 37 | **反例**: 38 | ```javascript 39 | var FIRST_US_PRESIDENT = "George Washington"; 40 | ``` 41 | 42 | **正例**: 43 | ```javascript 44 | const FIRST_US_PRESIDENT = "George Washington"; 45 | ``` 46 | 47 | ### 对功能类似的变量名采用统一的命名风格 48 | 49 | **反例**: 50 | ```javascript 51 | getUserInfo(); 52 | getClientData(); 53 | getCustomerRecord(); 54 | ``` 55 | 56 | **正例**: 57 | ```javascript 58 | getUser(); 59 | ``` 60 | 61 | ### 使用易于检索名称 62 | 我们需要阅读的代码远比自己写的要多,使代码拥有良好的可读性且易于检索非常重要。阅读变量名晦涩难懂的代码对读者来说是一种相当糟糕的体验。 63 | 让你的变量名易于检索。 64 | 65 | **反例**: 66 | ```javascript 67 | // 525600 是什么? 68 | for (var i = 0; i < 525600; i++) { 69 | runCronJob(); 70 | } 71 | ``` 72 | 73 | **正例**: 74 | ```javascript 75 | // Declare them as capitalized `var` globals. 76 | var MINUTES_IN_A_YEAR = 525600; 77 | for (var i = 0; i < MINUTES_IN_A_YEAR; i++) { 78 | runCronJob(); 79 | } 80 | ``` 81 | 82 | ### 使用说明变量(即有意义的变量名) 83 | **反例**: 84 | ```javascript 85 | const cityStateRegex = /^(.+)[,\\s]+(.+?)\s*(\d{5})?$/; 86 | saveCityState(cityStateRegex.match(cityStateRegex)[1], cityStateRegex.match(cityStateRegex)[2]); 87 | ``` 88 | 89 | **正例**: 90 | ```javascript 91 | const ADDRESS = 'One Infinite Loop, Cupertino 95014'; 92 | var cityStateRegex = /^(.+)[,\\s]+(.+?)\s*(\d{5})?$/; 93 | var match = ADDRESS.match(cityStateRegex) 94 | var city = match[1]; 95 | var state = match[2]; 96 | saveCityState(city, state); 97 | ``` 98 | 99 | ### 不要绕太多的弯子 100 | 显式优于隐式。 101 | 102 | **反例**: 103 | ```javascript 104 | var locations = ['Austin', 'New York', 'San Francisco']; 105 | locations.forEach((l) => { 106 | doStuff(); 107 | doSomeOtherStuff(); 108 | ... 109 | ... 110 | ... 111 | // l是什么? 112 | dispatch(l); 113 | }); 114 | ``` 115 | 116 | **正例**: 117 | ```javascript 118 | var locations = ['Austin', 'New York', 'San Francisco']; 119 | locations.forEach((location) => { 120 | doStuff(); 121 | doSomeOtherStuff(); 122 | ... 123 | ... 124 | ... 125 | dispatch(location); 126 | }); 127 | ``` 128 | 129 | ### 避免重复的描述 130 | 当类/对象名已经有意义时,对其变量进行命名不需要再次重复。 131 | 132 | **反例**: 133 | ```javascript 134 | var Car = { 135 | carMake: 'Honda', 136 | carModel: 'Accord', 137 | carColor: 'Blue' 138 | }; 139 | 140 | function paintCar(car) { 141 | car.carColor = 'Red'; 142 | } 143 | ``` 144 | 145 | **正例**: 146 | ```javascript 147 | var Car = { 148 | make: 'Honda', 149 | model: 'Accord', 150 | color: 'Blue' 151 | }; 152 | 153 | function paintCar(car) { 154 | car.color = 'Red'; 155 | } 156 | ``` 157 | 158 | ### 避免无意义的条件判断 159 | 160 | **反例**: 161 | ```javascript 162 | function createMicrobrewery(name) { 163 | var breweryName; 164 | if (name) { 165 | breweryName = name; 166 | } else { 167 | breweryName = 'Hipster Brew Co.'; 168 | } 169 | } 170 | ``` 171 | 172 | **正例**: 173 | ```javascript 174 | function createMicrobrewery(name) { 175 | var breweryName = name || 'Hipster Brew Co.' 176 | } 177 | ``` 178 | 179 | ### 不要出现魔术数字 180 | 181 | 魔术数字可以是指硬编码在代码里的具体数值。虽然编写时写的时候自己能了解数值的意义,但对其他人(甚至本人经过一段时间后),会难以了解这个数值的用途。 182 | 183 | **反例** 184 | ```javascript 185 | let priceTax = 1.05 * price 186 | ``` 187 | 188 | **正例** 189 | ```javascript 190 | const taxRate = 0.05 191 | let priceTax = (1 + taxRate) * price 192 | ``` 193 | 194 | ## **函数** 195 | ### 函数参数 (理想情况下应不超过 2 个) 196 | 限制函数参数数量很有必要,这么做使得在测试函数时更加轻松。过多的参数将导致难以采用有效的测试用例对函数的各个参数进行测试。 197 | 198 | 应避免三个以上参数的函数。通常情况下,参数超过两个意味着函数功能过于复杂,这时需要重新优化你的函数。当确实需要多个参数时,大多情况下可以考虑这些参数封装成一个对象。 199 | 200 | JS 定义对象非常方便,当需要多个参数时,可以使用一个对象进行替代。 201 | 202 | **反例**: 203 | ```javascript 204 | function createMenu(title, body, buttonText, cancellable) { 205 | ... 206 | } 207 | ``` 208 | 209 | **正例**: 210 | ```javascript 211 | var menuConfig = { 212 | title: 'Foo', 213 | body: 'Bar', 214 | buttonText: 'Baz', 215 | cancellable: true 216 | } 217 | 218 | function createMenu(menuConfig) { 219 | ... 220 | } 221 | 222 | ``` 223 | 224 | 225 | ### 函数功能的单一性 226 | 这是软件功能中最重要的原则之一。 227 | 228 | 功能不单一的函数将导致难以重构、测试和理解。功能单一的函数易于重构,并使代码更加干净。 229 | 230 | **反例**: 231 | ```javascript 232 | function emailClients(clients) { 233 | clients.forEach(client => { 234 | let clientRecord = database.lookup(client); 235 | if (clientRecord.isActive()) { 236 | email(client); 237 | } 238 | }); 239 | } 240 | ``` 241 | 242 | **正例**: 243 | ```javascript 244 | function emailClients(clients) { 245 | clients.forEach(client => { 246 | emailClientIfNeeded(client); 247 | }); 248 | } 249 | 250 | function emailClientIfNeeded(client) { 251 | if (isClientActive(client)) { 252 | email(client); 253 | } 254 | } 255 | 256 | function isClientActive(client) { 257 | let clientRecord = database.lookup(client); 258 | return clientRecord.isActive(); 259 | } 260 | ``` 261 | 262 | ### 函数名应明确表明其功能 263 | 264 | **反例**: 265 | ```javascript 266 | function dateAdd(date, month) { 267 | // ... 268 | } 269 | 270 | let date = new Date(); 271 | 272 | // 很难理解dateAdd(date, 1)是什么意思 273 | dateAdd(date, 1); 274 | ``` 275 | 276 | **正例**: 277 | ```javascript 278 | function dateAddMonth(date, month) { 279 | // ... 280 | } 281 | 282 | let date = new Date(); 283 | dateAddMonth(date, 1); 284 | ``` 285 | 286 | ### 函数应该只做一层抽象 287 | 当函数的需要的抽象多于一层时通常意味着函数功能过于复杂,需将其进行分解以提高其可重用性和可测试性。 288 | 289 | **反例**: 290 | ```javascript 291 | function parseBetterJSAlternative(code) { 292 | let REGEXES = [ 293 | // ... 294 | ]; 295 | 296 | let statements = code.split(' '); 297 | let tokens; 298 | REGEXES.forEach((REGEX) => { 299 | statements.forEach((statement) => { 300 | // ... 301 | }) 302 | }); 303 | 304 | let ast; 305 | tokens.forEach((token) => { 306 | // lex... 307 | }); 308 | 309 | ast.forEach((node) => { 310 | // parse... 311 | }) 312 | } 313 | ``` 314 | 315 | **正例**: 316 | ```javascript 317 | function tokenize(code) { 318 | let REGEXES = [ 319 | // ... 320 | ]; 321 | 322 | let statements = code.split(' '); 323 | let tokens; 324 | REGEXES.forEach((REGEX) => { 325 | statements.forEach((statement) => { 326 | // ... 327 | }) 328 | }); 329 | 330 | return tokens; 331 | } 332 | 333 | function lexer(tokens) { 334 | let ast; 335 | tokens.forEach((token) => { 336 | // lex... 337 | }); 338 | 339 | return ast; 340 | } 341 | 342 | function parseBetterJSAlternative(code) { 343 | let tokens = tokenize(code); 344 | let ast = lexer(tokens); 345 | ast.forEach((node) => { 346 | // parse... 347 | }) 348 | } 349 | ``` 350 | 351 | ### 移除重复的代码 352 | 永远、永远、永远不要在任何循环下有重复的代码。 353 | 354 | 这种做法毫无意义且潜在危险极大。重复的代码意味着逻辑变化时需要对不止一处进行修改。JS 弱类型的特点使得函数拥有更强的普适性。好好利用这一优点吧。 355 | 356 | **反例**: 357 | ```javascript 358 | function showDeveloperList(developers) { 359 | developers.forEach(developer => { 360 | var expectedSalary = developer.calculateExpectedSalary(); 361 | var experience = developer.getExperience(); 362 | var githubLink = developer.getGithubLink(); 363 | var data = { 364 | expectedSalary: expectedSalary, 365 | experience: experience, 366 | githubLink: githubLink 367 | }; 368 | 369 | render(data); 370 | }); 371 | } 372 | 373 | function showManagerList(managers) { 374 | managers.forEach(manager => { 375 | var expectedSalary = manager.calculateExpectedSalary(); 376 | var experience = manager.getExperience(); 377 | var portfolio = manager.getMBAProjects(); 378 | var data = { 379 | expectedSalary: expectedSalary, 380 | experience: experience, 381 | portfolio: portfolio 382 | }; 383 | 384 | render(data); 385 | }); 386 | } 387 | ``` 388 | 389 | **正例**: 390 | ```javascript 391 | function showList(employees) { 392 | employees.forEach(employee => { 393 | var expectedSalary = employee.calculateExpectedSalary(); 394 | var experience = employee.getExperience(); 395 | var portfolio; 396 | 397 | if (employee.type === 'manager') { 398 | portfolio = employee.getMBAProjects(); 399 | } else { 400 | portfolio = employee.getGithubLink(); 401 | } 402 | 403 | var data = { 404 | expectedSalary: expectedSalary, 405 | experience: experience, 406 | portfolio: portfolio 407 | }; 408 | 409 | render(data); 410 | }); 411 | } 412 | ``` 413 | 414 | ### 采用默认参数精简代码 415 | **反例**: 416 | ```javascript 417 | function writeForumComment(subject, body) { 418 | subject = subject || 'No Subject'; 419 | body = body || 'No text'; 420 | } 421 | 422 | ``` 423 | 424 | **正例**: 425 | ```javascript 426 | function writeForumComment(subject = 'No subject', body = 'No text') { 427 | ... 428 | } 429 | 430 | ``` 431 | 432 | ### 使用 Object.assign 设置默认对象 433 | 434 | **反例**: 435 | ```javascript 436 | var menuConfig = { 437 | title: null, 438 | body: 'Bar', 439 | buttonText: null, 440 | cancellable: true 441 | } 442 | 443 | function createMenu(config) { 444 | config.title = config.title || 'Foo' 445 | config.body = config.body || 'Bar' 446 | config.buttonText = config.buttonText || 'Baz' 447 | config.cancellable = config.cancellable === undefined ? config.cancellable : true; 448 | 449 | } 450 | 451 | createMenu(menuConfig); 452 | ``` 453 | 454 | **正例**: 455 | ```javascript 456 | var menuConfig = { 457 | title: 'Order', 458 | // User did not include 'body' key 459 | buttonText: 'Send', 460 | cancellable: true 461 | } 462 | 463 | function createMenu(config) { 464 | config = Object.assign({ 465 | title: 'Foo', 466 | body: 'Bar', 467 | buttonText: 'Baz', 468 | cancellable: true 469 | }, config); 470 | 471 | // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true} 472 | // ... 473 | } 474 | 475 | createMenu(menuConfig); 476 | ``` 477 | 478 | 479 | ### 不要使用标记(Flag)作为函数参数 480 | 这通常意味着函数的功能的单一性已经被破坏。此时应考虑对函数进行再次划分。 481 | 482 | **反例**: 483 | ```javascript 484 | function createFile(name, temp) { 485 | if (temp) { 486 | fs.create('./temp/' + name); 487 | } else { 488 | fs.create(name); 489 | } 490 | } 491 | ``` 492 | 493 | **正例**: 494 | ```javascript 495 | function createTempFile(name) { 496 | fs.create('./temp/' + name); 497 | } 498 | 499 | ---------- 500 | 501 | 502 | function createFile(name) { 503 | fs.create(name); 504 | } 505 | ``` 506 | 507 | ### 避免副作用 508 | 当函数产生了除了“接受一个值并返回一个结果”之外的行为时,称该函数产生了副作用。比如写文件、修改全局变量或将你的钱全转给了一个陌生人等。 509 | 510 | 程序在某些情况下确实需要副作用这一行为,如先前例子中的写文件。这时应该将这些功能集中在一起,不要用多个函数/类修改某个文件。用且只用一个 service 完成这一需求。 511 | 512 | **反例**: 513 | ```javascript 514 | // Global variable referenced by following function. 515 | // If we had another function that used this name, now it'd be an array and it could break it. 516 | var name = 'Ryan McDermott'; 517 | 518 | function splitIntoFirstAndLastName() { 519 | name = name.split(' '); 520 | } 521 | 522 | splitIntoFirstAndLastName(); 523 | 524 | console.log(name); // ['Ryan', 'McDermott']; 525 | ``` 526 | 527 | **正例**: 528 | ```javascript 529 | function splitIntoFirstAndLastName(name) { 530 | return name.split(' '); 531 | } 532 | 533 | var name = 'Ryan McDermott' 534 | var newName = splitIntoFirstAndLastName(name); 535 | 536 | console.log(name); // 'Ryan McDermott'; 537 | console.log(newName); // ['Ryan', 'McDermott']; 538 | ``` 539 | 540 | ### 不要写全局函数 541 | 在 JS 中污染全局是一个非常不好的实践,这么做可能和其他库起冲突,且调用你的 API 的用户在实际环境中得到一个 exception 前对这一情况是一无所知的。 542 | 543 | 想象以下例子:如果你想扩展 JS 中的 Array,为其添加一个 `diff` 函数显示两个数组间的差异,此时应如何去做?你可以将 diff 写入 `Array.prototype`,但这么做会和其他有类似需求的库造成冲突。如果另一个库对 diff 的需求为比较一个数组中首尾元素间的差异呢? 544 | 545 | 使用 ES6 中的 class 对全局的 Array 做简单的扩展显然是一个更棒的选择。 546 | 547 | **反例**: 548 | ```javascript 549 | Array.prototype.diff = function(comparisonArray) { 550 | var values = []; 551 | var hash = {}; 552 | 553 | for (var i of comparisonArray) { 554 | hash[i] = true; 555 | } 556 | 557 | for (var i of this) { 558 | if (!hash[i]) { 559 | values.push(i); 560 | } 561 | } 562 | 563 | return values; 564 | } 565 | ``` 566 | 567 | **正例**: 568 | ```javascript 569 | class SuperArray extends Array { 570 | constructor(...args) { 571 | super(...args); 572 | } 573 | 574 | diff(comparisonArray) { 575 | var values = []; 576 | var hash = {}; 577 | 578 | for (var i of comparisonArray) { 579 | hash[i] = true; 580 | } 581 | 582 | for (var i of this) { 583 | if (!hash[i]) { 584 | values.push(i); 585 | } 586 | } 587 | 588 | return values; 589 | } 590 | } 591 | ``` 592 | 593 | ### 采用函数式编程 594 | 函数式的编程具有更干净且便于测试的特点。尽可能的使用这种风格吧。 595 | 596 | **反例**: 597 | ```javascript 598 | const programmerOutput = [ 599 | { 600 | name: 'Uncle Bobby', 601 | linesOfCode: 500 602 | }, { 603 | name: 'Suzie Q', 604 | linesOfCode: 1500 605 | }, { 606 | name: 'Jimmy Gosling', 607 | linesOfCode: 150 608 | }, { 609 | name: 'Gracie Hopper', 610 | linesOfCode: 1000 611 | } 612 | ]; 613 | 614 | var totalOutput = 0; 615 | 616 | for (var i = 0; i < programmerOutput.length; i++) { 617 | totalOutput += programmerOutput[i].linesOfCode; 618 | } 619 | ``` 620 | 621 | **正例**: 622 | ```javascript 623 | const programmerOutput = [ 624 | { 625 | name: 'Uncle Bobby', 626 | linesOfCode: 500 627 | }, { 628 | name: 'Suzie Q', 629 | linesOfCode: 1500 630 | }, { 631 | name: 'Jimmy Gosling', 632 | linesOfCode: 150 633 | }, { 634 | name: 'Gracie Hopper', 635 | linesOfCode: 1000 636 | } 637 | ]; 638 | 639 | var totalOutput = programmerOutput 640 | .map((programmer) => programmer.linesOfCode) 641 | .reduce((acc, linesOfCode) => acc + linesOfCode, 0); 642 | ``` 643 | 644 | ### 封装判断条件 645 | 646 | **反例**: 647 | ```javascript 648 | if (fsm.state === 'fetching' && isEmpty(listNode)) { 649 | /// ... 650 | } 651 | ``` 652 | 653 | **正例**: 654 | ```javascript 655 | function shouldShowSpinner(fsm, listNode) { 656 | return fsm.state === 'fetching' && isEmpty(listNode); 657 | } 658 | 659 | if (shouldShowSpinner(fsmInstance, listNodeInstance)) { 660 | // ... 661 | } 662 | ``` 663 | 664 | ### 避免“否定情况”的判断 665 | 666 | **反例**: 667 | ```javascript 668 | function isDOMNodeNotPresent(node) { 669 | // ... 670 | } 671 | 672 | if (!isDOMNodeNotPresent(node)) { 673 | // ... 674 | } 675 | ``` 676 | 677 | **正例**: 678 | ```javascript 679 | function isDOMNodePresent(node) { 680 | // ... 681 | } 682 | 683 | if (isDOMNodePresent(node)) { 684 | // ... 685 | } 686 | ``` 687 | 688 | ### 避免条件判断 689 | 这看起来似乎不太可能。 690 | 691 | 大多人听到这的第一反应是:“怎么可能不用 if 完成其他功能呢?”许多情况下通过使用多态(polymorphism)可以达到同样的目的。 692 | 693 | 第二个问题在于采用这种方式的原因是什么。答案是我们之前提到过的:保持函数功能的单一性。 694 | 695 | **反例**: 696 | ```javascript 697 | class Airplane { 698 | //... 699 | getCruisingAltitude() { 700 | switch (this.type) { 701 | case '777': 702 | return getMaxAltitude() - getPassengerCount(); 703 | case 'Air Force One': 704 | return getMaxAltitude(); 705 | case 'Cessna': 706 | return getMaxAltitude() - getFuelExpenditure(); 707 | } 708 | } 709 | } 710 | ``` 711 | 712 | **正例**: 713 | ```javascript 714 | class Airplane { 715 | //... 716 | } 717 | 718 | class Boeing777 extends Airplane { 719 | //... 720 | getCruisingAltitude() { 721 | return getMaxAltitude() - getPassengerCount(); 722 | } 723 | } 724 | 725 | class AirForceOne extends Airplane { 726 | //... 727 | getCruisingAltitude() { 728 | return getMaxAltitude(); 729 | } 730 | } 731 | 732 | class Cessna extends Airplane { 733 | //... 734 | getCruisingAltitude() { 735 | return getMaxAltitude() - getFuelExpenditure(); 736 | } 737 | } 738 | ``` 739 | 740 | ### 避免类型判断(part 1) 741 | JS 是弱类型语言,这意味着函数可接受任意类型的参数。 742 | 743 | 有时这会对你带来麻烦,你会对参数做一些类型判断。有许多方法可以避免这些情况。 744 | 745 | **反例**: 746 | ```javascript 747 | function travelToTexas(vehicle) { 748 | if (vehicle instanceof Bicycle) { 749 | vehicle.peddle(this.currentLocation, new Location('texas')); 750 | } else if (vehicle instanceof Car) { 751 | vehicle.drive(this.currentLocation, new Location('texas')); 752 | } 753 | } 754 | ``` 755 | 756 | **正例**: 757 | ```javascript 758 | function travelToTexas(vehicle) { 759 | vehicle.move(this.currentLocation, new Location('texas')); 760 | } 761 | ``` 762 | 763 | ### 避免类型判断(part 2) 764 | 如果需处理的数据为字符串,整型,数组等类型,无法使用多态并仍有必要对其进行类型检测时,可以考虑使用 TypeScript。 765 | 766 | **反例**: 767 | ```javascript 768 | function combine(val1, val2) { 769 | if (typeof val1 == "number" && typeof val2 == "number" || 770 | typeof val1 == "string" && typeof val2 == "string") { 771 | return val1 + val2; 772 | } else { 773 | throw new Error('Must be of type String or Number'); 774 | } 775 | } 776 | ``` 777 | 778 | **正例**: 779 | ```javascript 780 | function combine(val1, val2) { 781 | return val1 + val2; 782 | } 783 | ``` 784 | 785 | ### 避免过度优化 786 | 现代的浏览器在运行时会对代码自动进行优化。有时人为对代码进行优化可能是在浪费时间。 787 | 788 | [这里可以找到许多真正需要优化的地方](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers) 789 | 790 | **反例**: 791 | ```javascript 792 | 793 | // 这里使用变量len是因为在老式浏览器中, 794 | // 直接使用正例中的方式会导致每次循环均重复计算list.length的值, 795 | // 而在现代浏览器中会自动完成优化,这一行为是没有必要的 796 | for (var i = 0, len = list.length; i < len; i++) { 797 | // ... 798 | } 799 | ``` 800 | 801 | **正例**: 802 | ```javascript 803 | for (var i = 0; i < list.length; i++) { 804 | // ... 805 | } 806 | ``` 807 | 808 | ### 删除无效的代码 809 | 不再被调用的代码应及时删除。 810 | 811 | **反例**: 812 | ```javascript 813 | function oldRequestModule(url) { 814 | // ... 815 | } 816 | 817 | function newRequestModule(url) { 818 | // ... 819 | } 820 | 821 | var req = newRequestModule; 822 | inventoryTracker('apples', req, 'www.inventory-awesome.io'); 823 | 824 | ``` 825 | 826 | **正例**: 827 | ```javascript 828 | function newRequestModule(url) { 829 | // ... 830 | } 831 | 832 | var req = newRequestModule; 833 | inventoryTracker('apples', req, 'www.inventory-awesome.io'); 834 | ``` 835 | 836 | ## **对象和数据结构** 837 | ### 使用 getters 和 setters 838 | JS 没有接口或类型,因此实现这一模式是很困难的,因为我们并没有类似 `public` 和 `private` 的关键词。 839 | 840 | 然而,使用 getters 和 setters 获取对象的数据远比直接使用点操作符具有优势。为什么呢? 841 | 842 | 1. 当需要对获取的对象属性执行额外操作时。 843 | 2. 执行 `set` 时可以增加规则对要变量的合法性进行判断。 844 | 3. 封装了内部逻辑。 845 | 4. 在存取时可以方便的增加日志和错误处理。 846 | 5. 继承该类时可以重载默认行为。 847 | 6. 从服务器获取数据时可以进行懒加载。 848 | 849 | 850 | **反例**: 851 | ```javascript 852 | class BankAccount { 853 | constructor() { 854 | this.balance = 1000; 855 | } 856 | } 857 | 858 | let bankAccount = new BankAccount(); 859 | 860 | // Buy shoes... 861 | bankAccount.balance = bankAccount.balance - 100; 862 | ``` 863 | 864 | **正例**: 865 | ```javascript 866 | class BankAccount { 867 | constructor() { 868 | this.balance = 1000; 869 | } 870 | 871 | // It doesn't have to be prefixed with `get` or `set` to be a getter/setter 872 | withdraw(amount) { 873 | if (verifyAmountCanBeDeducted(amount)) { 874 | this.balance -= amount; 875 | } 876 | } 877 | } 878 | 879 | let bankAccount = new BankAccount(); 880 | 881 | // Buy shoes... 882 | bankAccount.withdraw(100); 883 | ``` 884 | 885 | 886 | ### 让对象拥有私有成员 887 | 可以通过闭包完成 888 | 889 | **反例**: 890 | ```javascript 891 | 892 | var Employee = function(name) { 893 | this.name = name; 894 | } 895 | 896 | Employee.prototype.getName = function() { 897 | return this.name; 898 | } 899 | 900 | var employee = new Employee('John Doe'); 901 | console.log('Employee name: ' + employee.getName()); // Employee name: John Doe 902 | delete employee.name; 903 | console.log('Employee name: ' + employee.getName()); // Employee name: undefined 904 | ``` 905 | 906 | **正例**: 907 | ```javascript 908 | var Employee = (function() { 909 | function Employee(name) { 910 | this.getName = function() { 911 | return name; 912 | }; 913 | } 914 | 915 | return Employee; 916 | }()); 917 | 918 | var employee = new Employee('John Doe'); 919 | console.log('Employee name: ' + employee.getName()); // Employee name: John Doe 920 | delete employee.name; 921 | console.log('Employee name: ' + employee.getName()); // Employee name: John Doe 922 | ``` 923 | 924 | 925 | ## **类** 926 | ### 单一职责原则 (SRP) 927 | 如《代码整洁之道》一书中所述,“修改一个类的理由不应该超过一个”。 928 | 929 | 将多个功能塞进一个类的想法很诱人,但这将导致你的类无法达到概念上的内聚,并经常不得不进行修改。 930 | 931 | 最小化对一个类需要修改的次数是非常有必要的。如果一个类具有太多太杂的功能,当你对其中一小部分进行修改时,将很难想象到这一修够对代码库中依赖该类的其他模块会带来什么样的影响。 932 | 933 | **反例**: 934 | ```javascript 935 | class UserSettings { 936 | constructor(user) { 937 | this.user = user; 938 | } 939 | 940 | changeSettings(settings) { 941 | if (this.verifyCredentials(user)) { 942 | // ... 943 | } 944 | } 945 | 946 | verifyCredentials(user) { 947 | // ... 948 | } 949 | } 950 | ``` 951 | 952 | **正例**: 953 | ```javascript 954 | class UserAuth { 955 | constructor(user) { 956 | this.user = user; 957 | } 958 | 959 | verifyCredentials() { 960 | // ... 961 | } 962 | } 963 | 964 | 965 | class UserSettings { 966 | constructor(user) { 967 | this.user = user; 968 | this.auth = new UserAuth(user) 969 | } 970 | 971 | changeSettings(settings) { 972 | if (this.auth.verifyCredentials()) { 973 | // ... 974 | } 975 | } 976 | } 977 | ``` 978 | 979 | ### 开/闭原则 (OCP) 980 | “代码实体(类,模块,函数等)应该易于扩展,难于修改。” 981 | 982 | 这一原则指的是我们应允许用户方便的扩展我们代码模块的功能,而不需要打开 js 文件源码手动对其进行修改。 983 | 984 | **反例**: 985 | ```javascript 986 | class AjaxRequester { 987 | constructor() { 988 | // What if we wanted another HTTP Method, like DELETE? We would have to 989 | // open this file up and modify this and put it in manually. 990 | this.HTTP_METHODS = ['POST', 'PUT', 'GET']; 991 | } 992 | 993 | get(url) { 994 | // ... 995 | } 996 | 997 | } 998 | ``` 999 | 1000 | **正例**: 1001 | ```javascript 1002 | class AjaxRequester { 1003 | constructor() { 1004 | this.HTTP_METHODS = ['POST', 'PUT', 'GET']; 1005 | } 1006 | 1007 | get(url) { 1008 | // ... 1009 | } 1010 | 1011 | addHTTPMethod(method) { 1012 | this.HTTP_METHODS.push(method); 1013 | } 1014 | } 1015 | ``` 1016 | 1017 | 1018 | ### 利斯科夫替代原则 (LSP) 1019 | “子类对象应该能够替换其超类对象被使用”。 1020 | 1021 | 也就是说,如果有一个父类和一个子类,当采用子类替换父类时不应该产生错误的结果。 1022 | 1023 | **反例**: 1024 | ```javascript 1025 | class Rectangle { 1026 | constructor() { 1027 | this.width = 0; 1028 | this.height = 0; 1029 | } 1030 | 1031 | setColor(color) { 1032 | // ... 1033 | } 1034 | 1035 | render(area) { 1036 | // ... 1037 | } 1038 | 1039 | setWidth(width) { 1040 | this.width = width; 1041 | } 1042 | 1043 | setHeight(height) { 1044 | this.height = height; 1045 | } 1046 | 1047 | getArea() { 1048 | return this.width * this.height; 1049 | } 1050 | } 1051 | 1052 | class Square extends Rectangle { 1053 | constructor() { 1054 | super(); 1055 | } 1056 | 1057 | setWidth(width) { 1058 | this.width = width; 1059 | this.height = width; 1060 | } 1061 | 1062 | setHeight(height) { 1063 | this.width = height; 1064 | this.height = height; 1065 | } 1066 | } 1067 | 1068 | function renderLargeRectangles(rectangles) { 1069 | rectangles.forEach((rectangle) => { 1070 | rectangle.setWidth(4); 1071 | rectangle.setHeight(5); 1072 | let area = rectangle.getArea(); // BAD: Will return 25 for Square. Should be 20. 1073 | rectangle.render(area); 1074 | }) 1075 | } 1076 | 1077 | let rectangles = [new Rectangle(), new Rectangle(), new Square()]; 1078 | renderLargeRectangles(rectangles); 1079 | ``` 1080 | 1081 | **正例**: 1082 | ```javascript 1083 | class Shape { 1084 | constructor() {} 1085 | 1086 | setColor(color) { 1087 | // ... 1088 | } 1089 | 1090 | render(area) { 1091 | // ... 1092 | } 1093 | } 1094 | 1095 | class Rectangle extends Shape { 1096 | constructor() { 1097 | super(); 1098 | this.width = 0; 1099 | this.height = 0; 1100 | } 1101 | 1102 | setWidth(width) { 1103 | this.width = width; 1104 | } 1105 | 1106 | setHeight(height) { 1107 | this.height = height; 1108 | } 1109 | 1110 | getArea() { 1111 | return this.width * this.height; 1112 | } 1113 | } 1114 | 1115 | class Square extends Shape { 1116 | constructor() { 1117 | super(); 1118 | this.length = 0; 1119 | } 1120 | 1121 | setLength(length) { 1122 | this.length = length; 1123 | } 1124 | 1125 | getArea() { 1126 | return this.length * this.length; 1127 | } 1128 | } 1129 | 1130 | function renderLargeShapes(shapes) { 1131 | shapes.forEach((shape) => { 1132 | switch (shape.constructor.name) { 1133 | case 'Square': 1134 | shape.setLength(5); 1135 | case 'Rectangle': 1136 | shape.setWidth(4); 1137 | shape.setHeight(5); 1138 | } 1139 | 1140 | let area = shape.getArea(); 1141 | shape.render(area); 1142 | }) 1143 | } 1144 | 1145 | let shapes = [new Rectangle(), new Rectangle(), new Square()]; 1146 | renderLargeShapes(shapes); 1147 | ``` 1148 | 1149 | ### 接口隔离原则 (ISP) 1150 | “客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。” 1151 | 1152 | 在 JS 中,当一个类需要许多参数设置才能生成一个对象时,或许大多时候不需要设置这么多的参数。此时减少对配置参数数量的需求是有益的。 1153 | 1154 | **反例**: 1155 | ```javascript 1156 | class DOMTraverser { 1157 | constructor(settings) { 1158 | this.settings = settings; 1159 | this.setup(); 1160 | } 1161 | 1162 | setup() { 1163 | this.rootNode = this.settings.rootNode; 1164 | this.animationModule.setup(); 1165 | } 1166 | 1167 | traverse() { 1168 | // ... 1169 | } 1170 | } 1171 | 1172 | let $ = new DOMTraverser({ 1173 | rootNode: document.getElementsByTagName('body'), 1174 | animationModule: function() {} // Most of the time, we won't need to animate when traversing. 1175 | // ... 1176 | }); 1177 | 1178 | ``` 1179 | 1180 | **正例**: 1181 | ```javascript 1182 | class DOMTraverser { 1183 | constructor(settings) { 1184 | this.settings = settings; 1185 | this.options = settings.options; 1186 | this.setup(); 1187 | } 1188 | 1189 | setup() { 1190 | this.rootNode = this.settings.rootNode; 1191 | this.setupOptions(); 1192 | } 1193 | 1194 | setupOptions() { 1195 | if (this.options.animationModule) { 1196 | // ... 1197 | } 1198 | } 1199 | 1200 | traverse() { 1201 | // ... 1202 | } 1203 | } 1204 | 1205 | let $ = new DOMTraverser({ 1206 | rootNode: document.getElementsByTagName('body'), 1207 | options: { 1208 | animationModule: function() {} 1209 | } 1210 | }); 1211 | ``` 1212 | 1213 | ### 依赖反转原则 (DIP) 1214 | 该原则有两个核心点: 1215 | 1. 高层模块不应该依赖于低层模块。他们都应该依赖于抽象接口。 1216 | 2. 抽象接口应该脱离具体实现,具体实现应该依赖于抽象接口。 1217 | 1218 | **反例**: 1219 | ```javascript 1220 | class InventoryTracker { 1221 | constructor(items) { 1222 | this.items = items; 1223 | 1224 | // BAD: We have created a dependency on a specific request implementation. 1225 | // We should just have requestItems depend on a request method: `request` 1226 | this.requester = new InventoryRequester(); 1227 | } 1228 | 1229 | requestItems() { 1230 | this.items.forEach((item) => { 1231 | this.requester.requestItem(item); 1232 | }); 1233 | } 1234 | } 1235 | 1236 | class InventoryRequester { 1237 | constructor() { 1238 | this.REQ_METHODS = ['HTTP']; 1239 | } 1240 | 1241 | requestItem(item) { 1242 | // ... 1243 | } 1244 | } 1245 | 1246 | let inventoryTracker = new InventoryTracker(['apples', 'bananas']); 1247 | inventoryTracker.requestItems(); 1248 | ``` 1249 | 1250 | **正例**: 1251 | ```javascript 1252 | class InventoryTracker { 1253 | constructor(items, requester) { 1254 | this.items = items; 1255 | this.requester = requester; 1256 | } 1257 | 1258 | requestItems() { 1259 | this.items.forEach((item) => { 1260 | this.requester.requestItem(item); 1261 | }); 1262 | } 1263 | } 1264 | 1265 | class InventoryRequesterV1 { 1266 | constructor() { 1267 | this.REQ_METHODS = ['HTTP']; 1268 | } 1269 | 1270 | requestItem(item) { 1271 | // ... 1272 | } 1273 | } 1274 | 1275 | class InventoryRequesterV2 { 1276 | constructor() { 1277 | this.REQ_METHODS = ['WS']; 1278 | } 1279 | 1280 | requestItem(item) { 1281 | // ... 1282 | } 1283 | } 1284 | 1285 | // By constructing our dependencies externally and injecting them, we can easily 1286 | // substitute our request module for a fancy new one that uses WebSockets. 1287 | let inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2()); 1288 | inventoryTracker.requestItems(); 1289 | ``` 1290 | 1291 | ### 使用 ES6 的 classes 而不是 ES5 的 Function 1292 | 典型的 ES5 的类(function)在继承、构造和方法定义方面可读性较差。 1293 | 1294 | 当需要继承时,优先选用 classes。 1295 | 1296 | 但是,当在需要更大更复杂的对象时,最好优先选择更小的 function 而非 classes。 1297 | 1298 | **反例**: 1299 | ```javascript 1300 | var Animal = function(age) { 1301 | if (!(this instanceof Animal)) { 1302 | throw new Error("Instantiate Animal with `new`"); 1303 | } 1304 | 1305 | this.age = age; 1306 | }; 1307 | 1308 | Animal.prototype.move = function() {}; 1309 | 1310 | var Mammal = function(age, furColor) { 1311 | if (!(this instanceof Mammal)) { 1312 | throw new Error("Instantiate Mammal with `new`"); 1313 | } 1314 | 1315 | Animal.call(this, age); 1316 | this.furColor = furColor; 1317 | }; 1318 | 1319 | Mammal.prototype = Object.create(Animal.prototype); 1320 | Mammal.prototype.constructor = Mammal; 1321 | Mammal.prototype.liveBirth = function() {}; 1322 | 1323 | var Human = function(age, furColor, languageSpoken) { 1324 | if (!(this instanceof Human)) { 1325 | throw new Error("Instantiate Human with `new`"); 1326 | } 1327 | 1328 | Mammal.call(this, age, furColor); 1329 | this.languageSpoken = languageSpoken; 1330 | }; 1331 | 1332 | Human.prototype = Object.create(Mammal.prototype); 1333 | Human.prototype.constructor = Human; 1334 | Human.prototype.speak = function() {}; 1335 | ``` 1336 | 1337 | **正例**: 1338 | ```javascript 1339 | class Animal { 1340 | constructor(age) { 1341 | this.age = age; 1342 | } 1343 | 1344 | move() {} 1345 | } 1346 | 1347 | class Mammal extends Animal { 1348 | constructor(age, furColor) { 1349 | super(age); 1350 | this.furColor = furColor; 1351 | } 1352 | 1353 | liveBirth() {} 1354 | } 1355 | 1356 | class Human extends Mammal { 1357 | constructor(age, furColor, languageSpoken) { 1358 | super(age, furColor); 1359 | this.languageSpoken = languageSpoken; 1360 | } 1361 | 1362 | speak() {} 1363 | } 1364 | ``` 1365 | 1366 | 1367 | ### 使用方法链 1368 | 这里我们的理解与《代码整洁之道》的建议有些不同。 1369 | 1370 | 有争论说方法链不够干净且违反了[德米特法则](https://en.wikipedia.org/wiki/Law_of_Demeter),也许这是对的,但这种方法在 JS 及许多库(如 JQuery)中显得非常实用。 1371 | 1372 | 因此,我认为在 JS 中使用方法链是非常合适的。在 class 的函数中返回 this,能够方便的将类需要执行的多个方法链接起来。 1373 | 1374 | **反例**: 1375 | ```javascript 1376 | class Car { 1377 | constructor() { 1378 | this.make = 'Honda'; 1379 | this.model = 'Accord'; 1380 | this.color = 'white'; 1381 | } 1382 | 1383 | setMake(make) { 1384 | this.name = name; 1385 | } 1386 | 1387 | setModel(model) { 1388 | this.model = model; 1389 | } 1390 | 1391 | setColor(color) { 1392 | this.color = color; 1393 | } 1394 | 1395 | save() { 1396 | console.log(this.make, this.model, this.color); 1397 | } 1398 | } 1399 | 1400 | let car = new Car(); 1401 | car.setColor('pink'); 1402 | car.setMake('Ford'); 1403 | car.setModel('F-150') 1404 | car.save(); 1405 | ``` 1406 | 1407 | **正例**: 1408 | ```javascript 1409 | class Car { 1410 | constructor() { 1411 | this.make = 'Honda'; 1412 | this.model = 'Accord'; 1413 | this.color = 'white'; 1414 | } 1415 | 1416 | setMake(make) { 1417 | this.name = name; 1418 | // NOTE: Returning this for chaining 1419 | return this; 1420 | } 1421 | 1422 | setModel(model) { 1423 | this.model = model; 1424 | // NOTE: Returning this for chaining 1425 | return this; 1426 | } 1427 | 1428 | setColor(color) { 1429 | this.color = color; 1430 | // NOTE: Returning this for chaining 1431 | return this; 1432 | } 1433 | 1434 | save() { 1435 | console.log(this.make, this.model, this.color); 1436 | } 1437 | } 1438 | 1439 | let car = new Car() 1440 | .setColor('pink') 1441 | .setMake('Ford') 1442 | .setModel('F-150') 1443 | .save(); 1444 | ``` 1445 | 1446 | ### 优先使用组合模式而非继承 1447 | 在著名的[设计模式](https://en.wikipedia.org/wiki/Design_Patterns)一书中提到,应多使用组合模式而非继承。 1448 | 1449 | 这么做有许多优点,在想要使用继承前,多想想能否通过组合模式满足需求吧。 1450 | 1451 | 那么,在什么时候继承具有更大的优势呢?这取决于你的具体需求,但大多情况下,可以遵守以下三点: 1452 | 1453 | 1. 继承关系表现为"是一个"而非"有一个"(如动物->人 和 用户->用户细节) 1454 | 2. 可以复用基类的代码("Human"可以看成是"All animal"的一种) 1455 | 3. 希望当基类改变时所有派生类都受到影响(如修改"all animals"移动时的卡路里消耗量) 1456 | 1457 | **反例**: 1458 | ```javascript 1459 | class Employee { 1460 | constructor(name, email) { 1461 | this.name = name; 1462 | this.email = email; 1463 | } 1464 | 1465 | // ... 1466 | } 1467 | 1468 | // Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee 1469 | class EmployeeTaxData extends Employee { 1470 | constructor(ssn, salary) { 1471 | super(); 1472 | this.ssn = ssn; 1473 | this.salary = salary; 1474 | } 1475 | 1476 | // ... 1477 | } 1478 | ``` 1479 | 1480 | **正例**: 1481 | ```javascript 1482 | class Employee { 1483 | constructor(name, email) { 1484 | this.name = name; 1485 | this.email = email; 1486 | 1487 | } 1488 | 1489 | setTaxData(ssn, salary) { 1490 | this.taxData = new EmployeeTaxData(ssn, salary); 1491 | } 1492 | // ... 1493 | } 1494 | 1495 | class EmployeeTaxData { 1496 | constructor(ssn, salary) { 1497 | this.ssn = ssn; 1498 | this.salary = salary; 1499 | } 1500 | 1501 | // ... 1502 | } 1503 | ``` 1504 | 1505 | ## **测试** 1506 | [一些好的覆盖工具](http://gotwarlost.github.io/istanbul/)。 1507 | 1508 | [一些好的 JS 测试框架](http://jstherightway.org/#testing-tools)。 1509 | 1510 | ### 单一的测试每个概念 1511 | 1512 | **反例**: 1513 | ```javascript 1514 | const assert = require('assert'); 1515 | 1516 | describe('MakeMomentJSGreatAgain', function() { 1517 | it('handles date boundaries', function() { 1518 | let date; 1519 | 1520 | date = new MakeMomentJSGreatAgain('1/1/2015'); 1521 | date.addDays(30); 1522 | date.shouldEqual('1/31/2015'); 1523 | 1524 | date = new MakeMomentJSGreatAgain('2/1/2016'); 1525 | date.addDays(28); 1526 | assert.equal('02/29/2016', date); 1527 | 1528 | date = new MakeMomentJSGreatAgain('2/1/2015'); 1529 | date.addDays(28); 1530 | assert.equal('03/01/2015', date); 1531 | }); 1532 | }); 1533 | ``` 1534 | 1535 | **正例**: 1536 | ```javascript 1537 | const assert = require('assert'); 1538 | 1539 | describe('MakeMomentJSGreatAgain', function() { 1540 | it('handles 30-day months', function() { 1541 | let date = new MakeMomentJSGreatAgain('1/1/2015'); 1542 | date.addDays(30); 1543 | date.shouldEqual('1/31/2015'); 1544 | }); 1545 | 1546 | it('handles leap year', function() { 1547 | let date = new MakeMomentJSGreatAgain('2/1/2016'); 1548 | date.addDays(28); 1549 | assert.equal('02/29/2016', date); 1550 | }); 1551 | 1552 | it('handles non-leap year', function() { 1553 | let date = new MakeMomentJSGreatAgain('2/1/2015'); 1554 | date.addDays(28); 1555 | assert.equal('03/01/2015', date); 1556 | }); 1557 | }); 1558 | ``` 1559 | 1560 | ## **并发** 1561 | ### 用 Promises 替代回调 1562 | 回调不够整洁并会造成大量的嵌套。ES6 内嵌了 Promises,使用它吧。 1563 | 1564 | **反例**: 1565 | ```javascript 1566 | require('request').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', function(err, response) { 1567 | if (err) { 1568 | console.error(err); 1569 | } 1570 | else { 1571 | require('fs').writeFile('article.html', response.body, function(err) { 1572 | if (err) { 1573 | console.error(err); 1574 | } else { 1575 | console.log('File written'); 1576 | } 1577 | }) 1578 | } 1579 | }) 1580 | 1581 | ``` 1582 | 1583 | **正例**: 1584 | ```javascript 1585 | require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin') 1586 | .then(function(response) { 1587 | return require('fs-promise').writeFile('article.html', response); 1588 | }) 1589 | .then(function() { 1590 | console.log('File written'); 1591 | }) 1592 | .catch(function(err) { 1593 | console.error(err); 1594 | }) 1595 | 1596 | ``` 1597 | 1598 | ### Async/Await 是较 Promises 更好的选择 1599 | Promises 是较回调而言更好的一种选择,但 ES7 中的 async 和 await 更胜过 Promises。 1600 | 1601 | 在能使用 ES7 特性的情况下可以尽量使用他们替代 Promises。 1602 | 1603 | **反例**: 1604 | ```javascript 1605 | require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin') 1606 | .then(function(response) { 1607 | return require('fs-promise').writeFile('article.html', response); 1608 | }) 1609 | .then(function() { 1610 | console.log('File written'); 1611 | }) 1612 | .catch(function(err) { 1613 | console.error(err); 1614 | }) 1615 | 1616 | ``` 1617 | 1618 | **正例**: 1619 | ```javascript 1620 | async function getCleanCodeArticle() { 1621 | try { 1622 | var request = await require('request-promise') 1623 | var response = await request.get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin'); 1624 | var fileHandle = await require('fs-promise'); 1625 | 1626 | await fileHandle.writeFile('article.html', response); 1627 | console.log('File written'); 1628 | } catch(err) { 1629 | console.log(err); 1630 | } 1631 | } 1632 | ``` 1633 | 1634 | 1635 | ## **错误处理** 1636 | 错误抛出是个好东西!这使得你能够成功定位运行状态中的程序产生错误的位置。 1637 | 1638 | ### 别忘了捕获错误 1639 | 对捕获的错误不做任何处理是没有意义的。 1640 | 1641 | 代码中 `try/catch` 的意味着你认为这里可能出现一些错误,你应该对这些可能的错误存在相应的处理方案。 1642 | 1643 | **反例**: 1644 | ```javascript 1645 | try { 1646 | functionThatMightThrow(); 1647 | } catch (error) { 1648 | console.log(error); 1649 | } 1650 | ``` 1651 | 1652 | **正例**: 1653 | ```javascript 1654 | try { 1655 | functionThatMightThrow(); 1656 | } catch (error) { 1657 | // One option (more noisy than console.log): 1658 | console.error(error); 1659 | // Another option: 1660 | notifyUserOfError(error); 1661 | // Another option: 1662 | reportErrorToService(error); 1663 | // OR do all three! 1664 | } 1665 | ``` 1666 | 1667 | ### 不要忽略被拒绝的 promises 1668 | 理由同 `try/catch`。 1669 | 1670 | **反例**: 1671 | ```javascript 1672 | getdata() 1673 | .then(data => { 1674 | functionThatMightThrow(data); 1675 | }) 1676 | .catch(error => { 1677 | console.log(error); 1678 | }); 1679 | ``` 1680 | 1681 | **正例**: 1682 | ```javascript 1683 | getdata() 1684 | .then(data => { 1685 | functionThatMightThrow(data); 1686 | }) 1687 | .catch(error => { 1688 | // One option (more noisy than console.log): 1689 | console.error(error); 1690 | // Another option: 1691 | notifyUserOfError(error); 1692 | // Another option: 1693 | reportErrorToService(error); 1694 | // OR do all three! 1695 | }); 1696 | ``` 1697 | 1698 | 1699 | 1700 | ## **格式化** 1701 | 格式化是一件主观的事。如同这里的许多规则一样,这里并没有一定/立刻需要遵守的规则。可以在[这里](http://standardjs.com/rules.html)完成格式的自动化。 1702 | 1703 | ### 大小写一致 1704 | JS 是弱类型语言,合理的采用大小写可以告诉你关于变量/函数等的许多消息。 1705 | 1706 | 这些规则是主观定义的,团队可以根据喜欢进行选择。重点在于无论选择何种风格,都需要注意保持一致性。 1707 | 1708 | **反例**: 1709 | ```javascript 1710 | var DAYS_IN_WEEK = 7; 1711 | var daysInMonth = 30; 1712 | 1713 | var songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude']; 1714 | var Artists = ['ACDC', 'Led Zeppelin', 'The Beatles']; 1715 | 1716 | function eraseDatabase() {} 1717 | function restore_database() {} 1718 | 1719 | class animal {} 1720 | class Alpaca {} 1721 | ``` 1722 | 1723 | **正例**: 1724 | ```javascript 1725 | var DAYS_IN_WEEK = 7; 1726 | var DAYS_IN_MONTH = 30; 1727 | 1728 | var songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude']; 1729 | var artists = ['ACDC', 'Led Zeppelin', 'The Beatles']; 1730 | 1731 | function eraseDatabase() {} 1732 | function restoreDatabase() {} 1733 | 1734 | class Animal {} 1735 | class Alpaca {} 1736 | ``` 1737 | 1738 | 1739 | ### 调用函数的函数和被调函数应放在较近的位置 1740 | 当函数间存在相互调用的情况时,应将两者置于较近的位置。 1741 | 1742 | 理想情况下,应将调用其他函数的函数写在被调用函数的上方。 1743 | 1744 | **反例**: 1745 | ```javascript 1746 | class PerformanceReview { 1747 | constructor(employee) { 1748 | this.employee = employee; 1749 | } 1750 | 1751 | lookupPeers() { 1752 | return db.lookup(this.employee, 'peers'); 1753 | } 1754 | 1755 | lookupMananger() { 1756 | return db.lookup(this.employee, 'manager'); 1757 | } 1758 | 1759 | getPeerReviews() { 1760 | let peers = this.lookupPeers(); 1761 | // ... 1762 | } 1763 | 1764 | perfReview() { 1765 | getPeerReviews(); 1766 | getManagerReview(); 1767 | getSelfReview(); 1768 | } 1769 | 1770 | getManagerReview() { 1771 | let manager = this.lookupManager(); 1772 | } 1773 | 1774 | getSelfReview() { 1775 | // ... 1776 | } 1777 | } 1778 | 1779 | let review = new PerformanceReview(user); 1780 | review.perfReview(); 1781 | ``` 1782 | 1783 | **正例**: 1784 | ```javascript 1785 | class PerformanceReview { 1786 | constructor(employee) { 1787 | this.employee = employee; 1788 | } 1789 | 1790 | perfReview() { 1791 | getPeerReviews(); 1792 | getManagerReview(); 1793 | getSelfReview(); 1794 | } 1795 | 1796 | getPeerReviews() { 1797 | let peers = this.lookupPeers(); 1798 | // ... 1799 | } 1800 | 1801 | lookupPeers() { 1802 | return db.lookup(this.employee, 'peers'); 1803 | } 1804 | 1805 | getManagerReview() { 1806 | let manager = this.lookupManager(); 1807 | } 1808 | 1809 | lookupMananger() { 1810 | return db.lookup(this.employee, 'manager'); 1811 | } 1812 | 1813 | getSelfReview() { 1814 | // ... 1815 | } 1816 | } 1817 | 1818 | let review = new PerformanceReview(employee); 1819 | review.perfReview(); 1820 | ``` 1821 | 1822 | 1823 | ## **注释** 1824 | ### 只对存在一定业务逻辑复杂性的代码进行注释 1825 | 注释并不是必须的,好的代码是能够让人一目了然,不用过多无谓的注释。 1826 | 1827 | **反例**: 1828 | ```javascript 1829 | function hashIt(data) { 1830 | // The hash 1831 | var hash = 0; 1832 | 1833 | // Length of string 1834 | var length = data.length; 1835 | 1836 | // Loop through every character in data 1837 | for (var i = 0; i < length; i++) { 1838 | // Get character code. 1839 | var char = data.charCodeAt(i); 1840 | // Make the hash 1841 | hash = ((hash << 5) - hash) + char; 1842 | // Convert to 32-bit integer 1843 | hash = hash & hash; 1844 | } 1845 | } 1846 | ``` 1847 | 1848 | **正例**: 1849 | ```javascript 1850 | 1851 | function hashIt(data) { 1852 | var hash = 0; 1853 | var length = data.length; 1854 | 1855 | for (var i = 0; i < length; i++) { 1856 | var char = data.charCodeAt(i); 1857 | hash = ((hash << 5) - hash) + char; 1858 | 1859 | // Convert to 32-bit integer 1860 | hash = hash & hash; 1861 | } 1862 | } 1863 | 1864 | ``` 1865 | 1866 | ### 不要在代码库中遗留被注释掉的代码 1867 | 版本控制的存在是有原因的。让旧代码存在于你的 history 里吧。 1868 | 1869 | **反例**: 1870 | ```javascript 1871 | doStuff(); 1872 | // doOtherStuff(); 1873 | // doSomeMoreStuff(); 1874 | // doSoMuchStuff(); 1875 | ``` 1876 | 1877 | **正例**: 1878 | ```javascript 1879 | doStuff(); 1880 | ``` 1881 | 1882 | ### 避免位置标记 1883 | 这些东西通常只能代码麻烦,采用适当的缩进就可以了。 1884 | 1885 | **反例**: 1886 | ```javascript 1887 | //////////////////////////////////////////////////////////////////////////////// 1888 | // Scope Model Instantiation 1889 | //////////////////////////////////////////////////////////////////////////////// 1890 | let $scope.model = { 1891 | menu: 'foo', 1892 | nav: 'bar' 1893 | }; 1894 | 1895 | //////////////////////////////////////////////////////////////////////////////// 1896 | // Action setup 1897 | //////////////////////////////////////////////////////////////////////////////// 1898 | let actions = function() { 1899 | // ... 1900 | } 1901 | ``` 1902 | 1903 | **正例**: 1904 | ```javascript 1905 | let $scope.model = { 1906 | menu: 'foo', 1907 | nav: 'bar' 1908 | }; 1909 | 1910 | let actions = function() { 1911 | // ... 1912 | } 1913 | ``` 1914 | -------------------------------------------------------------------------------- /docs/guide/naming/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarDepth: 2 3 | --- 4 | 5 | # 命名规范 6 | 7 | ## 项目命名 8 | 9 | 全部采用小写方式, 以下划线分隔。 10 | 11 | > 例:my_project_name 12 | 13 | ## 目录命名 14 | 15 | 参照项目命名规则; 16 | 17 | 有复数结构时,要采用复数命名法。 18 | 19 | > 例:scripts, styles, images, data_models 20 | 21 | ## 目录命名 22 | 参照项目命名规则; 23 | 24 | 有复数结构时,要采用复数命名法。 25 | 26 | > 例:scripts, styles, images, data_models 27 | 28 | ## CSS, SCSS文件命名 29 | 30 | 参照项目命名规则。 31 | 32 | > 例:retina_sprites.scss 33 | 34 | ## HTML文件命名 35 | 36 | 参照项目命名规则。 37 | 38 | > 例:error_report.html 39 | -------------------------------------------------------------------------------- /docs/guide/react/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarDepth: 2 3 | --- 4 | 5 | # NCUHOME React 代码风格指南 6 | 7 | 8 | ## [react/jsx-curly-brace-presence](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-brace-presence.md) 9 | 10 | 禁止 jsx 中使用无用的引号 11 | 12 | ```javascript 13 | // bad 14 | {'Hello World'}; 15 | 16 | // good 17 | Hello World; 18 | ``` 19 | 20 | ## [react/jsx-fragments](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-fragments.md) 21 | 22 | 必须使用 <></> 而不是 React.Fragment 23 | 24 | ```javascript 25 | // bad 26 | ; 27 | 28 | // good 29 | <>; 30 | 31 |
32 | {[1, 2, 3].map((value) => ( 33 | {value} 34 | ))} 35 |
; 36 | ``` 37 | 38 | > <> 不需要额外引入 Fragment 组件 39 | 40 | ## [react/jsx-key](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-key.md) 41 | 42 | 数组中的 jsx 必须有 key 43 | 44 | ```javascript 45 | // bad 46 |
    47 | {[1, 2, 3].map((value) => ( 48 |
  • {value}
  • 49 | ))} 50 |
; 51 |
52 | {[1, 2, 3].map((value) => ( 53 | <>{value} 54 | ))} 55 |
; 56 | 57 | // good 58 |
    59 | {[1, 2, 3].map((value) => ( 60 |
  • {value}
  • 61 | ))} 62 |
; 63 |
64 | {[1, 2, 3].map((value) => ( 65 | {value} 66 | ))} 67 |
; 68 | ``` 69 | 70 | ## [react/jsx-no-comment-textnodes](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-comment-textnodes.md) 71 | 72 | 禁止在 jsx 中使用像注释的字符串 73 | 74 | ```javascript 75 | // bad 76 |
// empty div
; 77 |
/* empty div */
; 78 | 79 | 80 | // good 81 |
{/* empty div */}
; 82 |
; 83 | ``` 84 | 85 | ## [react/jsx-no-duplicate-props](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-duplicate-props.md) 86 | 87 | 禁止出现重复的 props 88 | 89 | ```javascript 90 | // bad 91 | ; 92 | 93 | // good 94 | ; 95 | ``` 96 | 97 | ## [react/jsx-no-undef](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md) 98 | 99 | 禁止使用未定义的组件 100 | 101 | ```javascript 102 | // bad 103 | ; 104 | 105 | // good 106 | import Foo from './Foo'; 107 | ; 108 | ``` 109 | 110 | ## [react/jsx-pascal-case](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md) 111 | 112 | 禁止使用 pascal 格式的组件 113 | 114 | ```javascript 115 | // bad 116 | ; 117 | ; 118 | 119 | // good 120 | ; 121 | ; 122 | ``` 123 | 124 | ## [react/jsx-uses-react](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md) 125 | 126 | 修复 React 被误报为未使用的变量的问题(仅在开启 no-unused-vars 时有效) 127 | 128 | ```javascript 129 | // bad 130 | /* eslint no-unused-vars: "error", react/jsx-uses-react: "off" */ 131 | import React from 'react'; 132 | 133 |
; 134 | 135 | // good 136 | /* eslint no-unused-vars: "error" */ 137 | import React from 'react'; 138 | 139 |
; 140 | ``` 141 | 142 | ## [react/jsx-uses-vars](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md) 143 | 144 | 已定义的组件必须使用 145 | 146 | ```javascript 147 | // bad 148 | /* eslint no-unused-vars: "error" */ 149 | import Foo from './Foo'; 150 | 151 | // good 152 | /* eslint no-unused-vars: "error" */ 153 | import Foo from './Foo'; 154 | 155 | ; 156 | ``` 157 | 158 | ## [react/no-children-prop](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-children-prop.md) 159 | 160 | 禁止将 children 作为一个 prop 161 | 162 | ```javascript 163 | // bad 164 |
; 165 | } />; 166 | 167 | // good 168 |
Hello World
; 169 | 170 | 171 | ; 172 | ``` 173 | 174 | ## [react/no-danger-with-children](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger-with-children.md) 175 | 176 | 禁止在使用了 dangerouslySetInnerHTML 的组件内添加 children 177 | 178 | ```javascript 179 | // bad 180 |
Hello World
; 181 | 182 | // good 183 |
; 184 |
Hello World
; 185 | ``` 186 | 187 | ## [react/no-deprecated](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-deprecated.md) 188 | 189 | 禁止使用已废弃的 api 190 | 191 | ```javascript 192 | // bad 193 | React.render(, app); 194 | 195 | class Foo extends React.Component { 196 | componentWillMount() {} 197 | componentWillReceiveProps() {} 198 | componentWillUpdate() {} 199 | } 200 | 201 | // good 202 | ReactDOM.render(, app); 203 | 204 | class Foo extends React.Component {} 205 | ``` 206 | 207 | ## [react/no-did-update-set-state](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md) 208 | 209 | 禁止在 componentDidUpdate 里使用 setState 210 | 211 | ```javascript 212 | // bad 213 | class Foo extends React.Component { 214 | componentDidUpdate() { 215 | this.setState(); 216 | } 217 | } 218 | 219 | // good 220 | class Foo extends React.Component { 221 | componentDidUpdate() {} 222 | } 223 | ``` 224 | 225 | ## [react/no-direct-mutation-state](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-direct-mutation-state.md) 226 | 227 | 禁止直接修改 this.state 228 | 229 | ```javascript 230 | // bad 231 | class Foo extends React.Component { 232 | componentDidMount() { 233 | this.state.name = 'foo'; 234 | } 235 | } 236 | 237 | // good 238 | class Foo extends React.Component { 239 | constructor() { 240 | this.state = { 241 | name: 'foo' 242 | }; 243 | } 244 | } 245 | ``` 246 | 247 | ## [react/no-find-dom-node](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-find-dom-node.md) 248 | 249 | 禁止使用 findDOMNode 250 | 251 | ```javascript 252 | // bad 253 | class Foo extends React.Component { 254 | componentDidMount() { 255 | const root = findDOMNode(this); 256 | } 257 | render() { 258 | return
; 259 | } 260 | } 261 | 262 | // good 263 | class Foo extends React.Component { 264 | constructor() { 265 | this.myRef = React.createRef(); 266 | } 267 | render() { 268 | return
; 269 | } 270 | } 271 | ``` 272 | 273 | ## [react/no-is-mounted](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-is-mounted.md) 274 | 275 | 禁止使用 isMounted 276 | 277 | ```javascript 278 | // bad 279 | class Foo extends React.Component { 280 | updateName() { 281 | if (this.isMounted()) { 282 | this.setState({}); 283 | } 284 | } 285 | } 286 | 287 | // good 288 | class Foo extends React.Component { 289 | updateName() { 290 | if (this._isMounted) { 291 | this.setState({}); 292 | } 293 | } 294 | } 295 | ``` 296 | 297 | > 它是已废弃的语法 298 | 299 | ## [react/no-redundant-should-component-update](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-redundant-should-component-update.md) 300 | 301 | 禁止在 React.PureComponent 中使用 shouldComponentUpdate 302 | 303 | ```javascript 304 | // bad 305 | class Foo extends React.PureComponent { 306 | shouldComponentUpdate() {} 307 | } 308 | 309 | // good 310 | class Foo extends React.Component { 311 | shouldComponentUpdate() {} 312 | } 313 | ``` 314 | 315 | ## [react/no-render-return-value](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-render-return-value.md) 316 | 317 | 禁止使用 ReactDOM.render 的返回值 318 | 319 | ```javascript 320 | // bad 321 | const app = ReactDOM.render(, document.getElementById('app')); 322 | 323 | // good 324 | ReactDOM.render(, document.getElementById('app')); 325 | ``` 326 | 327 | ## [react/no-string-refs](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-string-refs.md) 328 | 329 | 禁止使用字符串 ref 330 | 331 | ```javascript 332 | // bad 333 | class Foo { 334 | componentDidMount() { 335 | console.log(this.refs.foo); 336 | } 337 | render() { 338 | return
; 339 | } 340 | } 341 | 342 | // good 343 | import { useRef, useEffect } from 'react'; 344 | 345 | function Foo() { 346 | const foo = useRef(null); 347 | useEffect(() => { 348 | console.log(foo); 349 | }); 350 | return
; 351 | } 352 | ``` 353 | 354 | ## [react/no-this-in-sfc](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-this-in-sfc.md) 355 | 356 | 禁止在函数组件中使用 this 357 | 358 | ```javascript 359 | // bad 360 | function Foo() { 361 | return
{this.props.foo}
; 362 | } 363 | 364 | // good 365 | function Foo(props) { 366 | return
{props.foo}
; 367 | } 368 | ``` 369 | 370 | ## [react/no-typos](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-typos.md) 371 | 372 | 禁止组件的属性或生命周期大小写错误 373 | 374 | ```javascript 375 | // bad 376 | class Foo extends React.Component { 377 | static defaultprops = {}; 378 | componentdidupdate() {} 379 | } 380 | 381 | // good 382 | class Foo extends React.Component { 383 | static defaultProps = {}; 384 | componentDidUpdate() {} 385 | } 386 | ``` 387 | 388 | ## [react/no-unescaped-entities](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unescaped-entities.md) 389 | 390 | 禁止在组件的内部存在未转义的 >, ", ' 或 } 391 | 392 | ```javascript 393 | // bad 394 | Hel>lo; 395 | 396 | // good 397 | Hel&gt;lo; 398 | ``` 399 | 400 | ## [react/no-unknown-property](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md) 401 | 402 | 禁止出现 HTML 中的属性,如 class 403 | 404 | ```javascript 405 | // bad 406 |
; 407 | 408 | // good 409 |
; 410 | ``` 411 | 412 | ## [react/no-unsafe](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unsafe.md) 413 | 414 | 禁止使用不安全的生命周期方法 componentWillMount, componentWillReceiveProps, componentWillUpdate 415 | 416 | ```javascript 417 | // bad 418 | class Foo extends React.Component { 419 | componentWillMount() {} 420 | UNSAFE_componentWillMount() {} 421 | } 422 | 423 | // good 424 | class Foo extends React.Component {} 425 | ``` 426 | 427 | ## [react/prefer-es6-class](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-es6-class.md) 428 | 429 | 必须使用 Class 的形式创建组件 430 | 431 | ```javascript 432 | // bad 433 | const Foo = createReactClass({ 434 | render() { 435 | return
; 436 | } 437 | }); 438 | 439 | // good 440 | class Foo extends React.Component { 441 | render() { 442 | return
; 443 | } 444 | } 445 | ``` 446 | 447 | ## [react/require-render-return](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/require-render-return.md) 448 | 449 | render 方法中必须有返回值 450 | 451 | ```javascript 452 | // bad 453 | class Foo extends React.Component { 454 | render() { 455 |
; 456 | } 457 | } 458 | 459 | // good 460 | class Foo extends React.Component { 461 | render() { 462 | return
; 463 | } 464 | } 465 | ``` 466 | 467 | ## [react/self-closing-comp](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md) 468 | 469 | 组件内没有 children 时,必须使用自闭和写法 470 | 471 | ```javascript 472 | // bad 473 | ; 474 |
; 475 | 476 | // good 477 | Not empty; 478 |
Not empty
; 479 | ; 480 |
; 481 | ``` 482 | 483 | ## [react/sort-comp](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md) 484 | 485 | 组件内方法必须按照一定规则排序 486 | 487 | ```javascript 488 | // bad 489 | class Foo extends React.Component { 490 | render() { 491 | return
; 492 | } 493 | componentWillUnmount() {} 494 | componentDidMount() {} 495 | constructor() {} 496 | static defaultProps = {}; 497 | } 498 | 499 | // good 500 | class Foo extends React.Component { 501 | static defaultProps = {}; 502 | constructor() {} 503 | componentDidMount() {} 504 | componentWillUnmount() {} 505 | render() { 506 | return
; 507 | } 508 | } 509 | ``` 510 | 511 | ## [react/static-property-placement](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/static-property-placement.md) 512 | 513 | 类的静态属性必须使用 static 关键字定义 514 | 515 | ```javascript 516 | // bad 517 | class Foo extends React.Component {} 518 | Foo.defaultProps = {}; 519 | 520 | // good 521 | class Foo extends React.Component { 522 | static defaultProps = {}; 523 | } 524 | ``` 525 | 526 | ## [react/style-prop-object](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/style-prop-object.md) 527 | 528 | style 属性的取值必须是 object 529 | 530 | ```javascript 531 | // bad 532 |
; 533 | 534 | // good 535 |
; 536 | ``` 537 | 538 | ## [react/void-dom-elements-no-children](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/void-dom-elements-no-children.md) 539 | 540 | img, br 标签中禁止有 children 541 | 542 | ```javascript 543 | // bad 544 | foo; 545 |
bar
; 546 | 547 | // good 548 | ; 549 |
; 550 |
foo
; 551 | ``` -------------------------------------------------------------------------------- /docs/guide/ts/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarDepth: 2 3 | --- 4 | 5 | # NCUHOME TypeScript 代码风格指南 6 | 7 | 8 | 9 | ## [@typescript-eslint/adjacent-overload-signatures](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/adjacent-overload-signatures.md) 10 | 11 | 重载的函数必须写在一起 12 | 13 | ```javascript 14 | // bad 15 | declare namespace NSFoo1 { 16 | export function foo(s: string): void; 17 | export function foo(n: number): void; 18 | export function bar(): void; 19 | export function foo(sn: string | number): void; 20 | } 21 | 22 | type TypeFoo1 = { 23 | foo(s: string): void; 24 | foo(n: number): void; 25 | bar(): void; 26 | foo(sn: string | number): void; 27 | }; 28 | 29 | interface IFoo1 { 30 | foo(s: string): void; 31 | foo(n: number): void; 32 | bar(): void; 33 | foo(sn: string | number): void; 34 | } 35 | 36 | // good 37 | declare namespace NSFoo2 { 38 | export function foo(s: string): void; 39 | export function foo(n: number): void; 40 | export function foo(sn: string | number): void; 41 | export function bar(): void; 42 | } 43 | 44 | type TypeFoo2 = { 45 | foo(s: string): void; 46 | foo(n: number): void; 47 | foo(sn: string | number): void; 48 | bar(): void; 49 | }; 50 | 51 | interface IFoo2 { 52 | foo(s: string): void; 53 | foo(n: number): void; 54 | foo(sn: string | number): void; 55 | bar(): void; 56 | } 57 | ``` 58 | 59 | > 增加可读性 60 | 61 | ## [@typescript-eslint/class-name-casing](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/class-name-casing.md) 62 | 63 | 类名与接口名必须为驼峰式 64 | 65 | ```javascript 66 | // bad 67 | class Invalid_Class_Name {} 68 | 69 | interface invalidInterface {} 70 | 71 | // good 72 | class ValidClassName {} 73 | 74 | interface ValidInterface {} 75 | ``` 76 | 77 | ## [@typescript-eslint/consistent-type-assertions](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-assertions.md) 78 | 79 | 类型断言必须使用 as Type,禁止使用 <Type>,禁止对对象字面量进行类型断言(断言成 any 是允许的) 80 | 81 | ```javascript 82 | // bad 83 | let bar1: string | number; 84 | const foo1 = <string>bar1; 85 | 86 | const baz1 = { 87 | bar: 1 88 | } as object; 89 | 90 | // good 91 | let bar2: string | number; 92 | const foo2 = bar2 as string; 93 | 94 | const baz2 = { 95 | bar: 1 96 | } as any; 97 | ``` 98 | 99 | > <Type> 容易被理解为 jsx 100 | 101 | ## [@typescript-eslint/consistent-type-definitions](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-definitions.md) 102 | 103 | 优先使用 interface 而不是 type 104 | 105 | ```javascript 106 | // bad 107 | type Foo1 = { 108 | foo: string; 109 | }; 110 | 111 | // good 112 | interface Foo2 { 113 | foo: string; 114 | } 115 | ``` 116 | 117 | > interface 可以 implement, extend 和 merge 118 | 119 | ## [@typescript-eslint/explicit-member-accessibility](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md) 120 | 121 | 必须设置类的成员的可访问性 122 | 123 | ```javascript 124 | // bad 125 | class Foo2 { 126 | static foo = 'foo'; 127 | static getFoo() { 128 | return Foo2.foo; 129 | } 130 | constructor() {} 131 | bar = 'bar'; 132 | getBar() {} 133 | get baz() { 134 | return 'baz'; 135 | } 136 | set baz(value) { 137 | console.log(value); 138 | } 139 | } 140 | 141 | // good 142 | class Foo2 { 143 | private static foo = 'foo'; 144 | public static getFoo() { 145 | return Foo2.foo; 146 | } 147 | public constructor() {} 148 | protected bar = 'bar'; 149 | public getBar() {} 150 | public get baz() { 151 | return 'baz'; 152 | } 153 | public set baz(value) { 154 | console.log(value); 155 | } 156 | } 157 | ``` 158 | 159 | > 将不需要公开的成员设为私有的,可以增强代码的可理解性,对文档输出也很友好 160 | 161 | ## [@typescript-eslint/member-ordering](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-ordering.md) 162 | 163 | 指定类成员的排序规则 164 | 165 | ```javascript 166 | // bad 167 | class Foo1 { 168 | private getBar3() { 169 | return this.bar3; 170 | } 171 | protected getBar2() {} 172 | public getBar1() {} 173 | public constructor() { 174 | console.log(Foo1.getFoo3()); 175 | console.log(this.getBar3()); 176 | } 177 | private bar3 = 'bar3'; 178 | protected bar2 = 'bar2'; 179 | public bar1 = 'bar1'; 180 | private static getFoo3() { 181 | return Foo1.foo3; 182 | } 183 | protected static getFoo2() {} 184 | public static getFoo1() {} 185 | private static foo3 = 'foo3'; 186 | protected static foo2 = 'foo2'; 187 | public static foo1 = 'foo1'; 188 | } 189 | 190 | // good 191 | class Foo2 { 192 | public static foo1 = 'foo1'; 193 | protected static foo2 = 'foo2'; 194 | private static foo3 = 'foo3'; 195 | public static getFoo1() {} 196 | protected static getFoo2() {} 197 | private static getFoo3() { 198 | return Foo2.foo3; 199 | } 200 | public bar1 = 'bar1'; 201 | protected bar2 = 'bar2'; 202 | private bar3 = 'bar3'; 203 | public constructor() { 204 | console.log(Foo2.getFoo3()); 205 | console.log(this.getBar3()); 206 | } 207 | public getBar1() {} 208 | protected getBar2() {} 209 | private getBar3() { 210 | return this.bar3; 211 | } 212 | } 213 | ``` 214 | ::: tip 215 | 优先级: 216 | 1. static > instance 217 | 2. field > constructor > method 218 | 3. public > protected > private 219 | ::: 220 | ## [@typescript-eslint/no-empty-interface](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-empty-interface.md) 221 | 222 | 禁止定义空的接口 223 | 224 | ```javascript 225 | // bad 226 | interface Foo1 {} 227 | 228 | // good 229 | interface Foo2 { 230 | foo: string; 231 | } 232 | ``` 233 | 234 | ## [@typescript-eslint/no-inferrable-types](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-inferrable-types.md) 235 | 236 | 禁止给一个初始化时直接赋值为 number, string 的变量显式的声明类型 237 | 238 | ```javascript 239 | // bad 240 | const foo1: number = 1; 241 | const bar1: string = ''; 242 | 243 | // good 244 | const foo2 = 1; 245 | const bar2 = ''; 246 | ``` 247 | 248 | > 可以简化代码 249 | 250 | ## [@typescript-eslint/no-namespace](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-namespace.md) 251 | 252 | 禁止使用 namespace 来定义命名空间 253 | 254 | ```javascript 255 | // bad 256 | namespace foo1 {} 257 | 258 | // good 259 | declare namespace foo1 {} 260 | ``` 261 | 262 | > 使用 es6 引入模块,才是更标准的方式。 263 | 但是允许使用 declare namespace ... {} 来定义外部命名空间 264 | 265 | ## [@typescript-eslint/no-parameter-properties](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-parameter-properties.md) 266 | 267 | 禁止给类的构造函数的参数添加修饰符 268 | 269 | ```javascript 270 | // bad 271 | class Foo1 { 272 | constructor(private name: string) {} 273 | } 274 | 275 | // good 276 | class Foo2 { 277 | constructor(name: string) {} 278 | } 279 | ``` 280 | 281 | ## [@typescript-eslint/no-require-imports](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-require-imports.md) 282 | 283 | 禁止使用 require 284 | 285 | ```javascript 286 | // bad 287 | const fs = require('fs'); 288 | 289 | // good 290 | import * as fs from 'fs'; 291 | ``` 292 | 293 | > 统一使用 import 来引入模块,特殊情况使用单行注释允许 require 引入 294 | 295 | ## [@typescript-eslint/no-this-alias](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-this-alias.md) 296 | 297 | 禁止将 this 赋值给其他变量,除非是解构赋值 298 | 299 | ```javascript 300 | // bad 301 | function foo() { 302 | const self = this; 303 | setTimeout(function() { 304 | self.doWork(); 305 | }); 306 | } 307 | 308 | // good 309 | function foo() { 310 | const { bar } = this; 311 | setTimeout(() => { 312 | this.doWork(); 313 | }); 314 | } 315 | ``` 316 | 317 | ## [@typescript-eslint/no-useless-constructor](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-useless-constructor.md) 318 | 319 | 禁止出现没必要的 constructor 320 | 321 | ```javascript 322 | // bad 323 | class Foo1 { 324 | constructor() {} 325 | } 326 | 327 | class Bar1 extends Foo1 { 328 | constructor() { 329 | super(); 330 | } 331 | } 332 | 333 | // good 334 | class Foo2 { 335 | constructor() { 336 | this.doSomething(); 337 | } 338 | doSomething() {} 339 | } 340 | 341 | class Bar2 extends Foo1 { 342 | constructor() { 343 | super(); 344 | this.doSomething(); 345 | } 346 | doSomething() {} 347 | } 348 | ``` 349 | 350 | ## [@typescript-eslint/prefer-for-of](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-for-of.md) 351 | 352 | 使用 for 循环遍历数组时,如果索引仅用于获取成员,则必须使用 for of 循环替代 for 循环 353 | 354 | ```javascript 355 | // bad 356 | const arr1 = [1, 2, 3]; 357 | 358 | for (let i = 0; i < arr1.length; i++) { 359 | console.log(arr1[i]); 360 | } 361 | 362 | // good 363 | const arr2 = [1, 2, 3]; 364 | 365 | for (const x of arr2) { 366 | console.log(x); 367 | } 368 | 369 | for (let i = 0; i < arr2.length; i++) { 370 | // i is used to write to arr, so for-of could not be used. 371 | arr2[i] = 0; 372 | } 373 | 374 | for (let i = 0; i < arr2.length; i++) { 375 | // i is used independent of arr, so for-of could not be used. 376 | console.log(i, arr2[i]); 377 | } 378 | ``` 379 | 380 | > for of 循环更加易读 381 | 382 | ## [@typescript-eslint/prefer-function-type](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-function-type.md) 383 | 384 | 可以简写为函数类型的接口或字面类型的话,则必须简写 385 | 386 | ```javascript 387 | // bad 388 | interface Foo1 { 389 | (): string; 390 | } 391 | 392 | // good 393 | type Foo2 = () => string; 394 | ``` 395 | 396 | ## [@typescript-eslint/prefer-namespace-keyword](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-namespace-keyword.md) 397 | 398 | 禁止使用 module 来定义命名空间 399 | 400 | ```javascript 401 | // bad 402 | module Foo1 {} 403 | 404 | // good 405 | namespace Foo2 {} 406 | ``` 407 | 408 | > module 已成为 js 的关键字 409 | 410 | ## [@typescript-eslint/triple-slash-reference](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/triple-slash-reference.md) 411 | 412 | 禁止使用三斜杠导入文件 413 | 414 | ```javascript 415 | // bad 416 | /// <reference path="./Animal"> 417 | 418 | // good 419 | import Animal from './Animal'; 420 | ``` 421 | 422 | > 三斜杠是已废弃的语法,但在类型声明文件中还是可以使用的 423 | 424 | ## [@typescript-eslint/typedef](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/typedef.md) 425 | 426 | interface 和 type 定义时必须声明成员的类型 427 | 428 | ```javascript 429 | // bad 430 | type Foo1 = { 431 | bar; 432 | baz; 433 | }; 434 | 435 | // good 436 | type Foo2 = { 437 | bar: boolean; 438 | baz: string; 439 | }; 440 | ``` 441 | 442 | ## [@typescript-eslint/unified-signatures](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/unified-signatures.md) 443 | 444 | 函数重载时,若能通过联合类型将两个函数的类型声明合为一个,则使用联合类型而不是两个函数声明 445 | 446 | ```javascript 447 | // bad 448 | function foo1(x: number): void; 449 | function foo1(x: string): void; 450 | function foo1(x: any): any { 451 | return x; 452 | } 453 | 454 | // good 455 | function foo2(x: number | string): void; 456 | function foo2(x: any): any { 457 | return x; 458 | } 459 | ``` -------------------------------------------------------------------------------- /docs/guide/vue/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarDepth: 2 3 | --- 4 | 5 | # NCUHOME Vue 代码风格指南 6 | 7 | 8 | ## [vue/attributes-order](https://eslint.org/docs/rules/vue/attributes-order) 9 | 10 | 标签属性必须按规则排序 11 | 12 | ```vue 13 | // bad 14 | 15 | 16 | // good 17 | 18 | ``` 19 | 20 | ## [vue/comment-directive](https://eslint.org/docs/rules/vue/comment-directive) 21 | 22 | 支持在模版中使用 eslint-disable-next-line 等注释 23 | 24 | ```vue 25 | // bad 26 | 27 | 28 | // good 29 | 30 | ``` 31 | 32 | ## [vue/eqeqeq](https://eslint.org/docs/rules/vue/eqeqeq) 33 | 34 | 必须使用 === 或 !==,禁止使用 == 或 != 35 | 36 | ```vue 37 | // bad 38 | 41 | 42 | // good 43 | 46 | ``` 47 | 48 | ## [vue/jsx-uses-vars](https://eslint.org/docs/rules/vue/jsx-uses-vars) 49 | 50 | 已定义的 jsx element 必须使用 51 | 52 | ```vue 53 | // bad 54 | 55 | 56 | // good 57 | 58 | ``` 59 | 60 | ## [vue/no-async-in-computed-properties](https://eslint.org/docs/rules/vue/no-async-in-computed-properties) 61 | 62 | 计算属性禁止包含异步方法 63 | 64 | ```vue 65 | // bad 66 | 67 | 68 | // good 69 | 70 | ``` 71 | 72 | ## [vue/no-duplicate-attributes](https://eslint.org/docs/rules/vue/no-duplicate-attributes) 73 | 74 | 禁止出现重复的属性 75 | 76 | ```vue 77 | // bad 78 | 79 | 80 | // good 81 | 82 | ``` 83 | 84 | ## [vue/no-parsing-error](https://eslint.org/docs/rules/vue/no-parsing-error) 85 | 86 | 禁止出现语法错误 87 | 88 | ```vue 89 | // bad 90 | 93 | 94 | // good 95 | 98 | ``` 99 | 100 | ## [vue/no-reserved-keys](https://eslint.org/docs/rules/vue/no-reserved-keys) 101 | 102 | 禁止覆盖保留字 103 | 104 | ```vue 105 | // bad 106 | 107 | 108 | // good 109 | 110 | ``` 111 | 112 | ## [vue/no-textarea-mustache](https://eslint.org/docs/rules/vue/no-textarea-mustache) 113 | 114 | 禁止在 <textarea> 中出现 Mustache 语法 115 | 116 | ```vue 117 | // bad 118 | 119 | 120 | // good 121 | 122 | ``` 123 | 124 | ## [vue/no-unused-components](https://eslint.org/docs/rules/vue/no-unused-components) 125 | 126 | 禁止定义在 components 中的组件未使用 127 | 128 | ```vue 129 | // bad 130 | 131 | 132 | // good 133 | 134 | ``` 135 | 136 | ## [vue/no-unused-vars](https://eslint.org/docs/rules/vue/no-unused-vars) 137 | 138 | 模版中已定义的变量必须使用 139 | 140 | ```vue 141 | // bad 142 | 143 | 144 | // good 145 | 146 | ``` 147 | 148 | ## [vue/no-use-v-if-with-v-for](https://eslint.org/docs/rules/vue/no-use-v-if-with-v-for) 149 | 150 | 禁止在同一个元素上使用 v-if 和 v-for 指令 151 | 152 | ```vue 153 | // bad 154 | 155 | 156 | // good 157 | 158 | ``` 159 | 160 | ## [vue/order-in-components](https://eslint.org/docs/rules/vue/order-in-components) 161 | 162 | 组件的属性必须为一定的顺序 163 | 164 | ```vue 165 | // bad 166 | 167 | 168 | // good 169 | 170 | ``` 171 | 172 | ## [vue/require-component-is](https://eslint.org/docs/rules/vue/require-component-is) 173 | 174 | <component> 必须有 v-bind:is 175 | 176 | ```vue 177 | // bad 178 | 179 | 180 | // good 181 | 182 | ``` 183 | 184 | ## [vue/require-default-prop](https://eslint.org/docs/rules/vue/require-default-prop) 185 | 186 | props 如果不是 required 的字段,必须有默认值 187 | 188 | ```vue 189 | // bad 190 | 191 | 192 | // good 193 | 194 | ``` 195 | 196 | ## [vue/require-direct-export](https://eslint.org/docs/rules/vue/require-direct-export) 197 | 198 | 禁止手动 export default 199 | 200 | ```vue 201 | // bad 202 | 203 | 204 | // good 205 | 206 | ``` 207 | 208 | ## [vue/require-prop-type-constructor](https://eslint.org/docs/rules/vue/require-prop-type-constructor) 209 | 210 | props 的取值必须是构造函数 211 | 212 | ```vue 213 | // bad 214 | 215 | 216 | // good 217 | 218 | ``` 219 | 220 | ## [vue/require-render-return](https://eslint.org/docs/rules/vue/require-render-return) 221 | 222 | render 函数必须有返回值 223 | 224 | ```vue 225 | // bad 226 | 227 | 228 | // good 229 | 230 | ``` 231 | 232 | ## [vue/require-v-for-key](https://eslint.org/docs/rules/vue/require-v-for-key) 233 | 234 | v-for 指令的元素必须有 v-bind:key 235 | 236 | ```vue 237 | // bad 238 | 239 | 240 | // good 241 | 242 | ``` 243 | 244 | ## [vue/return-in-computed-property](https://eslint.org/docs/rules/vue/return-in-computed-property) 245 | 246 | 计算属性必须有返回值 247 | 248 | ```vue 249 | // bad 250 | 251 | 252 | // good 253 | 254 | ``` 255 | 256 | ## [vue/this-in-template](https://eslint.org/docs/rules/vue/this-in-template) 257 | 258 | 禁止在模版中用 this 259 | 260 | ```vue 261 | // bad 262 | 263 | 264 | // good 265 | 266 | ``` 267 | 268 | ## [vue/use-v-on-exact](https://eslint.org/docs/rules/vue/use-v-on-exact) 269 | 270 | 当一个节点上出现两个 v-on:click 时,其中一个必须为 exact 271 | 272 | ```vue 273 | // bad 274 | 275 | 276 | // good 277 | 278 | ``` 279 | 280 | ## [vue/v-on-function-call](https://eslint.org/docs/rules/vue/v-on-function-call) 281 | 282 | 禁止在 v-on 的值中调用函数 283 | 284 | ```vue 285 | // bad 286 | 287 | 288 | // good 289 | 290 | ``` 291 | 292 | ## [vue/valid-template-root](https://eslint.org/docs/rules/vue/valid-template-root) 293 | 294 | template 的根节点必须合法 295 | 296 | ```vue 297 | // bad 298 | 299 | 300 | // good 301 | 302 | ``` 303 | 304 | ## [vue/valid-v-bind](https://eslint.org/docs/rules/vue/valid-v-bind) 305 | 306 | v-bind 指令必须合法 307 | 308 | ```vue 309 | // bad 310 | 317 | 318 | // good 319 | 327 | ``` 328 | 329 | ## [vue/valid-v-cloak](https://eslint.org/docs/rules/vue/valid-v-cloak) 330 | 331 | v-cloak 指令必须合法 332 | 333 | ```vue 334 | // bad 335 | 336 | 337 | // good 338 | 339 | ``` 340 | 341 | ## [vue/valid-v-else](https://eslint.org/docs/rules/vue/valid-v-else) 342 | 343 | v-else 指令必须合法 344 | 345 | ```vue 346 | // bad 347 | 348 | 349 | // good 350 | 351 | ``` 352 | 353 | ## [vue/valid-v-else-if](https://eslint.org/docs/rules/vue/valid-v-else-if) 354 | 355 | v-else-if 指令必须合法 356 | 357 | ```vue 358 | // bad 359 | 360 | 361 | // good 362 | 363 | ``` 364 | 365 | ## [vue/valid-v-for](https://eslint.org/docs/rules/vue/valid-v-for) 366 | 367 | v-for 指令必须合法 368 | 369 | ```vue 370 | // bad 371 | 372 | 373 | // good 374 | 375 | ``` 376 | 377 | ## [vue/valid-v-html](https://eslint.org/docs/rules/vue/valid-v-html) 378 | 379 | v-html 指令必须合法 380 | 381 | ```vue 382 | // bad 383 | 384 | 385 | // good 386 | 387 | ``` 388 | 389 | ## [vue/valid-v-if](https://eslint.org/docs/rules/vue/valid-v-if) 390 | 391 | v-if 指令必须合法 392 | 393 | ```vue 394 | // bad 395 | 396 | 397 | // good 398 | 399 | ``` 400 | 401 | ## [vue/valid-v-model](https://eslint.org/docs/rules/vue/valid-v-model) 402 | 403 | v-model 指令必须合法 404 | 405 | ```vue 406 | // bad 407 | 408 | 409 | // good 410 | 411 | ``` 412 | 413 | ## [vue/valid-v-on](https://eslint.org/docs/rules/vue/valid-v-on) 414 | 415 | v-on 指令必须合法 416 | 417 | ```vue 418 | // bad 419 | 420 | 421 | // good 422 | 423 | ``` 424 | 425 | ## [vue/valid-v-once](https://eslint.org/docs/rules/vue/valid-v-once) 426 | 427 | v-once 指令必须合法 428 | 429 | ```vue 430 | // bad 431 | 432 | 433 | // good 434 | 435 | ``` 436 | 437 | ## [vue/valid-v-pre](https://eslint.org/docs/rules/vue/valid-v-pre) 438 | 439 | v-pre 指令必须合法 440 | 441 | ```vue 442 | // bad 443 | 444 | 445 | // good 446 | 447 | ``` 448 | 449 | ## [vue/valid-v-show](https://eslint.org/docs/rules/vue/valid-v-show) 450 | 451 | v-show 指令必须合法 452 | 453 | ```vue 454 | // bad 455 | 456 | 457 | // good 458 | 459 | ``` 460 | 461 | ## [vue/valid-v-text](https://eslint.org/docs/rules/vue/valid-v-text) 462 | 463 | v-text 指令必须合法 464 | 465 | ```vue 466 | // bad 467 | 468 | 469 | // good 470 | 471 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ncuhome-fe-style-guide", 3 | "version": "1.1.0", 4 | "description": "A Front End style guide for lovely NCUHOME", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "vuepress dev docs", 8 | "build": "vuepress build docs" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Wzb3422/Ncuhome-fe-style-guide.git" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/Wzb3422/Ncuhome-fe-style-guide/issues" 19 | }, 20 | "homepage": "https://github.com/Wzb3422/Ncuhome-fe-style-guide#readme", 21 | "dependencies": { 22 | "vuepress": "^1.1.0" 23 | }, 24 | "devDependencies": { 25 | "@vuepress/plugin-back-to-top": "^1.0.0-rc.1" 26 | } 27 | } 28 | --------------------------------------------------------------------------------