# Code Review

主题:前端代码书写规范,js代码技巧分享 日期:2024年6月28日 参加人:OSCE项目组 主持人:于俊凯

背景: 前端拆分项目重新整理,发现的规范性问题与更好的使用高级技巧分享;如何达到更好的代码可读性与更高的程序性能

# 一. 格式化问题

参考代码格式指南https://codeguide.bootcss.com/#css-declaration-order

  1. # vue代码块格式化问题

    <div v-else-if="item.type === '音频播放器组件'" class="template-detail"
        style=" border: 1px solid #000000;transform: translateZ(0);"
    >
     <audio :ref="'audio'+index" style="scale: 0.8;width: 100%;" :autoplay="item.autoplay"
            :controls="item.controls" :loop="item.loop" :src="item.filePath"
     />
     <i v-if="item.muted" :ref="'audioMuted'+index" :style="'font-size:'+item.audioMutedFontSize"
        class="mdi mdi-volume-high" @click="mutedAudio(index)"
     />
    </div>
    

    ​ 应遵循要么属性都在一行上要么每个属性独立一行,我的习惯是每个属性独立一行;

  2. # style代码块\文件格式化问题

       <style lang="scss" scoped>
       .upload-box {
         .upload-image-container {
           height: 116px;
         }
         .upload-image-container img {
           max-height: 116px;
           max-width: 148px;
         }
         .upload-file-name{
           padding: 5px 5px 0 5px;
           display: block;
           overflow: hidden;
           white-space: nowrap;
           text-overflow: ellipsis;
           font-size: 11px;
         }
         ::v-deep .hide-upload-btn {
           .el-upload.el-upload--picture-card {
             display: none;
           }
         }
         .no-file{
           overflow: hidden;
           background-color: #fff;
           border: 1px solid #c0ccda;
           border-radius: 6px;
           -webkit-box-sizing: border-box;
           box-sizing: border-box;
           width: 148px;
           height: 148px;
           margin: 0 8px 8px 0;
           display: inline-block;
           font-size: 18px;
           color:#97a8be;
           text-align: center;
           padding-top: 55px;
         }
       }
       .image {
         width: 98%;
         height: 110px;
         background-position: center center;
         background-size: cover;
         background-repeat: no-repeat;
         cursor: pointer;
         margin:1px 1px 0 1px;
         border-radius: 5px 5px 0 0;
         position:relative;
       }
       
       ::v-deep .el-upload-list__item {
         transition: none !important;
       }
       
       </style>
    

    css属性的书写应按css解析器的解析顺序书写,这样可以减少浏览器reflow(回流),提升浏览器渲染dom的性能。

    # CSS代码的命名规范
    • 必须以字母开头命名选择器,这样可保证在所有浏览器下都能兼容;
    • 不允许单个字母的类选择器出现;
    • 不允许命名带有广告等英文的单词,例如ad,adv,adver,advertising,已防止该模块被浏览器当成垃圾广告过滤掉。任何文件的命名均如此。
    • 下划线 ’ _ ’ 禁止出现在class命名中,全小写,统一使用’-‘连字符.
    • 禁止驼峰命名 className
    • 见名知意
    # CSS代码注意事项
    • 不要以完全没有语义的标签作为选择器,这会造成大面积污染
    • 简写CSS颜色属性值
    • 删除CSS属性值为0的单位
    • 删除无用CSS样式
    • 为单个CSS选择器或新申明开启新行
    • 避免过度简写 , .ico足够表示这是一个图标 , 而.i不代表任何意思
    • 使用有意义的名称,使用结构化或者作用目标相关的,而不是抽象的名称

    新OSCE管理端已增加styleLint工具帮助施行css书写规范

    webstorm需更改设置如下图

    WechatIMG12288-2024-6-2516:54:11.jpg

    VScode 操作如下图

    WechatIMG12289-2024-6-2516:54:40.jpg

  3. # Api接口书写规范

    // 恢复评分表
    export function restoreScore(examScoreId) {
      return request({
        url: '/se/exam/score/restore/' + examScoreId,
        method: 'post'
      })
    }
    

    Api的书写方法名尽量以'Api'为后缀结束,这样在使用时能明了的知道是在调用接口。

    Api的命名应同样遵循js方法名的命名规范,见名知意。比如getById就是bad name。

    Api的url属性最好使用js提供的所见即所得``尽量不用加号拼接参数。

    按规范书写应为

      // 恢复评分表
      export function restoreScoreApi(examScoreId) {
        return request({
          url: `/se/exam/score/restore/${examScoreId}`,
          method: 'post'
        })
      }
    

# 二. js代码技巧

  1. 解构赋值

    解构赋值语法是一种 Javascript 表达式。可以将数组中的值或对象的属性取出,赋值给其他变量。

    1. 对象的解构赋值

      const { name, age } = { name: '张三', age: 23 };
      
      // 结果: name = '张三', age = 23
      
      

      这里使用对象解构赋值的方式将对象中的属性直接提取到新的变量中。这种方法简化了访问对象属性的过程,并增强了代码的可读性。

    2. 数组的解构赋值

      // 我们有一个存放了名字和姓氏的数组
      let arr = ["John", "Smith"]
      
      // 解构赋值
      // sets firstName = arr[0]
      // and surname = arr[1]
      let [firstName, surname] = arr;
      
      alert(firstName); // John
      alert(surname);  // Smith
      
      // 不需要第二个元素
      let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
      
      alert( title ); // Consul
      
    3. 解构同时起别名

      const { name: na, age } = { name: '张三', age: 23 };
      
      // 结果: na = '张三', age = 23
      
      
  2. vuex辅助函数的使用

    1. mapState :

      当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:

      // 在单独构建的版本中辅助函数为 Vuex.mapState
      import { mapState } from 'vuex'
      
      export default {
        // ...
        computed: {
          ...mapState({
            // 箭头函数可使代码更简练
            count: state => state.count,
      
            // 传字符串参数 'count' 等同于 `state => state.count`
            countAlias: 'count',
      
            // 为了能够使用 `this` 获取局部状态,必须使用常规函数
            countPlusLocalState (state) {
              return state.count + this.localCount
            }
        	})
        }
      }
      

      当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。

      computed: mapState([
        // 映射 this.count 为 store.state.count
        'count'
      ])
      
    2. mapGetters

    `mapGetters` 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:
    
    ```js
    import { mapGetters } from 'vuex'
    
    export default {
      // ...
      computed: {
      // 使用对象展开运算符将 getter 混入 computed 对象中
        ...mapGetters([
          'doneTodosCount',
          'anotherGetter',
          // ...
        ])
      }
    }
    ```
    
    如果你想将一个 getter 属性另取一个名字,使用对象形式:
    
    ```js
    ...mapGetters({
      // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
      doneCount: 'doneTodosCount'
    })
    ```
    
    1. mapActions

      你在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store):

      import { mapActions } from 'vuex'
      
      export default {
        // ...
        methods: {
          ...mapActions([
            'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
      
            // `mapActions` 也支持载荷:
            'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
          ]),
          ...mapActions({
            add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
          })
        }
      }
      
    2. 带命名空间的绑定函数

      当使用 mapState, mapGetters, mapActionsmapMutations 这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:

      computed: {
        ...mapState({
          a: state => state.some.nested.module.a,
          b: state => state.some.nested.module.b
        })
      },
      methods: {
        ...mapActions([
          'some/nested/module/foo', // -> this['some/nested/module/foo']()
          'some/nested/module/bar' // -> this['some/nested/module/bar']()
        ])
      }
      
  3. 离线状态判断

    const isOnline = navigator.onLine ? '在线' : '离线';
    
    // 结果: isOnline = '在线' 或 '离线'
    

    这段代码使用三元运算符检查浏览器的在线状态(navigator.onLine),如果为真则返回'在线',否则返回'离线'。这是一种动态检查用户网络连接状态的方法。

  4. 特殊运算符的使用

    1. 逻辑或运算符(||):

      对于一组操作数的逻辑或||,逻辑析取)运算符,当且仅当其一个或多个操作数为真,其运算结果为真。它通常与布尔(逻辑)值一起使用。当它是布尔值时,返回一个布尔值。然而,|| 运算符实际上是返回一个指定的操作数的值,所以如果这个运算符被用于非布尔值,它将返回一个非布尔值。

      const a = 3;
      const b = -2;
      
      console.log(a > 0 || b > 0);
      // 结果: true
      
      true || true; // t || t returns true
      false || true; // f || t returns true
      true || false; // t || f returns true
      false || 3 === 4; // f || f returns false
      "Cat" || "Dog"; // t || t returns "Cat"
      false || "Cat"; // f || t returns "Cat"
      "Cat" || false; // t || f returns "Cat"
      "" || false; // f || f returns false
      false || ""; // f || f returns ""
      false || varObject; // f || object returns varObject
      
    2. 空值合并运算符 (??) :

      检查左侧是否为nullundefined,如果是,则使用默认值空值合并运算符右侧的值。这可确保后备值不会是其他假值(如''0)。这对于访问数据结构中可能不存在某些中间属性的深层嵌套属性非常有用。

      与逻辑或运算符(||)不同,逻辑或运算符会在左侧操作数为假值时返回右侧操作数。也就是说,如果使用 || 来为某些变量设置默认值,可能会遇到意料之外的行为。比如为假值(例如,''0)时。

      const user = { profile: { name: '张三' } };
      
      const userName = user.profile?.name ?? '匿名';
      
      // 结果: userName = '张三'
      
    3. 扩展运算符(...):

      • 浅克隆对象

        const originalObj = { name: '张三', age: 24 };
        
        const clonedObj = { ...originalObj };
        
        // 结果: clonedObj = { name: '张三', age: 24 }
        // 此时改变 clonedObj 的属性,将不会影响到原始对象 originalObj
        

        通过使用扩展运算符 (...) 创建originalObj浅克隆对象。此技术将所有可枚举的自身属性从原始对象复制到新对象。

      • 合并对象

        const obj1 = { name: '张三' };
        const obj2 = { age: 22 };
        
        const mergedObj = { ...obj1, ...obj2 };
        
        // 结果: mergedObj = { name: '张三', age: 22 }
        

        与克隆类似,通过扩展运算符obj1和合并obj2为一个新的对象。如果有重叠的属性,则最后一个对象的属性将覆盖前一个对象的属性。

      • 删除数组重复项

        const arr = [1, 2, 2, 3, 4, 4, 5];
        
        const unique = [...new Set(arr)];
        
        // 结果: unique = [1, 2, 3, 4, 5]
        

        这里利用了Set对象存储的值会保持唯一,以及扩展运算符能将Set转换回数组的特性。这是一种优雅的删除数组中重复项的方式。

上次更新时间: 6/28/2024, 4:56:08 PM