使用webpack+vue.js构建前端工程化

使用webpack+vue.js构建前端工程化

本篇主要介绍三块知识点:

  • node.js

  • vue.js

  • webpack前端工程化

本篇不是写给零基础的同学看的,读者应具备一些服务器开发、前端开发基础

本篇目标

node.js基本入门

node.js介绍

node.js可以让javascript程序在后端运行起来。我们之前所熟知的javascript都是运行在前端浏览器,我们编写好了javascript代码后,由浏览器解释执行。而node.js,可以让我们编写javascript,然后在后端运行起来。现在的javascript和java、python一样,可以操作I/O、操作数据库、或者其他各类操作系统资源。


上面这张图,和下面的这张图很像。可以认为,node.js是javascript的一种跨平台运行在主机的实现。


环境搭建

npm介绍

写过Java的同学应该都知道,我们要编写程序往往都需要导入很多其他额外的JAR包(官方的JavaSE包默认提供了)。早期在Maven出现之前,我们都是需要手动去下载各种框架的JAR包。自从有了Maven之后,我们不再需要手动下载了,直接在一个POM.XML文件中引入需要的依赖即可。

在node中,运行一个node程序也会依赖于其他的一些模块。当然node也会自带一些基本模块。那当我们需要使用这些模块的时候,我们需要从网络上下载。这个去网上下载非常麻烦。node官方提供了一个管理工具npm,这个工具可以根据需要自动从服务器下载各种包。这就省去了,我们自己去网上下载包的过程。

npm淘宝镜像

官方的npm在国内速度是比较慢的,可以使用淘宝的镜像

npm install -g cnpm --registry=https://registry.npm.taobao.org

安装完成后,就可以使用cnpm从淘宝镜像来下载、安装node包

使用cnpm安装包

cnpm install <包的名称>

包的名称可以去https://npm.taobao.org/上搜索

node.js模块化程序结构

每一个js文件就是一个模块(json等其实也可以作为一个模块),对应的就是一个module对象。下面这一段程序,表示从外部引入一个模块,然后执行模块中的函数。

var fs = require("fs");     // 引入fs模块

var data = fs.readFileSync('input.txt');    // 调用fs模块中的readFileSync方法

console.log(data.toString());

第一行的, required表示引入一个包,返回的就是module对象。这个fs包,是node自带的包,引入一个包后就可以调用这个包下的方法。但是包中的方法不是都可以被调用的,只有类似:


exports.world = function() {
    console.log('Hello World');
}

这样的代码,才能被调用。更简单来说,在包中定义的函数都是对外不可见的,要想被外部调用,需要使用exports来定义函数。


这个require函数有点类似于C语言中的#include。它首先会从文件cache缓存中查找传入的模块名,如果没有找到再查找原生模块,最后查找文件中加载。

node.js重要模块(API)介绍

node.js全局对象

变量名注释
__filename表示当前正在执行的脚本的文件名
__dirname表示当前执行脚本所在的目录。
console.log输出日志

node.js原生模块

模块名称介绍
os模块获取操作系统相关的信息,例如:获取临时文件夹,获取主机名、获取操作系统名称
http模块提供了开发服务器后端程序的相关API
path模块提供了处理文件路径相关的API
net模块提供了网络编程API

实现一个用户管理模块

  • 创建项目

    • 建立一个空的文件夹用于保存该项目

    • 进入命令提示符,使用npm init初始化项目,结束后会自动生成一个package.json文件,这个文件中包含了项目的所有依赖包,可以把这个package.json文件理解为项目文件

  • 创建客户Service模块

    
    /**
     * 客户Service
     */
    exports.CustomerService = function() {
        this.customers = [];
    
        // 添加客户
        this.add = function(cstm) {
            this.customers.push(cstm);
        }
    
        // 根据客户名字移除
        this.remove = function(name) {
            for(var i = 0; i < this.customers.length; ++i) {
                if(this.customers[i].name === name) {
                    this.customers.splice(i, 1);
                    break;
                }
            }
        }
    
        // 获取所有客户
        this.findAll = function() {
            return this.customers;
        }
    }
  • 创建入口模块index.js

    
    var customerService = require('./customerService');
    
    var cm = new customerService.CustomerService();
    cm.add({name: '小乔', age: 20, sex: '女'});
    cm.add({name: '二乔', age: 21, sex: '女'});
    cm.add({name: '大乔', age: 22, sex: '女'});
    
    // 查询所有客户
    var cstms = cm.findAll();
    console.log("---");
    console.log(cstms);
    
    // 删除客户
    cm.remove("小乔");
    console.log("---");
    console.log(cm.findAll())
  • 执行node index.js

    
    PS H:\code\nodejs\02> node .\index.js
    ---
    [ { name: '小乔', age: 20, sex: '女' },
      { name: '二乔', age: 21, sex: '女' },
      { name: '大乔', age: 22, sex: '女' } ]
    ---
    [ { name: '二乔', age: 21, sex: '女' },
      { name: '大乔', age: 22, sex: '女' } ]

vue.js基本入门

介绍

Vue是一套前端框架。它实现了前端页面视图(HTML/CSS)和模型数据的分离,而且它提供了快速简单构建组件的方式。让我们以一种全新的方式来开发前端。


基本编程模型(模板 + 模型)

使用Vue指令定义模板


<div id="app">
    <span>
        {{name}}
    </span>
</div>

使用Vue定义数据模型


var app2 = new Vue({
    el: '#app',
    data: {     // 定义模型数据
        name: 'Hello, Tom!'
    },
    method: {   // 定义绑定数据方法
        sayHello: function() {
            alert('hello!');
        }
    }
})

常用指令介绍

指令用途
{{模型名称}}插值,绑定模型数据(单向绑定)
v-bind:标签属性,可以缩写为:标签属性绑定模型数据到HTML标签的属性
v-if条件判断
v-for="item in list"foreach循环
v-on:click,可以缩写为@click绑定用户事件
v-model绑定表单数据
指令.修饰符.number(将输入的数据转换为数字)、.trim(去除输入的数据后面的空格)

详细语法参见:https://cn.vuejs.org/v2/guide/

组件化开发

在前端开发中引入组件化是一个大的进步。其实,如果我们用过一些UI框架就会知道,大多数的UI框架都定义了很多的组件,例如:button按钮、datagrid表格组件、tree树形组件、dialog对话框组件等等。但是,之前我们都是使用别人给我们开发好的框架。我们今天要学习如何使用Vue来开发一个自己的组件。将来,我们可以自己开发一套自己的组件库,然后使用这些组件库来构建我们的前端应用程序。

组件结构

可以把组件理解为就是一个小的页面视图。使用Vue开发的组件也分为两个部分:

  1. 视图模板

  2. 属性(模型数据)

视图模板依旧是使用HTML/CSS+Vue指令,但是模型数据现在跟之前不太一样,模型数据应该是使用组件的时候,动态传入进来的。所以,模型数据需要先定义出来。


实现一个购物车程序


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>购物车</title>
    <link rel="stylesheet" href="./bootstrap/css/bootstrap.css">
</head>
<body>
    <div id="app" class="container">
        <h1>购物车</h1>
        <hr>
        <btn-grp :buttons="buttons"></btn-grp>
        <btn-grp :buttons="buttons_test"></btn-grp>
        <btn-grp :buttons="buttons"></btn-grp>
        <btn-grp :buttons="buttons_test"></btn-grp>
        <br>
        <br>
        <table class="table table-bordered table-striped table-hover">
            <tr>
                <th>ID</th>
                <th>商品名称</th>
                <th>商品价格</th>
                <th>商品数量</th>
                <th>商品总价</th>
            </tr>
            <tr v-for="(prod, index) in products">
                <td>{{index+1}}</td>
                <td>{{prod.name}}</td>
                <td>{{prod.price}}</td>
                <td>
                    <button @click="changeCount(prod, -1)">-</button>
                    <input type="text" v-model="prod.count">
                    <button @click="changeCount(prod, 1)">+</button>
                </td>
                <td>{{prod.price * prod.count}}</td>
            </tr>
            <tr>
                <td colspan="4" class="text-right">总价:</td>
                <td class="text-primary">{{getTotalMoney()}}</td>
            </tr>
        </table>
    </div>
    <script src="./vue.js"></script>
    <script>
        // 自定义组件,这里实现了一个按钮组组件
        Vue.component('btn-grp', {
            props: ['buttons'],
            template: 
                '<div class="btn-group" role="group">'
                    + '<button type="button" @click="btn.handler" v-for="btn in buttons" :class="\'btn \' + (btn.class == null || btn.class == \'\' ? \'btn-default\':btn.class)">'
                        + '{{btn.title}}'
                    + '</button>'
                + '</div>'
        });

        var app = new Vue({
            el: '#app',
            data: {
                // 按钮组件测试
                buttons_test: [
                    {title: '测试1', class: 'btn-danger'},{title: '测试2'},{title: '测试3'},{title: '测试4'},
                ],
                buttons: [{
                    title: '添加',
                    class: 'btn-primary',
                    handler: function() {
                        alert('点击添加按钮');
                    }
                }, {
                    title: '修改',
                    class: 'btn-default',
                    handler: function() {
                        alert('点击修改按钮');
                    }
                }, {
                    title: '删除',
                    class: 'btn-default',
                    handler: function() {
                        alert('点击删除按钮');
                    }
                }, ],
                products: [
                    {
                        name: '小米6S',
                        price: 3999,
                        count: 1,
                    },
                    {
                        name: '锤子2',
                        price: 4999,
                        count: 1,
                    },
                    {
                        name: '华为P20',
                        price: 3599,
                        count: 1,
                    },
                    {
                        name: 'OPPO R15',
                        price: 2999,
                        count: 1,
                    },
                    {
                        name: 'OPPO R11',
                        price: 1999,
                        count: 1,
                    },
                ],
            },
            methods: {
                // 用户点击加减数量时调用
                changeCount: function(prod, num) {
                    if(num < 0) {
                        if(prod.count > 0) {
                            prod.count += num;
                        }
                    }
                    else {
                        prod.count += num;
                    }
                },
                // 获取总金额
                getTotalMoney: function() {
                    var totalMoney = 0.0;

                    for(var i = 0; i < this.products.length; ++i) {
                        totalMoney += parseFloat(this.products[i].price * this.products[i].count);
                    }

                    return totalMoney;
                }
            }
        });
    </script>
</body>
</html>
  • 效果图


使用webpack构建前端工程化

webpack介绍

webpack是一个前端项目构建工具。使用webpack可以把前端当成一个工程来开发。而且能够很好地把前端的各类资源统一管理、编译、打包。

  • 它是一个“编译器”,可以通过各种插件将基于node.js、sass、less编写代码编译成能够运行在前端浏览器的javascript、和css

  • 它是一个打包工具,可以将所有前端的资源打包到一个bundle.js中

有了webpack,我们可以像开发后端应用一样开发前端。

总体结构图


webpack是基于node.js开发的一款应用,其实webpack就是一个node.js的模块。


安装webpack

  • 创建一个空的文件夹

  • 使用npm init创建package.json

  • 安装cnpm淘宝镜像

  • 使用cnpm install vue-cli -g全局安装vue-cli模块

  • 使用vue init webpack cart创建基于webpack购物车项目

  • 使用cnpm install bootstrap --save安装bootstrap模块

具体步骤请参考:https://www.webpackjs.com/guides/installation/

使用webpack重构购物车程序

  • 使用vue-cli生成项目脚手架如下图:


  • 将原来编写的btn-grp组件单独编写到BtnGrp.vue文件中

    • 可以看到现在代码清晰了很多,template标签部分编写模板,script标签部分编写组件的交互代码,编写方式和原先写在HTML的代码一致

    • 现在整个前端应用都是基于组件化的,代码变得更加清晰了

    
    <template>
      <div class="btn-group" role="group">
          <button :key="btn.title" 
            type="button" @click="btn.handler" 
            v-for="btn in buttons" 
            :class="'btn ' + (btn.class == null || btn.class == '' ? 'btn-outline-secondary':btn.class)">
              {{btn.title}}
          </button>
      </div>
    </template>
    
    <script>
    export default {
      props: ['buttons']
    }
    </script>
    
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style scoped>
    </style>
    
  • 将原来写在HTML中的代码重构到App.vue中

    • 此处因为需要用到BtnGrp组件,所以需要先import 组件,然后在components中引用该组件

    
    <template>
        <div id="app" class="container">
            <h1>WEBPACK + VUE&nbsp;实现购物车</h1>
            <BtnGrp :buttons="buttons"></BtnGrp>
            <br/>
            <br/>
            <table class="table table-bordered table-striped table-hover">
                <tr>
                    <th>ID</th>
                    <th>商品名称</th>
                    <th>商品价格</th>
                    <th>商品数量</th>
                    <th>商品总价</th>
                </tr>
                <tr :key="index+1" v-for="(prod, index) in products">
                    <td>{{index+1}}</td>
                    <td>{{prod.name}}</td>
                    <td>{{prod.price}}</td>
                    <td>
                        <button class="btn btn-outline-info btn-sm" @click="changeCount(prod, -1)">-</button>
                        <input style="width:50px" type="text" v-model="prod.count">
                        <button class="btn btn-outline-info btn-sm" @click="changeCount(prod, 1)">+</button>
                    </td>
                    <td>{{prod.price * prod.count}}</td>
                </tr>
                <tr>
                    <td colspan="4" class="text-right">总价:</td>
                    <td class="text-primary">{{getTotalMoney()}}</td>
                </tr>
            </table>
        </div>
    </template>
    
    <script>
    /* eslint-disable no-new */
    import 'bootstrap/dist/css/bootstrap.min.css'
    import BtnGrp from './components/BtnGrp'
    
    export default {
      name: 'App',
      components: {BtnGrp},
      data () {
        return {
          products: [
            {
                name: '小米6S',
                price: 3999,
                count: 1,
            },
            {
                name: '锤子2',
                price: 4999,
                count: 1,
            },
            {
                name: '华为P20',
                price: 3599,
                count: 1,
            },
            {
                name: 'OPPO R15',
                price: 2999,
                count: 1,
            },
            {
                name: 'OPPO R11',
                price: 1999,
                count: 1,
            },
          ],
          buttons: [{
              title: '添加',
              class: 'btn-outline-primary',
              handler: function() {
                  alert('点击添加按钮');
              }
          }, {
              title: '修改',
              class: 'btn-outline-primary',
              handler: function() {
                  alert('点击修改按钮');
              }
          }, {
              title: '删除',
              class: 'btn-outline-danger',
              handler: function() {
                  alert('点击删除按钮');
              }
          }, ],
          changeCount: function(prod, num) {
              if(num < 0) {
                  if(prod.count > 0) {
                      prod.count += num;
                  }
              }
              else {
                  prod.count += num;
              }
          },
          getTotalMoney: function() {
              var totalMoney = 0.0;
    
              for(var i = 0; i < this.products.length; ++i) {
                  totalMoney += parseFloat(this.products[i].price * this.products[i].count);
              }
    
              return totalMoney;
          }
        }
      }
    }
    </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>

因为npm安装的bootstrap是bootstrap4,所以稍微对原先的样式调整了下。最终的效果图如下:


展开阅读全文

没有更多推荐了,返回首页