rule.md 16 KB

代码整体原则

组件设计

原则

  1. 将组件设计为“黑盒子”,即纯组件,外部对其使用依赖于确定值。

规范

  1. 始终使用 kebab-case 的事件名(原因
  2. 超过100行的Vue文件应拆分为独立的js,css,html文件;
  3. 事件类型统一添加on-前缀,即this.$emit('on-success', value)
  4. 事件处理器统一添加handle前缀,即@on-success="handleUpdateNameSuccess"

v-model 使用原则

  1. 尽量不要使用,v-model 的黑魔法是从组件的黑盒子内部对一个值的“突变”,存在不确定性和不可控性;
  2. 不确定性是无法确定值的改变是由外部代码操控,还是组件内部触控;
  3. 不可控性跟不确定性类似,组件的黑盒子内部可以不受外部控制的改变值,而这个值有可能会对外部的程序产生副作用。

v-model 的合适使用场景

  1. 表单型组件(比如i-input)的处理,在该场景下,表单组件的value是在外部明确期望获取的,而且明确由组件内部输入控制的,是对onChange的简写形式(本质上:期待的数据流向是明确的,从内部流向外部)。
  2. 组件可完全控制的场景。

    一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件(https://cn.vuejs.org/v2/guide/components-custom-events.html#自定义组件的-v-model)

示例代码

// 父组件
<tai-slider
    v-model="form.disk" // form表单期望获取的值
    :min="20"
    :max="1000"
/>

// 子组件
<div class="tai-form-slider">
    <div class="tai-slider-content">
        <i-slider
            :value="innerValue"  // 重新定义的值
            :min="minSize"
            :max="maxSize"
            :step="step"
            name="disk"
            show-tip="hover"
            class="tai-slider"
            @on-input="handleChangeDisk"
        />
        <i-input-number
            :value="innerValue"
            :min="minSize"
            :max="maxSize"
            :step="step"
            @on-change="handleChangeDisk"
            @on-blur="handleBlurInput"
        />GB
    </div>
    <div class="tai-slider-tip">
        <span class="min-size">{{ minSize }}GB</span>
        <span class="max-size">{{ maxSize }}GB</span>
        <span class="ram-tip">存储空间{{ minSize }}GB~{{ maxSize }}GB</span>
    </div>
</div>

// watch中监听,将传入的value值赋值给新定义的值,避免直接影响操作外部值。
value() {
    this.innerValue = this.value;
},

.syncv-model的区别

  1. 暂时没有想到两者严格的差异🤪;
  2. 表面上看只是$emit 'input'$emit 'update'的区别(.sync)。

参考

  1. 通过事件向父级组件发送消息
  2. 自定义事件

路由设计

原则

  1. 将路由简化抽象为RESTful的URL,即每一个URL可独立启动对应应用程序的状态和功能;
  2. 无状态,刷新浏览器后可完整还原页面的状态和功能,不依赖于之前的页面操作。

规范

  1. 均通过一个名称来标识一个‘叶子路由’,名称(name)严格使用camelCase格式,遵循module.nested.routeName的命名规范;
  2. 路径(path)全小写,和文件的命名格式保持一致,使用kebab-case格式;
  3. 路由统一使用描述地址的对象执行跳转,在:to和编程两种形式中保持一致。
  4. 路由打包名字(webpackChunkName)设置唯一

代码示例

export default {
    path: '/ecs-rabbit',
    name: 'ecs-rabbit',
    component: () => import(/* webpackChunkName: "ecs-rabbit" */ './app.vue'),
    children: [
        {
            // 注意路由之间会相互冲突
            path: ':page(\\d+)?/:keyword?',
            component: () => import(/* webpackChunkName: "ecs-rabbit" */ './views/index.vue'),
        },
        {
            path: 'detail/:id',
            component: () => import(/* webpackChunkName: "ecs-rabbit" */ './views/detail.vue'),
        },
    ],
};

简化路由设计

  1. 用query代替path中嵌套的查询参数,直观显示路由结构

路由更新代码示例(原路由设计如上)

export default {
    path: '/ecs-rabbit',
    name: 'ecs-rabbit',
    component: () => import(/* webpackChunkName: "ecs-rabbit" */ './app.vue'),
    children: [
        {
            // 注意路由之间会相互冲突
            path: '',
            component: () => import(/* webpackChunkName: "ecs-rabbit" */ './views/index.vue'),
        },
        {
            path: ':id',
            component: () => import(/* webpackChunkName: "ecs-rabbit" */ './views/detail.vue'),
        },
    ],
};

取参数代码示例

// 原先数据获取参数page
this.page.current = parseInt(this.$route.params.page, 10) || 1;

// 更新路由后获取参数page
this.page.current = parseInt(this.$route.query.page, 10) || 1;

传参时代码示例

// 原先传参,直接在路由中传递(如:http://localhost:8080/console/ecs-rabbit/1/test )
this.$router.push(`/ecs-rabbit/${page}/${this.keyword}`);

// 更新之后传参,作为参数传递(如:http://localhost:8080/console/ecs-rabbit?page=1&keyword=test )
this.$router.push({
    path: '/ecs-rabbit',
    query: {
        page,
        keyword: this.keyword,
    },
});

Store 设计

原则

  1. 保持单向数据流;
  2. 使用namespace 隔离各个功能状态;
  3. 从模型属性衍生出的视图模型属性,使用computed(getter)表达。

规范

  1. strict设置为true;
  2. namespace设置为true;
  3. 当使用 mapState, mapGetters, mapActions 和 mapMutations 这些函数来绑定带命名空间的模块时,将模块的空间名称字符串作为第一个参数传递给上述函数;

Request

原则

使用代理磨平开发、测试和生产环境的差异,直接使用最终的Path发起请求;


Filter

原则

定义一套标准来过滤一组对象;

规范

  1. 脱离业务逻辑而抽象出来的公用的过滤器(例如时间的格式化过滤器),放在common的filter文件夹中并且全局定义过滤器;
  2. 在业务模块中,过滤器定义在模块的filter文件中。需要使用过滤器的组件,引用filter文件并在组件中定义本地的过滤器;

文件及代码命名规范

文件夹命名规范

  1. 文件夹一律采用全小写中杠分隔的形式,例如 ecs-seahare 进行命名。

文件命名规范

  1. 文件一律采用全小写中杠分隔的形式,例如 toggle-password.vue 进行命名

代码命名规范

  1. 输出和注册组件一律采用大写驼峰形式,例如LoginTemplate
  2. 使用组件时一律采用全小写中杠分隔的形式,例如<login-template></login-template>
  3. 在书写 html 时,为区分iview自定义的标签和html原生标签,需要将iview标签写成带有i-前缀的形式。如:<i-menu-item></i-menu-item>
  4. importvue文件组件时必须添加vue后缀(原因:webpack无法区分同名的.js和.vue文件,两者会产生不确定随机引用,有可能造成文件入口错误);
  5. import频道(子App)内文件用相对路径,频道外文件用绝对路径('@/common/utils/request');

遗留待考虑问题

  1. 项目中js、css以及Vue文件是否需要进行文件夹归属拆分

API接口

  1. 所有的API接口字段信息保持一致;
  2. 时间统一以毫秒级别的形式进行传递,前端进行对时间数据的处理;
  3. API接口信息中不用的字段不应该传递;
  4. API接口字段尽量返回数据库原始数据,保持数据的可复用性以及原始性;

API处理

  1. 尽量减少接口的请求。能复用的请求可以直接复用。最好定义API时候保持API的纯粹性

URL使用

自动扩展URL

  1. 用户在浏览器或者地址href中不需要输入完整的URL地址,浏览器会自动扩展。
  2. 协议的自动扩展是为了兼容http和https两种协议。
// 如下href地址
<li><a href="//zhiyucloud.facethink.com/tools/OSSClientSetup.exe">客户端工具</a></li>

引申:自动扩展两种方式

1:主机名扩展

浏览器通常可以在没有帮助的情况下,将主机名扩展为完整的主机名,比如yahoo会自动构建为www.yahoo.com。 如果找不到与yahoo匹配的站点,有些浏览器会在放弃之前尝试几种扩展方式。 浏览器通过简单的技巧来节省你的时间,减少找不到的可能。

2:历史扩展:

浏览器将以前用户访问过的URL历史存储起来,当输入的URL与历史记录的前缀匹配,就会提供一些完整的选项供你选择。 如用户输入http时下拉框就会出现很多匹配链接。


时间处理规范

  1. 时间一律使用luxon来进行处理

接口错误处理

一个页面有时可能会涉及到多个接口同时请求,对着这种请求,我们需要对接口信息进行多种情况的判断,避免页面强制刷新带来重复请求

目前考虑到的有请求中请求成功有数据请求成功无数据请求失败这四种情况


模式

widget模式

widget 模式名字借鉴于 Web Widget,与其含义基本相同,表示小部件、小工具、挂件等,包含可在任意地方直接调用并执行的含义。

在一个页面中可以存在多个Vue实例,将独立小组件封装成单独Vue的实例,并提供函数的调用接口,即widget。

需要使用的时候即直接调用,本质上是在DOM种插入新的Vue实例,通过回调或Promise的形式完成数据的传递。

解决的问题

在典型的Vue架构中,UI组件的展示是对组件树中某个组件的状态改变(v-if、v-show等),对于跟业务代码执行某个小分叉逻辑有关的独立小组件(比如 Alert、Confirm、Prompt、Tip 等)有些刻板和沉重,在这种情况下,更有利于调用函数等形式会更便于使用。

由于减少组件树中此类组件的占位,也可以优化程序的性能。


常用自定义指令

1. 常用自定义过滤器dtf

作用

格式化时间字符串。

使用方法

dtfdtf('yyyy-MM-dd')API参考

2. v-timely-validate

表单的构成

通常表单共有 label、input(包含radio、checkbox、select、slider等)、placeholder、help-tip、error-message 5部分。

其中 error-message 可以看做 help-tip的加强版,是为了给用户更强烈的提示作用,促进用户更准确的填写表单。所以,error-message 应该是 help-tip 的拆解,按验证规则优先级顺序给用户更准确、更具体的错误信息。

在上面的前提下,为了简化用户所看到的表单结构,error-message 覆盖掉 help-tip,两类信息不同时出现。

作用

将表单验证规则rules中验证的触发时机从blur修改为change,以及时消除错误。

表单校验原则
  1. 尽量不强行阻止用户(比如text表单元素中不截断用户的超长度输入,number表单元素中不阻止用户输入字符等,及时提醒就好);
  2. 及时提醒用户出错;
  3. 不打扰用户正常输入(比如第一次输入用blur时机校验);
  4. 尽早消除错误提醒(比如有了出错提醒后用change时机校验);
使用方法
  1. i-form表单组件中添加v-timely-validate指令;
  2. i-input等表单元素组件中添加跟rules对应的name属性;
  3. i-formrules中使用blur触发;
  4. i-form-item中添加 help-tip 元素,并为该元素添加tai-form-item-help-tip类(借助了i-input所隐藏的状态机)。
代码示例
<i-form
    v-timely-validate
    ref="iForm"
    :model="form"
    :rules="rules"
    @submit.native.prevent="handleSubmit"
>
    <i-form-item label="用户账号" prop="account">
        <i-input v-model="form.account" name="account" placeholder="请输入用户账号" />
        <p class="form-tip-name form-tip">由小写字母,数字,下划线组成,字母开头,最长16个字符</p>
    </i-form-item>
</i-form>

QA

1. template中只包含简单的表达式

原因

复杂表达式会让你的模板过重且难以维护。我们应该尽量描述应该出现的是什么,而非如何计算那个值。

    {{
        fullName.split(' ').map(function (word) {
        return word[0].toUpperCase() + word.slice(1)
        }).join(' ')
    }}

解决方法

尽量写成有语义的计算属性或方法、filter,从而使得代码可以重用

    computed: {
        normalizedFullName: function () {
            return this.fullName.split(' ').map(function (word) {
                return word[0].toUpperCase() + word.slice(1)
            }).join(' ')
        }
    }

2. 项目文件夹层级过多

原因

文件夹的设置习惯于参照产品菜单来创建,菜单出现三级子菜单时,依照现有的文件夹建立方式会出现7级嵌套,不利于维护开发;

解决方法

跳出原有的思维限制,按照具体项目来建立一级文件夹,避免过深的文件夹层级嵌套,这样也有利于路由的配置简化;

通过路径规划设置,让配置更简练,屏蔽请求路径差异

3. 样式维护问题

场景

局部组件的class因为恰好和其他页面的class重名,结果导致样式覆盖,影响了其他页面的展示

解决

局部组件内的class应该只定义组件相关样式,必须加上scope限制作用域,如果有需要复用的class,应当提取到公共css内

4. package.json升级注意事项

当第一次 git clone项目到本地时,要先执行 npm install 安装依赖包,保证node_modules中包的完整性,然后执行 npm update, 如果在没安装依赖包时直接执行 npm update,会导致 package.json 没有自动更新

5. style标签中必须添加lang属性

原因:否则有可能会报错(升级vue-cli@3.0.5版本是个暴露点,但应该不是这个原因)。

6. 资源在js中通过import引用

场景

在data中或通过方法对资源的地址赋值,无法引用到该资源。

原因

在template模版里的路径会被webpack解析打包,而放在data和method中的路径,只是一个字符串,webpack不会处理。

解决方案

通过import或required引入。

7. 环境变量及模式

解决问题

发版过程中存在很多的发版环境,例如测试环境、开发环境等,我们需要根据不同的环境做相应的调整。

解决方法

1.增加模式 test vue-cli-service build --mode test 会在 test 模式下加载可能存在的 .env、.env.test 和 .env.test.local 文件然后构建出生产环境应用。 env.test

NODE_ENV=production
VUE_APP_API_BASE_URL=http://39.107.112.223:8008/

2.增加变量 test 以 VUEAPP 开头的变量会被 webpack.DefinePlugin 静态嵌入到客户端侧的包中 VUE_APP_SERVER_MODE=test vue-cli-service build --modern --mode production

8. 方法参数的传递,优先使用对象格式。

####原因:

  1. 便于参数的扩展;
  2. 有一定的自解释性;
  3. 对于顺序不要求