JS中使用var来声明一个变量时, 变量的作用域主要是和函数的定义有关.针对于其他块定义来说是没有作用域的,比如if/for等,这在我们开发中往往会引起一些问题。
总之用let就好了,var淘汰了
当我们修饰的标识符不会被再次赋值时, 就可以使用const来保证数据的安全性.
let name = "123" //以前的写法 let people1 = {name:name} //之后的写法 let people = {name}
fuc2:fuction(){ } fuc1(){ }
Promise是异步编程的一种解决方案
Promise.resovle():将数据包装成Promise对象,并且在内部回调resolve()函数
Promise.reject():将数据包装成Promise对象,并且在内部回调reject()函数
new Promise((resolve, reject) => { setTimeout(() => { resolve('aaa') }, 1000) }).then(res => { // 1.自己处理10行代码 console.log(res, '第一层的10行处理代码'); // 2.对结果进行第一次处理 return res + '111' }).then(res => { console.log(res, '第二层的10行处理代码'); return Promise.resolve(res + '222') }).then(res => { return new Promise((resolve, reject) => { resolve(res + '111') //reject('err') }) }).then(res=>{ })
为什么会出现模块化开发
在网页开发的早期,js制作作为一种脚本语言,做一些简单的表单验证或动画实现等,那个时候代码还是很少的。客户端需要完成的事情越来越多,代码量也是与日俱增。为了应对代码量的剧增,我们通常会将代码组织在多个js文件中,进行维护。但是这种维护方式,依然不能避免一些灾难性的问题。例如:全局变量同名等问题
匿名函数可以解决全局变量,但是另外一个文件中不容易使用,因为flag是一个局部变量
export let name = 5; export let age = 5;
let name = 5; export {name}
export class person{ int age; } export function test(){ }
class person{ int age; } function test(){ } export {test,person}
某些情况下,一个模块中包含某个的功能,我们并不希望给这个功能命名,而且让导入者可以自己来命名
export default在同一个模块中,不允许同时存在多个
export default function(){ }
import fun from '路径'
首先,我们需要在HTML代码中引入两个js文件,并且类型需要设置为module
<script type = "module" src="文件路径"></script>
import指令用于导入模块中的内容,比如main.js的代码
import {name,age} from "./info.js"
如果我们希望某个模块中所有的信息都导入,一个个导入显然有些麻烦
import * as info from "./info.js"info.ages
编程范式:声明式编程
方式一:直接CDN引入
可以选择引入开发环境本办还是生产环境版本
<!-- 开发环境版本,包含了有帮助的命令行警告 --><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><!-- 生产环境版本,优化了尺寸和速度 --><script src="https://cdn.jsdelivr.net/npm/vue"></script>
下载和引入
https://vuejs.org/js/vue.jshttps://vuejs.org/js/vue.min.js
NUM
用=={{}}==表示
里面可以进行运算,一些简单的表达式
v-once
第一次显示什么值就是什么值,不会因为Vue数据的改变发生改变
例子
<!DOCTYPE html><html lang="en"><head></head><body> <div id="app"> <div v-once>{{name}}</div> </div></body><script src="vue.js"></script><script> new Vue({ el: "#app", data: { name: "hello", url: "<a href='http://www.baidu.com'>百度一下</a>" } })</script></html>
v-html
将里面的值按照html解析出来
例子
<!DOCTYPE html><html lang="en"><head></head><body> <div id="app"> <div v-html = "url"></div> </div></body><script src="vue.js"></script><script> new Vue({ el: "#app", data: { url: "<a href='http://www.baidu.com'>百度一下</a>" } })</script></html>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dNSN2IoA-1621734333674)(图片\v-html测试图.png)]
v-text
v-text会把里面内容覆盖掉
例子
<!DOCTYPE html><html lang="en"><head></head><body> <div id="app"> <div v-text="url">{{name}}</div> <div>{{url}},{{name}}</div> </div></body><script src="vue.js"></script><script> new Vue({ el: "#app", data: { name: "hello", url: "<a href='http://www.baidu.com'>百度一下</a>" } })</script></html>
v-pre
v-pre不会将{{}}转义成数据,原封不动显示出来
例子
<!DOCTYPE html><html lang="en"><head></head><body> <div id="app"> <div v-pre>{{url}}</div> </div></body><script src="vue.js"></script><script> new Vue({ el: "#app", data: { url: "<a href='http://www.baidu.com'>百度一下</a>" } })</script></html>
v-cloak
由于Vue在解析的时候,还没有将{{}}转化,可能会让用户看见
在vue解析之前,div中有一个属性v-cloak,在vue解析之后,div中没有一个属性v-clcak
例子
<!DOCTYPE html><html lang="en"><head> <style> [v-cloak]{ display: none } </style></head><body> <div id="app" v-cloak> </div></body><script src="vue.js"></script><script> new Vue({ el: "#app", data: { } })</script></html>
<!DOCTYPE html><html lang="en"><head> <style> [v-cloak]{ display: none } </style></head><body> <div id="app" v-cloak> {{name}}, {{num1 + num2}} <div v-once>{{name}}</div> <div>{{url}}</div> <div v-html="url"></div> <div v-text="url">{{name}}</div> <div>{{url}},{{name}}</div> <div v-pre>{{url}}</div> </div></body><script src="vue.js"></script><script> new Vue({ el: "#app", data: { name: "hello", num1: 3, num2: 5, url: "<a href='http://www.baidu.com'>百度一下</a>" } })</script></html>
v-bind用于绑定一个或多个属性值,或者向另一个组件传递props值(这个学到组件时再介绍)
直接通过{}绑定一个类
<h2 :class="{'active': isActive}">Hello World</h2><h2 :class="{'active': isActive, 'line': isLine}">Hello World</h2><!--用法三:和普通的类同时存在,并不冲突注:如果isActive和isLine都为true,那么会有title/active/line三个类--><h2 class="title" :class="{'active': isActive, 'line': isLine}">Hello World</h2>
如果绑定的过于复杂,可以放在一个methods或者computed中
<h2 class="title" :class="classes">Hello World</h2><script> new Vue({ methods:{ classes(){ return {'active': isActive, 'line': isLine} } } })</script>
直接通过{}绑定一个类
<h2 :class="['active', 'line']">Hello World</h2><h2 class="title" :class="['active', 'line']">Hello World</h2>
如果过于复杂,可以放在一个methods或者computed中
<h2 class="title" :class="classes">Hello World</h2><script> new Vue({ methods:{ classes(){ return ['active', 'line'] } } })</script>
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .active { color: red; font-size: 50px; } .aaaa { color: blueviolet; font-size: 50px; } </style></head><body> <div id="app"> <a v-bind:href="url">百度</a> <a :href="url">百度</a> <a :class="{active:isActive}" :href="url">百度</a> <a :class="getClassesd" :href="url">百度</a> <a :class="[classA]" :href="url">百度</a> <a :class="['classA']" :href="url">百度</a> </div></body><script src="vue.js"></script><script> new Vue({ el: "#app", data: { url: "https://www.baidu.com", isActive: false, classA: "aaaa" }, methods: { getClassesd: function () { return { active: this.isActive } } } })</script></html>
对象的key是CSS属性名称
对象的value是具体赋的值,值可以来自于data中的属性
<body> <div id="app"> <p :style="{color:currentColor}">你好</p> <p :style="{color:'blue'}">你好</p> </div></body><script src="vue.js"></script><script> new Vue({ el: "#app", data: { currentColor: 'red' } })</script>
<body> <div id="app"> <p :style="[styleObject,baseSize]">你好</p> <p :style="{color:'blue'}">你好</p> </div></body><script src="vue.js"></script><script> new Vue({ el: "#app", data: { styleObject: { color: 'red', }, baseSize: { fontSize: '50px' } } })</script>
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .active { color: red; } </style></head><body> <div id="app"> <a v-bind:href="url">百度</a> <a :href="url">百度</a> <a :class="{active:isActive}" :href="url">百度</a> </div></body><script src="vue.js"></script><script> new Vue({ el: "#app", data: { url: "https://www.baidu.com", isActive: false }, method:{ getClasses(){ return {active:this.isActive} } } })</script></html>
我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示
每个计算属性都包含一个getter和一个setter。
很多情况下,我们只提供getter方法,在某些情况下,你也可以提供一个setter方法(不常用)。
计算属性有缓存,在没有改变值情况下,会保存在缓存
<!DOCTYPE html><html lang="en"><body> <div id="app"> <p>{{fullName}}</p> <p>{{tell}}</p> </div></body><script src="vue.js"></script><script> new Vue({ el: "#app", data: { first:"哈哈哈", second:"你好" }, computed:{ fullName(){ return this.first + " "+this.second; }, tell(){ get(){ return this.first + " "+this.second; } set(newValue){ this.first = "123" } } } })</script></html>
v-on:
介绍:绑定事件监听器
v-on也有对应的语法糖:v-on:click可以写成@click
注意事项:
如果该方法不需要额外参数,那么方法后的()可以不添加
如果方法本身中有一个或者多个参数,那么会默认将原生事件event参数传递进去
如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件
v-on修饰符
.stop:防止事件冒泡。等于调用 event.stopPropagation()
.prevent:阻止默认行为。等于调用 event.preventDefault()
.{keyCode | keyAlias}:只当事件是从特定键触发时才触发回调。
.native:监听组件根元素的原生事件
.once:只触发一次回调
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ae7MEdjR-1621734333678)(图片\v-on的表达式.png)]
这个其实和Vue的虚拟DOM的Diff算法有关系
有一个数组**[‘A’,‘B’,‘C’,‘D’,‘E’]**我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的
即把C更新成F,D更新成C,E更新成D,最后再插入E
使用key来给每个节点做一个唯一标识
Diff算法就可以正确的识别此节点
找到正确的位置区插入新的节点。
key的作用主要是为了高效的更新虚拟DOM
因为Vue是响应式的,所以当数据发生变化时,Vue会自动检测数据变化,视图会发生对应的更新。
直接修改不会更新,需要采用下面方法
Vue中包含了一组观察数组编译的方法。
filters: { showPrice(price) { return '¥' + price.toFixed(2) } }
<h2>总价格: {{totalPrice | showPrice}}</h2>
Vue中使用v-model指令来实现表单元素和数据的双向绑定
v-model其实是一个语法糖,它的背后本质上是包含两个操作
v-bind绑定一个value属性
v-on指令给当前元素绑定input事件
<input type="text" v-model="message"><!--等同于--><input type="text" v-bind:value="message" v-on:input="message = $event.target.value">
<div id="app"> <label for="male"> <input type="radio" id="male" value="男" v-model="sex">男 </label> <label for="female"> <input type="radio" id="female" value="女" v-model="sex">女 </label> <h2>您选择的性别是: {{sex}}</h2></div><script src="../js/vue.js"></script><script> const app = new Vue({ el: '#app', data: { message: '你好啊', sex: '女' } })</script>
<div id="app"> <label for="agree">--> <input type="checkbox" id="agree" v-model="isAgree">同意协议 </label> <input type="checkbox" value="篮球" v-model="hobbies">篮球 <input type="checkbox" value="足球" v-model="hobbies">足球 <input type="checkbox" value="乒乓球" v-model="hobbies">乒乓球 <input type="checkbox" value="羽毛球" v-model="hobbies">羽毛球</div><script src="../js/vue.js"></script><script> const app = new Vue({ el: '#app', data: { message: '你好啊', isAgree: false, // 单选框 hobbies: [], // 多选框, originHobbies: ['篮球', '足球', '乒乓球', '羽毛球', '台球', '高尔夫球'] } })</script>
<div id="app"> <!--1.选择一个--> <select name="abc" v-model="fruit"> <option value="苹果">苹果</option> <option value="香蕉">香蕉</option> <option value="榴莲">榴莲</option> <option value="葡萄">葡萄</option> </select> <h2>您选择的水果是: {{fruit}}</h2> <!--2.选择多个--> <select name="abc" v-model="fruits" multiple> <option value="苹果">苹果</option> <option value="香蕉">香蕉</option> <option value="榴莲">榴莲</option> <option value="葡萄">葡萄</option> </select> <h2>您选择的水果是: {{fruits}}</h2></div><script src="../js/vue.js"></script><script> const app = new Vue({ el: '#app', data: { message: '你好啊', fruit: '香蕉', fruits: [] } })</script>
组件化是Vue.js中的重要思想
组件化思想的应用
创建组件构造器
Vue.extend()
例子
没有分离写法
<script>const cpnC = Vue.extend({ template: ` <div> <h2>我是标题</h2> <p>我是内容, 哈哈哈哈</p> <p>我是内容, 呵呵呵呵</p> </div>` })</script>
分离写法
<!--分离写法--><template id="cpn"> <div> <h2>我是标题</h2> <p>我是内容,呵呵呵</p> </div></template><script> const i = Vue.extend({ template: "#cpn" })</script>
注册组件
Vue.component()
例子
全局注册
<script> Vue.component('cpn', cpnC)</script>
局部注册
<script> const app = new Vue({ components: { // cpn使用组件时的标签名 cpn: cpnC } })</script>
语法糖
<script> Vue.component('cpn1', { template: ` <div> <h2>我是标题1</h2> <p>我是内容, 哈哈哈哈</p> </div> ` })</script>
分离写法
template
<template id="cpn"> <div> <h2>我是标题</h2> <p>我是内容,呵呵呵</p> </div></template><script> Vue.component('cpn', { template: '#cpn' })</script>
script写法
<script type="text/x-template" id="cpn"><div> <h2>我是标题</h2> <p>我是内容,哈哈哈</p></div></script><script> Vue.component('cpn', { template: '#cpn' })</script>
使用组件
组件必须挂载在某个Vue实例下,否则它不会生效。
例子
<cpn></cpn>
Vue组件应该有自己保存数据的地方
组件自己的数据
在开发中,往往一些数据确实需要从上层传递到下层
props基本用法
可以类型限制、
String、Number、Boolean、Array、Object、Date、Function、Symbol
当我们有自定义构造函数时,验证也支持自定义的类型
cmessage: String
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ne3ZsQgX-1621734333682)(图片\数据类型.png)]
提供一些默认值, 以及必传值
cmessage: { type: String, default: 'aaaaaaaa', required: true },
如果是一个对象,数组需要定义为函数
cmovies: { type: Array, default() { return [] } }
在子组件上绑定号数据
<div id="app"> <!--<cpn v-bind:cmovies="movies"></cpn>--> <!--<cpn cmovies="movies" cmessage="message"></cpn>--> <cpn :cmessage="message" :cmovies="movies"></cpn></div>
完整代码
<div id="app"> <!--<cpn v-bind:cmovies="movies"></cpn>--> <!--<cpn cmovies="movies" cmessage="message"></cpn>--> <cpn :cmessage="message" :cmovies="movies"></cpn></div><template id="cpn"> <div> <ul> <li v-for="item in cmovies">{{item}}</li> </ul> <h2>{{cmessage}}</h2> </div></template><script src="../js/vue.js"></script><script> // 父传子: props const cpn = { template: '#cpn', // props: ['cmovies', 'cmessage'], props: { // 1.类型限制 // cmovies: Array, // cmessage: String, // 2.提供一些默认值, 以及必传值 cmessage: { type: String, default: 'aaaaaaaa', required: true }, // 类型是对象或者数组时, 默认值必须是一个函数 cmovies: { type: Array, default() { return [] } } }, data() { return {} }, methods: { } } const app = new Vue({ el: '#app', data: { message: '你好啊', movies: ['海王', '海贼王', '海尔兄弟'] }, components: { cpn } })</script>
自定义事件
自定义事件的流程
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body><!--父组件模板--><div id="app"> <cpn @item-click="cpnClick"></cpn></div><!--子组件模板--><template id="cpn"> <div> <button v-for="item in categories" @click="btnClick(item)"> {{item.name}} </button> </div></template><script src="../js/vue.js"></script><script> // 1.子组件 const cpn = { template: '#cpn', data() { return { categories: [ {id: 'aaa', name: '热门推荐'}, {id: 'bbb', name: '手机数码'}, {id: 'ccc', name: '家用家电'}, {id: 'ddd', name: '电脑办公'}, ] } }, methods: { btnClick(item) { // 发射事件: 自定义事件 this.$emit('item-click', item) } } } // 2.父组件 const app = new Vue({ el: '#app', data: { message: '你好啊' }, components: { cpn }, methods: { cpnClick(item) { console.log('cpnClick', item); } } })</script></body></html>
在ES6之前,我们要想进行模块化开发,就必须借助于其他的工具,让我们可以进行模块化开发。并且在通过模块化开发完成了项目后,还需要处理模块间的各种依赖,并且将其进行整合打包。而webpack其中一个核心就是让我们可能进行模块化开发,并且会帮助我们处理模块间的依赖关系。而且不仅仅是JavaScript文件,我们的CSS、图片、json文件等等在webpack中都可以被当做模块来使用(在后续我们会看到)。这就是webpack中模块化的概念。
就是将webpack中的各种资源模块进行打包合并成一个或多个包(Bundle)。并且在打包的过程中,还可以对资源进行处理,比如压缩图片,将scss转成css,将ES6语法转成ES5语法,将TypeScript转成JavaScript等等操作。
安装webpack首先需要安装Node.js,Node.js自带了软件包管理工具npm
查看自己的node版本
node -v
全局安装webpack(这里我先指定版本号3.6.0,因为vue cli2依赖该版本)
npm install webpack@3.6.0 -g
局部安装webpack
#cd ......npm install webpack@3.6.0 -g --save-dev
在终端直接执行webpack命令,使用的全局安装的webpack当在package.json中定义了scripts时,其中包含了webpack命令,那么使用的是局部webpack
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m5KxXZMJ-1621734333685)(\图片\webpack.png)]
打包命令
webpack ./src/main.js ./dist/bundle.js
处理完所有模块之间的关系后,将多个js打包到一个js文件中,引入时就变得非常方便了
每次书写还是比较麻烦,故需要写一个webpack.config.js
const path = require('path')module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', //publicPath: 'dist/'//输出到特定文件里面 },}
package.json中的scripts的脚本在执行时,会按照一定的顺序寻找命令对应的位置。首先,会寻找本地的node_modules/.bin路径中对应的命令。如果没有找到,会去全局的环境变量中寻找。
{ "name": "meetwebpack", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack" }, "author": "", "license": "ISC", "devDependencies": { "webpack": "^3.6.0" }}
在开发中我们不仅仅有基本的js代码处理,我们也需要加载css、图片,也包括一些高级的将ES6转成ES5代码,将TypeScript转成ES5代码,将scss、less转成css,将.jsx、.vue文件转成js文件等等,这时候需要loader
loader使用过程:
步骤一:通过npm安装需要使用的loader
步骤二:在webpack.config.js中的modules关键字下进行配置
const path = require('path')module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }, module: { rules: [ { test: /\.css$/, // css-loader只负责将css文件进行加载 // style-loader负责将样式添加到DOM中 // 使用多个loader时, 是从右向左 use: [ 'style-loader', 'css-loader' ] } ] }}
我们来安装style-loader
npm install --save-dev style-loader
这次因为webpack在读取使用的loader的过程中,是按照从右向左的顺序读取的
module: { rules: [ { test: /\.css$/, // css-loader只负责将css文件进行加载 // style-loader负责将样式添加到DOM中 // 使用多个loader时, 是从右向左 use: [ 'style-loader', 'css-loader' ] } ] }
npm install --save-dev less-loader less
{ 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 }]}
npm install --save-dev url-loader
{ test: /\.(png|jpg|gif|jpeg)$/, use: [ { loader: 'url-loader', options: { // 当加载的图片, 小于limit时, 会将图片编译成base64字符串形式. // 当加载的图片, 大于limit时, 需要使用file-loader模块进行加载. limit: 13000, //设置位置,名字和Hash值 name: 'img/[name].[hash:8].[ext]' }, } ] }
npm install --save-dev file-loader
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
{ test: /\.js$/, // exclude: 排除 // include: 包含 exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['es2015'] } } }
npm install vue --save
在main.js上
// 5.使用Vue进行开发import Vue from 'vue'// import App from './vue/app'import App from './vue/App.vue'new Vue({ el: '#app', template: '<App/>', components: { App }})
在index.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body><div id="app"></div><script src="./dist/bundle.js"></script></body></html>
在webpack.config.js
resolve: { // alias: 别名 extensions: ['.js', '.css', '.vue'], alias: { 'vue$': 'vue/dist/vue.esm.js' } }
演化
自定义了组件,也必须修改index.html来使用组件。为了避免修改----------------->定义template
在前面的Vue实例中,我们定义了el属性,用于和index.html中的#app进行绑定,让Vue实例之后可以管理它其中的内容这里,我们可以将div元素中的{{message}}内容删掉,只保留一个基本的id为div的元素我们定义一个template属性。在我们之前的学习中,我们知道el用于指定Vue要管理的DOM,可以帮助解析其中的指令、事件监听等等。而如果Vue实例中同时指定了template,那么template模板的内容会替换掉挂载的对应el的模板。这样做之后我们就不需要在以后的开发中再次操作index.html,只需要在template中写入对应的标签即可
书写template模块非常麻烦怎么办呢?
没有关系,稍后我们会将template模板中的内容进行抽离。
会分成三部分书写:template、script、style,结构变得非常清晰。
安装vue-loader和vue-template-compiler来处理新文件
npm install vue-loader vue-template-compiler --save-dev
修改webpack.config.js的配置文件
{ test: /\.vue$/, use: ['vue-loader'] }
plugin是插件的意思,通常是用于对某个现有的架构进行扩展。
webpack中的插件,就是对webpack现有功能的各种扩展,比如打包优化,文件压缩等等。
loader和plugin区别:
plugin的使用过程
该插件名字叫BannerPlugin,属于webpack自带的插件。
修改webpack.config.js
plugins: [ new webpack.BannerPlugin('最终版权归aaa所有')]
目前,我们的index.html文件是存放在项目的根目录下的。我们知道,在真实发布项目时,发布的是dist文件夹中的内容,但是dist文件夹中如果没有index.html文件,那么打包的js等文件也就没有意义了。所以,我们需要将index.html文件打包到dist文件夹中,这个时候就可以使用HtmlWebpackPlugin插件
HtmlWebpackPlugin插件可以为我们做这些事情:自动生成一个index.html文件(可以指定模板来生成)将打包的js文件,自动通过script标签插入到body中
使用插件,修改webpack.config.js文件中plugins部分的内容如下:这里的template表示根据什么模板来生成index.html另外,我们需要删除之前在output中添加的publicPath属性,否则插入的script标签中的src可能会有问题
npm install html-webpack-plugin --save-dev
plugins: [ new webpack.BannerPlugin('最终版权归aaa所有'), new HtmlWebpackPlugin({ template: 'index.html' }),]
npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
plugins: [ new webpack.BannerPlugin('最终版权归aaa所有'), new HtmlWebpackPlugin({ template: 'index.html' }), new UglifyjsWebpackPlugin()]
npm install --save-dev webpack-dev-server@2.9.1
devserver也是作为webpack中的一个选项,选项本身可以设置如下属性:
devServer: { contentBase: './dist', inline: true }
"scripts": { "dev": "webpack-dev-server --open" },
npm install webpack-merge -D
package.json
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack --config ./build/prod.config.js", "dev": "webpack-dev-server --open --config ./build/dev.config.js" },
dev.config.js
const webpackMerge = require('webpack-merge')const baseConfig = require('./base.config')module.exports = webpackMerge(baseConfig, { devServer: { contentBase: './dist', inline: true }})
base.config.js
const path = require('path')const webpack = require('webpack')const HtmlWebpackPlugin = require('html-webpack-plugin')const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, '../dist'), filename: 'bundle.js', // publicPath: 'dist/' }, module: { rules: [ { test: /\.css$/, // css-loader只负责将css文件进行加载 // style-loader负责将样式添加到DOM中 // 使用多个loader时, 是从右向左 use: [ 'style-loader', 'css-loader' ] }, { 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 }] }, { test: /\.(png|jpg|gif|jpeg)$/, use: [ { loader: 'url-loader', options: { // 当加载的图片, 小于limit时, 会将图片编译成base64字符串形式. // 当加载的图片, 大于limit时, 需要使用file-loader模块进行加载. limit: 13000, name: 'img/[name].[hash:8].[ext]' }, } ] }, { test: /\.js$/, // exclude: 排除 // include: 包含 exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['es2015'] } } }, { test: /\.vue$/, use: ['vue-loader'] } ] }, resolve: { // alias: 别名 extensions: ['.js', '.css', '.vue'], alias: { 'vue$': 'vue/dist/vue.esm.js' } }, plugins: [ new webpack.BannerPlugin('最终版权归aaa所有'), new HtmlWebpackPlugin({ template: 'index.html' }) ]}
#cnpm安装#由于国内直接使用 npm 的官方镜像是非常慢的,这里推荐使用淘宝 NPM 镜像。#你可以使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm:npm install -g cnpm --registry=https://registry.npm.taobao.org#这样就可以使用 cnpm 命令来安装模块了:#cnpm install [name]
npm install -g @vue/cli#上面安装的是Vue CLI3的版本,如果需要想按照Vue CLI2的方式初始化项目时不可以的npm install -g @vue/cli-init
#Vue CLI2初始化项目vue init webpack my-project
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Io52cpko-1621734333687)(图片\创建详解.png)]
#Vue CLI3初始化项目vue create my-project
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zNlTpPTl-1621734333689)(图片\CLI3.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RBEv6xZP-1621734333690)(图片\vue目录结构.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-32MvGiiR-1621734333691)(图片\CLI3目录结构.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QB6cyYSh-1621734333692)(图片\CLI3配置.png)]
vue.config.js
是一个可选的配置文件,如果项目的 (和 package.json
同级的) 根目录中存在这个文件,那么它会被 @vue/cli-service
自动加载。你也可以使用 package.json
中的 vue
字段,但是注意这种写法需要你严格遵照 JSON 的格式来写。
这个文件应该导出一个包含了选项的对象:
// vue.config.js/** * @type {import('@vue/cli-service').ProjectOptions} */module.exports = { // 选项...}
Runtime-Compiler:
new Vue({ el: '#app', router: router, template: '<router-view></router-view>'})
Runtime-only:
new Vue({ el: '#app', router: router, render: h => h('router-view')})
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZAZ60x4B-1621734333693)(图片\vue运行过程.png)]
new Vue({ el:"#app", render:(createElement)=>{// return createElement('标签',"相关数据对象(可以不传)",["内容数组"]), return createElement('div',{class:'box'},["create"]) return createElement('div',{class:'box'},["create",createElement('h2',["标题"])]) }})
const cpn = Vue.component('cpn',{ template:"<div></div>", data(){ return{ } }})new Vue({ el:"#app", render:(createElement)=>createElement(cpn)})
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-70xP2H4y-1621734333694)(图片\vueBuild.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VRaixjPv-1621734333695)(图片\vueDev.png)]
早期的网站开发整个HTML页面是由服务器来渲染的.
一个页面有自己对应的网址, 也就是URL.URL会发送到服务器, 服务器会通过正则对该URL进行匹配, 并且最后交给一个Controller进行处理.Controller进行各种处理, 最终生成HTML或者数据, 返回给前端.这就完成了一个IO操作.
当我们页面中需要请求不同的路径内容时, 交给服务器来进行处理, 服务器渲染好整个页面, 并且将页面返回给客户顿.这种情况下渲染好的页面, 不需要单独加载任何的js和css, 可以直接交给浏览器展示, 这样也有利于SEO的优化.
缺点:
前后端分离阶段:随着Ajax的出现, 有了前后端分离的开发模式.后端只提供API来返回数据, 前端通过Ajax获取数据, 并且可以通过JavaScript将数据渲染到页面中.这样做最大的优点就是前后端责任的清晰, 后端专注于数据上, 前端专注于交互和可视化上.并且当移动端(iOS/Android)出现后, 后端不需要进行任何处理, 依然使用之前的一套API即可.
单页面富应用阶段:
其实SPA最主要的特点就是在前后端分离的基础上加了一层前端路由.也就是前端来维护一套路由规则.
URL的hash也就是锚点(#), 本质上是改变window.location的href属性.我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新
history接口是HTML5新增的, 它有五种模式改变URL而不刷新页面.
安装vue-router
npm install vue-router --save
在模块化工程中使用它(因为是一个插件, 所以可以通过Vue.use()来安装路由功能)
router文件夹的index.js下
// 配置路由相关的信息import VueRouter from 'vue-router'import Vue from 'vue'// 1.通过Vue.use(插件), 安装插件Vue.use(VueRouter)// 2.创建VueRouter对象const routes = []const router = new VueRouter({ // 配置路由和组件之间的应用关系 routes,})// 3.将router对象传入到Vue实例export default router
main.js挂载
import Vue from 'vue'import App from './App'import router from './router'Vue.config.productionTip = falsenew Vue({ el: '#app', router,//挂载 render: h => h(App)})
创建路由组件
<template> <div> <h2>我是首页</h2> <p>我是首页内容, 哈哈哈</p> </div></template><script> export default { name: "Home" }</script><style scoped></style>
配置组件和路径的映射关系
const routes = [ { path: '', // redirect重定向 redirect: '/home' }, { path: '/home', component: Home }, { path: '/about', component: About }]
使用路由
: 该标签是一个vue-router中已经内置的组件, 它会被渲染成一个标签.
: 该标签会根据当前的路径, 动态渲染出不同的组件.
网页的其他内容, 比如顶部的标题/导航, 或者底部的一些版权信息等会和处于同一个等级.
在路由切换时, 切换的是挂载的组件, 其他内容不会发生改变.
当打包构建应用时,Javascript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了
路由懒加载
const Home = () => import('../components/Home')const HomeNews = () => import('../components/HomeNews')const HomeMessage = () => import('../components/HomeMessage')const routes = [ { path: '/home', component: Home, },]const router = new VueRouter({ // 配置路由和组件之间的应用关系 routes, mode: 'history', linkActiveClass: 'active'})
一个路径映射一个组件, 访问这个路径也会分别渲染其他个组件.
嵌套路由也可以配置默认的路径
<template> <div> <ul> <li>消息1</li> </ul> </div></template><script> export default { name: "HomeNews" }</script><style scoped></style>
<template> <div> <h2>我是首页</h2> <p>我是首页内容, 哈哈哈</p> <router-link to="/home/message">消息</router-link> <router-view></router-view> <h2>{{message}}</h2> </div></template><script> export default { name: "Home", data() { return { message: '你好啊', path: '/home/news' } } }</script><style scoped></style>
import VueRouter from 'vue-router'import Vue from 'vue'const HomeMessage = () => import('../components/HomeMessage')const routes = [ { path: '/home', component: Home, meta: { title: '首页' }, children: [ { path: '', redirect: 'news' }, { path: 'news', component: HomeNews }, { path: 'message', component: HomeMessage } ] }]const router = new VueRouter({ // 配置路由和组件之间的应用关系 routes, mode: 'history', linkActiveClass: 'active'})export default router
params的类型:
<router-link :to="'/user/'+userId">用户</router-link>
userClick() { this.$router.push('/user/' + this.userId) },
<h2>{{$route.params.id}}</h2>
query的类型
<router-link :to="{path: '/profile', query: {name: 'why', age: 18, height: 1.88}}">
profileClick() { this.$router.push({ path: '/profile', query: { name: 'kobe', age: 19, height: 1.87 } }) }
<h2>{{$route.query.name}}</h2> <h2>{{$route.query.age}}</h2> <h2>{{$route.query.height}}</h2>
vue-router提供的导航守卫主要用来监听监听路由的进入和离开的
vue-router提供了beforeEach和afterEach的钩子函数, 它们会在路由即将改变前和改变后触发
导航钩子的三个参数解析:
参数 | 意思 |
---|---|
to | 即将要进入的目标的路由对象. |
from | 当前导航即将要离开的路由对象. |
next | 调用该方法后, 才能进入下一个钩子(对于beforeEach) |
详细说明
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
它们有两个非常重要的属性:
<keep-alive exclude="Profile,User"> <router-view/> </keep-alive>
属性 | 意义 |
---|---|
to | 用于指定跳转的路径 |
tag | tag可以指定之后渲染成什么组件, 比如上面的代码会被渲染成一个
|
replace | replace不会留下history记录, 所以指定replace的情况下, 后退键返回不能返回到上一个页面中 |
active-class | 当对应的路由匹配成功时, 会自动给当前元素设置一个router-link-active的class, 设置active-class可以修改默认的名称.可以在路由上修改活跃class名 |
export default { name: 'App', methods: { homeClick() { // 通过代码的方式修改路由 vue-router // push => pushState // this.$router.push('/home') this.$router.replace('/home') console.log('homeClick'); }, aboutClick() { // this.$router.push('/about') this.$router.replace('/about') console.log('aboutClick'); } }}
const routes = [ { path: '/user/:id', component: About }]
<div> {{$route.params.id}}</div>
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Z8PMI3o-1621734333700)(图片\vuex.png)]
import Vue from 'vue'import App from './App'import store from './store'Vue.config.productionTip = falsenew Vue({ el: '#app', store, render: h => h(App)})
import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({ state: {}, mutations: {}, actions: {}, getters: {}, modules: {}})
在其他Vue组件中,我们就可以通过this.$store的方式,获取到这个store对象了
Vuex也使用了单一状态树来管理应用层级的全部状态。单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
有时候,我们需要从store中获取一些state变异后的状态,比如下面的Store。就是需要进行一定运算
import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({ state: {}, mutations: {}, actions: {}, getters: { powerCounter(state) { return state.counter * state.counter }, }, modules: {}})
自带两个参数
import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({ state: {}, mutations: {}, actions: {}, getters: { powerCounter(state,getters) { return state.counter * state.counter }, }, modules: {}})
getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数
getters: { moreAgeStu(state) { // return function (age) { // return state.students.filter(s => s.age > age) // } return age => { return state.students.filter(s => s.age > age) } } },
Vuex的store状态的更新唯一方式:提交Mutation
new Vuex.Store({mutations: { decrement(state) { state.counter-- },}})
通过mutation更新
subtraction() { this.$store.commit('decrement') // 2.特殊的提交封装 this.$store.commit({ type: 'incrementCount', count }) },
在通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数。参数被称为是mutation的载荷(Payload)
参数只有一个的时候,可以直接写。如果多个参数,传入对象
incrementCount(state, payload) { // console.log(count); state.counter += payload.count },
Vuex的store中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新.
这就要求我们必须遵守一些Vuex对应的规则:
提前在store中初始化好所需的属性.
当给state中的对象添加新属性时, 使用下面的方式:
方式一: 使用Vue.set(obj, ‘newProp’, 123)
Vue.set(state.info, 'address', '洛杉矶')
方式二: 用新对象给旧对象重新赋值
state.info={...state.info,address = "洛杉矶"}
export const SOME_MUTATION = 'SOME_MUTATION'
// store.jsimport Vuex from 'vuex'import { SOME_MUTATION } from './mutation-types'const store = new Vuex.Store({ state: { ... }, mutations: { // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名 [SOME_MUTATION] (state) { // mutate state } }})
Vuex要求我们Mutation中的方法必须是同步方法
处理异步操作
Vue组件中, 如果我们调用action中的方法, 那么就需要使用dispatch
同样的, 也是支持传递payload
你需要明白 store.dispatch
可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch
仍旧返回 Promise
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用context.commit
提交一个 mutation,或者通过 context.state
和 context.getters
来获取 state 和 getters
const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } }})
Vuex 允许我们将 store 分割成模块(module)
const moduleA = { state: () => ({ ... }), mutations: { ... }, actions: { ... }, getters: { ... }}const moduleB = { state: () => ({ ... }), mutations: { ... }, actions: { ... }}const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB }})store.state.a // -> moduleA 的状态store.state.b // -> moduleB 的状态
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
const moduleA = { state: () => ({ count: 0 }), mutations: { increment (state) { // 这里的 `state` 对象是模块的局部状态 state.count++ } }, getters: { doubleCount (state) { return state.count * 2 } }}
同样,对于模块内部的 action,局部状态通过 context.state
暴露出来,根节点状态则为 context.rootState
const moduleA = { // ... actions: { incrementIfOddOnRootSum ({ state, commit, rootState }) { if ((state.count + rootState.count) % 2 === 1) { commit('increment') } } }}
对于模块内部的 getter,根节点状态会作为第三个参数暴露出来
const moduleA = { // ... getters: { sumWithRootCount (state, getters, rootState) { return state.count + rootState.count } }}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xV0BGpFC-1621734333703)(图片\vuexFile.png)]
有时候, 我们可能需求同时发送两个请求
axios.defaults.baseURL = ‘123.207.32.32:8000’;axios.defaults.headers.post[‘Content-Type’] = ‘application/x-www-form-urlencoded’;
参数 | 意思 |
---|---|
请求地址 | url: ‘/user’ |
请求类型 | method: ‘get’ |
请根路径 | baseURL: ‘http://www.mt.com/api’ |
请求前的数据处理 | transformRequest:[function(data){}] |
请求后的数据处理 | transformResponse: [function(data){}] |
自定义的请求头 | headers:{‘x-Requested-With’:‘XMLHttpRequest’} |
URL查询对象 | params:{ id: 12 } |
查询对象序列化函数 | paramsSerializer: function(params){ } |
request body | data: { key: ‘aa’}, |
超时设置 | timeout: 1000 |
跨域是否带Token | withCredentials: false |
自定义请求处理 | adapter: function(resolve, reject, config){} |
身份验证信息 | auth: { uname: ‘’, pwd: ‘12’} |
响应的数据格式 | responseType: ‘json’(json / blob /document /arraybuffer / text / stream) |
const instance = axios.create({ baseURL: 'http://123.207.32.32:8000', timeout: 5000 })instance.interceptors.response.use(res => { return res.data })instance.interceptors.request.use(res => { return res.data })
// ES6 Promise的封装export function request(options) { return new Promise((resolve, reject) => { // 1.创建axios的实例对象 const instance = axios.create({ baseURL: 'http://123.207.32.32:8000', timeout: 5000 }) // 过滤器(拦截器) instance.interceptors.response.use(res => { return res.data }) // 通过实例发送网络请求 instance(options) .then(res => { resolve(res) }).catch(err => { reject(err) }) })}
ES5封装方式export function request(options, success, failure) { // 1.创建axios的实例对象 const instance = axios.create({ baseURL: 'http://123.207.32.32:8000', timeout: 5000 }) // 过滤器(拦截器) instance.interceptors.response.use(res => { return res.data }) // 通过实例发送网络请求 instance(options) .then(res => { success(res) }).catch(err => { failure(err) })}
父子组件错误用法:以子标签的形式在Vue实例中使用
因为当子组件注册到父组件的components时,Vue会编译好父组件的模块
该模板的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中的内容了)
是只能在父组件中被识别的。
类似这种用法,是会被浏览器忽略的。