倒不了的dota

倒不了的dota 查看完整档案

北京编辑  |  填写毕业院校jd  |  前端攻城狮 编辑 github.com/Yuwenbinjie 编辑
编辑

积跬步,致千里

个人动态

倒不了的dota 收藏了文章 · 2月16日

vue + typescript 项目起手式

vue + typescript 新项目起手式

最后更新于2018-06-30,技术文具有时效性,请知悉

我知道你们早就想用上 vue + ts 强类型了

还有后续 vue + typescript 进阶篇

  • 安装vue-cli
  • 安装ts依赖
  • 配置 webpack
  • 添加 tsconfig.json
  • 添加 tslint.json
  • ts 识别 .vue
  • 改造 .vue文件

什么是typescript

TypeScriptJavaScript 的强类型版本。然后在编译期去掉类型和特有语法,生成纯粹的 JavaScript 代码。由于最终在浏览器中运行的仍然是 JavaScript,所以 TypeScript 并不依赖于浏览器的支持,也并不会带来兼容性问题。

TypeScriptJavaScript 的超集,这意味着他支持所有的 JavaScript 语法。并在此之上对 JavaScript 添加了一些扩展,如 class / interface / module 等。这样会大大提升代码的可阅读性。

与此同时,TypeScript 也是 JavaScript ES6 的超集,GoogleAngular 2.0 也宣布采用 TypeScript 进行开发。这更是充分说明了这是一门面向未来并且脚踏实地的语言。

强类型语言的优势在于静态类型检查,具体可以参见 http://www.zhihu.com/question... 的回答。概括来说主要包括以下几点:

  1. 静态类型检查
  2. IDE 智能提示
  3. 代码重构
  4. 可读性
静态类型检查可以避免很多不必要的错误, 不用在调试的时候才发现问题

前言

随着vue2.5 更好的 TypeScript 集成,同时因为新开项目的契机,故准备动手尝试一下typescript + vue

都说ts万般好,不如一个段子来的直观,一个程序员自从用上了ts之后,连续写了3000+行代码一次编译通过一气呵成,然后很激动的打电话跟老婆炫耀这件事情,老婆回了一句

之前看文章或者 demo 一直认为 vue + typescript 之后就不能友好的写.vue单文件,并且我也在各种 live 中问vue + ts 或者 flow的集成,也一直没有问出什么好的实践,但是本上强上ts的念头,一个字,就是干!

终于决定自己动手,那接下来从 vue-cli 开始配置 ts,看看事实上集成 ts 的体验到底是如何呢?


先贴一张最后配置完毕的.vue文件 ,template 和 style 跟以前的写法保持一致,只有 script 的变化

图片描述

起手vue-cli

这步应该不用写了

Vue 引入 TypeScript

首先Cli之后,接下来需要安装一些必要/以后需要的插件

安装vue的官方插件
npm i vue-class-component vue-property-decorator --save

// ts-loader typescript 必须安装,其他的相信你以后也会装上的
npm i ts-loader typescript tslint tslint-loader tslint-config-standard --save-dev

这些库大体的作用,可以按需引入:

配置 webpack

首先找到./build/webpack.base.conf.js

  • 找到entry.appmain.js 改成 main.ts, 顺便把项目文件中的main.js也改成main.ts, 里面内容保持不变
entry: {
  app: './src/main.ts'
}
  • 找到resolve.extensions 里面加上.ts 后缀 (是为了之后引入.ts的时候不写后缀)
  resolve: {
    extensions: ['.js', '.vue', '.json', '.ts'],
    alias: {
      '@': resolve('src')
    }
  }
  • 找到module.rules 添加webpack对.ts的解析
module: {
  rules: [
    {
      test: /\.(js|vue)$/,
      loader: 'eslint-loader',
      enforce: 'pre',
      include: [resolve('src'), resolve('test')],
      options: {
        formatter: require('eslint-friendly-formatter')
      }
    },
// 从这里复制下面的代码就可以了
    {
      test: /\.ts$/,
      exclude: /node_modules/,
      enforce: 'pre',
      loader: 'tslint-loader'
    },
    {
      test: /\.tsx?$/,
      loader: 'ts-loader',
      exclude: /node_modules/,
      options: {
        appendTsSuffixTo: [/\.vue$/],
      }
    },
// 复制以上的
  }
}

是不是加完了,那现在来解释一下

ts-loader 会检索当前目录下的 tsconfig.json 文件,根据里面定义的规则来解析.ts文件(就跟.babelrc的作用一样)

tslint-loader 作用等同于 eslint-loader

添加 tsconfig.json

接下来在根路径下创建tsconfig.json文件

这里有一份参考的 tsconfig.json 配置,完成的配置请点击 tsconfig.json

{
  // 编译选项
  "compilerOptions": {
    // 输出目录
    "outDir": "./output",
    // 是否包含可以用于 debug 的 sourceMap
    "sourceMap": true,
    // 以严格模式解析
    "strict": true,
    // 采用的模块系统
    "module": "esnext",
    // 如何处理模块
    "moduleResolution": "node",
    // 编译输出目标 ES 版本
    "target": "es5",
    // 允许从没有设置默认导出的模块中默认导入
    "allowSyntheticDefaultImports": true,
    // 将每个文件作为单独的模块
    "isolatedModules": false,
    // 启用装饰器
    "experimentalDecorators": true,
    // 启用设计类型元数据(用于反射)
    "emitDecoratorMetadata": true,
    // 在表达式和声明上有隐含的any类型时报错
    "noImplicitAny": false,
    // 不是函数的所有返回路径都有返回值时报错。
    "noImplicitReturns": true,
    // 从 tslib 导入外部帮助库: 比如__extends,__rest等
    "importHelpers": true,
    // 编译过程中打印文件名
    "listFiles": true,
    // 移除注释
    "removeComments": true,
    "suppressImplicitAnyIndexErrors": true,
    // 允许编译javascript文件
    "allowJs": true,
    // 解析非相对模块名的基准目录
    "baseUrl": "./",
    // 指定特殊模块的路径
    "paths": {
      "jquery": [
        "node_modules/jquery/dist/jquery"
      ]
    },
    // 编译过程中需要引入的库文件的列表
    "lib": [
      "dom",
      "es2015",
      "es2015.promise"
    ]
  }
}

顺便贴一份自己的配置

{
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules"
  ],
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "allowJs": true,
    "module": "esnext",
    "target": "es5",
    "moduleResolution": "node",
    "isolatedModules": true,
    "lib": [
      "dom",
      "es5",
      "es2015.promise"
    ],
    "sourceMap": true,
    "pretty": true
  }
}

添加 tslint.json

在根路径下创建tslint.json文件

这里就很简单了,就是 引入 tsstandard 规范

{
  "extends": "tslint-config-standard",
  "globals": {
    "require": true
  }
}

让 ts 识别 .vue

由于 TypeScript 默认并不支持 *.vue 后缀的文件,所以在 vue 项目中引入的时候需要创建一个 vue-shim.d.ts 文件,放在项目项目对应使用目录下,例如 src/vue-shim.d.ts

declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}
敲黑板,下面有重点!

意思是告诉 TypeScript*.vue 后缀的文件可以交给 vue 模块来处理。

而在代码中导入 *.vue 文件的时候,需要写上 .vue 后缀。原因还是因为 TypeScript 默认只识别 *.ts 文件,不识别 *.vue 文件:

import Component from 'components/component.vue'

改造 .vue 文件

在这之前先让我们了解一下所需要的插件(下面的内容需要掌握es7装饰器, 就是下面使用的@符号)

vue-class-component

vue-class-componentVue 组件进行了一层封装,让 Vue 组件语法在结合了 TypeScript 语法之后更加扁平化:

<template>
  <div>
    <input v-model="msg">
    <p>msg: {{ msg }}</p>
    <p>computed msg: {{ computedMsg }}</p>
    <button @click="greet">Greet</button>
  </div>
</template>

<script lang="ts">
  import Vue from 'vue'
  import Component from 'vue-class-component'

  @Component
  export default class App extends Vue {
    // 初始化数据
    msg = 123

    // 声明周期钩子
    mounted () {
      this.greet()
    }

    // 计算属性
    get computedMsg () {
      return 'computed ' + this.msg
    }

    // 方法
    greet () {
      alert('greeting: ' + this.msg)
    }
  }
</script>

上面的代码跟下面的代码作用是一样的

export default {
  data () {
    return {
      msg: 123
    }
  }

  // 声明周期钩子
  mounted () {
    this.greet()
  }

  // 计算属性
  computed: {
    computedMsg () {
      return 'computed ' + this.msg
    }
  }

  // 方法
  methods: {
    greet () {
      alert('greeting: ' + this.msg)
    }
  }
}

vue-property-decorator

vue-property-decorator 是在 vue-class-component 上增强了更多的结合 Vue 特性的装饰器,新增了这 7 个装饰器:

  • @Emit
  • @Inject
  • @Model
  • @Prop
  • @Provide
  • @Watch
  • @Component (从 vue-class-component 继承)

在这里列举几个常用的@Prop/@Watch/@Component, 更多信息,详见官方文档

import { Component, Emit, Inject, Model, Prop, Provide, Vue, Watch } from 'vue-property-decorator'

@Component
export class MyComponent extends Vue {
  
  @Prop()
  propA: number = 1

  @Prop({ default: 'default value' })
  propB: string

  @Prop([String, Boolean])
  propC: string | boolean

  @Prop({ type: null })
  propD: any

  @Watch('child')
  onChildChanged(val: string, oldVal: string) { }
}

上面的代码相当于:

export default {
  props: {
    checked: Boolean,
    propA: Number,
    propB: {
      type: String,
      default: 'default value'
    },
    propC: [String, Boolean],
    propD: { type: null }
  }
  methods: {
    onChildChanged(val, oldVal) { }
  },
  watch: {
    'child': {
      handler: 'onChildChanged',
      immediate: false,
      deep: false
    }
  }
}

开始修改App.vue文件

  1. script 标签上加上 lang="ts", 意思是让webpack将这段代码识别为typescript 而非javascript
  2. 修改vue组件的构造方式( 跟react组件写法有点类似, 详见官方 ), 如下图
  3. vue-property-decorator语法改造之前代码

clipboard.png

当然也可以直接复制下面的代码替换就可以了

<template>
  <div id="app">
    <img data-original="./assets/logo.png">
    <router-view/>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'

@Component({})
export default class App extends Vue {
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

</style>

接下来用相同的方式修改HelloWorld.vue即可

npm run dev

这个时候运行项目就应该能正常跑起来了

到这里我们的配置就已经结束了

最后

如果按照文章没有配置出来,可以参考此repovue-typescript-starter (安全按照文章一步一步操作的版本)

总的来说,就如本文最初讲,ts 从数据类型、结构入手,通过静态类型检测来增强你代码的健壮性,从而避免 bug 的产生。

同时可以继续使用.vue单文件

而且我个人认为加上了typescript,项目逼格提升2个level,也能让后端大哥们不吐槽js弱语言的诟病了

相信之后 vue 对于 ts 的集成会更加友善,期待尤大之后的动作

还有后续 vue + typescript 进阶篇

参考链接/推荐阅读

查看原文

倒不了的dota 赞了文章 · 2月16日

vue + typescript 项目起手式

vue + typescript 新项目起手式

最后更新于2018-06-30,技术文具有时效性,请知悉

我知道你们早就想用上 vue + ts 强类型了

还有后续 vue + typescript 进阶篇

  • 安装vue-cli
  • 安装ts依赖
  • 配置 webpack
  • 添加 tsconfig.json
  • 添加 tslint.json
  • ts 识别 .vue
  • 改造 .vue文件

什么是typescript

TypeScriptJavaScript 的强类型版本。然后在编译期去掉类型和特有语法,生成纯粹的 JavaScript 代码。由于最终在浏览器中运行的仍然是 JavaScript,所以 TypeScript 并不依赖于浏览器的支持,也并不会带来兼容性问题。

TypeScriptJavaScript 的超集,这意味着他支持所有的 JavaScript 语法。并在此之上对 JavaScript 添加了一些扩展,如 class / interface / module 等。这样会大大提升代码的可阅读性。

与此同时,TypeScript 也是 JavaScript ES6 的超集,GoogleAngular 2.0 也宣布采用 TypeScript 进行开发。这更是充分说明了这是一门面向未来并且脚踏实地的语言。

强类型语言的优势在于静态类型检查,具体可以参见 http://www.zhihu.com/question... 的回答。概括来说主要包括以下几点:

  1. 静态类型检查
  2. IDE 智能提示
  3. 代码重构
  4. 可读性
静态类型检查可以避免很多不必要的错误, 不用在调试的时候才发现问题

前言

随着vue2.5 更好的 TypeScript 集成,同时因为新开项目的契机,故准备动手尝试一下typescript + vue

都说ts万般好,不如一个段子来的直观,一个程序员自从用上了ts之后,连续写了3000+行代码一次编译通过一气呵成,然后很激动的打电话跟老婆炫耀这件事情,老婆回了一句

之前看文章或者 demo 一直认为 vue + typescript 之后就不能友好的写.vue单文件,并且我也在各种 live 中问vue + ts 或者 flow的集成,也一直没有问出什么好的实践,但是本上强上ts的念头,一个字,就是干!

终于决定自己动手,那接下来从 vue-cli 开始配置 ts,看看事实上集成 ts 的体验到底是如何呢?


先贴一张最后配置完毕的.vue文件 ,template 和 style 跟以前的写法保持一致,只有 script 的变化

图片描述

起手vue-cli

这步应该不用写了

Vue 引入 TypeScript

首先Cli之后,接下来需要安装一些必要/以后需要的插件

安装vue的官方插件
npm i vue-class-component vue-property-decorator --save

// ts-loader typescript 必须安装,其他的相信你以后也会装上的
npm i ts-loader typescript tslint tslint-loader tslint-config-standard --save-dev

这些库大体的作用,可以按需引入:

配置 webpack

首先找到./build/webpack.base.conf.js

  • 找到entry.appmain.js 改成 main.ts, 顺便把项目文件中的main.js也改成main.ts, 里面内容保持不变
entry: {
  app: './src/main.ts'
}
  • 找到resolve.extensions 里面加上.ts 后缀 (是为了之后引入.ts的时候不写后缀)
  resolve: {
    extensions: ['.js', '.vue', '.json', '.ts'],
    alias: {
      '@': resolve('src')
    }
  }
  • 找到module.rules 添加webpack对.ts的解析
module: {
  rules: [
    {
      test: /\.(js|vue)$/,
      loader: 'eslint-loader',
      enforce: 'pre',
      include: [resolve('src'), resolve('test')],
      options: {
        formatter: require('eslint-friendly-formatter')
      }
    },
// 从这里复制下面的代码就可以了
    {
      test: /\.ts$/,
      exclude: /node_modules/,
      enforce: 'pre',
      loader: 'tslint-loader'
    },
    {
      test: /\.tsx?$/,
      loader: 'ts-loader',
      exclude: /node_modules/,
      options: {
        appendTsSuffixTo: [/\.vue$/],
      }
    },
// 复制以上的
  }
}

是不是加完了,那现在来解释一下

ts-loader 会检索当前目录下的 tsconfig.json 文件,根据里面定义的规则来解析.ts文件(就跟.babelrc的作用一样)

tslint-loader 作用等同于 eslint-loader

添加 tsconfig.json

接下来在根路径下创建tsconfig.json文件

这里有一份参考的 tsconfig.json 配置,完成的配置请点击 tsconfig.json

{
  // 编译选项
  "compilerOptions": {
    // 输出目录
    "outDir": "./output",
    // 是否包含可以用于 debug 的 sourceMap
    "sourceMap": true,
    // 以严格模式解析
    "strict": true,
    // 采用的模块系统
    "module": "esnext",
    // 如何处理模块
    "moduleResolution": "node",
    // 编译输出目标 ES 版本
    "target": "es5",
    // 允许从没有设置默认导出的模块中默认导入
    "allowSyntheticDefaultImports": true,
    // 将每个文件作为单独的模块
    "isolatedModules": false,
    // 启用装饰器
    "experimentalDecorators": true,
    // 启用设计类型元数据(用于反射)
    "emitDecoratorMetadata": true,
    // 在表达式和声明上有隐含的any类型时报错
    "noImplicitAny": false,
    // 不是函数的所有返回路径都有返回值时报错。
    "noImplicitReturns": true,
    // 从 tslib 导入外部帮助库: 比如__extends,__rest等
    "importHelpers": true,
    // 编译过程中打印文件名
    "listFiles": true,
    // 移除注释
    "removeComments": true,
    "suppressImplicitAnyIndexErrors": true,
    // 允许编译javascript文件
    "allowJs": true,
    // 解析非相对模块名的基准目录
    "baseUrl": "./",
    // 指定特殊模块的路径
    "paths": {
      "jquery": [
        "node_modules/jquery/dist/jquery"
      ]
    },
    // 编译过程中需要引入的库文件的列表
    "lib": [
      "dom",
      "es2015",
      "es2015.promise"
    ]
  }
}

顺便贴一份自己的配置

{
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules"
  ],
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "allowJs": true,
    "module": "esnext",
    "target": "es5",
    "moduleResolution": "node",
    "isolatedModules": true,
    "lib": [
      "dom",
      "es5",
      "es2015.promise"
    ],
    "sourceMap": true,
    "pretty": true
  }
}

添加 tslint.json

在根路径下创建tslint.json文件

这里就很简单了,就是 引入 tsstandard 规范

{
  "extends": "tslint-config-standard",
  "globals": {
    "require": true
  }
}

让 ts 识别 .vue

由于 TypeScript 默认并不支持 *.vue 后缀的文件,所以在 vue 项目中引入的时候需要创建一个 vue-shim.d.ts 文件,放在项目项目对应使用目录下,例如 src/vue-shim.d.ts

declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}
敲黑板,下面有重点!

意思是告诉 TypeScript*.vue 后缀的文件可以交给 vue 模块来处理。

而在代码中导入 *.vue 文件的时候,需要写上 .vue 后缀。原因还是因为 TypeScript 默认只识别 *.ts 文件,不识别 *.vue 文件:

import Component from 'components/component.vue'

改造 .vue 文件

在这之前先让我们了解一下所需要的插件(下面的内容需要掌握es7装饰器, 就是下面使用的@符号)

vue-class-component

vue-class-componentVue 组件进行了一层封装,让 Vue 组件语法在结合了 TypeScript 语法之后更加扁平化:

<template>
  <div>
    <input v-model="msg">
    <p>msg: {{ msg }}</p>
    <p>computed msg: {{ computedMsg }}</p>
    <button @click="greet">Greet</button>
  </div>
</template>

<script lang="ts">
  import Vue from 'vue'
  import Component from 'vue-class-component'

  @Component
  export default class App extends Vue {
    // 初始化数据
    msg = 123

    // 声明周期钩子
    mounted () {
      this.greet()
    }

    // 计算属性
    get computedMsg () {
      return 'computed ' + this.msg
    }

    // 方法
    greet () {
      alert('greeting: ' + this.msg)
    }
  }
</script>

上面的代码跟下面的代码作用是一样的

export default {
  data () {
    return {
      msg: 123
    }
  }

  // 声明周期钩子
  mounted () {
    this.greet()
  }

  // 计算属性
  computed: {
    computedMsg () {
      return 'computed ' + this.msg
    }
  }

  // 方法
  methods: {
    greet () {
      alert('greeting: ' + this.msg)
    }
  }
}

vue-property-decorator

vue-property-decorator 是在 vue-class-component 上增强了更多的结合 Vue 特性的装饰器,新增了这 7 个装饰器:

  • @Emit
  • @Inject
  • @Model
  • @Prop
  • @Provide
  • @Watch
  • @Component (从 vue-class-component 继承)

在这里列举几个常用的@Prop/@Watch/@Component, 更多信息,详见官方文档

import { Component, Emit, Inject, Model, Prop, Provide, Vue, Watch } from 'vue-property-decorator'

@Component
export class MyComponent extends Vue {
  
  @Prop()
  propA: number = 1

  @Prop({ default: 'default value' })
  propB: string

  @Prop([String, Boolean])
  propC: string | boolean

  @Prop({ type: null })
  propD: any

  @Watch('child')
  onChildChanged(val: string, oldVal: string) { }
}

上面的代码相当于:

export default {
  props: {
    checked: Boolean,
    propA: Number,
    propB: {
      type: String,
      default: 'default value'
    },
    propC: [String, Boolean],
    propD: { type: null }
  }
  methods: {
    onChildChanged(val, oldVal) { }
  },
  watch: {
    'child': {
      handler: 'onChildChanged',
      immediate: false,
      deep: false
    }
  }
}

开始修改App.vue文件

  1. script 标签上加上 lang="ts", 意思是让webpack将这段代码识别为typescript 而非javascript
  2. 修改vue组件的构造方式( 跟react组件写法有点类似, 详见官方 ), 如下图
  3. vue-property-decorator语法改造之前代码

clipboard.png

当然也可以直接复制下面的代码替换就可以了

<template>
  <div id="app">
    <img data-original="./assets/logo.png">
    <router-view/>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'

@Component({})
export default class App extends Vue {
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

</style>

接下来用相同的方式修改HelloWorld.vue即可

npm run dev

这个时候运行项目就应该能正常跑起来了

到这里我们的配置就已经结束了

最后

如果按照文章没有配置出来,可以参考此repovue-typescript-starter (安全按照文章一步一步操作的版本)

总的来说,就如本文最初讲,ts 从数据类型、结构入手,通过静态类型检测来增强你代码的健壮性,从而避免 bug 的产生。

同时可以继续使用.vue单文件

而且我个人认为加上了typescript,项目逼格提升2个level,也能让后端大哥们不吐槽js弱语言的诟病了

相信之后 vue 对于 ts 的集成会更加友善,期待尤大之后的动作

还有后续 vue + typescript 进阶篇

参考链接/推荐阅读

查看原文

赞 404 收藏 385 评论 104

倒不了的dota 发布了文章 · 2019-12-23

微信小程序开发实践:带你从0到1实现第一个小程序,并发布上线

前言

对Vue开发有一定了解,对微信小程序比较感兴趣,想要理解其开发流程,这篇文章可以帮助你少踩一些坑,实现并发布自己的第一个微信小程序。
gh_933be55951e2_258 (1).jpg

准备工作

  1. 登陆微信公众平台-小程序:https://mp.weixin.qq.com/cgi-bin/wx,完成申请与注册,生成一个重要的AppID(小程序唯一身份证:开发→开发设置获取)。
  2. 安装小程序开发工具:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html,界面使用类其他编译器,点击上方编译可在模拟器查看效果,点击预览可用手机扫码查看效果,如图:Jietu20191120-150156.jpg
  3. 创建第一个小程序项目,填入AppID。选择云开发可使用云函数、云数据库、云存储功能,如图:18237192-dcfbec816161eb51.png
  4. 小程序详细开发文档:https://developers.weixin.qq.com/miniprogram/dev/framework/quickstart/(遇到问题一定要看文档!看文档!看文档!重要的事情说3遍!!!详细易懂,少走弯路)

一. 小程序代码构成

1)文件目录:

.
├── cloudfunctions # 云开发函数
│   ├── login //登陆文件夹
│   │     ├── index.js //登陆云函数
│   │     └──package.json //npm包依赖
│   └── movielist //获取电影列表文件夹
├── miniprogram #本地开发目录
│   ├── style //静态wxss文件
│   ├── fonts //字体图标文件
│   ├── images //图片
│   ├── components //存放公共组件库
│   ├── utils //全局js方法
│   │     └── common.wxs //js处理方法
│   ├── pages //各页面
│   │     ├── movie //电影列表
│   │     │     ├── movie.js
│   │     │     ├── movie.json
│   │     │     ├── movie.wxml
│   │     │     └── movie.wxss
│   │     └── detail //电影详情
│   │           ├── detail.js
│   │           ├── detail.json
│   │           ├── detail.wxml
│   │           └── detail.wxss
│   ├── app.js //全局注册小程序对象
│   ├── app.json //全局配置
│   ├── app.wxss //全局样式
│   └── package.json //npm配置文件
├── project.config.json #项目配置文件
└── README.md #README

2).json 后缀的 JSON 配置文件:

  • app.json全局配置小程序的页面路由,底部tab,顶部标题等。
  • page.json页面配置可以声明不同于全局的配置,只在该页面生效,还可引入组件。

pages字段控制当前所有页面路径,第一个默认为首页;
window字段控制窗口背景颜色,标题等;
tabBar控制分成几个tab页,list数组长度2~5,参数有图标、字体颜色、页面路由。

<!-- app.json -->
//全局配置文件
"pages": [
    "pages/movie/movie",//第一个默认为首页
    "pages/profile/profile",
    "pages/detail/detail"
  ],
  "window": {
    "backgroundColor": "#F6F6F6",//下拉背景颜色
    "backgroundTextStyle": "light",//下拉字体样式
    "navigationBarBackgroundColor": "#00B51D",//窗口头部背景色
    "navigationBarTitleText": "最新电影",//窗口头部文案
    "navigationBarTextStyle": "white"//窗口头部字体颜色
  },
  "tabBar": {
    "color": "#000000",//tab栏默认字体颜色
    "selectedColor": "#E54847",//tab栏选中字体颜色
    "list": [{
      "pagePath": "pages/movie/movie",//tab路径
      "text": "电影",//tab栏文案
      "iconPath": "images/film.png",//默认图标路径
      "selectedIconPath": "images/film-actived.png"//选中图标路径
    },{
      "pagePath": "pages/profile/profile",
      "text": "我的",
      "iconPath": "images/profile.png",
      "selectedIconPath": "images/profile-actived.png"
    }]
  },
<!-- detail.json -->
//子页面配置文件
{
  "usingComponents": {//注册vant-ui插件
    "van-button": "vant-weapp/button",//按钮组件
    "van-rate": "vant-weapp/rate",//评分组件
    "van-icon": "vant-weapp/icon"//图标组件
  },
  "navigationBarBackgroundColor": "#333",//子页面标题背景色
  "navigationBarTextStyle": "white",//子页面字体颜色
  "navigationBarTitleText": "电影详情",//子页面标题
  "backgroundColor": "#eee",//子页面下拉刷新背景色
  "backgroundTextStyle": "dark",//子页面下拉刷新字体色
  "enablePullDownRefresh": true//子页面下拉刷新
}

3).wxml 后缀的 WXML 模板文件:

主要控制页面结构,开发模式为MVVM可参考Vue,注意如下:

  1. div,span等标签换为了view,text;
  2. 微信提供了一些现成组件:轮播图、日历、进度条等等,也可使用UI库vant-weapp。
  3. 在wxml文件中不能使用js方法,需在小程序脚本语言wxs文件中(不允许引入js文件)定义好处理数据的函数,然后在wxml中引入可使用。
<!-- common.wxs -->
var filter = {
    arrJoin: function(value){ return value.join('/') },
    formatDate: function(value){
        var date = getDate(value) return date.getFullYear() + '年' + date.getMonth() + '月' + date.getDate() + '日'
} }
module.exports={ arrJoin: filter.arrJoin, formatDate: filter.formatDate }
<!-- movie.wxml -->
<wxs module="filter" data-original="../../utils/common.wxs"></wxs>
<view class="movie" wx:for="{{movieList}}" wx:key="{{index}} wx:for-item="detailList"">
  ...
  <!--调用wxs文件中的数组分割方法-->
  <view>类型:{{filter.arrJoin(detailList.genres)}}</view>
  <!--调用wxs文件中的时间格式化方法-->
  <text class="tag-text">{{filter.formatDate(detailList.created_at)}}</text>
  ...
  <!--使用按钮和评分组件-->
  <van-rate value="{{detailList.rating.average}}" size="12" void-color="#999" void-icon="star" />
  <van-button icon="like-o" type="primary" size="small">想看</van-button>
  ...
</view>

4).wxss 后缀的 WXSS 样式文件:

提供了新的尺寸单位rpx(可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素)。推荐使用iPhone6为设计稿,单位之间好换算,字体一般用px。

设备rpx换算px (屏幕宽度/750)px换算rpx (750/屏幕宽度)
iPhone51rpx = 0.42px1px = 2.34rpx
iPhone61rpx = 0.5px1px = 2rpx
iPhone6 Plus1rpx = 0.552px1px = 1.81rpx
/* pages/movie/movie.wxss */
@import '../../style/common.wxss'; /* 引入公共样式 */

.movie{
  height: 420rpx;
  display: flex;
  border-bottom: 1rpx solid #eee;
  padding: 20rpx;
  color: #666;
}
.mv-img{
  height: 100%;
  width: 300rpx;
  margin-right: 20rpx;
}
...

5).js 后缀的 JS 脚本逻辑文件:

注意事项:

  1. 点击事件不能在方法中直接传参数,需要data-id="id"定义,通过e.currentTarget.dataset.id获取;
  2. 给data中数据赋值时需this.setData({id: e.currentTarget.dataset.id}),获取值方式为this.data.id。
<!--movie.wxml-->
...
<view class="collect-item" bindtap="gotoDetail" data-movieid="{{item.id}}">
...
<!--movie.js-->
getMovie: function(){//获取电影列表,从云函数movielist获取
    wx.showLoading({//调用微信加载组件
      title: '加载中',
    })
    wx.cloud.callFunction({//请求云函数
      name: 'movielist',
      data: {
        start: this.data.movieList.length,
        count: 10
      }
    }).then(res=>{
      console.log(JSON.parse(res.result).subjects)
      this.setData({//将返回结果赋值给movieList
        movieList: this.data.movieList.concat(JSON.parse(res.result).subjects)//数组拼接
      })
      wx.hideLoading()//返回结果关闭加载
    }).catch(err=>{
      console.log(err)
      wx.hideLoading()//返回错误关闭加载
    })
  },
gotoDetail: function (e) {//跳转至新页面
  wx.navigateTo({ 
    url: `../detail/detail?movieid=${e.currentTarget.dataset.movieid}`, 
  }) 
},

二、使用npm安装第三方插件

  1. 右键点击项目在终端打开,执行npm init初始化package.json文件;
  2. 执行npm install XX --production;
  3. 勾选 详情->本地设置→使用npm模块;
  4. 点击工具→构建npm,即可在项目中使用;

三、微信原生API

可以方便的调起微信提供的能力,列举一些常用API:

  • wx.navagateTo:保留当前页面,跳转到除了tabbar的其他页面,最多打开5个页面,可返回上一页;
  • wx.redirectTo:关闭当前页面,跳转到除了tabbar的其他页面;
  • wx.switchTab:跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面;
  • wx.showToast:显示消息提示框,可设置延时消失;
  • wx.showLoading:显示 loading 提示框。需主动调用 wx.hideLoading 才能关闭提示框;
  • wx.request:发起HTTP网络请求;
  • wx.chooseImage:从本地相册选择图片或使用相机拍照,视频/音频/文件同理;
  • wx.getLocation:获取当前的地理位置、速度;
  • wx.chooseLocation:打开地图选择位置,可获取目标经纬度和详细地址;
  • wx.getUserInfo:获取用户信息,用户昵称、性别、头像、国家城市、语言等;
  • wx.getWeRunData:获取用户过去30天微信运动步数;
  • wx.scanCode:调起客户端扫码界面进行扫码;
  • wx.makePhoneCall:拨打电话;

......

三、云开发

1)云数据库:

云开发提供了一个 JSON 数据库,数据库中的每条记录都是一个 JSON 格式的对象。一个数据库可以有多个集合(相当于关系型数据中的表),集合可看做一个 JSON 数组,数组中的每个对象就是一条记录,记录的格式是 JSON 对象。支持导入导出。
云开发可视化面板

// 1. 获取数据库引用
// collection 方法获取一个集合的引用
const db = wx.cloud.database()

//2. 构造查询语句 
// get 方法会触发网络请求,往数据库取数据
// where 方法传入一个对象,数据库返回集合中字段等于指定值的 JSON 文档。API 也支持高级的查询条件(比如大于、小于、in 等)
db.collection('movie-collect').where({
   _openid: res.result.openid
   }).get().then(res=>{
   console.log(res)
 }).catch(err=>{
   console.log(err)
 })

// 3. 构造插入语句
// add 方法会触发网络请求,往数据库添加数据
db.collection('user').add({
   data: {
     name: 'ywbj',
     age: 20
   }
 }).then(res => {
   console.log(res)
 }).catch(err => {
   console.log(err)
 })
},

// 4. 构造更新语句
// update 方法会触发网络请求,往数据库更新数据(doc中为唯一id)
db.collection('user').doc('dc9a49695da03b0f023d6cfd2fa15012').update({
 data: {
   age: 18
 }
 }).then(res => {
   console.log(res)
 }).catch(err => {
   console.log(err)
 })
},

// 5. 构造删除语句
// delete 方法会触发网络请求,使数据库删除数据(注意:在小程序中只能删除单条数据,批量删除需在云函数中操作)
db.collection('user').where({
   name: 'ywbj'
 }).remove()
 .then(res => {
   console.log(res)
 }).catch(err => {
   console.log(err)
 })
},

2)云存储:

云开发提供了一块存储空间,提供了上传文件到云端、带权限管理的云端下载能力,开发者可以在小程序端和云函数端通过 API 使用云存储功能。在小程序端可以分别调用 wx.cloud.uploadFile 和 wx.cloud.downloadFile 完成上传和下载云文件操作。

//上传文件
wx.chooseImage({// 让用户选择一张图片,生成临时图片路径
  success: chooseResult => { // 将图片上传至云存储空间
      wx.cloud.uploadFile({ // 指定上传到的云路径
          cloudPath: 'my-photo.png', // 指定要上传的文件的小程序临时文件路径
          filePath: chooseResult.tempFilePaths[0],
          success: res => { 
            console.log('上传成功,返回文件ID', res.fileID) 
          },
      }) 
   }
})
//下载文件
wx.cloud.downloadFile({
  fileID: '', // 文件 ID
  success: res => { // 返回临时文件路径 console.log(res.tempFilePath) },
  fail: console.error
})

3)云函数:

云函数是一段运行在云端的代码,无需管理服务器,在开发工具内编写、一键上传部署即可运行后端代码。可将功能相同的函数封装并上传至云函数统一调用,另外使用云函数发送请求的好处,不受5个可信域名限制,无需备案。发请求需npm安装request-promise并引入:https://github.com/request/request-promise

<!--movielist/index.js-->// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
var rp = require('request-promise')
// 云函数入口函数,假设已有一个获取电影列表的接口
exports.main = async (event, context) => {
 return rp(`http://api.douban.com/v2/movie/in_theaters?apikey=0df993c66c0c636e29ecbb5344252a4a&start=${event.start}&count=${event.count}`)
 .then(res => {
 return res
 })
}
 
<!--movie.js-->
wx.cloud.callFunction({
 name: 'movielist',
 data: {
   start: this.data.movieList.length,
   count: 10
 }
 })
查看原文

赞 0 收藏 0 评论 0

倒不了的dota 赞了文章 · 2019-07-16

实用webpack插件之DefinePlugin

clipboard.png

通过阅读这篇文章,可以学习到如何使用DefinePlugin插件使得前端项目更加工程化,说清晰点就是如何使用这个插件,在编译阶段根据NODE_ENV自动切换配置文件,提升前端开发效率。

  • DefinePlugin的正确用法
  • 如何使用DefinePlugin添加配置文件,构建期间自动检测环境变化,也就是如何根据NODE_ENV引入配置文件?

DefinePlugin的正确用法

DefinePlugin中的每个键,是一个标识符或者通过.作为多个标识符。

  • 如果value是一个字符串,它将会被当做code片段
  • 如果value不是字符串,它将会被stringify(包括函数)
  • 如果value是一个对象,则所有key的定义方式相同。
  • 如果key有typeof前缀,它只是对typeof 调用定义的。

这些值将内联到代码中,压缩减少冗余。

new webpack.DefinePlugin({
    PRODUCTION: JSON.stringify(true),
    VERSION: JSON.stringify('5fa3b9'),
    BROWSER_SUPPORTS_HTML5: true,
    TWO: '1+1',
    'typeof window': JSON.stringify('object'),
    'process.env': {
         NODE_ENV: JSON.stringify(process.env.NODE_ENV)
     }
});
console.log('Running App version' + VERSION);

plugin不是直接的文本值替换,它的值在字符串内部必须包括实际引用。典型的情况是用双引号或者JSON.stringify()进行引用,'"production"',JSON.stringify('production')。

重点:在vue-cli创建的项目中,凡是src下的文件,都可以访问到VERSION这个变量,例如main.js,App.vue等等

我们现在看一下上面的几种类型的key值,在代码中的输出。

console.log(PRODUCTION, VERSION, BROWSER_SUPPORTS_HTML5, TWO, typeof window, process.env);
PRODUCTION: true,
VERSION: "5fa3b9",
BROWSER_SUPPORTS_HTML5: true,
TWO: 2,
typeof window: "object",
process.env: {NODE_ENV: "development"},

在代码中,我们一般会有以下几种用途:

  • 根据process.env.NODE_ENV区分环境
  • 引入配置文件
  • 根据NODE_ENV引入配置文件(这个很重要,后面会讲到)
Feature Flag

可以控制新特性和实验特性的开关。

new webpack.DefinePlugin({
    'NICE_FEATURE': JSON.stringify(true),
    'EXPERIMENTAL': JSON.stringify(false),
})
process.env.NODE_ENV的正确配置方式是什么?
process: {
    env: {
        NODE_ENV: JSON.stringify('production')
    }
}

评价:非常不好,会overwrite整个process对象,仅仅保留新的NODE_ENV,破坏进程。
原始的process对象包含如下内容 ,包含了当前进程的很多信息。

process {
  title: 'node',
  version: 'v8.11.2',
  moduleLoadList: 
   [ 'Binding contextify',],
  versions: 
   { http_parser: '2.8.0'},
  arch: 'x64',
  platform: 'darwin',
  release: 
   { name: 'node' },
  argv: [ '/usr/local/bin/node' ],
  execArgv: [],
  env: 
   { TERM: 'xterm-256color'},
  pid: 14027,
  features: 
   { debug: false},
  ppid: 14020,
  execPath: '/usr/local/bin/node',
  debugPort: 9229,
  _startProfilerIdleNotifier: [Function: _startProfilerIdleNotifier],
  _stopProfilerIdleNotifier: [Function: _stopProfilerIdleNotifier],
  _getActiveRequests: [Function: _getActiveRequests],
  _getActiveHandles: [Function: _getActiveHandles],
  reallyExit: [Function: reallyExit],
  abort: [Function: abort],
  chdir: [Function: chdir],
  cwd: [Function: cwd],
  umask: [Function: umask],
  getuid: [Function: getuid],
  geteuid: [Function: geteuid],
  setuid: [Function: setuid],
  seteuid: [Function: seteuid],
  setgid: [Function: setgid],
  setegid: [Function: setegid],
  getgid: [Function: getgid],
  getegid: [Function: getegid],
  getgroups: [Function: getgroups],
  setgroups: [Function: setgroups],
  initgroups: [Function: initgroups],
  _kill: [Function: _kill],
  _debugProcess: [Function: _debugProcess],
  _debugPause: [Function: _debugPause],
  _debugEnd: [Function: _debugEnd],
  hrtime: [Function: hrtime],
  cpuUsage: [Function: cpuUsage],
  dlopen: [Function: dlopen],
  uptime: [Function: uptime],
  memoryUsage: [Function: memoryUsage],
  binding: [Function: binding],
  _linkedBinding: [Function: _linkedBinding],
  _events: 
   { newListener: [Function],
     removeListener: [Function],
     warning: [Function],
     SIGWINCH: [ [Function], [Function] ] },
  _rawDebug: [Function],
  _eventsCount: 4,
  domain: [Getter/Setter],
  _maxListeners: undefined,
  _fatalException: [Function],
  _exiting: false,
  assert: [Function],
  config: {},
  emitWarning: [Function],
  nextTick: [Function: nextTick],
  _tickCallback: [Function: _tickDomainCallback],
  _tickDomainCallback: [Function: _tickDomainCallback],
  stdout: [Getter],
  stderr: [Getter],
  stdin: [Getter],
  openStdin: [Function],
  exit: [Function],
  kill: [Function],
  _immediateCallback: [Function: processImmediate],
  argv0: 'node' }
'process.env': {
    NODE_ENV: JSON.stringify('production')
}

评价:不好,会overwrite整个process.env对象,破坏进程环境,导致破坏兼容性。
原始的process.env对象包含如下内容 ,包含了当前进程的很多信息。

{ TERM: 'xterm-256color',
  SHELL: '/bin/bash',
  TMPDIR: '/var/folders/lw/rl5nyyrn4lb0rrpspv4szc3c0000gn/T/',
  Apple_PubSub_Socket_Render: '/private/tmp/com.apple.launchd.dEPuHtiDsx/Render',
  USER: 'frank',
  SSH_AUTH_SOCK: '/private/tmp/com.apple.launchd.MRVOOE7lpI/Listeners',
  __CF_USER_TEXT_ENCODING: '0x1F5:0x19:0x34',
  PATH: '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/Wireshark.app/Contents/MacOS',
  PWD: '/Users/frank/Desktop/corporation/weidian-crm',
  XPC_FLAGS: '0x0',
  XPC_SERVICE_NAME: '0',
  SHLVL: '1',
  HOME: '/Users/frank',
  LOGNAME: 'frank',
  LC_CTYPE: 'zh_CN.UTF-8',
  _: '/usr/local/bin/node' }
'process.env.NODE_ENV': JSON.stringify('production')

评价:好。因为仅仅对NODE_ENV值进行修改,不会破坏完整进程,也不会破坏兼容性。

如何使用DefinePlugin添加配置文件,构建期间自动检测环境变化,也就是如何根据NODE_ENV引入配置文件?

情景:开发阶段的接口地址往往与生产阶段的接口地址是不一致的。例如开发时是development.foo.com,而生产时是production.foo.com,如果需要打包发布,那么需要手动去替换域名或者是一个分支维护一个专门的配置文件,这两种方式是非常笨重的。

  • 手动替换

文件效率低下,每次在development和production见切换都需要进行配置文件的更新,容易出错

  • 配置文件

相对手动替换高级一些,但是不能一次性查看development和production的全部配置信息,需要在分支间切换,效率低下,且不适用于多种环境的配置

  • webpack.DefinePlugin()

全局配置文件,自动检测环境变化,效率高效。

webpack的DefinePlugin正是为我们解决这样一个问题,它维护一个全局的配置文件,在编译期间会自动检测process.env.NODE_ENV,根据当前的环境变量去替换我们的接口域名。

下面我将以一个实例来介绍如何正确使用webpack.DefinePlugin。

/config/api.js

const NODE_ENV = process.env.NODE_ENV;
const config = {
     production: {
        FOO_API: 'production.foo.api.com',
        BAR_API: 'production.bar.api.com',
        BAZ_API: 'production.baz.api.com',
     },
     development: {
        FOO_API: 'development.foo.api.com',
        BAR_API: 'development.bar.api.com',
        BAZ_API: 'development.baz.api.com',
     },
     test: {
        FOO_API: 'test.foo.api.com',
        BAR_API: 'test.bar.api.com',
        BAZ_API: 'test.baz.api.com',
     }
}
module.exports = config[NODE_ENV];

webpack.dev.conf.js/webpack.prod.conf.js/webpack.test.conf.js

const apiConfig = require('./config/api');
const webpackConfig = {
    plugins: [
        new webpack.DefinePlugin({
            API_CONFIG: JSON.stringify(apiConfig);
        })
    ]
}
...

custom.component.vue

<template>
...
</template>
<script>
// 这里也可以访问到API_CONFIG
export default {
    // 这里无论是data函数,methods对象,computed对象,watch对象,都可以访问到API_CONFIG;
   data() {
       return {
           fooApi: API_CONFIG.FOO_API,
           user:{
               id: '',
               name: '',
           },
           hash: '',
        } 
    },
    computed: {
        userAvator() {
            return `${API_CONFIG.BAR_API}?id=${user.id}&name=${user.name}`
        }
    },
    methods: {
        uploadImage() {
            api.uploadImage({user: `${API_CONFIG.BAZ}\${hash}`})
                 .then(()=>{})
                 .catch(()=>{})
        }
    }
}
</script>

上述仅仅适用于vue-cli2.0时代,vue-cli3.0引入了webpack-chain,配置方式大大不同,下文将给出示例。

如何在vue.config.js中,使用使用DefinePlugin添加配置文件,构建期间自动检测环境变化,也就是如何根据NODE_ENV引入配置文件?

vue.config.js

const apiConfig = require('./config/api');

module.exports = {
    chainWebpack: config => {
        config
            .plugin('define')
            .tap(args => { 
                args[0].API_CONFIG = JSON.stringify(apiConfig)
                return args
            })
    }
}

需要注意的是,在vue-cli3.0中,我们不能直接SET NODE_ENV=production或者EXPORT NODE_ENV=production。
因为vue-cli-servive有3种模式,serve默认为development,build为production,若想修改vue-cli-service包中的NODE_ENV,需要通过vue-cli-service serve --mode production进行切换。
就像下面这样:

{
  "scripts": {
    "dev": "vue-cli-service serve", // mode默认为development 
    "production": "vue-cli-service serve --mode production", 
  },
}

注意:我们只能在development, production或者test 3个模式下进行切换,不能引入类似preproduction之类的自定义node环境,但是实际上这3个环境已经足以满足大多数的开发情况。

为什么vue-cli 3.0中的DefinePlugin可以用config.plugin('define')修改入参?

源码文件base.js中,有下面的代码:

    webpackConfig
      .plugin('define')
        .use(require('webpack/lib/DefinePlugin'), [
          resolveClientEnv(options)
        ])

这一点很关键!我们在vue.config.js中拿到的config.plugin('define'),实际上时vue-service内部创建的webpack.DefinePlugin实例的引用 !
明确了这一点,我们在以后增强webpack默认插件配置时,需要先到vue-service的源码中寻找一番,看看有没有对应plugin的引用,若有,必须根据vue-service定义的名字直接引用,否则会修改失败。

期待和大家交流,共同进步,欢迎大家加入我创建的与前端开发密切相关的技术讨论小组:

努力成为优秀前端工程师!

查看原文

赞 23 收藏 16 评论 7

倒不了的dota 关注了用户 · 2019-06-06

Ludovic @ludovic

关注 1

倒不了的dota 发布了文章 · 2019-06-05

点击网页空白区域,隐藏当前div弹框——jquery内置方法(closest)

一. closest方法介绍

1)定义

closest先检查当前元素是否匹配,如果匹配则直接返回元素本身;如果不匹配就会逐级向上查找父元素,直到找到匹配选择器的元素。如果什么都没找到则返回一个空的jQuery对象。

2)closest与parent方法对比

  • closest从当前元素开始向上匹配寻找,parent从父元素开始向下匹配寻找;
  • closest逐级向上查找,直到发现匹配的元素后就停止了,parent一直查找到根元素,然后把匹配的元素放进一个集合中;
  • closest返回0或1个元素,parent可能包含0个或多个元素。

二. 实践:点击蒙版后隐藏当前div

若点击时的对象在div上,返回的对象length为1;若点在div之外的区域,closest找不到匹配的元素返回对象length为0。因此可以实现当没有点击div时(返回对象length为0),隐藏div。

$('body').on('click', (ev)=>{
    let dom1 = $(ev.target).closest('.box').length//0为没有匹配找到,1为找到
    if (dom1 < 1) {//点击div之外时,将其隐藏
        this.show = false //定义v-show="show"控制div显示和隐藏
    }
})

图片描述

查看原文

赞 0 收藏 0 评论 0

倒不了的dota 发布了文章 · 2019-06-05

前端组件库(Element UI)的实现原理:教你如何实现自定义组件库,并打包发布至npm

前言

对Vue组件开发有一定了解,对UI组件库的实现比较感兴趣,想要理解其开发原理,这篇文章可以帮助你实现并发布自己的第一个自定义插件。

Demo地址

建议将demo拉取至本地,结合代码更容易理解:
git地址:https://github.com/Yuwenbinji...

git clone git@github.com:Yuwenbinjie/ywbj-ui.git
cd ywbj-ui/
npm i
npm run dev

一. 组件库开发流程

1)新建vue项目:

文件目录如下图

.
├── dist # 压缩后文件目录
├── site # 项目结构目录
│   ├── App.vue # 单页应用父组件 
│   └── main.js # 单页应用启动入口文件
├── src # 开发目录
│   ├── assets # 静态文件:scss/image
│   ├── components # 存放公共组件库
│   ├── index.js # 全局注册组件插件
│   └── style.js # 导入scss
├── index.html
├── package.json # 依赖管理
├── webpack.dev.js #项目启动配置文件:npm run dev
├── webpack.config.js #项目打包配置文件:npm run build
└── README.md #README

2)编写自定义组件,封装成插件:

业务组件和功能组件的主要区别

  • slot插槽占位符,可以实现父子组件传参,父组件templet模版可将子组件slot内容替换。当slot未命名时将父组件全部替换,当定义name时,可以实现父组件对子组件的指定位置显示内容或传参;

    <div :class="preCls+'-title'" v-show="showTitle">
      <slot name="modal-title">自定义标题</slot>
    </div>
    <div :class="preCls+'-body'" v-show="showBody">
       <slot name="modal-body">自定义内容</slot>
    </div>
  • 定义多个className时,可以将其class作为属性放入对象中,根据是否传入props参数进行判断;

    classBtn() {
     let {preCls, type, size, shape} = this
     let className = [
         `${preCls}`,
         {
             [`${preCls}-${type}`]: !!type,
             [`${preCls}-${size}`]: !!size,
             [`${preCls}-${shape}`]: !!shape,
         },
     ]
     return className
    }
  • props自定义验证,当没有遵循传入规则时需要对其进行一个预先检查,validator可以通过自定义函数对传入的参数进行校验;

    type: {
       type: String,
       default: 'default',//['default',success', 'warning', 'error', 'info']
       validator(value) {
           let types = ['default','success', 'warning', 'error', 'info']
           return types.includes(value) || !value
       }
    },
  • 在src/index.js来封装组件,该文件即是webpack配置的入口文件,install是挂载组件的方法,有了它就可以在外部引用并Vue.use一个插件了;

    import * as components from './components'
    var VuePlugin = {}
    VuePlugin.install = function(Vue, options) {
       for(let component in components) {
           Vue.component('yw'+component, components[component]);
       }
    }
    export default VuePlugin
此时,你已经在本地成功创建好了一个组件库插件,在本地main.js中引入插件和css文件import '@/assets/index.scss',import YwVue from '@'并注册使用Vue.use(YwVue)即可在项目中查看自定义的组件效果。以下步骤将你的插件打包并发布到npm:

3)修改配置项

webpack.config.js:

  • 将src中的index作为入口文件,出口文件(yw.js)必须与pakage.json中的main相同;

     entry: {
       style: './src/style.js',
       yw: './src/index.js',
     },
     output: {
       path: path.resolve(__dirname, './dist'),
       publicPath: '/dist/',
       filename: '[name].js',
       library: 'Yw',
       libraryTarget: 'umd'
     },
  • 通过extract-text-webpack-plugin插件将scss单独打包(yw.css),便于外部引用;

    {
         test: /\.scss$/,
         use: ExtractTextPlugin.extract({
            fallback: 'vue-style-loader',
            use: ['css-loader', 'sass-loader']
          })
    },
    module.exports.plugins = (module.exports.plugins || []).concat([
       new ExtractTextPlugin("yw.css",{allChunks: true}),
       ......
    ])

pakage.json:

  • main结点是打包后的快捷路径(yw-ui),可以直接引用,无需指定相对的路径(yw-ui/dist/yw.js);
  • 开源协议license为"MIT";
  • 将private设为false组件即可公用;

    {
         "name": "ywbj-ui", //包名,npm install ****
         "description": "A components of pc base on vue2.X",
         "version": "1.0.1", //每次打包上传,必须更新版本号
         "author": "yourName <****@**.com>", //输入你的信息和邮箱
         "main": "dist/yw.js", //必须和webpack打包的出口文件名一致
         "license": "MIT", //开源协议
         "private": false, //true为私有,false为公共
    }
  • 配置完成后执行npm run build,生成打包文件夹dist;

图片描述

4)发布到npm

  • 首先在npm官网(https://www.npmjs.com/)注册用户
  • 将本地npm源设置为https://registry.npmjs.org/ (若为淘宝源则登陆不成功);
  • 进入项目文件夹,执行npm login会要求你输入用户名、密码和邮箱;
  • npm publish将打包后的文件发布到npm,成功后可在官网搜到包名(注意:每次publish之前需要更新pakage.json中的版本号,否则上传不成功);

图片描述

二. 安装使用自己的插件

#安装插件
npm install ywbj-ui --save
 
#引入项目中
import YwUI from 'ywbj-ui'
import 'ywbj-ui/dist/yw.css'
Vue.use(YwUI)
 
#组件中直接使用
<yw-button type="success" isDisabled>success</yw-button>

三. 自定义组件API文档

Button按钮组件

参数说明类型可选值默认值
shape按钮的形状string圆角:circle', 直角:'rectangle'‘ ’
isDisabled是否禁用按钮booleantrue/falsefalse
type按钮的类型string'default',success', 'warning', 'error', 'info'default
size按钮的大小string'large', 'medium', 'small'‘ ’

Modal对话框组件

参数说明类型可选值默认值
isShow是否显示booleantrue/falsefalse
width宽度number
showTitle是否显示标题booleantrue/falsetrue
showBody是否显示主体booleantrue/falsetrue
showFooter是否显示按钮booleantrue/falsetrue
size对话框大小string'large', 'medium', 'small''medium'

四. 引用效果展示

图片描述
图片描述

查看原文

赞 7 收藏 5 评论 2

倒不了的dota 发布了文章 · 2019-06-05

前端组件库(Element UI)的实现原理:教你如何实现自定义组件库,并打包发布至npm

前言

对Vue组件开发有一定了解,对UI组件库的实现比较感兴趣,想要理解其开发原理,这篇文章可以帮助你实现并发布自己的第一个自定义插件。

Demo地址

建议将demo拉取至本地,结合代码更容易理解:
git地址:https://github.com/Yuwenbinji...

git clone git@github.com:Yuwenbinjie/ywbj-ui.git
cd ywbj-ui/
npm i
npm run dev

一. 组件库开发流程

1)新建vue项目:

文件目录如下图

.
├── dist # 压缩后文件目录
├── site # 项目结构目录
│   ├── App.vue # 单页应用父组件 
│   └── main.js # 单页应用启动入口文件
├── src # 开发目录
│   ├── assets # 静态文件:scss/image
│   ├── components # 存放公共组件库
│   ├── index.js # 全局注册组件插件
│   └── style.js # 导入scss
├── index.html
├── package.json # 依赖管理
├── webpack.dev.js #项目启动配置文件:npm run dev
├── webpack.config.js #项目打包配置文件:npm run build
└── README.md #README

2)编写自定义组件,封装成插件:

业务组件和功能组件的主要区别

  • slot插槽占位符,可以实现父子组件传参,父组件templet模版可将子组件slot内容替换。当slot未命名时将父组件全部替换,当定义name时,可以实现父组件对子组件的指定位置显示内容或传参;

    <div :class="preCls+'-title'" v-show="showTitle">
      <slot name="modal-title">自定义标题</slot>
    </div>
    <div :class="preCls+'-body'" v-show="showBody">
       <slot name="modal-body">自定义内容</slot>
    </div>
  • 定义多个className时,可以将其class作为属性放入对象中,根据是否传入props参数进行判断;

    classBtn() {
     let {preCls, type, size, shape} = this
     let className = [
         `${preCls}`,
         {
             [`${preCls}-${type}`]: !!type,
             [`${preCls}-${size}`]: !!size,
             [`${preCls}-${shape}`]: !!shape,
         },
     ]
     return className
    }
  • props自定义验证,当没有遵循传入规则时需要对其进行一个预先检查,validator可以通过自定义函数对传入的参数进行校验;

    type: {
       type: String,
       default: 'default',//['default',success', 'warning', 'error', 'info']
       validator(value) {
           let types = ['default','success', 'warning', 'error', 'info']
           return types.includes(value) || !value
       }
    },
  • 在src/index.js来封装组件,该文件即是webpack配置的入口文件,install是挂载组件的方法,有了它就可以在外部引用并Vue.use一个插件了;

    import * as components from './components'
    var VuePlugin = {}
    VuePlugin.install = function(Vue, options) {
       for(let component in components) {
           Vue.component('yw'+component, components[component]);
       }
    }
    export default VuePlugin
此时,你已经在本地成功创建好了一个组件库插件,在本地main.js中引入插件和css文件import '@/assets/index.scss',import YwVue from '@'并注册使用Vue.use(YwVue)即可在项目中查看自定义的组件效果。以下步骤将你的插件打包并发布到npm:

3)修改配置项

webpack.config.js:

  • 将src中的index作为入口文件,出口文件(yw.js)必须与pakage.json中的main相同;

     entry: {
       style: './src/style.js',
       yw: './src/index.js',
     },
     output: {
       path: path.resolve(__dirname, './dist'),
       publicPath: '/dist/',
       filename: '[name].js',
       library: 'Yw',
       libraryTarget: 'umd'
     },
  • 通过extract-text-webpack-plugin插件将scss单独打包(yw.css),便于外部引用;

    {
         test: /\.scss$/,
         use: ExtractTextPlugin.extract({
            fallback: 'vue-style-loader',
            use: ['css-loader', 'sass-loader']
          })
    },
    module.exports.plugins = (module.exports.plugins || []).concat([
       new ExtractTextPlugin("yw.css",{allChunks: true}),
       ......
    ])

pakage.json:

  • main结点是打包后的快捷路径(yw-ui),可以直接引用,无需指定相对的路径(yw-ui/dist/yw.js);
  • 开源协议license为"MIT";
  • 将private设为false组件即可公用;

    {
         "name": "ywbj-ui", //包名,npm install ****
         "description": "A components of pc base on vue2.X",
         "version": "1.0.1", //每次打包上传,必须更新版本号
         "author": "yourName <****@**.com>", //输入你的信息和邮箱
         "main": "dist/yw.js", //必须和webpack打包的出口文件名一致
         "license": "MIT", //开源协议
         "private": false, //true为私有,false为公共
    }
  • 配置完成后执行npm run build,生成打包文件夹dist;

图片描述

4)发布到npm

  • 首先在npm官网(https://www.npmjs.com/)注册用户
  • 将本地npm源设置为https://registry.npmjs.org/ (若为淘宝源则登陆不成功);
  • 进入项目文件夹,执行npm login会要求你输入用户名、密码和邮箱;
  • npm publish将打包后的文件发布到npm,成功后可在官网搜到包名(注意:每次publish之前需要更新pakage.json中的版本号,否则上传不成功);

图片描述

二. 安装使用自己的插件

#安装插件
npm install ywbj-ui --save
 
#引入项目中
import YwUI from 'ywbj-ui'
import 'ywbj-ui/dist/yw.css'
Vue.use(YwUI)
 
#组件中直接使用
<yw-button type="success" isDisabled>success</yw-button>

三. 自定义组件API文档

Button按钮组件

参数说明类型可选值默认值
shape按钮的形状string圆角:circle', 直角:'rectangle'‘ ’
isDisabled是否禁用按钮booleantrue/falsefalse
type按钮的类型string'default',success', 'warning', 'error', 'info'default
size按钮的大小string'large', 'medium', 'small'‘ ’

Modal对话框组件

参数说明类型可选值默认值
isShow是否显示booleantrue/falsefalse
width宽度number
showTitle是否显示标题booleantrue/falsetrue
showBody是否显示主体booleantrue/falsetrue
showFooter是否显示按钮booleantrue/falsetrue
size对话框大小string'large', 'medium', 'small''medium'

四. 引用效果展示

图片描述
图片描述

查看原文

赞 7 收藏 5 评论 2

倒不了的dota 关注了用户 · 2019-06-05

FrontEndJie @frontendjie

在这里,记录我的学习,分享我的知识,我相信,能给你讲明白,我也是会了的。在我看来,编程也是一次艺术创作。

              -------我是一个前端开发者,fighting!!!

关注 23

倒不了的dota 关注了用户 · 2019-06-05

边城 @jamesfancy

从事软件开发 20 年,在软件分析、设计、架构、开发及软件开发技术研究和培训等方面有着非常丰富的经验,近年主要在研究 Web 前端技术、基于 .NET 的后端开发技术和相关软件架构。欢迎搜索并关注公众号“边城客栈” ⇐ 点这里

关注 10934

认证与成就

  • 获得 7 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2019-06-05
个人主页被 270 人浏览