Vue学习

181 阅读8分钟

VUE

引导

官网

封装 VS 库 VS 框架

封装通常指一小部分通用业务逻辑,多个封装形成一个模块或者文件,多个模块或者文件就发展成为或者框架,而插件是为库或者框架发布后做后期补充,可以有官网或者第三方提供的,有点外挂的意思,有时候一个模块就是一个文件,有时候一个文件里面有多个模块,把不同的文件按类别放置到不同的目录里,这个目录叫做,框架改变了编码思想,库只是个工具你用或者不用不会影响你的编码思想

编码思想

jquery你用或者不用,你的编码思想都会是面向事件,开发一款插件中间用到什么库不重要,你会面向对象开发,如果用vue你要做的就是面向数据

MVC由来

一个思想、是后端产物,是为了前后端分离,早期的开发组成↓

工种 涉及
后端、后台、程序员 java(jsp+html) php(php+html+js)
前台、前端、美工 html+table+css
编辑、如今叫小编 登录后端开发的管理平台,对网页内容做增删改查
美工、设计师 ps,dw,flash

前端MVC

对js分离,js业务如今是厉害了(繁杂了)

目录 分类 举例 复用
model/xx.js 数据(M) ajax/jsonp/数据解析
view/xx.js 视图(V) 展示数据、创建元素,变色,运动
control/xx.js 控制层(C) 串业务,事件驱动 ×

伪代码举例

//M
function readBaidu(str,callback){..拿着需求str,求数据,调用回调带数据出去.}
//V
function writeLi(data,callback){...拿着数据写页面}
//C
window.onload=function(){
  oBtn.onclick=function(){
    readBaidu('xxx',function(res){
      writeLi(res);
      winObj.close()
    })
  }
}

vue是MVC?

MVC衍生出很多变体,MVP,MVVM,MV*, VUE是MVVM,M----V----VM,M数据,V视图, VM是负责M与V相互通信

开发工具

HbuilderX

软件自动引入依赖

手动引入依赖

hello world

vue的使用,简单理解,new出来一个Vue的实例,传一堆配置参数,控制一片html

<script src="vue"></script>
<body>
  V
  <div id="app">
    要被控制的html{{key}}
  </div>
</body>
<script>
	let vm = new Vue({
    el:'#app'  //要控制的那片html代码
    data:{key:value}//数据  M
  })
</script>

data选项

初始化数据位置,元数据,是vue实例的一个实例属性,所接受的数据类型,number/string/boolean/array/json/undefined/null

数据绑定

插值表达式

{{数据名}} mustache语法 声明式渲染

指令

v-text="数据名"

v-html="数据" 非转义输出

属性

v-bind:html属性="数据" 属性值动态化 :html属性="数据" 简写

v-bind:[属性名]="数据" 属性名动态化

给div属性绑定了一个xxx的属性 值是yyy

data:{ value:'yyy' }

给div属性绑定了一个xxx的属性 值是yyy

data:{ attr:'xxx' }

拿对象属性用[]

var y = 'aaa' var x = { [y]:111, bbb:222 } x 为 { aaa:111, bbb:222 }

v-text 和 {{}} 的区别

v-text 为innerText

v-html为innerHTML

标签里不可以再写别的东西

{{}} 插值表达式可以 在插值表达式外继续写别的东西 比较灵活

v-text="变量名 + 'str'" 变量 + 字符串

v-text里可以写表达式

{{}}也可以写表达式

1+1

3>4?'right':'wrong'

5==3

(function(){})() 立即调用的函数表达式

列表渲染

把数据指定要一些dom中去渲染,推荐操作的数据类型:变量数组、对象、字符、数字

<li v-for="值 in 数据">{{值}}</li>
<li v-for="值 of 数据">{{值}}</li>
<li v-for="(值,索引) in 数组">{{值}}/{{索引}}</li>
<li v-for="(对象,索引) in 数组">{{对象.key}}/{{索引}}</li>
<li v-for="(值,键) in 对象">
<li v-for="(数,索引) in 数字">
<li v-for="(单字符,索引) in 字符">

空数组,null,undefined不循环

条件渲染

一段dom可以根据数据有条件的渲染,使用指令v-show,或者v-if,对应的值是布尔

<div v-show="true">box1</div>
<div v-if="false">box2</div>

v-show VS v-if

v-show="布尔" v-if="布尔"
区别 操作css 操作dom
场景 适合频繁切换 适合不频繁切换
性能消耗 初始渲染消耗 频繁切换消耗

事件绑定

vue通过v-on指令绑定事件,处理函数,需要丢到一个methods选项当中去

<button v-on:事件名="方法"..
<button	@事件名="方法"	...
<button	@事件名="方法(参数)" .....
<button	@事件名="方法($event,参数)"	.....

事件名 不带on

new Vue({
  methods:{
    方法:function(ev,参数){业务}
    方法2(ev,参数){业务}
  }
})

ev 事件对象,参数可以有多个

注意:vue提供的选项的值如果是函数时,不可用箭头函数 , 会造成this丢失

双向绑定

视图控制数据,数据也可控制视图,可通过属性+事件绑定实现,也可以使用系统指令v-model,这个指令可以用在能生产数据的表单元素上

<input type="text" :value="data数据" v-on:input="checkIptValue">
<input type="text" v-model="data数据">

自定义指令

为了操作dom元素 通常会写成自定义指令

      //定义到全局
      Vue.directive('color',(el,binding)=>{
        //[^el]: 使用指令的DOM元素
        //[^binding]: 是个对象 含有调用指令时传入的 参数
        // console.log('指令运行了',el,binding);
        el.style.background=binding.value || 'red'
        console.log('指令里面的',this);
        
      })

定义到局部

let vm = new Vue({
       el:'#app',
       data:{
         ylw:'yellow',
         title:'标题'
       },
       directives:{
         color(el,binding){
           el.style.background=binding.value || 'red'
           console.log('指令里面的',this);
         }
       }
     })

自定义指令里的this都指向全局

        directives:{
          focus:{
            inserted(el,binding){
              console.log('inserted');
              el.focus()
            },
            bind(el,binding){
              console.log('bind');
            },
            update(el,binding){
              console.log('update');
            },
            componentUpdated(el,binding){
              console.log('componentUpdated');
            }
          }
        }

自定义指令也有四个钩子函数

类和实例API

Vue 是个类,所以Vue.protoname,是类属性|静态属性,Vue.methodname()是类方法|静态方法,new Vue返回的是实例,所以vm.protoname是实例属性,vm.methodname()是实例方法,同时vue类内部的this指向的是实例vm,实例vm.$protoname对等vue选项的key

非响应式情况

情况总是要发生

  • 对数组使用了 非变异 (non-mutating method) 方法(返回的了新数组)
  • 修改数组的长度时
  • 修改数组索引上的值(根索引)
  • 给对象添加了不存在的属性

问题还是要解决

Vue.set(数组, index, value)

vm|this.$set(对象, key, value)

this.$forceUpdate() 强制刷新

吃亏后的经验

不要修改数组的根键,不要修改数组的长度,数据一开始都要声明在data选项内部,不要对数组使用非变异的api

key的问题

给指定循环的dom一个key 是数据的id,确保key唯一性,避免数据错乱导致的视图问题,同时提供性能优化

模板表达式

在dom里面插入数据,数据周围可以出现表达式,但不是语句,如{{数据+表达式}} v-指令="数据+表达式"

表达式:

title + 'abc'
`${title}呵呵哒` 
bl ? '处' : '非处'
'i love you'.split(' ').reverse().join(' ')

计算属性

是一个函数,所依赖的元数据变化时,会再次执行,平时会缓存,是响应式的,需要在模板中渲染才可调用

可以当作属性使用

首次执行

语法

//定义
computed:{
  计算属性: function(){return 返回值}		
}

//使用
使用:	{{计算属性}} |  v-指令="计算属性"

computed VS method

method computed
方法会每次调用 基于它们的响应式依赖进行缓存的
一般 性能高
{{methodname()}} {{computedname}}
适合强制执行和渲染 适合做筛选

methods里可以定义公共的函数和事件响应函数

Vue里也可以自己定义options

new Vue({
    el,
    data,
    methods,
    foo(){
        
    }
})
//this.$options.foo
//可以拿到这个函数
在methods 里 调用
this.$options.foo.call(this)

作业

vue+bootstrap实现留言板

官方教程

复习文档

官方教程

多vue实例控制多片html

    <!--下面两个title分别是两个vue实例中的实例属性title -->
    <div id="app1">
      <h3>{{title}}</h3>
    </div>
    
    <div id="app2">
      <h3>{{title}}</h3>
    </div>

    <script type="text/javascript">
      
      let vm1 = new Vue({
        el: "#app1",
        data: {
          title: '标题1',
        },
        
      })
      
      let vm2 = new Vue({
        el: "#app2",
        data: {
          title: '标题2',
        },
        
      })
      
    </script>

计算属性的getter/setter

        computed:{
          fullName: {
            get() {
              console.log('getter');
              return this.firstName + ' ' + this.lastName
            },
            set: function(newValue) {
              console.log('setter');
              var names = newValue.split(' ')
              this.firstName = names[0]
              this.lastName = names[names.length - 1]
            }
          }
          //调用fullName 时 打印getter
          //修改fullName 时 打印setter getter
        }

template元素

适合内部渲染多条 相同的条件分为一组

​ 包裹元素,有逻辑,自身不渲染

if + v-else-if + v-else

双向绑定选择框

 <body>

    <div id="example-5">
      <select v-model="selected">
        <option disabled value="">请选择</option>
        <option
          v-for="(item,index) of ops"
          :key="item.id"
          :value="item.value"
        >{{item.text}}</option>
      </select>
      <span>Selected: {{ selected }}</span>
      <span>Selected: {{ cptSelValue }}</span>
    </div>


    <script type="text/javascript">
      let vm = new Vue({
        el: "#example-5",
        data: {
          selected: '',
          ops:[
            {id:1,value:'sh',text:'上海'},
            {id:2,value:'bj',text:'北京'},
            {id:3,value:'db',text:'东北'},
            {id:4,value:'qd',text:'青岛'}
          ]
        },
        
        computed:{
          cptSelValue(){
            let result = '';
            this.ops.map((item,index)=>{
              if(item.value===this.selected){
                result = item.text
              }
            })
            return result;
          }
        }
      })
      
    </script>
  </body>

属性检测

需要在数据变化时执行异步或开销较大的操作时,而计算属性是同步的,这个时候需要属性检测watch

watch 默认首次不执行 除非配置 immediate 为 true

定义一个选项 watch

methods:{
    函数名(newValue,oldValue){
    //newValue 改后的值
    //oldValue 改前的值
        console.log('watch 数据名',newValue,oldValue)
    }
}
watch:{
/*使用方式 三种*/
  数据名:'method函数名'    //数据名==data的key
  数据名:函数体(new,old){}
  数据名:{
    handler:fn(new,old){},
    deep: true //深度检测 可以检测对象 数组
    immediate: true //首次运行
  }
}

计算属性 VS 函数 VS 属性检测

计算属性 函数 属性检测
依赖模板调用 - ×
是否缓存 ×
异步 ×

使用watch检测引用数据类型,检测之后和检测之前都是修改之后的值,可以通过

'title.a':{
			 handler(n,o){
				 console.log(n,o)
			 },
			  deep:true
		  }

检测title中a 的变化

handler中可以写Promise

watch 支持异步操作

样式操作

操作样式,就是属性绑定,只不过绑定的属性是class和style

绑定方式

<div v-bind:class="数据|属性|变量|表达式"></div>
<div :class="数据|属性|变量|表达式"></div>

<div v-bind:style="数据|属性|变量|表达式"></div>
<div :style="数据|属性|变量|表达式"></div>

属性值的类型支持

字符/对象 / 数组

<div :class="'active t1'"></div>
<div :class="{active:true,t1:false}"></div>
<div :style="[{css属性名:值},{css属性名小驼峰:值}]"></div>

指令

扩展了html语法功能,区别了普通的html属性,vue系统自带了指令,也可自定义指令来扩展,所有系统指令在官方文档的API处提供

其他系统指令

v-pre

保留字不编译,原样输出,跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译

v-cloak

防闪烁,模板没编译完,电脑配置差,有可能会看到{{}},体验不佳,不如用css先隐藏,之后再显示,包括被包裹的子元素

v-once

只渲染元素一次。随后的重新渲染被忽略,包括被包裹的子元素。这可以用于优化更新性能

自定义指令

div标签上写v-指令名

定义时不用写v-

主要用来操作dom

系统指令在不够用的情况下,考虑自定义,指令是个函数|对象,用来操作dom的, 里面的this 返回window

全局定义 全局定义的自定义指令,每个vue实例都可以使用

此时指令中的this 是window

定义到全局的自定义指令 必须写在实例的上边,否则会报错。

Vue.directive('指令名',函数(el,binding){})

局部定义

new Vue({
	directives:{
    指令名	: function(el,binding){},//简写方式: bind + update
  	指令名(el,binding){},
    指令名:{
		inserted:fn(el,binding){}//绑定指令的元素插入到父节点时调用  v-focus
		bind:fn//指令第一次绑定到元素时调用	v-drag
		update:fn//指令所在的元素的model层的数据,view有更新请求时
		componentUpdated:fn//更新完成时
    }
  }
})

自动获取焦点 希望在元素插入父节点时就调用,那用到的最好是用 inserted

父节点存在即可调用,不必存在于 document 中

    focus:{
	inserted(el,binding){
        console.log('inserted');
		el.focus()
	},
	bind(el,binding){
		console.log('bind');
	},
	update(el,binding){
		 console.log('update');
	},
	componentUpdated(el,binding){
	    console.log('componentUpdated');
	}
}

过滤器

对数据在模板中的表现过滤,符合预期,比如数据是0和1,想要表现成对错、成功失败、男女,数据需要过滤器来格式化,vue1.x版本有系统自带过滤器,vue2.x之后完全需要自定义,没有自带过滤器

Vue. 支持在{{}}插值的尾部添加一小管道符 “ | ” 对数据进行过滤,经常用于格式化文 本,比如字母全部大写、货币千位使用逗号分隔等。过滤的规则是自定义的, 通过给 Vue 实例添 加选项 filters 来设置

使用

{{数据名 | 过滤器名(参数1,参数2)}}
:属性="数据| 过滤器名(参数1,参数2) "

全局定义

Vue.filter('过滤器名称',函数(要过滤的元数据,参数1,参数n){})

局部定义

filters:{
  过滤器名称:函数(要过滤的元数据,参数){}	//函数必须要有返回值
}

预购阿里云服务器(ecs云服务器)

链接 -》个人云 -》突发性能型 t5(74/年)-》系统(centOs系统,认准ecs云服务器-》控制台做一下实名认证

域名购买

链接

数据交互

向服务器发送ajax请求,抓取数据,【vue:你看我看嘛】

解决方案

  • 自行通过XMLHttpRequest对象封装一个ajax
  • 使用第三方自带的ajax库,如:jquery ×
  • 把XMLHttpRequest,封装成一个promise
  • 使用js原生自带的promise语法糖 fetch
  • 使用第三方ajax封装成promise习惯的库,如:vue-resourceaxios

封装ajax到promise

function maxios(options) {
  
  return new Promise((resolve, reject) => {
    
    //-1  整理options
    options = options || {};
    options.data = options.data || {};
    options.timeout = options.timeout || 0;
    options.method = options.method || 'get';

    //0 整理data
    var arr = [];
    for (var key in options.data) {
      arr.push(key + '=' + encodeURIComponent(options.data[key]));
    }
    var str = arr.join('&');

    //1	创建ajax对象
    if (window.XMLHttpRequest) {
      var oAjax = new XMLHttpRequest(); //[object XMLHttpRequest]
    } else {
      var oAjax = new ActiveXObject('Microsoft.XMLHTTP')
    }

    if (options.method == 'get') {
      //2.
      oAjax.open('get', options.url + '?' + str, true);
      //3.
      oAjax.send();
    } else {
      //2.
      oAjax.open('post', options.url, true);
      //设置请求头
      oAjax.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
      oAjax.send(str); //身子
    }

    //3.5	超时
    if (options.timeout) {
      var timer = setTimeout(function() {
        alert('超时了');
        oAjax.abort(); //中断ajax请求	
      }, options.timeout);
    }


    //4.
    oAjax.onreadystatechange = function() { //当准备状态改变时
      if (oAjax.readyState == 4) { //成功失败都会有4
        clearTimeout(timer);
        if (oAjax.status >= 200 && oAjax.status < 300 || oAjax.status == 304) {
          resolve(JSON.parse(oAjax.responseText))
        } else {
          reject(oAjax.status)
        }
      }
    };
  })
}

axios

this.$http|axios({配置}).then(成功回调(res)).catch(失败回调(res))
this.$http|axios.get(url,{配置}).then(成功回调(res)).catch(失败回调(res))
this.$http|axios.post(url,pollfill).then(成功回调(res)).catch(失败回调(res))

配置:

url:“地址“ method: “ 提交姿势” params:{} 地址栏携带的数据 data:{} 非地址栏携带数据

res: 响应体 数据是在res.data内部 vue-resource 返回的数据在res.body内部

访问本地json

// axios({ //第三方的axios
  this.$http({ //把axios绑定到Vue的原型
    url: './data/user.json'
  }).then(
    res => console.log('maxios res', res)
  ).catch(
    err => console.log('maxios err', err)
  )

读取php接口get

axios({
  url: 'http://localhost:80/php7/get.php',
  params: {//地址栏数据
    a: 11,
    b: 22
  }
}).then(
  res => this.list = res.data
)

跨域

有时,前端和后端的工程文件不在同一个域,也会出现跨域,以下是解决方案

后端解决

部分接口允许

//node 要允许的接口内部 
res.setHeader('Access-Control-Allow-Origin', req.headers.origin)

//php端
header('Access-Control-Allow-Origin:*');

所有接口允许

//node端
let cors = require('cors');

app.use(cors({
  //允许所有前端域名
  "origin": ["http://localhost:8001","http://localhost:5000","http://localhost:8080"],  
  "credentials":true,//允许携带凭证
  "methods": "GET,HEAD,PUT,PATCH,POST,DELETE", //被允许的提交方式
  "allowedHeaders":['Content-Type','Authorization']//被允许的post方式的请求头
}));

前端解决

jsonp

浏览器装插件 正向代理

开发环境做代理(webpack,正向代理,客户端代理)

读取php接口post

let params = new URLSearchParams(); //创建一个url传输的实例
  params.append("a", "1"); //添加一个key/value
  params.append("b", "22"); //添加一个key/value
  // params.delete("a")  删除
  // params.set("a","11");//修改
  // params.has("a")  返回 true/false

axios({
  url: 'http://localhost:80/php7/post.php',
  method: 'POST', //提交姿势
  // data: {//非地址栏数据 ***
  //   a: 11,
  //   b: 22
  // },
  // data: "a=11&b=22",
  data: params,//URLSearchParams类型
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  } //php 默认要求传递请求头
}).then(
  res => this.list = res.data
)

URLSearchParams

URLSearchParams 接口定义了一些实用的方法来处理 URL 的查询字符串。
一个实现了 URLSearchParams 的对象可以直接用在 for...of 结构中,例如下面两行是相等的:
for (const [key, value] of mySearchParams) {}
for (const [key, value] of mySearchParams.entries()) {}

构造函数
URLSearchParams()
返回一个 URLSearchParams 对象。

方法
该接口不继承任何属性。
URLSearchParams.append()
 插入一个指定的键/值对作为新的搜索参数。
URLSearchParams.delete()
 从搜索参数列表里删除指定的搜索参数及其对应的值。
URLSearchParams.entries()
 返回一个iterator可以遍历所有键/值对的对象。
URLSearchParams.get()
 获取指定搜索参数的第一个值。
URLSearchParams.getAll()
 获取指定搜索参数的所有值,返回是一个数组。
URLSearchParams.has()
 返回 Boolean 判断是否存在此搜索参数。
URLSearchParams.keys()
返回iterator 此对象包含了键/值对的所有键名。
URLSearchParams.set()
 设置一个搜索参数的新值,假如原来有多个值将删除其他所有的值。
URLSearchParams.sort()
 按键名排序。
URLSearchParams.toString()
 返回搜索参数组成的字符串,可直接使用在URL上。
URLSearchParams.values()
 返回iterator 此对象包含了键/值对的所有值。

读取node接口get

axios({
  url: 'http://localhost:3001/api/follow',
  params: {
    _page: 1,
    _limit: 2
  }
}).then(
  res => this.list = res.data
)

读取node接口post

//测试 formdata类型

let fileInput = document.getElementById('icon'); //抓取到file dom
let formData = new FormData(); //创建formData类型

formData.append("icon", fileInput.files[0]); //icon键的值是个文件体

formData.append("username", "damenmen")
formData.append("password", "damenmenda123")
formData.append("nikename", "我子个启的")

axios({
  url: 'http://localhost:3001/api/reg',
  data: formData,//formdata类型
  method: 'POST',
}).then(
  res => this.list = res.data
)

读取第三方接口

axios({
  url: 'https://douban.uieee.com/v2/movie/in_theaters',
  params: {
    start: 0,
    count: 3
  }
}).then(
  res => this.list = res.data
)

发送jsonp请求

axios不支持jsonp请求,vue-resource支持,解决axios支持jsonp需要自行封装或使用第三方axios插件

自行封装

//options url,data,timeout,success,error
function jsonp(options){
	
	options = options || {};
	if(!options.url){
		return;
	}
	options.data = options.data || {};
	options.cbName = options.cbName || "cb";
	options.timeout = options.timeout || 0;
	
	var fnName = "jsonp_"+ Math.random();
	fnName = fnName.replace("." ,"");
	options.data[options.cbName] = fnName;
	
	var arr = [];
	for(var i in options.data){
		arr.push(i + "=" + encodeURIComponent(options.data[i]));
	}
	var str = arr.join("&");
	
	
	window[fnName] = function (json){

		options.success && options.success(json);
		
		clearTimeout(timer);
		oHead.removeChild(oS);
		window[fnName] = null;
	}
		
	var oS = document.createElement("script");
	oS.src = options.url + "?" + str;
	var oHead = document.getElementsByTagName("head")[0];
	oHead.appendChild(oS);
	
	if(options.timeout){
		var timer = setTimeout(function(){
			options.error && options.error();
			//window[fnName] = null;
			window[fnName] = function(){};
		},options.timeout);
	}
		
}

axios.jsonp=jsonp//axios需要先引入

使用自行封装

<script src="axios"></script>
<script src="自行封装的jsonp"></script>
<script>axios.jsonp({配置})</script>

使用第三方axios的jsonp插件

推荐 axios-jsonp

作业

利用axios读取node|php接口,渲染一张页面,页面中要求有过滤器和计算属性及指令的使用,其他自由发挥

生命周期 每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会

组件

Vue根实例表示1个应用,一个应用有若干个组件拼装而成

使用组件

<组件名></组件名>
<组件名/>

定义组件

let 组件变量名= Vue.extend({
  template:'<div class="header">我是header组件</div>' //组件使用的模板
});

let 组件变量名={};  //√

注册组件

//全局注册
Vue.component('组件名',组件变量名);

//局部注册
components:{ //选项
  组件名:组件变量名	//√
}

组件数据

一个组件的 data 选项必须是一个函数,且要有返回object,因此每个实例可以维护一份被返回对象的独立的拷贝,否则组件复用时,数据相互影响,也就是说组件的作用域是独立的

组件模板

外部模板,字符模板(行间模板|inline-template)

//=========外部模板========

//模板定义1
<template id="id名">
	<div>...</div>
</tempalte>

//模板定义2
<script type="x-template" id='id名'></script>

<script>
let 组件变量名= {
  template:'#id名' //组件使用的外部模板
};
</script>


//=========字符模板========
<script>
let 组件变量名= Vue.extend({
  template:'<div>...</div>'
});
</script>

组件避讳

  • 组件名不可和html同名

  • 组件没有el选项,只有根实例存在el

  • 组件的模板一定要有根元素

  • 组件的data是个函数

书写风格

  • 组件变量名: 大驼峰 XxxXxx parscel-case
  • 组件名: xx-xx (kebab-case)| 大驼峰|app-xx-xx

小驼峰 camel-case 大驼峰 parscel-case xx-xx kebab-case

camel-case parscel-case 定义的都可以用kebab-case引用

单文件组件

xx..vue,内部组成(script + template + style)

vue-dev-tools安装

方案1: 登录谷歌应用商城->索引vue-dev-tools->安装->重启浏览器

方案2:

  https://github.com/vuejs/vue-devtools
  1. 	Clone this repo  downloadzip 到桌面
  2.	npm install  安装依赖
  3.	npm run build	build目录项目就有各种浏览器的安装包
  4.	打开 chrome -> 设置->更多工具->扩展应用->开发者模式->加载扩展程序->指向->build目录项目下的chrome

项目环境

通过官方脚手架,手动搭建模块化,工程化,自动化开发环境

任何一款全局包 登录要是adimn

vue -V  //查看版本 

//非3.x/4.x 时卸载
npm unstall vue-cli -g  

//安装
npm install -g @vue/cli  //装3/4
npm install -g @vue/cli-init //桥接2  

//测试
vue -V

//初始开发环境
vue create 目录

//node + npm + npx  node安装会下载三个东西

//单cli没装上
npx vue create 目录
//npx 避开 注册表写入
//直接在内存中运行

HbuildX自动搭建模块化,工程化,自动化开发环境

工作区-》右键-》创建项目-》普通项目-》vue-cli项目

直接copy他人生成的项目环境

环境讲解

/**
 * main.js详解
 * 先导入需要用到的包
 * import Vue from 'vue'
 * new一个Vue实例
 * 
 new Vue({
	//向实例上注入属性
	data:{
    a:'通用数据'
	},
	//render 渲染函数
	render: h => h(App),
	// router:router
	router
}).$mount('#app')
//手动挂载 可用于项目延时挂载
//源码
if (vm.$options.el) {
  vm.$mount(vm.$options.el)
}
 * 
 * render函数 简化过程
 * //render函数是vue通过js渲染dom结构的函数createElement
 * render: function (createElement) {
 *  return createElement(App);
 *}
 * 简写
 * render(createElement){
	 * return createElement(App)
 *}
  * 转为箭头函数
  * render: createElement=> createElement(App),
  * 将 createElement简写为h
  * render: h=> h(App),
 * */

main

Vue.config.productionTip = false//设置为 false 以阻止 vue 在启动时生成生产提示。
render: h => h(App)
//↓
render: function(createElement){
  // return createElement('h3','内容')
  // return createElement(组件)
  return createElement(App)
}

es6模块化复习

输入

import a from './mod/a.js'  //输入 默认输出 的任意类型
import {aa,cc} from './mod/a.js'  //输入批量输出的 属性,需要一个对象包装
import * as a from './mod/a.js'  //输入批量输出的 属性,包装到一个对象里面使用

输出 默认导出就要不加{},其他就需要加{}

//批量输出  any  导出多次
export const 对外变量 = 模块内部的变量
export {a,b,c} //批量输出模块内部的属性
/**
*用import * as 任意变量名 from '路径' 来接受 批量输出和默认输出的元素
*用 import {} from '' 批量导入
*import {a as q} from './mod/a.js' 可以给批量导出的变量重命名 用 原变量名 as 新命名 这种方式来实现
*import a,{b,c,d} from '' 其中a为默认导出的 {}里的为批量导出的
**/
//默认输出可以输出任意类型 但是只能输出一次
//默认输出  any   只能导出一次
export default any

css 规则

style-loader 插入到style标签,style标签多了选择器冲突问题就出来了,解决方案如下

/* 方案1:	命名空间 推荐BEM命名风格*/
/*B__E--M  block 区域块  element 元素  midiler 描述|状态 */
.search{}
.search__input{}
.search__input--focus{}
.search__input--change{}
.search__button{}

//  B__E--M
//  b-b-b__e-e-e--m-m-m
<!-- $style 要和style 标签中的module结合使用 -->
<!--方案2 css模块化 -->
<template>
	<div :class="$style.box">
    ...
  </div>
</template>

<script>
export default {
  mounted(){
    this.$style //返回一个对象 {选择器:'混淆后的选择器',xx:oo}
  }
}
</script>

<style module>
  /* 注意: 选择器名(支持 id,类,或者id、类开头其他选择器) */
  .box{}
  #box2{}
  .box3 div{}
  #box3 div{}
</style>
<!--方案3 独立样式作用域-->
<template></template>
<script></script>
<style scoped></style>

template 没有被 export default 为什么会被导出

原文链接:blog.csdn.net/txwsmsm7023…

组件中data为什么是函数

data 是函数的话,每次创建一个组件后,调用 data 函数,用return返回初始数据的一个全新副本数据对象,就避免了所有实例共享引用同一个数据对象。

一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝: data: function () { return { count: 0 } }

不同组件data不会冲突是因为只能在组件上使用,是其私有的,父组件直接调用不了子组件的方法的,所以才有了父子组件间的通信传递

data:()=>{
      return{}
    }
return 只有一行所以可以 省略箭头函数后的{}
data:()=>{}
因为return 数据要换行 所以用 ()包起来 
简写为
data:()=>({
    
})

组件里生命周期也是个函数

created(){

}

关于同一个vue页面同时写多个template

//vue 会默认导出最后一个template , 即使 使用template:'#id名' 定义了组件使用的外部模板
<template id="first">
  <div>
1231
  </div>
</template>
<template>
  <div>
1456
  </div>
<script>

export default {
  name: 'App',
  template:'#first'
  components: {
  }
}
</script>

<style>

</style>


Vue.config.productionTip = false;

// 设置为 false 以阻止 vue 在启动时生成生产提示。 生产提示

在vue-cli 中使用axios

  • 安装axios npm i -S axios
  • main.js 导入axios import axios from axios
  • 配置默认路径 axios.defaults.baseURL = '/api' //使每次请求都会带一个api前缀
  • 往Vue原型上挂载axios Vue.prototype.$axios = axios
  • 配置vue.config.js 设置允许跨域
  • 使用
vue.config.js
module.exports = {
    devServer:{
        proxy:{
            '/api':{
                //将/api替换成 要请求的路径
                target:'http://localhost/exercises/weibo-vue-basic/weibo5.php',
                //设置允许跨域
                changeOrigin:true,
                ws:true,
                pathRewrite:{
                    '^/api':''
                }
            }
        }
    }
}
this.$axios({
                // 由于 main.js 里全局定义的 axios,此处直接使用 $axios 即可。
                // 由于 main.js 里定义了每个请求前缀,此处的 / 即为 /api/, 
                // 经过 vue.config.js 配置文件的代理设置,会自动转为 你要请求的路径,从而解决跨域问题
            url:'/',
            params:{}
          })
          .then()
          .catch()

组件通讯基础

父子(props)

父组件通过属性绑定,子组件通过选项props接收,props是响应式的,props完成单向下行绑定

单向数据流

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。 父传

<子 :自定义属性="父数据"></..>

子收

props:['自定义属性']
props:{自定义属性:{type/default/required/validator...}}

<div>
  {{自定义属性}}
</div>

注意:

​ props是只读的

props命名:

​ props: ['postTitle'] ​

奇葩情况

在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态

子父

通过自定义事件实现,给子组件绑定自定义事件,子组件触发自定义事件时传递,事件函数是父组件方法,父方法负责接收

父绑定父接收

<template>
	..
	<子 @自定义事件="父方法"></..>
	..
</template>
<script>
export default {
  methods:{
    父方法(接受数据){处理}
  }
}
</script>

子触发子传递

<script>
	this.$emit('自定义事件',子.数据名)
</script>

集中管理($root)

把数据存到根实例的data选项,其他组件直接修改或者使用

定义

new Vue({
  data:{a:1}
})

使用 在子组件的template 中使用实例属性时 要用$root.实例属性名

在子组件的js代码中使用实例属性时要用this.$root.实例属性名

实例方法也是同理

//子组件内部
this // 组件本身
this.$root // vm
this.xx //组件内部数据
this.$root.a //根实例数据

作业

完成微博的数据读取,页面渲染,有能力的完成组件的拆分

乱入一个防抖

inputEle.addEventListener("keyup", (function(e){ //这是一个自运行函数 var t = null; ////变成了局部变量,不会造成全局污染 return function(){ //真正的事件函数在这里 clearTimeout(t); //每次触发,都把前面的定时器关闭,尽管第一次定时器并不存在 t = setTimeout(function(){ //开启新的定时器 //ajax(...); 发送请求到服务器 }, 300); } })())

路由

用来SPA (single page application 单页面应用),页面跳转,官网

单页VS多页

页面模式 多页面模式(MPA Multi-page Application) 单页面模式(SPA Single-page Application)
页面组成 多个完整页面, 例如page1.html、page2.html等 由一个初始页面和多个页面模块组成, 例如:index.html
公共文件加载 跳转页面前后,js/css/img等公用文件重新加载 js/css/img等公用文件只在加载初始页面时加载,更换页面内容前后无需重新加载
页面跳转/内容更新 页面通过window.location.href = "./page2.html"跳转 通过使用js方法,append/remove或者show/hide等方式来进行页面内容的更换
数据的传递 可以使用路径携带数据传递的方式,例如:index.html?account="123"&password=123456"",或者localstorage、cookie等存储方式 直接通过参数传递,或者全局变量的方式进行,因为都是在一个页面的脚本环境下
用户体验 如果页面加载的文件相对较大(多),页面切换加载会很慢 页面片段间切换较快,用户体验好,因为初次已经加载好相关文件。但是初次加载页面时需要调整优化,因为加载文件较多
场景 适用于高度追求高度支持搜索引擎的应用 高要求的体验度,追求界面流畅的应用
转场动画 不容易实现 容易实现

单页面模式:相对比较有优势,无论在用户体验还是页面切换的数据传递、页面切换动画,都可以有比较大的操作空间 多页面模式:比较适用于页面跳转较少,数据传递较少的项目中开发,否则使用cookie,localstorage进行数据传递,是一件很可怕而又不稳定的无奈选择

基础使用

安装引入注册

npm i vue-router -S

// src/main.js
import router form './plugins/router.js'
new Vue({
  router
})

配置路由

// src/plugins/router.js
import Vue from 'vue'

//1. 引入路由包
import VueRouter from 'vue-router'

//2. 安装插件包到Vue上, 
Vue.use(VueRouter);

//3. 路由配置
let routes = [
  {path: '/home',component: Home}, //route  一条路由的配置
]

//4.路由实例
let router = new VueRouter({ //插件路由对象
  // routes:routes
  routes,
});

//5.导出路由实例,让他去控制vue根
export default router

声明式跳转


    <!-- 使用 router-link 组件来导航. -->
    <!-- 通过传入 `to` 属性指定链接. -->
    <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
    <!--可以通过tag指定渲染成什么元素-->
<router-link to="/home">声明式跳转</router-link>
<router-link to="/home" tag='li'>声明式跳转</router-link>
<router-link to="/home" tag='li' active-class='类名'>声明式跳转</router-link>
<!--  <!-- 路由匹配到的组件将渲染在这里 -->-->
<router-view>展示区</router-view>

router-link 组件属性: tag='li' 指定编译后的标签 active-class='类名' 指定激活后的样式 模糊匹配 exact-active-class='类名' 指定激活后的样式 严格匹配 router-link和router-view组件是vue-router插件提供的

重定向

{
  path: '/',  //默认页
  redirect: '/home' //配置型跳转
}, 

404

{
  path: '*',
  component: NoPage组件
}

子路由嵌套

// src/plugins/router.js
routes=[
  {},
  {path:'xx/:变量',component:xx},
  {
    path:xx
    component:xx
    children:[  //子路由
      {}
      ..
    ]
  },
  {}
]

路由传参

// 组件中
<router-link to='xx/参数?a=1b=2'></..>
<router-link :to='{name:'名字',params:{id:1},query:{a:2,b:3}}'></..>
两种传参方式的区别:
<router-link to='xx/参数?a=1b=2'></..> 传过去的参数都变成了字符串 、
<router-link :to='{name:'名字',params:{id:1},query:{a:2,b:3}}'></..> 传过去的时候是什么类型 接受的时候还是什么类型

除此之外没有任何区别

命名路由

{path: '/home',component: Home, name:'名字'}, //route  一条路由的配置

组件接参

//模板template
{{$route.params|query|path}} 
//组件 script
this.$route.params|query

编程式跳转

this.$router.push(string|obj)
this.$router.push({name:'...'})   //添加一个路由 (记录到历史记录)
this.$router.replace({name:'...'})   //替换一个路由 (不记录到历史记录)
this.$router.go(-1|1)|back()  //回退/前进

路由模式

// src/plugins/router.js

let router = new VueRouter({ //插件路由对象
  routes,
  // mode:'hash'//哈希模式   location.href
  mode:'history'//历史记录   history.pushState
});

扩展

路由守卫

全局守卫

// src/plugins/router.js

//前置
router.beforeEach((to, from, next) => {
  
  //	to: 目标路由
  //	from: 当前路由
  
  // next() 跳转  一定要调用
  next(false);//走不了
  next(true);//走你
  next('/login')//走哪
  next({path:'/detail/2',params:{},query:{}})//带点货
  
  // 守卫业务
  if(to.path=='/login' || to.path=='/reg' || to.path=='/register'){
    //判断是不是登录了
    //axios请求 携带 token 
    next()
  }else{
    next('/login');
  }
  
})

//后置
router.afterEach((to,from)=>{
  //全局后置守卫业务
})

路由独享守卫

// src/plugins/router.js
{
  path: '/user',
  component: User,
  beforeEnter: (to,from,next)=>{ //路由独享守卫 前置 
    console.log('路由独享守卫');
    if(Math.random()<.5){
      next()
    }else{
      next('/login')
    }
  }
 },

独享,没有后置

组件内部守卫

//组件内部钩子
//组建生命周期多出了三个钩子函数

beforeRouteEnter (to, from, next) {//前置
  // 不!能!获取组件实例 `this`
  // 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
  // 在当前路由改变,但是该组件被复用时调用
  // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
  // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
  // 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {//后置
  // 导航离开该组件的对应路由时调用
  // 可以访问组件实例 `this`
}

路由元信息

定义路由的时候配置 meta 字段

//src/plugins/router.js
{
  path: '/home',
  component: Home,
  meta: { requiresAuth: true }
}

访问 meta 字段

this.$route.meta
to.meta

滚动行为

SPA是单页面,只有一个滚动条,路由跳转时滚动条会影响到元素位置,使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样

对于所有路由导航,简单地让页面滚动到顶部

// src/plugins/router.js
const router = new VueRouter({
  scrollBehavior (to, from, savedPosition) {
    return { x: 0, y: 0 }
  }
})

数据获取

有时候,进入某个路由后,需要从服务器获取数据,解决的方案有导航完成之后获取,和导航完成之前获取

导航完成之后获取

组件会先渲染,哪怕空数据,之后数据回来会再次渲染,组件会渲染至少两次,数据没回来之前可以显示一个loading

导航完成之前获取

又叫数据预载,在前置守卫里面完成数据读取后再跳转到目标组件,数据未回来之前,画面不动,显示loading 或骨架屏,组件只渲染一次

beforeRouteEnter(to,from,next){
  //读取数据附加到目标路由上 to.query.数据名=值
  //读取数据 next( _this => _this.属性="拿到的数据")  √
}