Vue2.x模块化开发知识点总结

模块化开发

常见的 模块化的规范

CommonJS AMD CMD 也有 ES6的 Modules

CommonJS

需要底层支撑

commonJS的导出:

module.exports = {
    flag:true,
    test(a,b){
        return a+b
    },
    demo(a,b){
        return a*b
    }
}

commonJS的导入:

let {test,demo,flag} = require('moduleA');
//等同于
let _mA = require('moduleA');
let test = _mA.test;
let demo = _mA.demo;
let flag = _mA.flag;

ES6的 Modules

export指令用于导出变量,比如下面的代码:

首先需要在页面引入 js 文件的时候 给一个 type 属性,值为 module,本身script标签具备跨域请求,不受同源策略影响,但由于添加了type =module 请求方式改为了 file:// 文件请求,受到同源策略的影响

1.导出方式一:

 // info.js
 export let name = 'why'
 export let age = 18 
 export let height = 1.88

上面的代码还有另一种写法:

2.导出方式二:

// info.js
let name = 'why'
let age = 18 
let height = 1.88

export {name, age, height}

3.可以导出函数、类:

export function mul(num1,num2){
    return num1 + num2
}

4.export default 注意!!! 只能一个导出为这个,否则import导入时候会产生错乱

var count = 88
export default count
import alger from "./main"
console.log(alger);

导入的三种方式:

// 导入  //对象 括号不能去
import {name, age, height} from '文件地址'     

// 当导出为  export   default  
import 自己起的名字 from '文件地址'

// 以这种方式导入的时候 起的名字是alger  ,因此这个模块中要想使用导入的数据需要添加  alger前缀
import * as alger from './main.js'
// 只有这个特殊一些,其他的直接可以拿到原来的变量,default 则是把导出的变量用自己起的名字代替了
// 这里导入的是'./main.js' 导出的所有变量  包括 default, 都包含着自己命名的这个对象中

webpack开始

  • 认识webpack
  • webpack 的安装
  • webpack 的起步
  • webpack 的配置
  • loader的使用
  • webpack中配置Vue
  • plugin的使用
  • 搭建本地服务器

认识webpack

从本质上来将,webpack是现代的javascript应用的静态模块打包工具

模块 打包来解释

前端模块化:
  • 目前使用前端模块化的一些方案:AMD、CMD、CommenJS 、ES6
  • 在ES6之前,我们想进行模块化开发,就必须借助于其他的工具,让我们可以进行模块化开发
  • 并且在通过模块化开发完成了项目后,还需要处理模块之间的各种依赖,并且将其进行整合打包
  • 而webpack其中一个核心就是让我们可以进行模块化开发,并且会帮助我们处理模块间的依赖关系
  • 而且不仅仅是JavaScript 文件,我们的CSS、图片、json文件等等在webpack中都可以被当做模块化来使用
打包
  • 理解了webpack可以帮助我们进行模块化,并且处理模块间的各种复杂关系后,打包的概念就非常好理解了
  • 就是讲webpack中的各种资源模块进行整合并称一个或多个包

webpack和node之间关系

webpack模块化打包工具,为了可以正常运行,必须依赖node环境,node环境为了可以正常的执行很多代码,必须其中包括各种依赖的包,npm工具(node packges manager)

安装node

安装webpack 首先需要安装Node.js,Node.js自带了软件包管理工具npm

查看自己的node 版本: node -v

全局安装webpack (这里我先指定版本3.6.0,因为vue cli2依赖该版本)

npm install webpack@3.6.0 -g

局部安装 webpack(后续才需要)

  • –save-dev 是开发时的依赖,项目打包后不需要继续使用的
vscode打包报错解决方案
在通过vs code 运行webpack进行打包时,报错webpack : 无法加载文件 D:\nodejs\node_global\webpack.ps1,因为在此系统上禁止运行脚本。
解决方案:

以管理员身份运行vs code
执行:get-ExecutionPolicy,显示Restricted,表示状态是禁止的
执行:set-ExecutionPolicy RemoteSigned
这时再执行get-ExecutionPolicy,就显示RemoteSigned
此时发现再进行打包就没有问题了
webpack基本使用
webpack ./src/main.js ./dist/bundel.js

由于每次打包都要执行这样的代码 ,太过于麻烦,因此我们需要对webpack 进行一些配置

webpack 配置

可以在项目根目录创建一个webpack.config.js文件,对webpack进行一些配置

const path = require('path')

module.exports = {
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundel.js'
  },
}

1.官方推荐 output出口需要传个对象,对象中的属性 path,filename. 其中 path需要传一个绝对路径。

2.而我们并不希望这个路径被写死。 此需要用到 node中的 path模块, 当用到node模块时 可以先执行 npm init 初始化下,得到 package.json 文件。

3.然后导入node 的path模块

const path = require('path')

4.调用 path.resolve() 函数,该函数传递两个参数,会将两个参数进行拼接。 第一个参数传递 __dirname, 注意两个下划线,第二个参数传递当前文件目录 下的目标文件

5.然后直接执行 webpack 即可 按照 webpack.config.js 配置文件中的配置项 进行配置

package.js 配置

1.原本我们已经通过 webpack.config.js 配置, 只需要运行webpack 就会自动进行 src 下的 main.js打包到 dist文件下的 bundel.js

2.package.js文件中"scripts" 对象下存储的都是 运行 npm run … 的快捷方式 。 我们如果在这个对象下 定义 "build": "webpack" 则运行npm run build 会自动运行 webpack, 也就是进行打包

注意!!!! package.json中的script的脚本在执行时,会按照一定的顺序寻找命令对应的位置

  • 首先,会寻找本地的node_modules/bin路径中对应的命令
  • 如果没有找到,会去全局的环境变量中寻找
  • 如何执行我们的build指令呢 npm run build

也就是说在cmd中直接使用webpack命令 则一定会使用全局配置的webpack,然而使用脚本命令,则会优先使用局部配置的webpack,这也是配置脚本运行的一个原因

关于全局webpack 和 本地webpack

1.由于每个项目都有自己依赖的webpack版本,所以不是所有项目都可以运行全局的webpack进行打包,因此我们需要在本地也安装相应版本的webpack.

2.安装本地webpack 执行命令npm install [email protected] --save-dev 此时 package.js文件下的devDependencies 中就有了安装的webpack信息了。 【devDependencies 中 develop的缩写,翻译:开发,dependencies 翻译:依赖】

配置css样式文件

0.在引入css样式的入口文件中需要导入,css不需要变量接收,因此直接使用commenJS中的语法 ,require('./src/css/normal.css')

1.本身webpack没有办法对css类型的文件进行解析打包。

2.需要依赖其他的loader 来配置,官网上看了后发现需要用到 css-loader 因此需要 通过 npm来安装

3.执行命令 npm install --save-dev css-loader 安装完毕后再 package.json 文件中的开发依赖可以看到这个loader,

4.安装结束后,需要在webpack 配置文件中进行配置,打开webpack.config.js 之前我们配置了 入口,出口,现在需要配置module,如下:

module:{
    rules:[{
    	test: /\.csss$/,
    	use: ['style-loader', 'css-loader']
    }]
}

5.不难发现rules 是定义一些模块规则的, test是匹配这些文件,模块的,当匹配到的时候,则会依据use,执行里面相应的包。

6.注意此处的css-loader是用来解析css 文件的,你并不能将该css文件绑定到相应的DOM上,所以此时又需要使用 style-loader

7.因此我们需要安装 npm install --save-dev style-loader , 安装后依然是在use中配置,style-loader 应该是css文件解析结束后再调用,因此需要现在css-loader前面,因为 use中执行顺序是 从右到左

配置less样式文件

方法与配置css基本一致

安装: npm install --save-dev less-loader less

配置: 在 webpack.config.js 中进行配置

	{
        test: /\.less$/,
        use: [{
          loader: "style-loader" // creates style nodes from JS strings
        }, {
          loader: "css-loader" // translates CSS into CommonJS
        }, {
          loader: "less-loader" // compiles Less to CSS
        }]
      }
配置图片文件

一:配置 url-loader

  1. 安装依赖 npm install --save-dev url-loader

  2. 配置 webpack.config.js 环境

    {
        test: /\.(png|jpg|gif|jpeg)$/,
        use: [{
           loader: 'url-loader',
            options: {
              limit: 8192
             }
        }]
    }
    

    一般 limit 限制大小 为 8k ,小于8k webpack会将其打包为 base64的图片,但是大于 8k的话 就不能这样处理了,进行打包的话会报错,提醒找不到 file-loader 整个模块,因此我们需要安装,file-loader

二:配置 file-loader

1.安装依赖 npm install --save-dev file-loader

2.不需要单独对file-loader 进行配置 ,配置的进行直接在url-loader中

3.此时再打包,默认大于 limit 的文件会直接调用 file-loader

4.默认情况下 是使用原来的扩展名,使用hash值作为文件名,直接打包在打包文件下。

5.此时我们需要在webpack.config.js的出口 配置上 publicPath:'dist/' 将输出解析文件的目录改为 dist ,url 相对于 HTML 页面

图片文件处理—修改文件名称

我们发现webpack自动帮助我们生成了一个非常长的名字

  • 这是一个32位hash值,目的是防止名字重复
  • 但是,真是开发中,我们可能对打包的图片名字有一定的要求
  • 比如,将所有图片放在一个文件夹中,跟上图片原来的名称,同时也要防止重复 写法 img/name.hash:8.ext

所以我们可以在options中添加上如下选项:

  • img:文件要打包到的文件夹
  • name:获取图片原来的名字,放在该位置
  • hash:8 为了防止图片名称冲突,依然使用hash,但是我们只保留8位
  • ext:使用图片原来的扩展名

但是,我们发现图片并没有显示出来,这是因为图片使用的路径不正确

  • 默认情况下,webpack会将生产的路径直接返回给使用者
  • 但是,我们整个程序打包在dist文件下的,所以这里我们需要在路径下再添加一个dist

因此 最终我们只需要在 url-loader 配置的 limit下配置一个 name: 'img/[name].[hash:8].[ext]' 中括号说明是变量,hash:8说明截取hash前八位, .ext说明使用默认的扩展名

ES6语法处理

webpack打包的js文件,写的ES6语法并没有转化为ES5,那么就意味着可能一些ES6还不支持的浏览器没有办法很好的运行我们的代码

此时我们需要使用babel

  • webpack中,我们直接使用babel对应的loader就可以了

  • npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
    

配置webpack.config.js文件

{
     test: /\.js$/,
     exclude: /(node_modules|bower_components)/,
     use: {
       loader: 'babel-loader',
       options: {
         presets: ['es2015']
       }
     }
   }

重新打包,查看bundle.js 文件,发现es6语法 改为了es5

如何模块化使用Vue

1.安装 Vue 因为Vue不仅仅是开发时候我们需要使用,运行的时候也需要使用,因此npm安装的时候 不再是-dev 而是 npm install --save vue

2.在 main.js 中通过import Vue from 'vue'进行导入 ,因为vue内部导出的方式是 export default vue , 因此可以这样导入,注意大小写

3.打包发现报错You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.

4.出现的原因,Vue构建最终发布版本的时候,构建了两类版本

  • runtime-only 代码中不可以有任何template
  • runtime-compiler 代码中,可以有template,因为有compiler可以用于 编译 【compiler 编译】

5.不难看出错误信息,简单翻译:你是用的是 runtime-only 进行构建Vue,template 不可以使用。

6.解决方式,进行webpack的配置

resolve:{
    alias:{
        // 此处是使用了 vue的 esm的一个版本,这个版本有template 语法
        'vue$': 'vue/dist/vue.esm.js'
    }
}
vue实例下的 el属性 和 template属性

当同时 有 el属性和 template属性时候,则会将el位置的模板用 template模板进行替换

.vue文件封装处理

但是一个组件以js对象的形式进行组织和使用的时候是非常不方便的

  • 一方面编写template模块非常的麻烦
  • 另一方面如果有样式的话,我们写在哪里比较合适呢

现在我们以一种全新的方式来组织一个vue组件

但是这个时候这个文件被正确加载吗?

  • 必然不可以,这种特殊的文件以及特殊的格式,必须有人帮助我们处理
  • 谁来处理呢? vue-loader以及vue-template-compiler

安装 vue-loader以及vue-template-compiler

**注意 ** vue版本,13版本以上还需要多加载其他loader,因此这里我们使用 vue-loader 的 13.0.0版本即可

修改webpack.config.js的配置文件:

 {
    test: /\.vue$/,
    use: ['vue-loader']
 }

如果想要省略导入时候的后缀名的话,需要在 配置文件webpack.config.js中的 resolve 中添加 extensions: ['.js','.css','.vue'] 这样导入的时候就可以不加 扩展名了。 【extensions: 拓展,扩展】,但是注意同名文件产生意想不到的错误

认识 plugin

loader 和 plugin 的区别

  • loader主要用于转换某些类型的模块,他是一个转换器
  • plugin是插件,它是对webpack本身的扩展,是一个扩展器

plugin的使用过程

​ 步骤一: 通过npm安装需要使用的plugin(某些webpack已经内置的插件不需要安装)

​ 步骤二: 在webpack.config.js 中的plugin中配置插件

版权插件 BannerPlugin

本身是webpack自带的插件,因此只需要在 webpack.config.js中配置就可以了

module.exports = {
	...
	plugins:[
		new webpack.BannerPlugin('最终版权归coderWyj所有')
	]
}

由于此处new了 webpack ,因此这个页面需要导入webpack ,因此这个文件头部需要 const webpack = require('webpack')

HtmlWebpackPlugin插件

目前我们的index.html文件是存放在项目的根目录下的

  • 发布真实项目的时候,发布的是dist文件夹中的内容,但是dist 文件夹中没有index.html文件,那么打包js等文件也就没有意义
  • 所以我们需要将index.html文件打包到dist文件夹中,这个时候就可以使用 HtmlWebpackPlugin 插件了

HtmlWebpackPlugin 插件可以为我们做什么事情

  • 自动生成一个index.html文件(可以指定模板来生成)
  • 将打包的js 文件,自动通过script标签插入到body中

安装HtmlWebpackPlugin 插件

npm install html-webpack-plugin --save-dev

使用插件,修改webpack.config.js文件中 plugins部分 的内容如下:

  • 这里的template 表示根据什么模板来生成index.html
  • 另外,我们需要删除之前在 output中添加的publicPath属性
  • 否则插入的script标签中的src可能会有问题

1.安装插件

2.配置插件 const HtmlWebpackPlugin = require('html-webpack-plugin')new HtmlWebpackPlugin()

3.**另外,我们需要删除之前在 output中添加的publicPath属性 ** 否则插入的script标签中的src可能会有问题

这时我们还有一个问题需要解决 因为index.html文件body下 需要一个

因此我们在webpack.config.js 中配置这个参数时候可以传入一个 template参数

new HtmlWebpackPlugin({
      template: 'index.html'
    })

此时 ,打包的时候,就会自动查找 这个index.html ,以这个文件模板进行编译至 dist文件夹

js压缩的Plugin

在项目发布之前,我们必然需要对js等文件进行压缩处理

  • 这里我们就对打包的js文件进行压缩

  • 我们使用一个第三方的插件uglifyjs-webpack-plugin,并且版本号指定1.1.1,和 CLI2 保持一致

  • npm install [email protected] --save-dev
    

修改webpack.config.js文件进行配置,使用插件:

const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')

new uglifyJsPlugin()

查看打包后的 bundel.js 文件,是已经被压缩过了的

搭建本地服务器

webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果

  1. 不过它是一个单独的模块,在webpack中使用之前需要先安装它
  • npm install --save-dev webpack-dev-server@2.9.1
    

**注意:**此处由于webpack使用的是 3.6.0版本 ,对应了 CLI2 因此这里的webpack-dev-server 也需要对应版本,所以使用了 2.9.1

  1. devserver 也是作为webpack中的一个选项,选项本身可以设置如下属性:
  • contentBase :为哪一个文件提供本地服务,默认是根文件夹,我们这里要填写./dist
  • port : 端口号
  • inline : 页面实时刷新(true , false)
  • historyApiFallback :在SPA页面中,依赖HTML5的 history模式

webpack.config.js 文件配置修改如下:

devServer: {
    contentBase:'./dist'
    inline: true
}
  1. 我们可以再配置另一script:
  • –open 参数表示直接打开浏览器
"dev": "webpack-dev-server --open"
webpack 配置文件抽离

因为有些配置文件是开发阶段需要使用的,还有一些时开发结束,最终打包时候需要使用的,因此需要对配置文件 webpack.config.js 进行抽离

1.创建 build目录存放 这些webpack的配置文件,

  • base.config.js
  • develop.config.js
  • product.config.js

2.需要安装 npm install --save-dev webpack-merge

安装之后就可以在这三个配置文件中进行使用了,先进行抽离

// develop.config.js   开发时候需要的配置
const webpackMerge = require('webpack-merge')
const baseConfig = require('./base.config')

module.exports = webpackMerge(baseConfig, {
  devServer: {
    contentBase: '/dist',
    port: 3000,
    inline: true
  }
})


// product.config.js   生产发布时候需要的配置
 const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
 const webpackMerge = require('webpack-merge')
 const baseConfig = require('./base.config')

 module.exports = webpackMerge(baseConfig, {
   plugins: [
     new UglifyjsWebpackPlugin()
   ]
 })

注意,此处两个文件因为需要拼接base.config.js 因此都需要加载 webpackMerge的包,其次,由于都需要用到 base.config.js 因此需要先导入这个文件, 然后通过 webpackMerge(基础文件,需要拼接的内容)

3.由于webpack.config.js文件抽离了,原本package.json中的 scripts 配置需要更改,定义的 npm run 快捷中 build的快捷需要 指定 --config ./build/product.config.js , dev 的快捷也需要添加 ` --config ./build/develop.config.js

4.base.config.js中输出出口 为path: path.resolve(__dirname, './dist') ,此时会在当前文件夹下建dist文件存储输出的文件,我们需要更改下 path: path.resolve(__dirname, '../dist')

Vue CLI 开始

什么是Vue CLI

  • 如果你只是简单写几个Vue的 demmo程序,那么你不需要Vue CLI
  • 如果每个项目都要手动完成这些工作,那无疑效率比较低,所以我们通常会使用一些脚手架工具来帮助完成这些事情

CLI是什么意思

  • CLI是Command-Line Interface ,翻译为命令行界面,但是俗称脚手架
  • Vue CLI 是一个官方发布的vue.js项目脚手架
  • 使用vue-cli 可以快速搭建 Vue 开发环境以及对应的webpack配置
cnpm安装(淘宝镜像)
npm insatll -g cnpm --registry=http://registry.npm.taobao.org

脚手架2

1.安装Vue 脚手架

  • npm install -g @vue/cli 脚手架全局安装就好了
    • 脚手架3安装后,可以拉脚手架2 没有必要单独安装脚手架2

2.注意:上面安装的是Vue CLI3的版本,如果想按照VueCLI2的方式初始化项目是不可以的

  • vueCLI3 和旧版使用了相同的vue命令,所以vue CLI2(vue-cli)被覆盖了,如果你仍然想使用旧版本,可以全局安装一个桥接工具

  • npm install -g @vue/cli-init
    // vue init 的影响效果将会跟  'vue-cli2'相同
    

3.初始化方式

CLI2 : 命令: vue init webpack my-project

CLI3 : 命令 : vue create my-project

4.初始化后进行的一些配置

  • Project name myvuejsproject 项目名字(默认是文件夹名字)
  • Project description (项目描述)
  • Author (项目作者)
  • Vue build (创建模式 runtime-only 还是runtime-compiler)
  • Install vue-router (是否使用路由)
  • Use ESLint to line your cord (是否 严格要求代码格式) 关闭是在 config文件夹下的 index.js文件中, true 改为 false
  • Set UP unit tests (单元测试)
  • Set UP e2e test with Nightwatch? (端到端测试)
  • Should we run npm install for you after the project hasbeen created? (使用的哪种包管理工具)

runtime-only 和 runtime-compiler区别

runtime-compiler( v1 ) 整个解析渲染过程:

  • template -> ast -> render -> vdom -> UI

runtime-only ( v2 ) 整个解析渲染过程:

  • render -> vdom -> UI

因此可以看出 runtime-only 性能更高,代码量更少,了解区别后,开发尽量使用 runtime-only

其实 后一个版本是在前一个版本的基础上加上了 compiler ,然而在 vue2.0版本后 最终的渲染都是通过 render函数的,如果写template 属性的话,则会对template进行编译,这样其实是对性能的一种损耗。因此我们尽量去使用runtime-only

那么runtime-only 怎么使用,注意点是什么呢?

  • 直接组件中来一个 render: function( creatElement ){ return creatElement (组件)}
const APP = require('./src/components/App.vue')
new Vue ({
    el:'#app',
    render: h => h(App)
})

//此处的参数 h  其实就是 createElement 这个函数 ,这个函数有两种用法

//1. 传递三个参数   元素名字  属性名字(可选)   内容,其中数组内容中可以继续去嵌套createElement函数
new Vue ({
    el:'#app',
    render: createElement => createElement('h2',
              {class: 'box'},
              ['Hello MOTO', createElement('button',['按钮'])])
})

//2.开发中常用  就是直接传一个vue  组件
new Vue ({
    el:'#app',
    render: h => h(App)
})

因此我们在实例中就不要再使用 template 定义模板了 ,直接使用render 函数去渲染

原理:

此时你可能会有疑惑,我们定义组件的时候 也就是 App.vue 文件中还是有 template 便签啊, 其实在我们将App导入进来的时候,导入的这个App对象 中是没有 template属性的,为什么没有呢???? 是因为我们之前是用来一个loader, 就是 vue-template-compiler ,它已经将我们的 .vue文件解析过了 ,因此其实已经没有template 了。

脚手架3:

vue-cli3 与 2 版本区别

  • vue-cli 3 是基于 webpack 4 打造的, vue-cli2还是webpack3
  • vue-cli 3 的设计原则是( 0配置) 移除的配置文件根目录下的 build和 config等目录
  • vue-cli 3 提供了 vue ui 命令,提供了可视化配置,更加人性化
  • 移除了 static文件夹,新增了public文件夹,并且index.html 移动到 public中
创建CLI3 :  命令  :   vue create my-project

修改webpack一些配置的方式

  1. 命令行启动 vue ui 打开vue ui界面进行修改
  2. 去node_modules 中隐藏的@vue中的 cliservice 中的lib 中的service.js中修改
  3. 根目录创建一个 vue.config.js 进行配置的一些修改 ,文件名字固定不能改

Vue router(重点)

内容概述:

  • vue-router基本使用
  • vue-router嵌套路由
  • vue-router参数传递
  • vue-router 导航守卫
  • keep-alive

前端路由的两种方式

  • location.hash
location.hash = 'foo'    //路由后面会添加上 /foo  ,执行后未对服务端发送请求
  • h5 中的 history
history.back() // 后退一个路由/栈   等价于下面:
history.go(-1)

history.forward() //向前,前进一个路由/栈  等价于:
history.go(1)
  • h5中压栈两种方式(两个函数都是三个参数:一个对象,一个title,一个url)
    • history.pushState( {} , ‘’ , ‘home’ )
    • history.replaceState( ( {} , ‘’ , ‘home’ )

两种方法都是往栈结构里压url,但是pushState 可以通过上面几种方法前进后退,浏览器有记录,replaceState 方法 不可以通过上面几种方法前进后退,,浏览器没有记录

安装和使用vue-router

因为我们已经学习了webpack,后续开发中我们主要是通过工程化的方式进行开发的

  • 所以后续,我们直接使用npm来安装路由即可
  • 步骤一:安装vue-router
  • 步骤二:在模块化工程中使用它(因为是一个插件,所以可以通过Vue.use()来安装路由功能)
    • 第一步:导入路由对象,并且调用Vue.use(VueRouter)
    • 第二步:创建路由实例,并且传入路由映射配置
    • 第三步:在Vue实例挂载创建的路由实例
// 配置路由相关的信息
//1.导入路由
import Vue from 'vue'
import VueRouter from 'vue-router'

// 2.通过Vue.use()安装插件
Vue.use(VueRouter)

// 3.创建VueRouter对象
const routes = []
const router = new VueRouter({
  // 配置路由和组件之间的路由关系
  routes
})

// 4.将router对象传入到Vue实例中
export default router

使用vue-router的步骤

  • 第一步;创建路由组件
  • 第二步:配置路由映射:组件和路径映射关系
  • 第三部:使用路由:通过

简单使用:

1.创建路由组件 home.vue about.vue

2.配置路由映射:组件和路径映射关系

  • router文件夹下index.js中配置

  • const routes = [{
        path: '/home',
        component: Home
      },
      {
        path: '/about',
        component: About
      }
    ]
    

    几个路由对应几个对象,几个关系

3.使用路由:通过 渲染

  • 找到 App.vue组件,标签中使用 首页 这个作用是当点击首页,url改变
  • 这个标签是个占位符,到时候对应的路由组件就会在这个占位里渲染

路由的默认路径

我们这里还有一个不太好的实现:

  • 默认情况下,进入网站的首页,我们希望 渲染首页的内容
  • 但是我们实现中,默认没有显示首页的组件,必须让用户点击才可以

如何可以让路径默认跳到首页,并且渲染首页组件呢

  • 非常简单,我们只需要多配制一个映射就可以了
const routes = [
    {
        path: '/',
        redirect: '/home'
    }
]

配置解析

  • 我们在routes中又配置了一个映射
  • path配置的根路径: /
  • redirect 是重定向,也就是我们将根路径重定向到 /home的路径下,这样就得到我们想要的结果了

如何将路径改为history模式

  • 我们前面说过改变路径的方式有两种:
    • URL的hash
    • HTML5的history
    • 默认情况下,路径的改变使用的URL的hash
  • 如果希望使用HTML5的history模式,非常简单,进行如下配置
const router = new VueRouter({
  // 配置路由和组件之间的路由关系
  routes,
  mode: 'history',
})

router-link补充

在前面的 router-link 中,我们只是使用了一个属性:to, 用于指定跳转的路径

router-link 中还有一些其他属性:

  • tag: tag可以指定 router-link 之后渲染成什么组件,比如上面的代码被渲染称一个li元素,而不是a
  • replace:replace不会留下hostory记录,所以指定replace的情况下,回退键不能返回到上个页面
  • active-class: router-link 对应的路由匹配成功时,会自动给当前元素设置一个 router-link-active 的 class ,设置active-class 可以修改默认的名称
    • 在进行高亮显示的导航菜单或者底部tabbar时 ,会使用到该类
    • 但是通常不会修改类的属性,会直接使用默认的router-link-active
    • 当标签较多的时候也可以在路由规则中修改,使用 linkActiveClass

路由代码跳转

前面我们使用的是router-link这个标签的 to来更改路由,如果不使用这个标签我们该怎么做呢?

  • 首先,我们用任意的标签,对这个标签进行一个事件监听
  • 监听事件传递的方法中对 $router进行一个操作。
  • 因为我们使用了 vue-router 因此所有组件中都会有一个 r o u t e r 对 象 , 此 时 我 们 只 需 要 调 用 t h i s . router对象,此时我们只需要调用 this. routerthis.router.push(’/home’) 就可以对路由url进行更改,当然使this.$router.replace(’/home’) 也是可以的

问题,此处连续点同一个按钮,会报一个promise的错误,原因不明???????????

动态路由:

首先是普通的路由配置,创建路由对应的组件,将组件导入路由,进行映射,在出口用router-link,通过to属性去使用。

当使用动态路由则:

  • 进行映射的时候原本path 路径后面添加 :userId 此时这个path路径就是动态的了,那么怎么确定 userId呢,在出口组件的data中定义 userId, 然后通过 v-bind 属性绑定的方式动态给router-link 动态绑定 to属性,此时更改data中的 userId,则 router-link 中 to的 路径也会改变。这样就做到了动态绑定了路由
  • 那么我们怎么在组件中拿到 userId 这个值呢,可以通过父子组件传值,或者更简单的方式 $route.params.userId拿到

$router 拿到的是路由 router这个实例对象 ; $route 拿到的是 活跃状态组件的路由,也就是new VueRouter({routes}),中处于活跃状态的那个, $route下有一个属性 params ,对应的就是活跃状态路由的参数,里面有{ “userId”: “kenan” } 对象,这样就可以拿到

路由的懒加载

官方给出的解释

  • 当打包构建应用时候,Javascript包会变的非常大,影响页面加载
  • 如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了

官方在说什么呢?

  • 首先我们知道路由中通常会定义很多不同的页面
  • 这个页面最后被打包在哪里呢?一般情况下是放在一个js文件夹中
  • 但是页面这么多放在一个js文件夹中,必然会造成这个页面非常大
  • 如果我们一次性从服务器请求下来这个页面,可能需要花费一定的时间,甚至用户的电脑还会出现短暂的空白
  • 如何避免这种情况被?使用路由懒加载就可以了
    • 就是每个路由对应的组件都打包到不同的js文件中

路由懒加载做了什么?

  • 路由懒加载的主要作用就是将路由对应的组件打包成一个个的js代码块
  • 只有在这个路由被访问到的时候,才加载对应的组件
路由懒加载的效果
  • 不适用懒加载的方式
import Home from '../components/home'
import About from '../components/about'
import User from '../components/user'

const routes = [{
    path: '/',
    redirect: '/home'
  }, {
    path: '/home',
    component: Home
  },
  {
    path: '/about',
    component: About
  }, {
    path: '/user/:userId',
    component: User
  }
]
const router = new VueRouter({
  // 配置路由和组件之间的路由关系
  routes,
  mode: 'history',
  linkActiveClass: 'active'
})

打包出来后,这三个组件的js代码都是在一起的

  • 使用懒加载的方式
const routes = [{
    path: '/',
    redirect: '/home'
  }, {
    path: '/home',
    component: () => import('../components/home')
  },
  {
    path: '/about',
    component: () => import('../components/about')
  }, {
    path: '/user/:userId',
    component: () => import('../components/user')
  }
]
const router = new VueRouter({
  // 配置路由和组件之间的路由关系
  routes,
  mode: 'history',
  linkActiveClass: 'active'
})

总结 : 就是将路由routes属性中的 component 通过 () => import('../components/home') 函数的方式绑定上,此时打包的时候就会按照组件个数分别打包成js代码块 。 当然 () => import('../components/home') 可以抽离出去。

路由的嵌套(重点)

嵌套路由是一个很常见的功能

  • 比如在home页面中,我们希望通过 /home/news 和 /home/message访问内容
  • 一个路径映射一个组件,访问这两个路径也会分别渲染两个组件

实现嵌套路由有两个步骤:

  • 创建对应的子组件,并且在路由映射中配置对应的子路由
  • 在组件内部使用标签

1.创建自组件

2.配置映射,因为是路由的嵌套,因此先找到嵌套在哪个路由中

3.在相应的路由中添加属性 children 并传一个数组,数组内传多个 路由对象,其中路由对象的 path 属性直接 给一个不带 / 的 相对路径即可,vue内部会自动拼接

4.通过 在嵌套的组件中 通过to属性 绑定这个路径,在通过 将组件页面挂在上去

传递参数(重点)

传递参数主要有两种类型;paramsquery

params的类型:

  • 配置路由格式:/router/:id
  • 传递的方式: 在path后面跟上对应的值
  • 传递后形成的路径:/router/123 , /router/abc

query 的类型:

  • 配置路由格式: /router ,也就是普通配置
  • 传递的方式; 对象中使用query 的 key 作为传递方式
  • 传递后形成的路径: /router?id=123&name=jack , /router?id=abc

1.以普通的方式创建路由,以及组件,在router-link to属性传值的时候,传递一个对象

2.对象中含有path属性,给与与路由映射相应的path, 然后传递第二个属性 query属性

3.该query属性是一个对象,对象中传递的键值对最后会被以 ?key=value&key1=value1的形式拼接到path属性后面

4.组件想要拿到数据可以通过,$route.query 拿到?后拼接的数据

导航守卫

每次路由变化,标题的title也进行相应的变化

  • 使用生命周期函数created 每次组件被创建的时候,就会执行这个生命周期,因此在每个路由组件中定义这个生命周期,将document.title赋值为相应的标题
  • 通过导航守卫,vue-router提供了beforeEach() 和 afterEach()钩子函数,他们会在路由即将改变前和改变后触发
    • 该函数有三个参数,to,from,next
    • to是改变后的路由那个对象,from是改变前的那个route对象 ,next是一个函数,只有执行这个函数参会接着往下执行
    • 我们在定义routes的时候,传入 meta【元数据】对象,给每个meta对象一个title属性,值为相应的值
    • 此时我们通过 to中的 matched 数组中的第 0 个中的 meta对象下的title就拿到了相应的title,将其赋值给document.title 即可
导航守卫补充
  • 如果是后置钩子,也就是afterEach,不需要主动调用next()函数
  • 上面我们使用的导航守卫,被称为全局守卫,除了这些还有:
    • 路由独享的守卫
    • 组件内的守卫

keep-alive

keep-alive 是Vue内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染

  • 也就是组件的生命周期 created 和 destroyed 不会被频繁触发
  • 他有两个非常重要的属性
    • include - 字符串或正则表达,只有匹配的组件会被缓存
    • exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存
    • 注意这两个属性传入的是组件的name属性,多个的时候用逗号隔开,但是一定不能有空格!!!!

router-view也是一个组件,如果直接被包在keep-alive里面,所有路径匹配的视图组件都会被缓存

业务需求:我们想要首页每次跳转到关于,用户等界面后回到首页,能够记录当时首页选择的是新闻还是信息

1.首先要想每次切换回首页能够不重新创建首页组件进行渲染,那么首先需要将router-view 包裹在keep-alive中,这样就能够每次切换路由,回来后不是重新创建,再渲染

2.此时这个router-view中会渲染的组件都不会重新创建再渲染了,当我们选择信息后切换到关于界面,再返回,此时点击首页则会触发路由 /home, 由于路由中又有重定向,因此又会重定向回/home/news。 这不是我们想要的。

3.那么我们去掉重定向呢??? 那么每次点击 首页 就会去请求/home这个路由

4.我们一开始进入首页以及后续点击首页(多次点击) 就会反复请求/home这个路由,当反复请求这个路由,我们还是需要让他指向 /home/news 的,因此不建议更改重定向

5.那么我们就需要在 home 组件中定义一个path去保存路径,每次离开home这个组件的时候,要去拿到当时 this.$route.path 这个值 ,也就需要用到组件内路由守卫beforeRouteLeave

6.当我们从关于界面重新跳转到首页,那么就会触发activated()这个函数,由于跳转回来首先是重定向到/home/news,那么此时我们进行一个判断,如果保存的这个path 不等于 重定向的这个path,那么我们就调用 this.$router.push(this.path) 更改路由

export default {
  name: 'Home',
  data() {
    return {
      path: '/home/news'
    }
  },
  activated() {
    if (this.$route.path !== this.path) {
      this.$router.replace(this.path)
    }
  },
  beforeRouteLeave(to, from, next) {
    this.path = this.$route.path
    next()
  }
}

TabBar 实现思路(重要)

(视频老师思路)

1.如果在下方有一个单独的TabBar组件,你如何封装?

  • 自定义TabBar组件,在APP中使用
  • 让TabBar处于底部,并且设置相关的样式

2.TabBar 中显示的内容由外界决定

  • 定义插槽
  • flex布局平分

3.自定义TabBarItem,可以传入 图片和文字

  • 定义TabBarItem,并且定义两个插槽: 图片、文字
  • 给两个插槽外层包装div,用于设置样式
  • 填充插槽,实现底部TabBar 的效果

4.传入高亮图片

  • 定义另外一个插槽,插入 active-icon 的数据
  • 定义一个变量 isActive , 通过 v-show来决定是否显示对应 icon

5.TabBarItem绑定路由数据

  • 安装路由 npm install vue-router --save
  • 完成router/index.js 的内容,以及创建对应的组件
  • main.js中注册router
  • APP中加入 router-view 组件

6.点击item跳转到对应的路由,并且动态决定 isActive

  • 监听item的点击,通过this.$router.replace()替换路由路径
  • 通过this.$route.path.indexOf(this.link) !== -1来判断是否是active

7.动态计算active样式

  • 封装新的计算属性: this.isActive ? { ‘color’:‘red’ } : { }

(个人思路)

分析这个tabbar界面,tabbar应该抽成一个组件,然后该组件注册为App.vue 的子组件,并且挂到这个组件上

由此开始,先封装tabbar组件

  • 搭建界面 tabbar下应该有四个 tabbar-item ,tabbar-item中应该有 icomoon图标 和 文字说明
  • 封装好tabbar组件后,App.vue里注册这个组件,并且在App.vue组件中使用
  • 调样式,通过flex布局,让tabbar-item 弹性分布
  • 此时,tabbar中的内容都是写死的,然而我们希望图标和文字说明应该是 使用组件的人自己去设置的,因此需要在tabbar-item中插入插槽,让App.vue 传入 这个元素内容去填充这个插槽,这样组件的使用者就可以通过App.vue写标签的形式去自行更改内容了。
  • 上一步处理后,会发现App.vue 中代码有有点冗余,我们将tabbar抽离为组件,也可以将每个 tabbar-item 也抽离会一个个组件,然后将tabbar-item 放入 tabbar中即可,那么我们有管理整个tabbar的组件,也有每个小item的组件,将item抽离为组件,跟tabbar组件一样,注册为App.vue的子组件,然后在App.vue中通过 添加到页面中,此时我们又发现 TabBarItem中的icomoon和文本又写死了
  • 我们需要再一次添加插槽,因此TabBarItem中 可变的标签内容有两个,不能直接通过slot 单个插槽标签解决,因此我们需要使用到具名插槽,注意此处由于使用的是 CLI2 ,vue版本在2.6以下,因此可以使用 slot以前的具名插槽方式
  • 抽离后,通过多次使用 TabBarItem 组件 传入不同的数据,就可以完全将数据写活了
  • 此时我们基本的页面就写好了,css也需要进行一些规范,App.vue 中引入清除默认样式的css,保存在src/assets 中,这里面保存一个引入的文件,本身每个组件对应的样式写入对应的组件中,这样方便管理
  • 接下来就是配置路由了,先安装路由 npm install vue-router --save
  • 然后配置路由,路由本身在 src下创建router文件夹,并且创建入口文件 index.js
  • 在index.js中 我们导入 Vue 导入 VueRouter ,然后通过 Vue.use( VueRouter),进行一个注册,然后new 一个路由实例对象,将这个对象导出
  • 在main.js中需要导入上一步导出的 router实例,然后挂在到 vue实例上,路由的简单配置就配置好了,接下来是配置路由映射
  • 在 src下创建view文件夹 存放对应的路由组件,建议先建对应路由模块的文件夹,然后创建组件,这样后期管理更清晰,然后const routes = [{path:'/home',component:() => import('../views/home/Home)}]
  • 如果不想要 hash模式的path ,只要在创建路由实例的时候传入 mode:'history'即可 ,然后只需要在需要挂在路由的App.vue页面 加入标签 router-view即可, 但是因为没有 router-link标签,因此没有办法监听路由变化,所以我们需要给每个TabBarItem,绑定点击事件,并且传入方法 使用 this. r o u t e r . p a s h ( ) 或 者 t h i s . router.pash( ) 或者 this. router.pash()this.router.replace( )方法进行路由跳转,传入的参数不应写死,应该通过父传子的方式拿到数据,因此通过props 定义 link, 在父组件页面每个 TabBarItem组件上绑定 link 属性,并且传递参数为该组件的路由,然后 点击事件传递一个方法,方法通过 props拿到的字符串 ,然后执行 this. r o u t e r . p a s h ( t h i s . l i n k ) 或 者 t h i s . router.pash( this.link) 或者 this. router.pash(this.link)this.router.replace( this.link) 注意:此处也许你会疑问 props拿到的到底是App.vue中的哪一个link呢,其实我们在App.vue使用了4次TabBarItem组件,因此每一次都有拿到,只是点击了哪一个就会执行相应的那个组件的方法,传入相应组件的link
  • 接下来是点击变色,首先想到的肯定是v-bind绑定动态class属性,定义计算属性 isActive, 通过字符串方法 indexOf() 判断 this.$route.path和 this.link,返回 true或false,然后通过 :class="{active:isActive}“来决定是否显示active这个class属性样式
  • 但是我们不想写死变换的颜色,因此通过动态绑定class方式就不再可取,需要动态绑定style
  • 动态绑定style的话,需要再写一个计算属性 activeStyle, 通过props拿到 使用组件的人传递进行的参数 activeColor=“blue”,并且给activeColor一个默认值 red, 限定类型为 String。 activeStyle中通过一个三元表达式 , isActive?{color:this.activeColor}:{}
  • 这样我们就将活跃的颜色写活了!!!!

vue项目中起别名

CLI2中找到 build中 webpack.base.comfig.js 找到起别名resolve 中 的alias, 然后只要是通过import导入的 都可以使用这个别名的绝对路径,如果是html中 src等属性使用的话 就需要在路径前面加上~符号 表示 也是用这个别名

ES6 promise 学习

什么是Promise呢?

ES6中一个非常重要和好用的特性及时Promise

  • 但是初次接触Promise会一脸懵逼,这是什么东西?

Promise到底是做什么的呢?

  • Promise是异步编程的一种解决方案

那什么时候我们会来处理异步事件呢?

  • 一种很常见的场景应该就是网络请求了
  • 我们封装一个网络请求的函数,因为不能立即拿到结果,所以不能简单的像 3+4=7一样结果返回
  • 所以往往我们会传入一个另外函数,在数据请求成功时候,将数据通过传入的函数回调出去
  • 如果只是一个简单的网络请求,那么这种方案不会给我们带来很大的麻烦

但是非常复杂的时候,就会出现回调地狱

什么情况下使用Promise呢?

一般情况下是有异步操作时,使用Promise对这个异步操作进行封装

new => 构造函数(1.保存了一些状态信息 2. 执行传入的函数)

在执行传入的回调函数时,会传入两个参数,resolve,reject,本身又是函数

 new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('helloworld')
        // reject('error Javascript')
      }, 1000)
    }).then((date) => {
      console.log(date);
      console.log(date);
      console.log(date);
    }).catch((error) => {
      console.log(error);
      console.log(error);
      console.log(error);
    })

// 另一种写法   then方法中传递成功和失败  两个回调
 new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('helloworld')
        // reject('error Javascript')
      }, 1000)
    }).then(date => {
      console.log(date);
      console.log(date);
      console.log(date);
    },error => {
      console.log(error);
      console.log(error);
      console.log(error);
    }) 

Promise三种状态?

首先,当我们开发中有异步操作时,就可以给异步操作包装一个Promise

  • 异步操作之后会有三种状态

我们一起来看看这三种状态

  • pending:等待状态,比如正在进行网络请求,或者定时器没有到时间
  • fulfill:满足状态,当我们主动回调了 resolve 时,就处于该状态,并且会回调 .then( )
  • reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调 .catch( )

Promise的链式调用

就是每次成功的回调后面返回一个新的 Promise实例对象,对新的异步操作进行处理,然后接着上一个then( )函数后面继续 .then( )函数处理新的成功或者失败操作,如果有很多个,一直循环往复即可

 new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('hello111')
      }, 1000)
    }).then(data => {
      console.log(data);
      console.log(data);
      console.log(data);
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('hello222')
        }, 1000)
      })
    }).then(data => {
      console.log(data);
      console.log(data);
      console.log(data);
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('hello333')
        }, 1000)
      })
    }).then(data => {
      console.log(data);
      console.log(data);
      console.log(data);
    })

当第一次异步操作拿到数据后,后续继续想通过promise对数据进行一些操作的话可以省略返回的 new Promise的写法,直接返回一个 Promise.resolve(),在括号内对数据进行一些操作, 当然也可以直接返回 括号内对数据的操作 继续省略 Promise.resolve()。 本身promise内部还是会对返回的 数据进行一层promise的封装

当然这种方式也可用在reject错误回调上, Promise.resolve()改为 Promise.reject() 即可,当然除了通过 reject()回调拿到错误,还可以通过 throw 抛出异常的方式,catch依然会拿到错误对象

	// 第一种
    new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('success')
      }, 1000)
    }).then(data => {
      console.log(data);
      return new Promise((resolve, reject) => {
        resolve(data + 'ooo')
      })
    }).then(data => {
      console.log(data);
      return new Promise((resolve, reject) => {
        resolve(data + 'aaa')
      })
    }).then(data => {
      console.log(data);
    })

    // 第二种
    new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('success')
      }, 1000)
    }).then(data => {
      console.log(data);
      return Promise.resolve(data + 'www')
    }).then(data => {
      console.log(data);
      return Promise.resolve(data + 'kkk')
    }).then(data => {
      console.log(data);
    })

    // 第三种
    new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('success')
      }, 1000)
    }).then(data => {
      console.log(data);
      return data + 'www'
    }).then(data => {
      console.log(data);
      return data + 'kkk'
    }).then(data => {
      console.log(data);
    })

Promise的all()方法使用

	Promise.all([
      new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve({
            name: '皮卡丘',
            attribute: '电'
          })
        }, 2000)
      }),
      new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve({
            name: '小火龙',
            attribute: '火'
          })
        }, 1000)
      })
    ]).then(result => {
      console.log(result);
    })

Promise.all( ) 方法可以对多个异步操作直接结束通过result拿到多次的异步resolve结果,结果以数组的形式保存在result中, 本身Promise.all( ) 传递一个数组,数组中传递Promise实例对象

Promise.race()方法

同样的它传入的也是promise对象列表 不过他们执行顺序是按照谁快 谁先输出

const pro1 = new Promise((resolve,reject) => {
    setTimeout(resolve,100,'1');
});

const pro2 = new Promise((resolve,reject) => {
    setTimeout(resolve,200,'2');
});

const pro3 = new Promise((resolve,reject) => {
    setTimeout(resolve,300,'3');
});

const pro4 = new Promise((resolve,reject) => {
    setTimeout(resolve,10,'4');
});


Promise.race([pro4,pro1,pro2,pro3]).then(data => {
    console.log(data);  // 1   输出最快的那个
}).catch(err => {
    console.log(err);
})

你可能感兴趣的:(前端,Vue,vue)