Vue2.0的笔记

Vue2.0

声明式编程,命令式编程
响应式: 数据改,界面自动改
复制代码

image-20210329231033315.png

image-20210329231349374.png

.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; 
复制代码

image-20210403194017905

在数组中间插入一个新的元素
比如我们有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

复制代码

image-20210411232934442.png

父子组件的访问方式

// 父组件访问子组件
$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发布) --- 打包的要发布的东西
/////////////////////////////////////////////////////////////////////////
复制代码

image-20210413221640550.png

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 路径中对应的命令,如果没有再去全局)
  
  /////////////////////////////////////////////////////////////////////////
复制代码

CSSless处理

  直接在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
        }
    },
}
复制代码

image-20210428174113927.png

补充

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

image-20210423153504724.png

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文件
复制代码

image-20210428195002333.png

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程序运行过程图
复制代码

image-20210428210518051.png

// 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

image-20210429001045103.png

image-20210429001052551.png

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  到远程仓库
复制代码

image-20210429094236972.png

image-20210429203943714.png

项目运行

npm run build // 最终打包发行版本
npm run serve //  开发时运行
复制代码

Vue Router

image-20210501193524377.png

前端 / 后端渲染

1. 后端(服务端)渲染
早期浏览器开发:
html,css没有js,用的jsp(java server page)/php ,写出一个网页传到浏览器,让浏览器展示
没有ajax,在服务器那里网页已经通过jap技术渲染好了
( html+css+java,java从数据库动态读取数据并且放在页面中)
 服务器里就是最终的网页,传给浏览器时,只有html和css
复制代码

image-20210501193020452.png

2.前后端分离阶段-前端渲染-jquery开发模式
通过ajax去请求数据
复制代码

image-20210501202248955.png

3.SPA页面
SPA:simple page web application--单页面富应用
在前后端分离阶段,静态资源服务器上保存的为  多套的 html+css+js,以及对应的url
但是在静态资源服务器上只有一个index.html,有一些或者是一个 css,js
(请求一次拿到全部)这不代表只有一个网站
比如一个网站他有三个页面,但是在静态资源服务器里面只有一个html,css,js
你必须依靠前端路由技术来分离页面的代码
你点击按钮,前端路由生成url,这时候不会向服务器请求资源,
而是通过js代码的判断,从全部代码中抽取页面代码(一个个的组件)
复制代码

前端路由中url和组件的关系(一个页面就是一个大的组件)

image-20210501203849794.png

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的 入栈和出栈

image-20210501205640295.png

// 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
    },
];
复制代码

image-20210502195706998.png

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.vue1个插槽,该组件实现了,动态的内部插槽布局(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一个特性,是异步编程的一种解决方案
// 什么是同步? 我们写的代码,一行行下来按顺序执行,就是一种同步。
// 同理,如果同步,我们发出网络请求,之后,无法响应用户的操作,必须等到收到请求资源/回应后
才能响应用户的操作。
// 因此:我们处理网络请求,需要异步,也就是响应用户操作的同时,发出网络请求,
之后,继续响应用户操作,而网络请求--数据请求成功,则将数据通过传入的函数回调出去
复制代码

回调地狱

但是如果网络请求十分复杂,就会出现回调地狱
复制代码

image-20210506104017376.png

// 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>
})
复制代码

image-20210506210525874.png

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;
复制代码

image-20210511200511649.png

image-20210511201137211.png

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'
等等
复制代码

image-20210511235941074.png

改进

当业务多的时候,全局配置就不适用了

// 创建对应的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
喜欢就支持一下吧
点赞0 分享