Vue2.x学习笔记(二)

目录

  • 一、axios 的使用
    • 1. 基本使用
    • 2. axios 请求配置
    • 3. axios 拦截器
  • 二、vue-router 路由的使用
    • 1. vue-router 实现原理的简单实现
    • 2. vue-router 的基本使用
    • 3. 命名路由
    • 4. 路由参数
    • 5. 嵌套路由
    • 6. keep-alive 在路由中的使用
    • 7. 在路由中通过 meta 进行权限控制
    • 8. vue-router 导航完成之后异步获取数据
    • 9. vue-router导航守卫之在导航完成前获取数据
  • 三、webpack 入门
    • 1. 安装
    • 2. 简单使用
    • 3. webpack 打包执行顺序
    • 4. webpack 配置文件
  • 四、webpack 解析器和插件
    • 1. CSS 文件处理
    • 2. less 文件处理
    • 3. 图片文件处理
    • 4. html 文件插件
    • 5. webpack-dev-server 热加载插件
    • 6. ES6 语法解析
    • 7. vue组件单文件引入
    • 8. CommonsChunkPlugin 的使用
    • 9. webpack.ensure 异步加载
  • 五、v-for 中 key 的作用

一、axios 的使用

axios 详细资料可以参考axios 中文文档

axios作为局部模块时,为了使用,需要先进行挂载,挂载的方法有两种:

  1. 使用Vue.use();的方式挂载。
  2. 使用Vue.prototype.$axios = axios;的方式挂载。

1. 基本使用

(1) 简单 GET 请求

var App = {
    template: "
", methods: { getData() { this.$axios.get("http://jsonplaceholder.typicode.com/todos") // GET请求 .then(res => { // 请求成功的处理逻辑 console.log(res.data[0]); }) .catch(err => { // 请求失败的处理逻辑 console.log(err); }) } } }; Vue.prototype.$axios = axios; // 将axios挂载到Vue实例 new Vue({ el: "#app", template: '', components: { App } })

(2) 并发请求

var App = {
    data() {
        return {
            getRes: "",
            postRes: ""
        }
    },
    template: `
        
GET请求响应:{{getRes}}
POST请求响应:{{postRes}}
`, methods: { concurrentRequest() { this.$axios.defaults.baseURL = "http://jsonplaceholder.typicode.com/"; // 设置请求的baseURL let getReq = this.$axios.get("posts/55"); // GET请求 let postReq = this.$axios.post("posts", "variable=helloWorld"); // POST请求 this.$axios.all([getReq, postReq]) .then(this.$axios.spread((res1, res2) => { this.getRes = res1.data; this.postRes = res2.data; })) .catch(err => { // 任意一个请求失败都将导致所有请求不成功 console.log(err); }) } } }; Vue.prototype.$axios = axios; // 挂载axios到Vue实例 new Vue({ el: "#app", template: '', components: { App } })

2. axios 请求配置

var App = {
    template: `
        
响应结果:{{getRes}}
`, methods: { getData() { this.$axios.defaults.baseURL = "http://jsonplaceholder.typicode.com/posts/"; this.$axios.get('', { params: {id: 10}, // URL参数 transformResponse: [ // 请求返回后,执行then/catch之前,修改响应数据 function (data) { console.log("修改之前:", data); // 接收到的data是字符串,需要先转成JSON data = JSON.parse(data); data[0].title = "Hello World"; return data; } ] }) .then(res => { console.log(res.data); }) .catch(err => { console.log(err); }); this.$axios.post('', "name=Jack", { transformRequest: [ // 请求发送之前执行,可以修改请求将要提交的数据。只能用于PUT、POST、PATCH请求中 function (data) { console.log("修改之前:", data); data = "name=Rose"; return data; } ] }) .then(res => { console.log(res.data); }) .catch(err => { console.log(err); }) } } }; Vue.prototype.$axios = axios; // 挂载 axios new Vue({ el: "#app", template: '', components: {App} })

3. axios 拦截器

在请求或响应被thencatch处理前可以拦截它们,然后进行业务逻辑处理。

var App = {
        template: "
", methods: { sendRequest() { // 添加请求拦截器 this.$axios.interceptors.request.use(config => { console.log(config); // 模拟获取cookie登录状态,并修改请求URL let userId = localStorage.getItem("userId"); if (userId) { config.url = "65"; } return config; }, function (err) { return Promise.reject(err); }); // 添加响应拦截器 this.$axios.interceptors.response.use(response => { console.log(response.data); // 模拟登录,返回cookie if (response.data.userId === 6) { localStorage.setItem('userId', response.data.userId) } return response; }, function (err) { return Promise.reject(err); }); this.$axios.defaults.baseURL = "http://jsonplaceholder.typicode.com/posts/"; this.$axios.get("55") .then(res => { console.log(res); }) .catch(err => { console.log(err); }) } } }; Vue.prototype.$axios = axios; new Vue({ el: "#app", components: {App}, template: "" })

二、vue-router 路由的使用

vue-router官方文档
路由实现方式:

  1. 传统开发方式url改变后,立刻发生请求响应整个页面,有可能资源过多导致页面出现白屏。
  2. 单页面应用SPA(Single Page Application),锚点改变后,不会立刻发送请求,而是在某个合适的时机,发起ajax请求,页面局部渲染。

1. vue-router 实现原理的简单实现




    
    Vue-router


    登录
    注册
    

2. vue-router 的基本使用

vue-router的基本使用示例:

Vue.use(VueRouter);  // 当Vue不是全局对象时,需要将VueRouter应用到Vue对象上

let Login = {
    template: "
我是登录页面
" }; let Register = { template: "
我是注册页面
" }; // 创建router对象 var router = new VueRouter({ // 配置路由对象 routes: [ // 路由匹配规则 { path: "/login", component: Login }, { path: "/register", component: Register } ] }); let App = { template: `
登录页面 注册页面
` }; new Vue({ el: "#app", components: {App}, router: router, // 将router路由对象交给Vue实例管理 template: "" })

3. 命名路由

命名路由就是给路由规则添加name属性,然后将router-linkto属性改为动态绑定。

Vue.use(VueRouter);

let Login = {
    template: "
我是登录页面
" }; let Register = { template: "
我是注册页面
" }; // 创建router对象 var router = new VueRouter({ routes: [ { name: "login", // 路由命名 path: "/login", component: Login }, { name: "register", // 路由命名 path: "/register", component: Register } ] }); let App = { template: `
登录页面 注册页面
` }; new Vue({ el: "#app", components: {App}, router: router, // 将router路由对象交给Vue实例管理 template: "" })

4. 路由参数

路由参数包括:1. 动态路由参数;2. URL参数。

let UserParams = {
    template: "
动态路由参数页面
", created() { // VueRouter引入之后,Vue实例上会挂载有$router、$route两个属性对象, // 组件会继承Vue实例上的$router、$route对象;通过这两个对象,可以在组件内部获得路由参数。 console.log(this.$router); console.log(this.$route.params) } }; let UserQuery = { template: "
URL参数页面
" }; // 创建router对象 var router = new VueRouter({ routes: [ { name: "UserParams", path: "/user/:id", // 动态路由参数,以冒号开头 component: UserParams }, { name: "UserQuery", path: "/UserQuery", component: UserQuery } ] }); let App = { // 两种路由参数传入 router-link 的示例: // 动态路由参数通过 params 属性选项传入参数;URL参数通过 query 属性选项传入参数。 template: `
动态路由参数 URL参数
` }; new Vue({ el: "#app", components: { App }, router: router, template: "" })

当使用路由参数时,例如从/user/foo导航到/user/bar,原来的组件实例会被复用。因为两个路由都渲染同一个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用

如果在复用组件时,想对路由参数的变化作出响应的话,可以简单地 watch (监测变化) $route 对象:

const User = {
  template: '...',
  watch: {
    '$route' (to, from) {
      // 对路由变化作出响应...
    }
  }
}

或者使用 2.2 中引入的 beforeRouteUpdate 导航守卫:

const User = {
  template: '...',
  beforeRouteUpdate (to, from, next) {
    // react to route changes...
    // don't forget to call next()
  }
}

5. 嵌套路由

在路由对象router中通过children属性,定义子路由。

let Song = {
    template: "
歌曲内容页
" }; let Movie = { template: "
影视内容页
" }; let Home = { template: `
首页内容
歌曲 影视
` }; // 创建router对象 var router = new VueRouter({ routes: [ { name: "home", path: "/home", component: Home, children: [ { name: 'song', path: 'song', component: Song }, { name: 'movie', path: 'movie', component: Movie } ] } ] }); let App = { // 两种路由参数传入 router-link 的示例 template: `
首页
` }; new Vue({ el: "#app", components: { App }, router: router, template: "" })

6. keep-alive 在路由中的使用

内置组件keep-alive可以将组件的状态缓存,当路由切换后可以保持路由时加载的组件的状态。

let Timeline = {
        template: "

这是首页组件

", created() { console.log('首页组件创建了'); }, mounted() { console.log('首页组件DOM加载了'); }, destroyed() { console.log('首页组件销毁了'); } }; let Pins = { template: "

这是沸点组件

", methods: { clickHandler(e) { e.target.style.color = 'red'; } }, created() { console.log('沸点组件创建了'); }, mounted() { console.log('沸点组件DOM加载了'); }, destroyed() { console.log('沸点组件销毁了'); } }; let router = new VueRouter({ routes: [ { path: '/timeline', component: Timeline }, { path: '/pins', component: Pins } ] }); let App = { template: `
首页 沸点
` }; new Vue({ el: "#app", router, components: {App}, template: "" })

7. 在路由中通过 meta 进行权限控制

示例代码知识点总结:

  • 可以将某些数据保存到本地的 localStorage 中。
  • 在方法中,可以通过编程式路由跳转到指定页面。
  • 给路由设置meta属性,以规定该路由是否需要权限验证。
  • 在全局前置导航守卫中执行路由权限验证的逻辑。
  • 必须调用全局前置导航守卫中的next()方法,否则页面不会跳转。
let Home = {template: "
这是首页
"}; let Blog = {template: "
这是博客
"}; let Login = { data() { return {name: "", passwd: ""} }, template: `
`, methods: { loginHandler() { // 将数据保存到本地的 localStorage 中,以模拟登录 localStorage.setItem("user", {name: this.name, passwd: this.passwd}); // 通过编程式导航跳转到目标页面 this.$router.push({ name: "blog" }) } } }; const router = new VueRouter({ routes: [ { path: "/", redirect: "/home" }, { path: "/home", component: Home }, { path: "/blog", name: "blog", component: Blog, // 给路由做权限控制 meta: { // 规定这个路由是否需要登录 authValidate: true } }, { path: "/login", name: "login", component: Login } ] }); router.beforeEach((to, from, next) => { console.log(to); console.log(from); // 通过目的路由的meta属性来判断组件是否设定了权限验证 if (to.meta.authValidate) { // 路由到有登录验证的组件时执行 if (localStorage.getItem("user")) { // 判断是否已经登录,若已登录,则直接放行 next(); } else { next({ // 若未登录,则跳转到登录页面 path: '/login' }); } } else { // 路由到没有登录验证的组件时执行 if (localStorage.getItem("user")) { if (to.name === "login") { console.log(to.name); next({ path: "/home" }) }else { next(); } } else { next(); } } }); new Vue({ el: "#app", router, template: `
` })

8. vue-router 导航完成之后异步获取数据

需求:在导航完成之后加载数据,渲染DOM




    
    Vue-router在导航完成后获取数据


9. vue-router导航守卫之在导航完成前获取数据

导航守卫官方文档
有三种方法实现导航完成前获取数据:

  1. 通过vue-router全局守卫beforeEach;
  2. 通过watch属性侦听$route的变化;
  3. 通过vue-router的组件内守卫beforeUpdate



    
    Vue-router导航守卫之在导航完成前获取数据


三、webpack 入门

1. 安装

  1. 执行命令npm init初始化;
  2. 执行命令npm install [email protected] -D下载webpack

2. 简单使用

手动实现一个简易vue-cli脚手架工具,同时学习webpack的使用。

(1) 创建index.html文件




    


    

(2) 创建main.js作为项目的入口文件

// ECMAScript6的模块导入
import Vue from "vue/dist/vue"
import App from "./App.js"
import {num1, num2, add} from "./App.js"

// 导入模块时,还可以: import * as app from "./App.js"
// 在调用时,通过: app.num1; app.add; app.default

console.log(num1, num2);
console.log(add(3, 6));

new Vue({
    el: "#app",
    components: {App},
    template: ""
})

(3) 创建App.js组件文件

var app = {
    template: "
程序入口组件
" }; // 三种抛出方式 export default app; // 直接抛出 export var num1= 1; // 声明并抛出 var num2 = 2; export {num2}; // 先声明,再抛出 export function add(x, y) { // 抛出一个函数 return x + y; }

(4) 打包

1. 如果 npm 全局安装 webpack,可以执行命令`webpack ./main.js ./dist/build.js`。
2. 如果`webpack`安装在项目目录,可以按如下进行配置使用:
- 设置"package.json"文件`scripts`属性"build": "webpack ./main.js ./dist/build.js";
- 执行命令`npm run build`进行打包。

(5) build.js文件解读

build.js 文件中有"0~6"注释的编号,它们分别是:

  • 0: 设置一个全局变量,在 web 端指向window对象;
  • 1: 载入main.js的代码,一个Vue实例对象;
  • 2: 载入vue源码本身;
  • 3,4,5: 都是与node_modules/setimmediate(Vue 的 DOM 异步更新)相关;
  • 6: 与App.js解析相关。

3. webpack 打包执行顺序

  1. 把所有模块的代码放到函数中,用一个数组保存起来;
  2. 根据require时传入的数组索引,能知道需要哪一段代码;
  3. 从数组中,根据索引取出包含我们代码的函数;
  4. 执行该函数,传入一个对象module.exports
  5. 我们的代码,按照约定,正好是用module.exports = 'xxx'进行赋值;
  6. 调用函数结束后,module.exports从原来的空对象,就有值了;
  7. 最终return module.exports;作为require函数的返回值。

4. webpack 配置文件

webpack 可以以通过指定配置文件的方式去执行打包。

  • 当全局安装webpack,且配置文件名称为预设的webpack.config.js时,可以通过执行命令: webpack打包;
  • 当全局安装webpack,但配置文件名称非预设时,可以通过执行命令: webpack --config <配置文件路径>打包。
  • 当非全局安装webpack,可以将打包命令webpack --config <配置文件路径>写入到 package.json 文件scripts属性中。

webpack 配置文件说明:

var path = require('path')  // node.js语句

module.exports = {
    // 入口
    entry: {
        // 可以有多个入口,也可以只有一个
        // 如果只有一个,就默认从这个入口开始解析
        "main": "./main.js"
    },
    output: {
        path: path.resolve('./dist'),  // 相对路径转绝对路径
        filename: "./build.js"
    },
    watch: true  // 监视文件改动,自动打包成build.js
};

四、webpack 解析器和插件

webpack 在打包过程中遇到各种不同的文件时,会需要不同的解析器去解析相应的文件。例如:遇到.css文件时,需要用到css-loaderstyle-loader。解析器需要配置到webpack配置文件的module属性里。

1. CSS 文件处理

  • ES6模块导入语法:.css文件的导入语句是import 'xxx.css'
  • 解析器下载:在命令行窗口执行命令npm i css-loader style-loader -D下载。
  • 配置webpack配置文件:
var path = require('path')

module.exports = {
    entry: {
        "main": "./main.js"
    },
    output: {
        path: path.resolve('./dist'),
        filename: "./build.js"
    },
    // 声明模块 包含各个loader
    module: {
        loaders: [
            {   // 添加处理css文件的loader
                test: /\.css$/,
                loader: 'style-loader!css-loader'  // 先用css-loader解析,后用style-loader载入
            }
        ]
    },
    watch: true
};

webpack 在打包过程中,遇到.css文件,会先用css-loader解析器去解析这个文件,然后用style-loader解析器生成 style 标签,并放到 head 标签里。

2. less 文件处理

  • ES6模块导入语法:.less文件的导入语句是import 'xxx.less'
  • less 模块下载:在命令行执行命令npm i less -D下载。
  • 解析器下载:在命令行窗口执行命令npm i less-loader -D下载。
  • 配置webpack配置文件:
var path = require('path')

module.exports = {
    entry: {
        "main": "./main.js"
    },
    output: {
        path: path.resolve('./dist'),
        filename: "./build.js"
    },
    module: {
        loaders: [
            {   // 添加处理css文件的loader
                test: /\.css$/,
                loader: 'style-loader!css-loader'
            },
            {   // 添加处理less文件的loader
                test: /\.less$/,
                loader: 'style-loader!css-loader!less-loader'
            }
        ]
    },
    watch: true
}

3. 图片文件处理

  • ES6模块导入语法:图片文件的导入语句是import imgSrc from 'xxx.jpg'
  • 解析器下载:在命令行窗口执行命令npm i url-loader file-loader -D下载。
  • 配置webpack配置文件:
module.exports = {
    entry: {"main": "./main.js"},
    output: {filename: "./dist/build.js"},
    // 声明模块 包含各个loader
    module: {
        loaders: [
            {   // css文件处理
                test: /\.css$/,
                loader: 'style-loader!css-loader'
            },
            {   // less文件处理
                test: /\.less$/,
                loader: 'style-loader!css-loader!less-loader'
            },
            {   // 图片文件处理
                test: /\.(jpg|png|jpeg|gif|svg)$/,
                loader: 'url-loader?limit=4000'
            }
        ]
    }
};

图片大小比limit设置的值小时,html 页面中会使用base64编码载入图片,这可以减少图片的网络请求;图片大小比limit设置的值大时,会生成一个图片副本,html 页面中图片的路径指向该副本,图片副本会和 html 页面混在一起,导致项目的代码结构混乱;因此设置一个合理的limit值是很有必要的。

特别说明:
webpack 最终会将各个模块打包成一个文件,因此样式中的url路径是相对于入口 html 页面的,而不是相对于原始 CSS 文件所在路径的,这就会导致引入失败。这个问题是通过配置file-loader解决的,file-loader可以解析项目中的url引入(不仅限于 CSS 文件),然后根据配置将文件复制到相应的路径,修改打包后文件的引用路径。

4. html 文件插件

  • 插件下载:在命令行窗口执行命令npm i html-webpack-plugin --save-dev下载。
  • 配置webpack配置文件:
var path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');  // 载入插件对象

module.exports = {
    entry: {
        "main": "./src/main.js"
    },
    output: {
        path: path.resolve('./dist'),
        filename: "./build.js"
    },
    module: {
        loaders: [
            {
                test: /\.css$/,
                loader: 'style-loader!css-loader'
            },
            {
                test: /\.less$/,
                loader: 'style-loader!css-loader!less-loader'
            },
            {
                test: /\.(jpg|png|jpeg|gif|svg)$/,
                loader: 'url-loader?limit=400000'
            }
        ]
    },
    // 声明插件
    plugins: [
        new HtmlWebpackPlugin({  // 生成html文件的插件
            template: './src/index.html'  // html源文件
        })
    ],
    watch: true
};

5. webpack-dev-server 热加载插件

  • 插件下载:在命令行窗口执行命令npm install webpack-dev-server --save-dev下载。
  • 常用配置参数:
    • --open 自动打开浏览器
    • --hot 热更新,不刷新替换 css 样式
    • --inline 自动刷新
    • --port 指定端口
    • --process 显示编译进度
  • webpack-dev-server插件的配置,需要写在package.json文件中:
{
  "scripts": {
    "dev": "webpack-dev-server --open --hot --inline --config ./webpack.dev.config.js"
  }
}

6. ES6 语法解析

  • 模块下载:在命令行执行命令npm i babel-core babel-loader babel-preset-dev babel-plugin-transform-runtime -D
  • 配置webpack配置文件:
var path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');  // 载入插件对象

module.exports = {
    entry: {
        "main": "./src/main.js"
    },
    output: {
        path: path.resolve('./dist'),
        filename: "./build.js"
    },
    module: {
        loaders: [
            {    // css文件处理
                test: /\.css$/,
                loader: 'style-loader!css-loader'
            },
            {    // less文件处理
                test: /\.less$/,
                loader: 'style-loader!css-loader!less-loader'
            },
            {    // 图片文件处理
                test: /\.(jpg|png|jpeg|gif|svg)$/,
                loader: 'url-loader?limit=400000'
            },
            {
                // 处理ES6,7,8
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: '/node_modules/',  // 排除对node_modules的解析
                options: {
                    presets: ['env'], // 处理关键字
                    plugins: ['transform-runtime']  // 处理函数
                }
            }
        ]
    },
    // 声明插件
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html'  // html源文件
        })
    ],
    watch: true
};

ES6 语法解析模块介绍:
1)babel-core
babel-core 的作用是把 js 代码分析成 ast(抽象语法树),方便各个插件分析语法进行相应的处理。有些新语法在低版本 js 中是不存在的,如箭头函数、rest 参数,函数默认值等,这种语言层面的不兼容只能通过将代码转为 ast,分析其语法后再转为低版本 js。
2)babel-loader
babel-core 会使用 abel 转译器,abel 转译器提供了 babel 的转译API,如 babel.transform 等,用于对代码进行转译。abel 转译器通过 babel-loader 调用这些 API 来完成将 ES6 代码进行转译。所以 babel-core 和 babel-loader 需要联合使用。
3)babel-preset-env
自行配置转译过程中使用的各类插件非常麻烦,所有 babel 官方帮我们做了一些预设的插件集,称之为preset。这样我们只需要使用对应的 preset 就可以了。以 JS 标准为例,babel 提供了: es2015、es2016、es2017、env。es20xx 的 preset 只转译该年份批准的标准;env 代指最新的标准,包括了 latest 和 es20xx 各年份。
4)babel-plugin-transform-runtime
babel 默认只转换新的 JavaScript 语法,而不转换新的 API。像Iterator,Generator,Set,Maps,Proxy,Reflect,Symbol,Promise等全局对象,以及一些定义在全局对象上的方法(如Object.assign)都不会转译。如果想使用这些新的对象和方法,必须使用 babel-polyfill 模块,为当前环境提供一个垫片。

7. vue组件单文件引入





  • 创建入口文件main.js:
import Vue from "vue"

import App from "./App.vue"

new Vue({
    el: "#app",
    render: c => c(App)
});

Render函数是Vue2.x版本新增的一个函数。它基于 JavaScript 计算,使用虚拟 DOM 来渲染节点提升性能。通过使用createElement(h)来创建 DOM 节点,createElementrender的核心方法。Vue 编译的时候会把 template 里面的节点解析成虚拟 DOM。

  • 配置webpack配置文件:
var path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: {
        "main": "./src/main.js"
    },
    output: {
        path: path.resolve('./dist'),
        filename: "./build.js"
    },
    module: {
        loaders: [
            {
                test: /\.css$/,
                loader: 'style-loader!css-loader'
            },
            {
                test: /\.less$/,
                loader: 'style-loader!css-loader!less-loader'
            },
            {
                test: /\.(jpg|png|jpeg|gif|svg)$/,
                loader: 'url-loader?limit=400000'
            },
            {
                // 处理vue单文件组件
                test:/\.vue$/,
                loader: 'vue-loader'
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html'
        })
    ],
    watch: true
};

8. CommonsChunkPlugin 的使用

CommonsChunkPlugin 主要是用来提取第三方库和公共模块,避免首屏加载的 bundle 文件或者按需加载的 bundle 文件体积过大,从而导致加载时间过长,着实是优化的一把利器。

(1) chunk(代码块)的分类

  • webpack当中配置的入口文件(entry)是 chunk,可以理解为entry chunk
  • 入口文件以及它的依赖文件通过 code splite (代码分割)出来的也是 chunk,可以理解为children chunk
  • 通过 CommonsChunkPlugin 创建出来的文件也是 chunk,可以理解为commons chunk

(2) CommonsChunkPlugin 可配置的属性

  • name:可以是已经存在的 chunk (一般指入口文件)对应的name,那么就会把公共模块代码合并到这个 chunk 上 ;否则,会创建名字为namecommons chunk进行合并。
  • filename:指定commons chunk的文件名。
  • chunks:指定source chunk,即指定从那些 chunk 当中去找公共模块,省略该选项的时候,默认就是entry chunk
  • minChunks:既可以是数字,也可以是函数,还可以是 Infinity,具体用法和区别下面讨论。

(3) 代码块分离的三种情况

package.json中的dependences属性记录了项目中依赖的第三方库。使用模块下载命令npm install vue.js -D会将模块添加到该属性中。

示例背景说明:项目依赖第三方库 Vue.js;两个入口文件 main1.js、main2.js;入口文件都用到了自定义公共模块 common.js。

[1] 不分离出第三方库和自定义公共模块

修改 webpack.config.js 配置文件

const path = require('path');

module.exports = {
    entry: {  // 多入口文件的配置
        "main1": "./src/main1.js",
        "main2": "./src/main2.js"
    },
    output: {
        path: path.resolve('./dist'),
        filename: "[name].js"  // 对应多入口的多出口配置
    },
    watch: true
};

此时,第三方库和自定义公共模块会被打包到所有入口文件中,造成代码冗余及重复加载。

[2] 分离出第三方库、自定义公共模块、webpack运行文件,但他们在同一个文件中

修改 webpack.config.js 配置文件,新增一个入口文件 vendor,并添加 CommonsChunkPlugin 插件进行模块提取分离:

const path = require('path');
const webpack = require('webpack');  // 导入webpack运行文件
const packagejson = require('./package.json');  // 导入项目package.json文件

module.exports = {
    entry: {  // 多入口文件的配置
        "main1": "./src/main1.js",
        "main2": "./src/main2.js",
        "vendor": Object.keys(packagejson.dependencies)  // 获取生产环境依赖的库
    },
    output: {
        path: path.resolve('./dist'),
        filename: "[name].js"  // 对应多入口的多出口配置
    },
    watch: true,
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({  // 模块提取分离到vendor.js文件中
            name: ['vendor'],
            filename: '[name].js'
        })
    ]
};

此时第三方库、自定义公共模块、webpack运行文件被分离到同一个文件中。但是每次打包时,webpack 运行文件都会变,如果不分离出 webpack 运行文件,每次打包生成 vendor.js 对应的哈希值都会变化,使浏览器认为缓存的 vendor.js失效,而重新去服务器中获取。

[3] 单独分离第三方库、自定义公共模块、webpack运行文件,它们各自在不同文件中

第一步:抽离 webpack 运行文件

修改 webpack.config.js 配置文件

plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: ['vendor', 'runtime'],  // runtime为抽离的webpack运行文件的名字,名字是固定的
            filename: '[name].js'
        })
    ]

上面这段代码等价于下面这段代码:

plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor',
            filename: '[name].js'
        }),
        new webpack.optimize.CommonsChunkPlugin({  // 用于抽离webpack运行文件
            name: 'runtime',
            filename: '[name].js',
            chunks: ['vendor']  // 从哪里抽离,即"source chunks"是谁
        })
    ]

这段抽离 webpack 运行文件的代码的意思是:创建一个名为 runtime 的 commons chunk 进行 webpack 运行文件的抽离,其中source chunks是 vendor.js。

第二步:抽离第三方库和自定义公共模块

从第三方库中分离自定义公共模块,必须定义minChunks属性才能成功抽离。minChunks 可以设置为数字、函数和 Infinity,默认值是数字2(官方文档说默认值为入口文件的数量)。

minChunks取值:

  • 数字:模块被多少个 chunk 公共引用才被抽取出来成为commons chunk;
  • 函数:接受(module, count)两个参数,返回一个布尔值,可以在函数内进行规定好的逻辑来决定某个模块是否提取成为commons chunk
  • Infinity:只有当入口文件(entry chunks)大于 3 时才生效,用来从第三方库中分离自定义的公共模块。

修改 webpack.config.js 配置文件,要把第三方库和自定义公共模块分别单独抽离出来,首先需要将minChunks属性设置为Infinity

const path = require('path');
const webpack = require('webpack');  // 导入webpack运行文件
const packagejson = require('./package.json');  // 导入项目package.json文件

module.exports = {
    entry: {  // 多入口文件的配置
        "main1": "./src/main1.js",
        "main2": "./src/main2.js",
        "vendor": Object.keys(packagejson.dependencies)  // 获取生产环境依赖的库
    },
    output: {
        path: path.resolve('./dist'),
        filename: "[name].js"  // 对应多入口的多出口配置
    },
    watch: true,
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: ['vendor', 'runtime'],
            filename: '[name].js',
            minChunks: Infinity  // 设置minChunks属性
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'common',
            filename: '[name].js',
            chunks: ['main1.js', 'main2.js']  // 从哪些文件中抽取commons chunk
        })
    ]
};

此时 vendor.js、第三方文件、自定义公共模块、webpack 运行文件就抽离出来,并分别在不同文件中。

9. webpack.ensure 异步加载

webpack.ensure有人称为异步加载,也有人叫它代码切割。其实就是把 JS 模块独立导出到一个.js文件,然后在使用这个模块的时候,webpack 会构造script dom元素,由浏览器发起异步请求获取这个.js文件。

webpack.ensure 的原理:
把一些 JS 模块独立成一个个.js文件,然后需要用到的时候,再创建一个script对象,加入到document.head对象中。浏览器会自动发起请求,去请求这个.js文件,再通过回调函数,去定义得到这个.js文件后,需要执行什么业务逻辑操作。

示例背景说明
main.js依赖三个js文件:

(1) A.js是封装aBtn按钮点击后才执行的业务逻辑;

(2) B.js是封装bBtn按钮点击后才执行的业务逻辑;

(3) vue.js是封装了main.js需要利用的包。
A.js和B.js都不是main.js必须的,都是未来才可能发生的操作,那么可以利用异步加载,当发生的时候再去加载。
vue.js是main.js立即依赖的工具箱,但它又非常大,所以将其配置打包成一个公共模块,利用浏览器的并发加载,加快下载速度。

index.html 文件:



    
    webpack的使用


    

main.js 文件

// ECMAScript6的模块导入
import Vue from "vue"
console.log(Vue);

document.getElementById('aBtn').onclick = function () {
    // 异步的加载A.js
    require.ensure([], function () {
        var A = require("./A.js");
        alert(A.data);
    })

};

document.getElementById('bBtn').onclick = function () {
    // 异步的加载B.js
    require.ensure([], function () {  // ensure函数的第一个参数(数组[])用于添加回调函数中异步加载的JS文件的依赖文件的路径
        var B = require("./B.js");
        alert(B.data);
    })
};

A.js & B.js

// A.js
var A = {
    "data": "Hello A"
};
module.exports = A;

// B.js
var B = {
    "data": "Hello B"
};
module.exports = B;

webpack 配置文件

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');  // 导入webpack运行文件
const packagejson = require('./package.json');  // 导入项目package.json文件

module.exports = {
    entry: {
        "main": "./src/main.js",
        "util": Object.keys(packagejson.dependencies)
    },
    output: {
        path: path.resolve('./dist'),
        filename: "[name].js"  // 对应多入口的多出口配置
    },
    watch: true,
    plugins: [
        new webpack.optimize.CommonChunkPlugin({
            name: "common",
            filename: "[name].js"
        }),
        new HtmlWebpackPlugin({
            // 主要用于多入口文件,当有多个入口文件的时候,它就会编译生成多个打包后的文件,chunks就能选择你要使用哪些JS文件
            chunks: ["common", "util", "main"],
            template: "./src/index.html",
            inject: true  // inject有四个值 true、body、head
        })
    ]
};

五、v-for 中 key 的作用

1. 当数据发生变化时,vue是怎样更新节点的?

渲染真实DOM的开销是很大的。所以,我们先根据真实 DOM 生成一棵virtual DOM,当virtual DOM某个节点的数据改变后会生成一个新的Vnode。然后VnodeoldVnode作对比,发现有不一样的地方就直接修改在真实的DOM上,然后更新oldVnode的值为Vnode。这个过程就是diff算法:调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁。

深度剖析 如何实现一个 Virtual DOM 算法
所谓的 Virtual DOM 算法,包括几个步骤:

  1. 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
  2. 当状态变更的时候,重新构建一棵新的对象树,然后用新的树和旧的树进行比较,记录两棵树的差异
  3. 把2中所记录的差异应用到步骤1所构建的真正的 DOM 树中,视图就更新了

2. vue-for 中 key 的作用

在使用v-for循环遍历时,我们需要使用 key 来给每个节点做一个唯一的标识,Diff算法就可以正确的识别此节点,找到正确的位置插入新的节点。总之一句话,key 的作用主要是为了高效的更新虚拟 DOM。另外 vue 在使用相同标签元素的过渡切换时,也会使用到 key 属性,其目的也是为了让 vue 可以区分它们,否则 vue 只会替换其内部属性而不会触发过渡效果。
参考链接:https://github.com/livoras/blog/issues/13

v-for使用 key 的示例:




    
    v-for中的key
    


如果不加 key ,点击“改变顺序”按钮,输入框的内容会和其相应的主题错乱开来。
总结一句话:当组件使用 v-for 遍历的时候,一定要绑定:key属性,避免 vue 去计算 DOM。

你可能感兴趣的:(Vue2.x学习笔记(二))