Skip to content

前端项目开发工程化探索 #5

@lvisei

Description

@lvisei

PPT - 前端项目开发工程化探索.pdf

PPT由 Markdown Preview Enhanced 生成


presentation:
width: 1920
height: 1080
theme: league.css
minScale: 0.2
maxScale: 2
slideNumber: true
mouseWheel: true
showNotes: true
enableSpeakerNotes: true
center: true

前端项目开发工程化探索


2020.08.07

内容


  • 现阶段遇到的问题 ?
  • 工程化探索
  • 搭建“管理系统业务”项目技巧
    • 目录结构约定
    • Vue CLI4 生成项目工程
    • 工程配置
    • 编辑器
  • 编码规范
    • 常规编码规范与技巧
    • Vue 编码常识性规范
  • Vue 开发小技巧
  • Vue 项目性能调优
  • 常见业务设计与注意问题

现阶段遇到的问题?


  • 不只是一个项目的问题
  • 业务复杂:多功能、多页面、多状态
  • 多人协作、团队协作
  • 开发效率
  • 开发质量
  • 代码可维护性

什么是前端工程化 ?



工程化探索需要关注的东西


  • 生成标准项目目录
  • 整合工程化优秀方案
    • 代码压缩、资源打包、代码检查、本地调试、开发工具,编辑器插件、代码提交与格式化、代码推送、···​​​​​
  • 易于扩展、协同开发
  • 模块化、组件化
  • 规范化、自动化

建立规范


  • 文件目录结构

  • 代码规范

  • 组件管理

  • 模块管理

  • 接口规范

  • commit规范

  • code review

  • ......

管理系统业务项目搭建技巧


一切实际从业务出发



思考业务常见的业务场景 🤔



目录结构约定

参考结构:

目录约定


文件目录结构化、项目业务模块化、文件结构遵循就近原则

1. JS 文件


所有的 .js 文件都遵循横线连接 kebab-case

  • 例子:

  • @/src/utils/open-window.js

  • @/src/components/SvgIcon/require-icons.js

  • @/src/views/LeafletMap/default-options.js

2. componentsviews


  • 所有的 components 文件都是以大写开头 PascalCase,这也是官方所 推荐的

  • 但除了 index.vue

  • 例子:

  • @/src/components/BackToTop/index.vue
  • @/src/components/Charts/Line.vue
  • @/src/views/LeafletMap/MapContainer/index.vue


  • componentsviews 的区别在于组件本身是否是系统业务组件,views 可理解为各模块页面公用的路由视图组件,component 则是脱离系统业务的公用组件。

3. Pages

  • pages 目录下,代表路由的 .vue 文件都使用横线连接 kebab-case,代表路由的文件夹也是使用同样的规则。

  • 例子:

  • @/src/pages/data-query/index.vue
  • @/src/pages/data-query/helper.js
  • @/src/pages/data-query/components/BarChart.vue

使用 kebab-case 来命名 pages 考虑。

  • 横线连接 (kebab-case) 也是官方推荐的命名规范之一 文档

  • pages下的.vue文件代表的是一个路由,所以它需要和 component 进行区分,component 都是大写开头

  • 页面的url 也都是横线连接的,比如https://www.xxx.com/user-management,所以路由对应的pages应该要保持统一

  • 没有大小写敏感问题

就近原则

  • components

  • helpers

4. storerouterapi

  • storerouterapi 文件遵循 pages 目录下的业务分块。

  • store 里面设计到系统全局使用到状态,建议配置成全局 getters

  • 对于 api 目录下的各业务模块都涉及的方法,建议提取成封装成公用模块。

5. helpersutils

  • helpersutils 的区别在于是否是系统业务工具方法
  • helpers 可理解为业务相关的方法助手
  • utils 则是脱离业务的可复用的工具。

6. assets

  • assets 目录下的 iconsimages 文件夹分别是 svg 目录与静态图片目录,
  • 目录下的静态资源可以根据业务模块的大小分类放置。

7. themes

  • themes 系统相关主题目录
  • 主要是自定义第三方组件库主题样式及组件样式覆写
  • 系统 chart 图表主题样式文件等。

搭建 Vue 项目工程化

基于 Vue CLI4 搭建 Vue 2.0 项目

项目生成过程

搭建 Vue 项目工程化

工程配置

  • Eslint

    • ESLint 配置
    • ESLint 插件
    • ESLint 确保代码上传
  • Prettier

    • Prettier 配置
    • Prettier 插件
    • Prettier 确保代码上传

Babel

Webpack

  • .vue.config.js
  • devServer
  • publicPath
  • productionSourceMap
  • loader
  • plugins
  • optimization.splitChunks

编码规范

  • 常规编码规范与技巧
  • Vue 编码常识性规范

常规编码规范与技巧

Vue 编码常识性规范

风格指南

Vue 开发小技巧

Vue 开发小技巧

一、组件相关

1、生命周期 @hook

  • 父组件监听子组件挂载后mounted
  • 一般我们会在每个子组件中去this.$emit事件,我们也可以使用 @hook 来方便的做这个事情

子组件

export default {
    mounted() {
        this.$emit('listenMounted')
    }
}

父组件

<template>
    <div>
        <List @listenMounted="listenMounted" />
    </div>
</template>

其实还有一种简洁的方法,使用 @hook 即可监听组件生命周期,组件内无需做任何改变。

同样的, createdupdated 等也可以使用此方法。

<template>
    <Child @hook:mounted="childMounted" />
</template>

2、动态注册 hook

  • 常见业务场景:

    • 使用定时器,生命周期内清除定时器
    • 事件监听移除
    • 摧毁实例化的对象

一般我们会这样做

export default {
  created() {
    addEventListener("click", Function, false);
  },

  beforeDestroy() {
    removeEventListener("click", Function, false);
  },
};

避免可能遗忘,我们可以这样

export default {
  mounted() {
    const picker = new Pickaday({
      // ...
    });

    this.$once("hook:beforeDestroy", () => {
      picker.destroy();
    })
  }
};

3、 避免进行数据劫持

Vuedata 的数据默认会 Object.defineProperty 对数据进行劫持, 以实现双向数据绑定

示例

export default {
  data() {
    return {
      tableList: [],
    };
  },

  created() {
    const tableList = [
      { name: "张四", gender: "male", age: "26" },
      { name: "李三", gender: "fmale", age: "24" },
    ];
    this.tableList = Object.freeze(tableList);
  },
};

4、批量数据更新

一次性更新多个数据时

可使用如下方法一起赋值

const {name, age, gender} = resData
this = Object.assign(this, {name, age, gender})

5、依赖注入

示例:

const map = this.$parent.map || this.$parent.$parent.map

问题:

  • $parent property
  • property props
  • 不需要数据劫持,避免不必要的性能浪费

provide / inject

<template>
  <Map>
    <MapTool />
    <MapLegend />
  </Map>
</template>

父组件注入依赖地图实例方法

export default {
  name: "Parent",

  provide: () => {
    return {
      getMap: this.getMap,
    };
  },
};

子组件接收指定方法

export default {
  name: "Child",

  inject: ['getMap'],
  
  created () {
    console.log(this.getMap())
  }
}

在有些业务场景下,我们希望子组件访问父组件较多的方法与熟悉,父组件可以像这样注入组件实例

export default {
  name: "Parent",

  provide: [this]
};

不过需要注意的是 provideinject 绑定并不是可响应的

6、Vue.observable

我们可以利用这个 Vue.observable( object ) 来应对一些简单的跨组件数据状态共享的情况

my-store.js Demo.vue

7、跨组件属性与事件传递

  • $attrs
  • $listeners

示例:

<template>
  <Child v-bind="$attrs" v-on="$listeners" />
</template>
 
<script>
  import Child from "./Child";
  export default {
    props: {
      title: {
        required: true,
        type: String
      }
    }
    components: {
      Child
    }
  };
</script>

8、作用域插槽

示例:

<template>
  <Promised :promise="usersPromise">
    <template v-slot:pending>
      <p>Loading...</p>
    </template>

    <template v-slot="users">
      <ul>
        <li v-for="user in users">{{ user.name }}</li>
      </ul>
    </template>

    <template v-slot:rejected="error">
      <p>Error: {{ error.message }}</p>
    </template>
  </Promised>
</template>

9、.sync 修饰符

update:myPropName

  • 子组件触发
this.$emit('update:modalVisible', false)
  • 父组件 property
<ModalPane :modal-visible.sync="visible"></ModalPane>

10、动态组件与动态引入

示例:

<template>
  <card>
    <menu @on-select="onMenuSelect" />
    <component v-bind:is="currentComponent" />
  </card>
</template>

<script>
export default {
  name: 'Demo',
  data() {
    return {
      currentComponent: ''
    }
  },
  created() {
    const currentComponent = 'defaultComponent'
    this.onMenuSelect(currentComponent)
  },
  methods: {
    onMenuSelect(component) {
      import(`./${component}`).then(module => {
        this.currentComponent = module.default
      })
    }
  }
}
</script>

11、watch监听多个变量

示例:

export default {
    data() {
        return {
            msg1: 'apple',
            msg2: 'banana'
        }
    },
    compouted: {
        msgObj() {
            const { msg1, msg2 } = this
            return {
                msg1,
                msg2
            }
        }
    },
    watch: {
        msgObj: {
            handler(newVal, oldVal) {
                if (newVal.msg1 != oldVal.msg1) {
                    console.log('msg1 is change')
                }
                if (newVal.msg2 != oldVal.msg2) {
                    console.log('msg2 is change')
                }
            },
            deep: true
        }
    }
}

二、路由相关

1、使路由组件传参

一般情况会会这么做

export default {
    methods: {
        getParamsId() {
            return this.$route.params.id
        }
    }
}

使用 props 将组件和路由解耦之后

const User = {
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User, props: true }
  ]
})
  • 这样使用后路由组件可以解耦路由传参 $route.params 的任何参数。

2、按需 keep-alive 与强制刷新路由

router-view

<keep-alive>
		<router-view v-if="$route.meta.keepAlive" name="content" />
</keep-alive>
<router-view v-if="!$route.meta.keepAlive" name="content" :key="$route.fullPath" />

导航加 key

this.$router.push({ name: 'ToRouteName', query: { new: new Date().getTime() } })

优化 vue 性能技巧

Vue 开发小技巧

常见业务设计与注意问题

  • 权限集成
  • 弹窗封装
  • 分页查询列表
  • 地图业务

Code Review 时间

更多🤔


参考实践模版
开发者建议

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions