Vue2.0
声明式编程,命令式编程
响应式: 数据改,界面自动改
复制代码
.stop 阻止子元素的监听 触发父元素
.prevent 阻止默认事件发生,form-input
input-text,@keyup类似@click
@keyup.enter="函数名" 监听特定键帽的点击(这里为enter)
@click.once="函数名" 只触发一次
/******************************************************/
v-if="变量名" 控制标签是否要渲染出来,true,false
对应的有v-else-if,v-else。
用v-if和v-else,做一个切换输入用户名和用户邮箱界面
div-> 两个v-if/else的span,-> label for="xx"和input id="xx" placeholder
你会发现输入点东西后,再点切换,input栏内的内容保留,没有清空
/* label for="xxx" 点击文字,会选中id为xxx的input type="radio" name="sex/age..." */
为什么?(vue-底层)
vue会把我们将要显示的东西放到内存里面,在内存中创建vdom(virtual dom),在进行dom渲染时,出于性能考虑,会尽可能复用已经 存在的元素,而不是创建新的。所以我们还是用的原来的label和input,只是对比渲染,把除了用户输入的内容外,都清空替换了。
解决方案:
给相应的input添加key,并且保证key的不同,就不会进行复用
/*****************************************************/
v-if 和 v-show 都可以选择是否显示页面元素
但是false的时候,元素消失方式不同
v-if 完全消失,不存在于dom中--创建和删除
v-show 只是加了个display:none;
复制代码
在数组中间插入一个新的元素
比如我们有4个li,对应内容含有A B C D。这时候,我们要在BC中间插入E
我们可以假设目前在vdom中为 0-A 1-B 2-C 3-D (vue底层) 在dom中为 A B C D
这时候,在vdom中插入,0-A 1-B 2-E 3-C 4-D
vdom中,在E插入后,3号位置变为原先2号C,创建4号位置变为D
dom中, 依据diff算法,A B C D变成A B E C D变化的次序和上一行类似,(也是一个个从上往下变C变E,D变C,最后插入D)
效率十分的低
这时候,我们需要设置key来给每个节点做一个 唯一 标识。--如果多个li的key相同会报错
eg: <li v-for="item in array" :key="item">{{item}}</li>
加入了key之后的数组插入,就如上图With Keys所示,有一一对应那种感觉,0对A,1对B等等。
要插入的E在原数组配对好后,会到合适的位置上去,而不是让数据一个个的变
/***********************************************************************/
push--- 尾部添加 unshift ---首部添加 (添加都可以传多个值)
pop--- 尾部删除 shift--- 首部删除
可变参数 function sum(...num) {
clg(num); }
splice(起始位置,删除个数,用于替换的元素) 删除/插入/替换
splice(1)删除1以及后面所有
Vue.set(this.array,0,'xxx') 第一个参数是数组对象,第二个为index,第三个是修改成的值
上述都是响应式,而this.array[index]='xxx',会修改值,但不是响应式,页面不变
/***********************************************************************/
复制代码
filter/map/reduce
filter / map /reduce
过滤器: filter中的回调函数有一个要求:必须返回一个boolean类型的值
const nums = [10,20,123,450,45]
let newNums = nums.filter(function(n){
return n<100; } )
返回true,函数内部会自动将:“此次回调的n” (满足条件的数组元素) 加入到新的数组中
/*这个新的数组自动创建,我们负责接收就行 */
false: 会过滤掉此次的n
map函数: 映射操作
const nums = [10,20,123,450,45]
let newNums = nums.map(function(n){
return n*2; } )
会把返回的值作为,数组中原位置的,新值。
reduce函数:对数组中所有的内容进行汇总
/*(这个函数定义用的是ts)有函数重载功能,可以写两个函数,同名不覆盖,参数数量不同使得功能有所差异*/
const nums = [10,20,123,450,45]
let total = nums.reduce(function(preValue,n){
return preValue+ n
},0)
两个参数,参数一本身为一个函数,参数二为初始化值。
参数一作为一个函数,有两个参数 preValue为前一次return的值,n为每一次数组遍历的值,第一次preValue为 reduce的参数二初始值。
/* 第一次 preValue==0,n==10 . 第二次 preValue==0+10==10,n==20 第三次preValue==20+10==30依次类推。。return preValue+n 可以得出数组所有数的和, 最终结果返回,用total接收。*/
综合:函数式编程
const nums = [10,20,123,450,45]
let total = nums.filter(function(n){
return n<100
}).map(function(n){
return n*2
}).reduce(function(preVaule,n){
return preValue + n
},0)
箭头函数
let total =nums.filter(n=>n<100).map(n=>n*2).reduce((pre,n)=>pre+n);
这里reduce的参数二默认初始值为0了
/***********************************************************************/
编程范式--命令式编程/声明式编程
编程范式--面向对象编程(第一公民:对象)/函数式编程(第一公民:函数)
/***********************************************************************/
以后监听事件(@)函数别加括号了,可能出错,@input有问题,@click没问题
v-model原理:
语法糖,1.v-bind绑定一个value属性
2.v-on 指令给当前元素绑定input事件
<input type="text" v-model="message">
等同于
<input type="text" v-bind:value="message" v-on:input="message=$event.target.value">
/////////////////////////////////////////////////////////////////////////
<input type="number" /*只能输入数字 加上*/ v-model="age">
<h2>{{age}}--{{typeof age}}</h2>
Vue中
age:0 ---> 一开始为0和number
在input框中输入东西后,显示为: 输入的数字和string
也就是说双向绑定,实时传过去的值,是作为一个string类型传的
要想变成number,直接
v-model.number="age" 即可解决问题
.lazy 懒加载 input失去焦点后显示变化后的value
.trim 去除空格
/*即使数据string中 有很多空格,浏览器显示的时候,会忽略的,但是数据处理时候可以用trim去除空格*/
/////////////////////////////////////////////////////////////////////////
复制代码
组件化
组件:一个封闭的空间,我可以有自己的数据,有自己的方法
component -- 缩写cpn
ES6补充,` 这里面可以包含换行 ` 而''以及""如果要换行需要分两部分加引号,中间加+
可以把Vue实例当作一个根上的组件(root组件)
要想使用某个组件,必须要保证它在作用域注册过,要么是全局,要么是对应父组件的components中注册过
不然就会报错,Unknow custom element <标签名>
//例子:
const cpnC1=Vue.extend({
template:`<div><span>nothing1</span></div>`,
})
const cpnC2=Vue.extend({
template:`<div><span>nothing1</span></div>`,
components:{
cpn1:cpnC1
}
})
const app = new Vue({
el:'#app',
components:{
cpn2:cpnC2
}
})
<div id="app">
<cpn2></cpn2> //直接调用了组件2
<cpn1></cpn1> // 报错,因为cpnC1是在cpnC2组件内部注册的,作用域就是组件cpnC2
</div>
// 解决办法
// 组件可以注册多次,在Vue实例中components中再注册一次
复制代码
组件中代码与html分离
1. script type="text/x-template"
2. 外层套个 template 标签
注意不管是script还是template标签都要加个id
然后在js代码中,template:'#id'
////////////////////////////////////////////////////////////////////////////
组件内部不能访问Vue实例data数据
所以组件内部有一个data属性,但是不能是对象类型,
应该是个function,并且返回一个对象,对象内部保存着数据
<template id="theid">
<div> h2.....p....{{title}} </div>
</template>
Vue.component('cpn',{
template:'#theid',
data(){
return {
title:'abc'
}
}
})
组件对象也有methods等属性,有声明周期函数,很像一个Vue实例,组件的原型就是指向Vue的
////////////////////////////////////////////////////////////////////////////
为什么Vue组件中data必须是一个函数?
每次函数执行时候都会在栈空间创建新变量
// 例子
function abc(){
return { name:'whe', age:18}
}
let obj1= abc() let obj2=abc()
obj1和obj2是两个不同的对象
测试: obj1.name="mmmm" console.log(obj2.name)
你做一个模块,功能就是计时器,一个count的div,一个加和一个减的按钮
你引用了2次模块,那么两次模块里,count是两个不同的变量。
总结:用函数每次返回的都是新的对象,可以避免-多个组件实例共用变量的情况
要想人为共用变量,可以const obj={ count:0 }
Vue.component中 data(){ return obj }, 这样就共用了obj的count
////////////////////////////////////////////////////////////////////////////
HTML是不区分大小写的。而JS区分。所以一般情况下,JS的大小写变量放到HTML中,会将大写改成小写,并在前面添加短杠。
所以我们事件命名时候不要使用 camelCase 和 PascalCase 来命名了,
因为就算你使用该命名,在DOM模板中还是会被自动转化为全小写,
如 <input type="text" v-on:keyup.F2=113> 会被自动转换为
<input type="text" v-on:keyup.f2=113>
以及:vue中v-bind/on等等。不支持驼峰命名---》 父子组件通信,变量别用驼峰命名,会显示默认值
除非,把大小写自己做个转换,eg:
子组件cpn中数据textMyMessage 在写到html的cpn标签中时
转换为: text-my-message
////////////////////////////////////////////////////////////////////////////
定义子组件模板的时候,引用数据
<template>
<h2>{{cmessage}}</h2>
<h2>{{aamessage}}</h2>
</template>
会报错,需要在两个数据引用的外层套一个div
<template>
<div>
<h2>{{cmessage}}</h2>
<h2>{{aamessage}}</h2>
</div>
</template>
///////////////////////////////////////////////////////////////////////////////////
前面说了组件中v-bind不用驼峰命名,为什么在.vue文件中就可以
.vue文件中的 template script style标签会编译成一个组件对象,且这个对象中没有template
它会把组件对象渲染成一个render函数,在这个渲染的过程中解析驼峰命名的名字,是能正常进行的
html则是大写变小写
///////////////////////////////////////////////////////////////////////////////////
父传子: props ---子组件构造器中写,常写成对象形式,cpn标签中对应赋值父组件中值
子传父: events ---子组件 $emit()触发事件,父组件 v-on、@ 监听子组件事件
补充: 父传子,父组件(一个Vue实例)中
Vue({
data:{
num1:1
},
components:{
//子组件--语法糖
cpn:{
props:{
number1:Number // 定义变量名和类型
}
}
},
})
在template标签中:
如果要input v-model双向绑定数据的话,不要绑定到number1去
虽然这样你仍然可以实现,但是会报错的,为什么?
因为:number1本身的值,就是从父组件那里拿来的,你不能既被父组件数据改,又被input改
// Instead, use a data or computed property based on the prop's value
因此做法可以为: 在子组件中,利用data或者computed来整个新变量
eg: 在上面的component中
cpn:{
props:{
number1:Number
},
data(){
return {
dnumber1: this.number1,
}
}
}
然后input text v-model="dnumber1" 这样才是合理的做法
之后只有dnumber1和input内的输入双向绑定,而number1 不变,还是拿的父组件的num1
*如果要改变num1的值,那么:
先不选择v-model,或者说,换种写法,变成 @和v-on
template标签中:
<input :value="dnumber1" @input="num1Input"> // 监听事件num1Input
// 到子组件去定义函数 num1Input
子组件:methods:{
num1Input(event){
this.dnumber1 = event.target.value; // 这个只改变了dnumber1
this.$emit('num1change',this.dnumber1) // 每次input,发射数据给父组件
}
}
然后:
div id="app"中, cpn标签,
<cpn :number1="num1" @num1change="parent_num1change" />
父组件:
methods:{
parents_num1change(value){ // 这里的value就是子组件发射传来的 dnumber1
this.num1= parseInt(value) // 传过来的是string
}
}
//字符串转number ,一:乘以1. 二: parseInt(string)
再在之后input内输入值,会改变 dnumber1,也会改变num1,然后改变 number1
假如有个num2,number2,dnumber2,要改变num2且为num1的100倍,在子组件的methods中最后加上:
this.dnumber2 = this.denumber1*100;
this.$emit('num2change',this.dnumber2);
在组件中,与data,template,props,methods,computed同级的还有 watch
作用:监听变量的变化----注意:如果是子组件,返回的一个对象中的数据也可以监听
eg:
data(){
return {
dnumber1:this.number1;
}
},
watch:{
dnumber1(newValue){
this.dnumber1 = newValue*10;
this.$emit('num1change',newValue);
// 比如这里有个 this.denumber2=0;
}
// 如果在上面改变一个变量,下面也可以监听到的
dnumber2(newValue){
// 会因监听到dnumber2的改变而运行
}
}
// 除了newValue,还可以有第二个参数,oldValue
复制代码
父子组件的访问方式
// 父组件访问子组件
$children 或者 $refs (reference 引用)
// 子组件访问父组件
$parent
比如,现在 在一个Vue实例里边,有一个子组件cpn,
子组件 methods里面定义了一个方法showMessage
showMessage(){
console.log('showMessage');
}
父组件 methods里面定义了一个方法btnClick // 通过div #app中的一个button的click触发
btnClick(){
console.log(this.$children);
// 返回一个数组,数组中放着VueComponent类型-组件对象
this.$children[0].showMessage(); // 控制台打印 showMessage,引用了子组件方法
}
// 如果在html中引用多次的cpn标签(即便相同),那么返回的数组就会多个VueComponent对象
// 有index的一般应变性不行,比如突然要你在前面加一个组件,index就全变了。故$children一般不用
而开发常用的 $refs 初始就是一个空的对象
在组件标签中加: <cpn ref="aa"></cpn>
即可通过, this.$refs.aa来引用这个VueComponent对象
////////////////////////////////////////////////////////////////
$parent 一般用的很少,因为会使得组件的复用性降低
$root 访问根组件---最高级组件-Vue实例,但是一般这个组件data为空。
复制代码
组件化高级
// 插槽
在template标签中,使用slot标签可以表示预留插槽
之后在`div #app` 中cpn标签中间就可以加入若干个标签,比如p,div,button等,多个
这些标签会渲染在slot标签的位置上
如果slot标签中带了其他标签,表示: 当cpn标签中没设置其他标签时的默认
// 具名插槽
<template id="cpn">
<div>
<slot name="left"><span>左边</span></slot>
<slot name="right"><span>右边</span></slot>
</div>
</template>
<div id="app">
<cpn><span slot="left">替换文字</span></cpn>
// 如果没有加slot属性,那么会把上面的两个slot的span改为”替换文字“
</div>
// 补充作用域
`template`标签内属于cpn子组件作用域
`div #app`标签内属于vue父组件作用域
测试的话,标签v-show,组件内data定义boolean变量ishow
官方准则: 父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译
////////////////////////////////////////////////////////////////////////
作用域插槽---目的: `父组件替换插槽的标签,但是内容由子组件来提供`
-- 父组件对子组件的展示的数据不满意,要以另一种方式展示,所以需要从子组件拿数据
eg: 子组件的data内有一个数组array,我希望把内容展示出来
<templat id="cpn">
<div>
<slot> ul <li v-for="item in array" >{{item}} </li> /ul </slot>
</div>
</template>
在div #app中
<cpn><cpn>
<cpn><cpn>
为了使第二次的组件显示中,array数据的展示有所变化,
比如: 显示在一行,中间用 - 隔开
这时候需要在第二个cpn中拿到子组件的数据array
直接在cpn中类似li标签进行v-for是不行的,为什么:看上面的 组件作用域
那要怎么做? 首先想办法让子组件把数据传给父组件
<templat id="cpn">
<div> // 在slot标签中加入一个属性(可自命名)这里叫dat了,等于要的数组array
<slot :dat="array"> ul <li v-for="item in array" >{{item}} </li> /ul </slot>
</div>
</template>
第二个cpn中
<cpn>
<template slot-scope="slot">
// <span v-for="item in slot.dat">{{item}} - </span>
<span>{{slot.dat.join(' - ')}}</span>
</template>
</cpn>
复制代码
Webpack
ES6模块化补充
// 先是ES6一些模块化的东西
在 script标签中加入 type="module" 可以使得.js文件模块化
其他文件无法直接访问
通过 export导出, import导入
// 法一
export{
变量名1,变量名2...
}
import {变量名1,变量名2} from "./文件名.js";
// 法二:
export var num1=1000;
import {num1} from "./文件名.js";
// 默认导出
const address = 'guanzhou'
export default address // export default 可以有,但也只能有一个
导入的时候: import add from "./文件名.js" // 这里的 add就是其他人给address的重命名
如果变量太多了,可以直接
import * as info from './文件名.js'
// 会把其他模块的导出,全部存到info这个对象中(对象自己命名),之后通过属性引用
复制代码
简介
// Webpack is a static module bundler for modern javascript application
// Webpack 是现代JavaScript应用的静态模块打包工具-----前端模块化打包工具
相比,grunt/gulp和webpack,
grunt/gulp的核心是Task,更加强调的是前端流程的自动化,模块化不是它的核心
webpack更加强调模块化开发管理,而文件压缩合并,预处理等功能,是它附带的功能
文件夹名字
src 源码 --- 开发的东西
dist (distribution发布) --- 打包的要发布的东西
/////////////////////////////////////////////////////////////////////////
复制代码
JavaScript
处理
你有多个`.js`文件-组件化,他们直接有相互的依赖(调用其他组件方法/变量)
你不需要再通过`script`一个个引入,可以直接`webpack`打包到`dist`文件夹中
而且打包的时候,只需要指定个别的即可,
组件间的依赖(其他用到的组件)`webpack`会帮你处理好一块打包
最后直接引入在`dist`生成的文件即可
eg: 打开到文件目录, `webpack ./src/main.js ./dist/bundle.js`
// 上面那行代码,把main.js组件打包到dist文件夹,名字 bundle.js
////////////////////////////////////////////////////////////////////////////
如果现在要想直接打个 webpack 就实现上面的打包功能
创建一个文件, `webpack.config.js` 名字暂时固定
代码:
const path = require('path')
module.exports = {
entry:'./src/main.js',
output: {
path: path.resolve(__dirname,'dist'), // path其实是一个函数,可以对两个路径进行拼接,__dirname(全局变量,保存当前文件的路径) dist (放到'dist')
filename: 'bundle.js'
},
}
这个path--动态获取路径,需要在本文件下,终端输入:
`npm init` // 初始化,生成package.json,其文件中,开源才需要license属性
`npm install` // 生成 package-lock.json
/////////////////////////////////////////////////////////////////////////
但是如果我要想只通过`npm run 名字`来运行呢?
全局和本地的webpack,版本不同,只有本地的和项目版本相同
安装本地的webpack:
`npm install webpack@3.6.0`
---webpack的作用就是打包文件成包,之后发给服务器,webpack在打包之后就没用了
如果使用:
`npm install webpack@3.6.0 --save-dev`
// 在package.json文件会多出 "devDependencies": { "webpack": '^3.6.0' }
// devDependencies: 开发时依赖
只要是在终端里面敲命令webpack,用的都是全局的webpack
所以需要在`package.json` / `node_modules 的 library root `中
的`scripts`中配置脚本,加入webpack以及自定义名字 (类似设置快捷键)
然后 通过`npm run 自定义名字` 来打包,这样会优先使用本地的`webpack`版本
(package.json中的script脚本在执行的时候,会按照一定的顺序寻找命令对应的位置,
首先,找本地的 node_modules/bin 路径中对应的命令,如果没有再去全局)
/////////////////////////////////////////////////////////////////////////
复制代码
CSS
和less
处理
直接在main.js对css文件require进行依赖,之后 npm run serve/webpack
打包出错,需要安装loader
npm run install --save-dev css-loader
// "css-loader": "^2.0.2", "style-loader": "^0.23.1", "webpack": "^3.6.0"
const path = require('path')
module.exports = {
entry:'./src/main.js',
output: {
path: path.resolve(__dirname,'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ] // 使用多个loader时,从右向左读
}
]
}
}
// css-loader 只负责将css文件进行加载,不负责解析
// css-loader负责将样式添加到DOM
// npm install style-loader --save-dev 或者 npm install --save-dev style-loader
// 在组件后边加个@和版本号就可以改版本信息
/////////////////////////////////////////////////////////////////////////
如果想要打包 `.less`文件
npm install --save-dev less-loader@4.1.0 less
注意最后的 `less` 不能少,不然出错,它是一个包,npm利用的,真正起作用的工具
module: {
rules: [
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
},
{
test:/\.less$/,
use:[{
loader:"style-loader"
},{
loader:"css-loader"
},{
loader:"less-loader"
}]
}
]
}
// 第二个test也可以写作数组,不过写成对象的话,之后可以加入细节
// 记得三个 loader 的顺序, 也是反着来
复制代码
图片资源处理
// 在css中通过url引入文件中的图片,npm run serve 报错。
安装
npm install --save-dev url-loader
webpack.config.js 配置代码
module--rules数组中
{
test: /\.(png|jpg|gif|jpeg)$/,
use:[{
loader:"url-loader",
options:{ // options记得加s
limit: 16000
}
}]
}
// 打包后,会把图片大小小于limit的图片,转换为 base64格式的东西,
// 通过服务器请求base64的字符串,放到url中
// 比如图片为10K,那么就应该填10240或者更大,如果图片大小大于limit,需要使用file-loader模块进行加载
file-loader
npm install --save-dev file-loader
这个就不用配置代码了
// 运行后,会在dist中重新打包图片(并且重新命名,32位哈希值防止重复),将来打包发行,我们用的也是dist中的图片,而不是打代码时文件夹里那张
// 接下里的问题就是,我们如何在html中的url使用dist中的图片路径
我们需要在 webpack.config.js 文件中,
module.exports = {
entry:'./src/main.js',
output: {
path: path.resolve(__dirname,'dist'),
filename: 'bundle.js',
publicPath:'dist/' // 这里加publicPath,以后url都会在前面加个dist路径,去那找
},
module: {
rules: [
{ text:'',use:[] },
{ text:'',use:[{loader:" "},{loader:" "},{loader:" "}]
] }
}
// 最后 因为index.html也要放到dist拿去发行,最后publicPath不需要,记得删除
// 但是不可能最后,所有图片堆在dist文件里面,如果采取 放到 img/name 文件里面,可以知道图片名字,但是也可能重复,所以还是要加hash值 ----> img/name.hash:8.ext (:8 表示截取八位, 扩展名ext:extension)
解决方法:
webpack.config.js 配置代码
module--rules数组中
{
test: /\.(png|jpg|gif|jpeg)$/,
use:[{
loader:"url-loader",
options:{ // options记得加s
limit: 16000,
name:"img/[name].[hash:8].[ext]"
}
}]
}
复制代码
ES6转ES5
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
// 其中babel-loader类似less安装时候的‘less’,也要安装
webpack.config.js 配置代码
{
test:/\.js$/,
// exclude 排除,
// 局部环境install webpack生成的文件夹中.js文件不需要转换
exclude: /(node_modules|bower_components)/,
use:{
loader:'babel-loader',
options:{
//presets:['@babel/preset-env']
presets:['es2015']
}
}
}
复制代码
配置Vue
以前是把 vue.js 下载成文件引入,这不是模块化的方法:
现在直接
npm install vue --save // 不-dev因为运行时也依赖,而不只是开发时依赖
// 会在 node_modules 中 export default Vue
之后要使用的时候,在.js文件中通过 import Vue from 'vue'
这里的'vue'没有加路径,因为直接到node_modules 中去找了
// 但是在div中加入#app显示{{message}},那个简单安利,
// 直接npm run serve 后还是报错 用的runtime-only版本
runtime-only 代码中不允许有template
runtime-compiler 代码中允许有template,因为compiler可以用于编译template
解决方案:声明使用runtime-compiler版本
webpack.config.js 配置代码
module.exports ={
entry:'./src/main.js',
output:{},
module{},
resolve:{
// alias: 别名
// 好比 git commit -m '注释' 可以起别名成 git c '注释'
alias:{
'vue$':'vue/dist/vue.esm.js'
// 表示:以后imp ort Vue from ‘vue’ 按照文件目录去找vue
// 会到node_modules 找vue,里面的dist,找到vue.esm.js
// vue.esm.js 里面就有compiler
// 默认的就是使用相同路径下的 vue.runtime.js
}
},
}
复制代码
补充
SPA(simple page web application)单页Web应用--> 多页面通过vue-router(前端路由跳转)
// 如果我们希望 data中数据展示,那就得修改html代码,但是我们不希望频繁的改动html
// 所以:我们需要使用 el和template
// 在Vue中定义template属性,template中的html标签,会在之后替换到el挂载的div的位置
// 但是我们进一步分离
html中就一个div id='app'
在main.js文件中
import Vue from 'vue'
const App={
template:`
<div @click="btnClick">{{message}}</div>
`,
data(){
return {
message:'!!!',
}
},
methods:{
btnClick(){
console.log('1');
}
}
}
new Vue({
el:"#app",
template:'<App/>',
components:{
App
}
})
// 在src下建立文件夹 vue, vue 下创建文件app.js 里面
export default{
template:`
<div @click="btnClick">{{message}}</div>
`,
data(){
return {
message:'!!!',
}
},
methods:{
btnClick(){
console.log('1');
}
}
}
之后在原来的main.js文件中,直接
import App from './vue/app' 就可以引用上面的代码
// 这样就把组件的数据,方法,内容分离了出来,但是模板没有分离出来(template)
在vue文件中创建,app.vue文件
将 app.js 文件中的数据分块移动到 app.vue 文件, app.js文件之后不用了
<template>
<div @click="btnClick" class="title">{{message}}</div>
</template>
<script>
export default {
name:'App',
data(){
return {
message:'!!!',
}
},
methods:{
btnClick(){
console.log('1');
}
}
}
</script>
<style>
.title {
color:green;
}
</style>
// main.js中
import App from './vue/app.vue'
// 之后因为引入了新的文件--vue文件,下面要安装配置
npm install vue-loader vue-template-compiler --save-dev
安装后 npm run serve 报错,因为 vue-loader 14.几之后的版本,需要安装插件
这里就webpack.config.js改一下版本到13 然后 npm install 一下
// 组件调用组件,在一个组件的.vue文件中的script标签中加入
import 用组件时的标签名 from './组件名' (./ 一般同文件下)
// 然后export default中组件注册,记得components,有个s 有个s
// 记得组件名/文件名,首个字母大写
// 测试了下,.vue文件中
export default中的name可以不要,按文件名来,
template中对组件标签的引用两种都可以
//eg: <Omg></Omg> 或者 <Omg/>
复制代码
plugin–插件
// 插件: 对某个现有的架构进行扩展
loader: 转换器/加载器,主要用于转换某些类型的模块
plugin: 扩展器,对webpack本身的扩展 --打包优化,文件压缩
// 首先: 添加版权Plugin
webpack.config.js文件中
const webpack = require('webpack')
module.exports = {
..
plugins:[
new webpack.BannerPlugin('xxxxxx')
]
}
重新打包,在dist文件夹内的bundle.js首部加了xxxx信息
// 打包html的Plugin
自动生成index.html文件---可以指定模板
将打包的.js文件自动通过script标签插入到body中
// npm install html-webpack-plugin --save-dev (注意版本案例用:3.2.0)
然后 webpack.config.js 中
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry:'./src/main.js',
output:{},
plugins:[
new webpack.BannerPlugin('最终版权归Kuoc所有'),
new HtmlWebpackPlugin({
template:'index.html'
})
],
...
}
// npm run serve
// 对JS进行压缩,uglify-丑化,开发阶段不需要丑化,发行才需要
// npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
然后 webpack.config.js 中
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
entry:'./src/main.js',
output:{},
plugins:[
new webpack.BannerPlugin('最终版权归Kuoc所有'),
new HtmlWebpackPlugin({
template:'index.html'
}),
new UglifyjsWebpackPlugin()
],
...
}
// npm run serve
复制代码
搭建本地服务器
不需要每次都npm run serve 生成发行文件dist(放在磁盘中)
每次修改代码会自动刷新----更新内存中代码
// npm install --save-dev webpack-dev-server@2.9.1
然后 webpack.config.js 中
module.exports = {
entry:'./src/main.js',
output:{},
plugins:[
new webpack.BannerPlugin('最终版权归Kuoc所有'),
new HtmlWebpackPlugin({
template:'index.html'
}),
new UglifyjsWebpackPlugin()
],
devServer:{
contentBase:'./dist',
inline: true // 是否实时监听
//port 端口号,默认8080
}
...
}
// 这里直接 webpack-dev-server 会报错,因为这相当于是命令行全局查找,但是这个插件是局部安装的
所以:
法一: // .\node_modules\.bin\webpack-dev-server
法二: package.json 文件中,script对象加属性,"dev":"webpack-dev-server --open"
之后 npm run dev 即可 // 加--open自动打开网页
最后,Ctrl + C , Y 推出当前操作,但是服务器仍然搭好了
开发的时候,需要 devServer,但是发行的时候就不需要了(JS丑化相反),所以我们可以做一个分离
复制代码
Webpack配置分离
有部分的插件,你在开发时需要,但是发行之后不需要了
建立一个config文件夹,
内部建立:开发时的 dev.config.js 发行时的 prod.config.js 公共部分 base.config.js
将对应代码分块到文件内,比如
JS Uglifyjs放到prod.config.js 本地服务器搭建devServer放在dev.config.js
// 解下来就是拼接了
// npm install webpack-merge@4.1.5 --save-dev
dev.config.js文件
// 开发时依赖
const webpackMerge = require('webpack-merge')
const baseConfig = require('./base.config')
module.exports = webpackMerge(baseConfig,{
devServer:{
contentBase:'./dist',
inline:true // 是否实时监听
//port 端口号,默认8080
}
})
prod.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()
]
})
// webpack.config.js文件可以删除了,然后到 package.json文件中
scripts中,修改serve,添加dev
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"serve": "webpack --config ./config/prod.config.js", // 发行
"dev": "webpack-dev-server --open --config ./config/dev.config.js" // 开发
},
// 之后直接npm run serve,打包发行的的dist文件夹在config文件夹的下面,
// 而不是在整个项目文件夹的下面
解决方法:
base.config.js文件中
module.exports = {
entry:'./src/main.js',
output: {
path: path.resolve(__dirname,'../dist'), // 这里的dist前面加../跳到上一级目录,不新生成
filename: 'bundle.js',
publicPath:'dist/'
},.......
}
复制代码
Vue CLI
脚手架---CLI--Command-Line-Interface 命令行界面
依赖node和webpack
// 安装cmd中: npm install -g @vue/cli
// vue --version 查看版本
脚手架2.x模板的拉取
// npm install -g @vue/cli-init
(my-project-项目名称-自己取)
// CLI---2.x初始化项目: vue init webpack my-project
// CLI---3.x初始化项目: vue create my-project
复制代码
Vue CLI2
Lint --限制
ESLint ES限制,即:js代码不规范则报错--比如函数 function sun () { }以及最后不加分号;还有函数要被使用
config文件夹index.js文件,useEslint:false;关闭
e2e -> end to end 端到端测试
build 和 config 都是项目配置,后者插件配置,前者变量定义以及文件merge
在static文件(静态资源,该文件的图片和数据会原封不动的复制到dist文件中),package.json文件中,
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
两者一样的
"lint": "eslint --ext .js,.vue src", 限制
"build": "node build/build.js" 打包项目
// 补充:node(c++开发,谷歌开源:核心V8引擎)为js提供了运行环境,
// 以前js只能跑浏览器上, 在服务器上跑不起来
// extract 抽离
直接 node test.js 可以运行js代码,且会显示console.log 内容
.editorconfig文件-对项目代码规范进行约束
package-lock.json文件:是node_modules文件和package.json文件之间的映射关系
eg:---package.json中loader的版本 ^4.0.1表示大于等于4.0.1版本。但是我们不知道安装的哪个版本在node_modules所以需要 package-lock.json文件
复制代码
runtimecompiler / runtimeonly
// 区别在main.js文件
公共部分
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
区别部分
runtimecompiler:
new Vue({
el:"#app",
template: '<App/>',
components: {App}
})
runtimeonly:
new Vue({
el:'#app',
render: h=>h(App)
/* 相当于
render: function(h){
return h(App)
}
*/
})
差比的解释:先看 Vue程序运行过程图
复制代码
// template---> AST---> render ---> Virtual dom ---> Dom(UI)
runtimeonly直接: render--> Vdom--> UI
相比于 runtime-compiler,runtimeonly 性能更高 代码更少
而且 runtimeonly中 .vue文件的 template标签,也都不会被解析成 AST,那么由谁处理?
// .vue文件 被解析成对象,这个对象里面没有template的信息,
// 所有template都被vue-template-compiler转成了render函数
所以我们不需要 template 转换为AST 再通过 compiler转换为 render函数
直接 runtimeonly
复制代码
npm run build/dev
Vue CLI3
版本差异
// Vue2.5.21 -> Vue2.x -> flow-type类型检测(facebook)
// Vue3.x -> TypeScript(microsoft)
// vue-cli2 基于 webpack3 打造
// vue-cli3 基于 webpack4 打造
Vue-CLI3 -- 0配置,移除配置文件根目录下的 build和config等目录
提供了 vue ui 命令,提供了可视化配置
移除了static文件夹,新增了public文件夹,而且index.html移动到public中
(其实可以把 public 看作是 static -- [ˈstæ^tɪk] )
复制代码
创建过程
vue create testvuecli3(项目名)
终端弹出:
please pick a preset:// preset:配置 [ˌpriːˈset]
// Manually 手动的 [ˈmænjuəli]
选手动之后:多项选择--按空格选中
回车提交
// vcs --> version control system (git/svn)
git init 生成本地git仓库
git add . 进行一次提交
git commit -m '注释' 提交到本地仓库
git push 到远程仓库
复制代码
项目运行
npm run build // 最终打包发行版本
npm run serve // 开发时运行
复制代码
Vue Router
前端 / 后端渲染
1. 后端(服务端)渲染
早期浏览器开发:
html,css没有js,用的jsp(java server page)/php ,写出一个网页传到浏览器,让浏览器展示
没有ajax,在服务器那里网页已经通过jap技术渲染好了
( html+css+java,java从数据库动态读取数据并且放在页面中)
服务器里就是最终的网页,传给浏览器时,只有html和css
复制代码
2.前后端分离阶段-前端渲染-jquery开发模式
通过ajax去请求数据
复制代码
3.SPA页面
SPA:simple page web application--单页面富应用
在前后端分离阶段,静态资源服务器上保存的为 多套的 html+css+js,以及对应的url
但是在静态资源服务器上只有一个index.html,有一些或者是一个 css,js
(请求一次拿到全部)这不代表只有一个网站
比如一个网站他有三个页面,但是在静态资源服务器里面只有一个html,css,js
你必须依靠前端路由技术来分离页面的代码
你点击按钮,前端路由生成url,这时候不会向服务器请求资源,
而是通过js代码的判断,从全部代码中抽取页面代码(一个个的组件)
复制代码
前端路由中url和组件的关系(一个页面就是一个大的组件)
URL的hash 和 H5的history
URL: scheme:// host:port/path?query #fragment
协议 主机 端口 路径 查询
如何改变了url但页面不刷新?(F12-Network,查看资源请求知道刷新与否)
法1. location.hash = 'foo',后面'foo'随意
法2. history.pushState({},'','foo'),控制台返回undefined,并修改url,可以返回/back()
法3. history.replaceState({},'','foo'),不能返回
history.go(-1)等同于 一个history.back(), go(-2),等同于两个,也可以为正数
history.go(1)等同于 一个 history.forward()
法123三个接口等同于浏览器界面的前进后退
href: hyper reference 超链接
复制代码
history.pushState的 入栈和出栈
// redirect 重定向
// parameters 参数---params
打包后的三个js文件
// app 应用程序开发的所有代码--业务代码
// vendor 提供方,第三方vue
// manifest 为打包的代码做底层支撑的(import,export....)
导入导出
// commonjs的导入 const {a,b,c} = require('./xxx.js')
// es6的导入 import {a,b,c} from './xxx'
$route 和 $router
$route -- 哪个组件处于活跃,就拿到哪个
$router -- 在App.vue 中创建的那个对象
eg:
computed: {
userId(){
return this.$route.params.useId
}
}
复制代码
懒加载
const routes = [
{
path:'/home',
component:()=>import('../components/Home')
},
{
path:'/about',
component:()=>import('../components/About')
},
];
// 为了好管理,还是选择下面的写法
const Home = ()=>import('../components/Home')
const About = ()=>import('../components/About')
const HomeNews = ()=>import('../components/HomeNews')
const routes = [
{
path:'/home',
component: Home,
children:[
{
path:'news' // news前不要加/
component:HomeNews
},
]
},
{
path:'/about',
component:About
},
];
复制代码
Profile // 档案----我的页面
在router文件夹中的index.js文件,
1.//////////////////////
import VueRouter from 'vue-router'
import Vue from 'vue'
import User from './User.vue' // 路由
//////////////////
2.
Vue.use(VueRouter)
//////////////////
3.
const routes = [
{
path:'',
redirect:'/home'
},
{
path:'/user/:userId',
component:User
}
]
const router = new VueRouter({ // VueRouter含有push,replace,back等方法
routes,
mode:'history',
linkActiveClass:'active'
})
//////////////////
4.
export default router
//////////////////
其中第二部分Vue.use(VueRouter),实际是VueRouter.install,对插件进行安装
而在打包文件node_modules中,找到src,找到util,找到install.js看源码
可以知道,有一行意义代表:(以及全局注册的 RouterView 和 RouterLink )
// Vue.prototype.$router = return this._routerRoot._rooter;
// 补充说明,所有的组件都继承自 Vue类的原型!!!,这样所有组件都有$router属性
// defineProperty(obj,属性名,属性值) 定义属性
而这个 _router,就是我们在项目的 main.js中挂载进去的 router
import Vue from 'vue'
import App from 'app'
import router from 'router'
Vue.config.productionTip=false
new Vue({
el:'#app',
router,
render: h=>h(App)
})
////////////////////////////////////////////
而 this.$route 表示的是我们配置的活跃中的路由(new VueRouter创建的router对象中的映射)
复制代码
导航守卫
// 定义:
对路由跳转的过程进行监听,在监听函数里做操作
// 引入:
当我们点击 导航栏上的首页,新闻,信息时候,希望document.title能够改变
我们可以利用组件中的生命周期函数:created(){ document.title='xxx'}
但是,一个个的改,效率低,麻烦
因为每次点击跳转,其实都是路由的跳转
所以:我们考虑能不能监听路由的跳转,在跳转的过程中操作
// 方法:
在routes数组 的路由对象中 加入 meta
{
path:'/home',
component:Home,
meta:{ // meta: 元数据 -- 描述数据的数据
title:'首页'
},
children:[
{
path:'xxx'.......
}
]
}
// 前置钩子(hook/guard守卫) -- 钩子:回调
// 前置:在路由跳转之前, 同理有后置钩子,afterEach(hook)路由跳转后--不需要主动调用next
router.afterEach((to,from)=>{
})
router.beforeEach((to,from,next) => {
document.title = to.meta.title
next() // 这里next调用,一定不能少,调用后才能进入下一个钩子
})
但是有个问题,如果是涉及到路由嵌套
比如 首页home 内部 新闻 信息 也有路由的跳转,那么首页的title显示undefined
打印to,可以看到 home的 title不存在, 改进:
router.beforeEach((to,from,next) => {
document.title = to.matched[0].meta.title // 这样就没任何问题了
next()
})
// 上述两个都是全局守卫 还有路由独享守卫,组件内守卫
前置钩子,可以用于判断用户是否登录了,如果登录了,那么就执行next
否则 next(false)
复制代码
保存路由状态
// 引入:
Home同级有Profile,子级(嵌套路由有new和message)
Home默认显示new,用户在Home点击了message,之后去到Profile,
回来Home时,变成了new,而不是原来的message
每次路由的跳转,都是一次旧的销毁和新的创建,
可以通过生命周期函数:destoryed(){ } 检测 // destoryed销毁后
keep-alive: Vue 内置组件
router-view 也是一个组件,如果直接被包在keep-alive里面,所以路径匹配到的视图组件都会被缓存
// 解决方案
在App.vue中直接
<keep-alive> <route-view/> </keep-alive>
keep-alive保证 组件不会被销毁
在Home.vue组件中,有new和message两个嵌套路由,对应首页的新闻和信息
data(){
return {
message:'hello',
path:'/home/news'
}
}
activated(){
this.$router.push(this.path);
}
beforeRouteLeave(to,from,next){
this.path = this.$route.path
next()
}
/*为什么不能用 activated 和 deactivated
deactivated(){
this.path = this.$route.path;
}
因为$route始终指向的是活跃的路由,activated为如果当前路由活跃的回调函数 deactivated就是不活跃时候调用,此时当前路由都不活跃了,$route自然不会指向自己,而是指向活跃的一边
当点击 用户界面的时候,this.$route已经是user了,而不是首页的信息
而beforeRouteLeave表示在路由跳转前回调
activated 和 deactivated 这两个函数只有在router-view使用了keep-alive包裹时才有效
*/
// 引入' :
router-view被keep-alive包裹,相当于所有的路由对应组件都keep-alive缓冲不被销毁,
也就是说,每个路由的生命周期函数的created回调只有一次
但是如果要人为的给单独一个路由设置多次呢?
<keep-alive exclude="组件名(.vue文件中export default中的name属性)">....</keep-alive>
// exclude排除什么什么,include包括什么。
复制代码
TabBar案例
首先TabBar.vue给1个插槽,该组件实现了,动态的内部插槽布局(flex)样式
在TabBarItem.vue文件中给插槽 slot v-if v-else没有问题
这里的slot终将会被App.vue中插槽内容取代
因为v-if/else是先判断,然后选择要取代的其中一个slot(另一个插槽无效)
但是:
你如果在插槽上写动态的class,不行
<slot :class="{active: isActive}" name="item-text"></slot>
直接被 App.vue中的插槽内容替代了,而替代后的内容,没有动态class
解决方法:
<div :class="{active: isActive}">
<slot name="item-text"></slot>
</div>
为了保险起见,以后所有插槽如果有v-if/else,动态class等等,
都外包裹一个div,相关操作加 到div上
复制代码
在案例中会用到svg图片,如果在抽离的过程中没有注意文件路径,很容易出错,而且改起来麻烦
所以,可以选择对文件起别名,
在项目文件build中的webpack.base.conf.js文件中,
resolve:{
'@':resolve('src') 代表给src路径起别名,叫做@
}
所以我们可以这样改下,引入@作为src路径,之后就不用该路径了
import TabBar from '@/components/tabbar/TabBar'
为了更方便,eg: CLI2
alias: {
'@': resolve('src'),
'assets':resolve('src/assets'), 直接用@表示src,'@/assets'---CLI3
'components': resolve('src/components'),
'views': resolve('src/views'),
}
该配置后,重新npm run dev
注意:
用别名的方法只能 直接 用在import的from上,如果要使用在img的src上。需要:
src="~assets/img/tabbar/home.svg",在最前面加上符号 ~
复制代码
Promise
Promise 是Es6一个特性,是异步编程的一种解决方案
// 什么是同步? 我们写的代码,一行行下来按顺序执行,就是一种同步。
// 同理,如果同步,我们发出网络请求,之后,无法响应用户的操作,必须等到收到请求资源/回应后
才能响应用户的操作。
// 因此:我们处理网络请求,需要异步,也就是响应用户操作的同时,发出网络请求,
之后,继续响应用户操作,而网络请求--数据请求成功,则将数据通过传入的函数回调出去
复制代码
回调地狱
但是如果网络请求十分复杂,就会出现回调地狱
复制代码
// executor 执行.exe
具体内容 Vue-Day8,上课代码
// sync 同步 synchronization
// async 异步 asynchronization
// fulfilled 满足的
// pending 等待的
复制代码
Promise.all
引入,如果有两个异步请求,用ajax去接,但是你不知道哪个请求先到,
而只有两个请求都到的时候,你才能进行操作。
不用Promise.all的话,你只能定义两个boolean类型变量到处理函数handleResult()中,都接收到了true&&true
// 例子
new Promise 中,参数为函数
Promise.all() 中为一数组,数组中放可迭代(可遍历)的对象
Promise.all([
new Promise((resolve,reject) => {
$ajax({
url:'url1',
success: function(data) {
resolve(data)
}
})
}),
new Promise((resolve,reject) => {
$ajax({
url:'url2',
success: function(data) {
resolve(data)
}
})
})
]).then(results => { // 这样两个请求的回馈都保存在了 results 数组中
// 这里代码为:当两个数据都接收到后执行
results[0]******
results[1]******
})
复制代码
Vuex
Vuex 组件的状态管理 (大管家)
也就是,多个组件之间,他们的状态(目前理解为:变量,变量一般用来保存状态)希望可以共享
依据组件树,底层要向高层,隔层太多,请求麻烦
可以建立一个对象,作为中间人集中管理状态---vuex还是响应式的
// 响应式是关键,当对象中的变量发生改变,相应的,应用了这个变量的组件也会进行刷新
如果不是响应式,那么就其他的实现方法了,比如:
const ShareObj = {
name:'why',
}
Vue.prototype.ShareObj = ShareObj
之后Vue实例组件以及在其中注册的组件,都可以通过
this.ShareObj.name来访问
// 但是没有响应式,你把name改了,相应组件中变量不会发生相应的变化
管理的状态:
token证书,用户信息,登录状态,地理位置
商品收藏,购物车等等
复制代码
最基本的使用
// npm install vuex --save
类似与router,要先
import Vue from 'vue'
import Vuex from 'vuex' 然后
Vue.use(Vuex)
为了防止main.js过大,另外放到一个文件夹
创建文件夹, 不要命名为vuex,而是 store // store 仓库
在store文件夹下创建index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 创建对象,这里有点不同
const store = new Vuex.Store({
state: {
// 保存状态
counter:1000 // 其他.vue组件的template标签中可以通过 $store.state.counter引用(响应式)
},
mutations: {
// 方法
increment(state){ // 这里的state直接默认就是上面的state对象,不需要加this
state.counter++
},
decrement(state){
state.counter--
}
},
actions: {
},
getters: {
},
modules: {
}
})
// 导出store对象
export default store
// 最后在main.js中挂载一下
import store from './store'
new Vue({
el:'#app',
store, // 本质上为 Vue.prototype.$store = store 全局属性,和router很像
methods: {
// <button @click="addition">++</button>
// <button @click="subtraction">--</button>
addition() {
this.$store.commit('increment')
},
subtraction() {
this.$store.commit('decrement')
},
}
// 如果函数有参数 count
subtraction(count){
this.$store.commit('decrement',count)
}
// 对应的mutation中
decrement(state,count){
state.counter -= count
}
// 最后在App.vue的template组件中<button @click="decrement(5)" >-5</button>
})
复制代码
Devtools 可以记录每一次State的状态
因为要按着图中顺序来,所以上文中,如果
<div>{{$store.state.counter}}</div>
<button @click="$store.state.counter++">
相当于直接从Vue Components连接到State,虽然能改变counter的值,但是逆向了
Devtools不能跟踪State的状态了
所以要想修改 State。一定通过Mutations,不然调试的时候,devtools里你显示不出来state的变化
但是允许跳过Action,直接从Vue Components到Mutation
Action的作用:当你修改Mutation的时候,如果你有异步操作,一定先到Action里面做
(Devtools只能跟踪同步操作)
而异步操作通常为:发送网络请求,所以连接到Backend API(backend:后端,frontend:前端)
复制代码
核心的概念
getters
State单一状态树: 英文名 Sing Source of Truth----单一数据源
Getters:类似单个组件的计算属性(当某一个数据需要经过一系列变化后显示到页面,就用计算属性)
state: {counter: 1000}
getters: {
powerCounter(state){
return state.counter * state.counter
},
// 你不需要在每一个组件中写相应的 methods函数,直接$store.getters.powerCounter即可
}
<h2>{{$store.getters.powerCounter}}
补充:
1.
getters中的函数也可以调用getters中的函数,比如:
powerCounter2(state,getters){ return getters.powerCounter.length}
所有函数的第二个参数,不管叫什么,都是代表的getters
2.
如果要让 别人在template标签中传入数据,而自己函数在getters中又没有新参数去容纳数据
就return一个函数,函数内部再return
比如:
moreAgeStu(state){
return age => {
return state.students.filter(s => s.age>age)
}
}
<h2>{{$store.getters.moreAgeStu(8)}}</h2>
复制代码
mutations
Mutation: Vuex的store状态的唯一更新方式:提交Mutation
当我们更新数据的时候,可能希望携带一些额外的参数(mutation的载荷/负载 Payload)
这里就引入了 提交的方式,在App.vue中,methods:
第一种 addCount(count){
this.$store.commit('incrementCount',count)
}
第二种 addCount(count){
this.$store.commit({
type:'incrementCount',
count,
})
}
如果传给函数的参数count为 数字5, 按照第一种,传过去的是Number 5
但是按照第二种,count无论如何都是作为参数---一个对象payload--的属性存在,如下
mutations:{
incrementCount(state,payload) {
state.counter += payload.count
}
}
官方建议mutations的写法:
在store文件夹下,创建一个mutations-types.js 文件里面放置 函数名
eg: export const INCREMENT ='increment'
在store文件夹的index.js文件中,
import {
INCREMENT
} from './store/mutations-types'
mutations: {
[INCREMENT](state){ state.counter++ },
}
在App.vue文件中,
import {
INCREMENT
} from './store/mutations-types'
如果在mutations中的函数updateInfo中,加入
setTimeout(()=>{
state.info.name='xxxx'
},1000)
state的数据会改变的,但是,调试的时候,因为你是异步操作,而不是同步操作
即使页面上显示的是修改之后的数据,但是调试工具显示的值,是未修改的
那么,就需要 actions 了
复制代码
actions
Action
actions: {
内部的函数也都有默认的参数,getters和mutations都有默认的state,
而actions的默认参数为 context ----上下文,
可以暂时将context理解为 store对象 (const store=new Vuex.Store({......}))
}
现在要解决上面的那个异步问题
mutations: {
updateInfo(state) {
state.info.name = "xxx"
}
},
actions: {
For_UpdateInfo(context) {
setTimeout(() => {
context.commit('updateInfo')
},1000)
}
},
App.vue中,methods: {
updateInfo() {
this.$store.dispatch('For_UpdateInfo')
}
}
actions:中也是可以传递参数的,payload,原理和前面同
技巧:
你在App.vue 中的this.$store.dispatch('For_UpdateInfo','aaa')
这里调用了 For_UpdateInfo函数,
如果该函数返回一个promise对象,eg:
在store的index.js中:
For_UpdateInfo(context,payload) {
return new Promise((resolve,reject) => {
setTimeout(() => {
context.commit('updateInfo');
console.log(payload); // aaa
resolve(111)
},1000)
})
}
那么相当于,会把return的 new Promise((resolve,reject) => {.....resolve(111).... })
返回到App.vue中this.$store.dispatch('For_UpdateInfo','aaa')的位置,并替代原代码
那么你大可在App.vue文件中:
this.$store
.dispatch('For_UpdateInfo','aaa')
.then( res => {
console.log(res); // 111
})
而较为差一点的方法,就是在App.vue文件中,
updateInfo(){
this.$store.dispatch('For_UpdateInfo',{
第二个参数传入一个对象
message:'aaaa',
success: () => {
console.log('xxxx')
}
})
}
复制代码
modules
Module
一个store变得臃肿的时候,可以将store分割成模块Module,每个模块有自己的state,mutations,actions...
eg:
const moduleA = { state: {....}, mutations:{...}, ....}
const store = new Vuex.Store({
state: {....},
modules: {
a: moduleA,
}
})
但是本质上,a: moduleA 最后是被放到了 state里面,
在template中: {{$store.state.a}}
modules中定义的mutations,在App.vue文件中正常commit就行
在模块modules中的方法,可以有第三个参数,rootState,可以访问最大的,非modules的state
模块modules中actions:的方法的参数,context不是指的最大的store,
这里的context.commit只会提交自己的mutations
复制代码
响应式补充
要想响应式,前提是你的变量要在state提前声明好,才能添加到响应式系统,
而不是后期动态加入(会增加到state中,但是无响应式)
// 非响应式
this.letter[0]='aaa'
delete state.info.age
// 响应式
this.letter.splice(0,1,'aaa')
Vue.set(this.letter, 0,'aaa') ---> 此方法也可应用于对象
Vue.delete(state.info,'age')
复制代码
抽离
// 首先,语法补充
const obj = {
name:'xxx',
age:18,
height:1.8,
address:'china'
}
// 解构,obj对象我只拿两个属性
const {name,height} = obj;
// 数组的解构
const names = ['why','kobe','james']
const [name1,name2,name3] = names;
复制代码
axios网络模块封装
axios---可暂时理解为: ajax io system
测试用的网站: httpbin.org
项目用服务器: 123.207.32.32:8000
接口: http://123.207.32.32:8000/home/multidata
http://123.207.32.32:8000/home/data?type=sell&page=3
status:200 请求成功
安装--- npm install axios --save
main.js中
import axios from '.......'
axios({
url:'http://123.207.32.32:8000/home/multidata' // 只传url,默认get请求
method: 'get' // 手动修改模式get/put
}).then(res => { // 因为会直接返回一个Promise对象,所以.then
console.log(res);
})
axios({
url:'http://123.207.32.32:8000/home/data'
params:{ // 专门针对get请求的参数拼接
type: 'sell',
page: 3
} // 相当于 url:'http://123.207.32.32:8000/home/data?type=sell&page=3'
}).then(res => {
console.log(res);
})
也可以:
axios.get(....)
结果: console.log(res)这个res是一个Object, 服务器返回的数据都在data里面
其他的属性都是axios框架生成的
axios.all可以对多个网络请求进行合并,axios.all---传入数组,数组里面写要发出的请求
axios.all([axios({
url: 'http://123.207.32.32:8000/home/data'
}),axios({
url:'http://123.207.32.32:8000/home/data'
params: {
type:'sell',
page:5
}
})]) // axios返回一个数组,可以用axios.spread展开
.then(axios.spread((res1,res2) => {
console.log(res1);
console.log(res2);
}))
复制代码
全局配置
main.js
// 记得: default + s
axios.defaults.baseURL = '....'
axios.defaults.timeout = 5000 // 毫秒,超时
axios.defaults.headers.post['Content-Type'] ='application/x-www-form-urlencoded'
等等
复制代码
改进
当业务多的时候,全局配置就不适用了
// 创建对应的axios的实例
const instance1 = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout:5000
})
// 利用实例instance1给几个发送的网络请求设置相同的baseURL
instance1({
url: '/home/multidata'
}).then(res => {
console.log(res);
})
instance1({
url:'/home/data',
params:{
type:'pop',
page: 1
}
}).then(res => {
console.log(res);
})
// 同理,之后就 onst instance2 = axios.create({ baseURL:'http://222.......' })
也可以在每一个.vue组件中,import axios from 'axios' 然后
利用生命周期钩子,created(){ axios({ulr:'http:.....'}).then(res=>{this.data里面数据=res}) }
拿到请求数据,但是不好,对第三方框架依赖性太强了
以后但凡是一个项目里面要用到第三方的框架,绝对,要有封装的思想
涉及到网络层的东西都封装到src文件的network文件夹下
axios都整到,network的request.js下
为了导出多个实例,不用 export default
而是 export function request(){ } export function instance(){ } ....
// request.js
import axios from 'axios'
export function request(config) {
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000'
timeout: 5000
})
// 发送真正的网络请求
instance(config).then( res => {
// 结果应该传出去,而不是在这里处理
}).catch( err => {
// 这里也是,想办法回调出去
})
}
// 把结果回调出去的方式
///////////////////////////////////////////////////////////
1.
// 传入的参数success和failure为函数
export function request(config, success, failure){
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000'
timeout: 5000
})
instance(config).then(res => {
success(res) // 把收到的数据res作为参数传入 main.js中传过来的函数success
})
.catch(err => {
failure(err)
})
}
// main.js
import {request} from './network/request';
request({
url:'/home/multidata'
}, res => { // 相当于 传过去给了success一个匿名函数,参数为res
console.log(res);
}, err => {
console.log(err);
})
///////////////////////////////////////////////////////////
2.
// request.js
export function request(config) {
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000'
timeout: 5000
})
instance(config.baseConfig)
.then( res => {
config.success(res);
}).catch( err => {
config.failure(err);
})
}
// main.js
import {request} from './network/request';
request({
baseConfig: {
},
success: function(res) {
},
failure: function(err) {
}
})
/////////////////////////////////////////////////////////////////////
// 第三种,最终方案
// request.js
export function request(config) {
return new Promise((resolve,reject) => {
const instance = axios.create({
baseURL:'http://123.207.32.32:8000',
timeout: 5000
})
instance(config)
.then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
}
// 或者--> 最好这个
export function request(config) {
// 1.创造axios实例
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
// 3. 发送真正的网络请求
return instance(config) // instance(config)调用后本身就返回一个promise
}
// main.js
import {request} from './network/request';
request({
url:'/home/multidata'
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
复制代码
拦截
// request.js
请求成功/失败,响应成功/失败,一共四种拦截
// 请求拦截
export function request(config) {
// 1.创造axios实例
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
// 2. axios的拦截器
// use两个参数,一个代表请求/响应成功,另一个代表..失败
axios.interceptors.request.use(); //全局的
instance.interceptors.request.use(config => {
console.log(config);
return config // 如果没有return,这里就把数据拦截了,那你main.js相应操作会报错
},err => {
console.log(err);
}); //只给instance的请求
// 这里可以看出use第一个参数为config,第二个为err
instance.interceptors.response.use(); //只给instance的响应
// 请求拦截的几个适用情况:
1. config中一些信息不符合服务器的要求,需要修饰一下
2. 每次发送网络请求的时候,界面中显示的一个请求的图标
3. 某些网络请求(比如登录token),必须携带一些特殊信息
// 3. 发送真正的网络请求
return instance(config) // instance(config)调用后本身就返回一个promise
}
// 响应拦截
export function request(config) {
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
instance.interceptors.response.use(res => {
console.log(res); // 真正要用的,只是res.data
return res.data
},err => {
console.log(err);
});
return instance(config)
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END