Vue的特点和Web开发中常见的高级功能:
1、解耦视图和数据
2、双向数据绑定
3、可复用的组件
4、前端路由技术
5、状态管理
6、虚拟DOM(js对象)
注意:
学习Vue框架,必须严格遵循他的语法规则,Vue代码的书写思路和以前的JQuery完全不一样。所以,在项目中使用Vue之前,必须先学习Vue的基本语法规则。
方式一:直接CDN引入
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
<script src="https://cdn.jsdelivr.net/npm/vue">script>
方式二:下载和引入
// 开发环境 https://vuejs.org/js/vue.js
// 生产环境 https://vuejs.org/js/vue.min.js
方式三:NPM安装(今天先不掌握)
后续通过Vue-Cli4(脚手架)方式引入,我们使用该方式
先看js代码,会发现创建了一个Vue对象
创建Vue对象的时候,传入了一个对象:{}
2.1 {}中的el属性:该属性决定了这个Vue对象挂载到哪一个元素上,很明显,我们这里挂载到了id为app的元素上。
2.2 {}中包含了data属性:该属性中通常会存储一些数据,好像上面例子中的str1就是直接定义出来的数据
可以在里面填入正则表达式,变量,可以进行加减乘除等的运算或者字符串拼接
var vm = new Vue({
el: "要绑定的标签",
data: {
变量名1:值1,
变量名2:值2,
变量名3:值3,
...
},
methods: {
方法名1: function(){
},
方法名2: function(){
},
方法名3: function(){
}
}
});
{{ 变量key值 }}
百度
淘宝
可以使用简写 ‘:’
简写方法 “@”
如果使用函数,必须在创建的Vue对象里面,添加methods
methods:{
add:function(){
this.num += 5
},
add2(){
this.num += 10
}
可以是多个的类名
style样式(动态控制类名)
文字
可以使用对象
文字
可以使用数组
文字
:style="{'background-image': 'url('+userInfoFn.headImg变量+')'}"
三元表达式:
:class="['iconfont样式名',item.checked变量()? 'true走这边': 'false走这边']"
v-show是对样式层面的控制
例如
新闻
在页面中的显示是
style="display: none
v-if是对dom节点的控制
例如: v-if指令的作用: 控制标签是否展示,不展示则删除
常与 v-eles 一起用(中间可以插入别的,但是一般识别距离最近的 v-if ; v-eles)
bool1的值为true 第一个盒子被保留,删除第二个盒子,
为false的话,第2个盒子保留,删除第1个盒子
两者的具体应用
v-show 一般用于频繁切换操作,否则可以使用v-if
语法:v-for="(item, index) in 需要遍历的数组数组"
元素 索引
具体用法: <li v-for="i in list1">{{ i }}li>
还可以做数组的添加和删除
unshift > 在前面新增
push > 在后面新增
shift > 在前面删除
pop > 在后面删除
语法:v-for="(value, key) in 需要遍历的对象"
value值 key值
具体的用法:
{{i}} {{j}}
v-model的值是,vue接收表单元素的值的变量名, 即v1的值就是用户填写的内容
(甚至可以在data中设置v1的值,从而设置表单元素的默认值)
可以设置初始值,捕获改变后的值(最新的值)
可以做tab栏的切换
<div id="app">
<input type="text" v-model="txt1"> <!-- v-model表示了用户输入的这个数据-->
<p>{{ txt1 }}</p>
<select v-model="sel1">
<option value="0">北京</option>
<option value="1">上海</option>
<option value="2">广州</option>
</select>
</div>
<script>
var vm = new Vue({
el:"#app",
data:{
//通过改变txt1或者sel1,来使对应的表单元素的v-model的值发生了变化,所以这个表单元素的value就变化了(v-model看成我们之前将的value)
txt1: '默认值',
sel1: 0
}
})
</script>
v-html 往标签内部插入html文本
data:{
htmlText:"你好!世界!
"
}
显示:你好!世界!
v-text 往标签内部插入普通文本(解析不了标签)
显示:你好!世界!
v-pre 在界面上直接展示胡子语法
{{htmlText}}
显示:{{htmlText}}
v-cloak 隐藏数据渲染到页面之前,胡子语法在界面上的展示
<p v-cloak>{{htmlText}}p>
搭配 style 隐藏 使用
<style>
[v-cloak]{
display: none;
}
style>
如果不使用隐藏,在加载完成之前,显示:{{htmlText}} 加载完成之后,显示:<p>你好!世界!p>
与加载的网络有关
var arr = [1, 2, 3]
// 往数组最后一位添加一个数字
arr.push(4) // [1, 2, 3, 4]
// 删除数组最后一个数字
arr.pop() // [1, 2, 3]
console.log(arr)
// 往数组第一位添加一个数字
arr.unshift(0)
console.log(arr)
// 删除数组第一个元素
arr.shift()
console.log(arr)
// splice
// 删除第一个元素
arr.splice(1, 2)
console.log(arr)
arr.splice(1, 2, 2, 4, 5)
console.log(arr)
// 合并数组
console.log([1, 6].concat([5, 7]))
push(返回数组长度)、unshift(返回数组长度)、shift(返回删除的值)、pop(返回删除的值)、splice、concat(返回新数组)
过滤
let arr = [1, 2, 3, 4, 5, 6]
// filter() 过滤 ,如果是true 则保留,如果是false 则过滤
// 不会修改原数组
let new1 = arr.filter((item, index) => {
return item > 3
})
console.log(new1, arr);// [4, 5, 6] [1, 2, 3, 4, 5, 6]
// map() 修改数组里面的每一个元素,并且新的数组和旧的数组长度是一样的
// 不会改变原数组
let new2 = arr.map((item, index) => {
return item + 2
})
console.log(new2);//[3, 4, 5, 6, 7, 8]
let new3 = arr.map((item, index) => {
return {id:item+2}
})
console.log(new3);// [{…}, {…}, {…}, {…}, {…}, {…}]
利用reduce方法遍历数组的每一个元素,reduce()调用结果最后返回一个最终值(最后一次return值)。
//数组名.reduce(回调函数,pre的初始值)
arr.reduce(function(pre, current){
// reduce这个方法被调用时,会遍历arr这个数组的每一个元素,每遍历一个元素,就执行一次这里的代码
// current表示当前正在遍历的这个元素
// pre 是上一次的这个函数return的值
// !!!因为第一次遍历没有上一个return值,所以,交给了第二个参数,设置pre的初始值
console.log(pre, current)
return 10
},0)
//!!!并且reduce方法最终会返回最后一次的return值
每一次,都返回pre
// reduce: 遍历数组,不会改变原数组
/*
特点:
1、reduce 第二个参数如果没有,name第一次的prev取数组的第一个元素,回调函数是从数组的第二个元素开始
如果有第二个参数,name第一次的prev的值用的就是传入reduce的第二个参数,回调函数从数组的第一个元素开始遍历
2、reduce里面的prev的值,第一次是由reduce第二个参数决定的,其他的情况下是上一次回调函数的返回值所决定的。
reduce 返回值就是最后一次回调函数的返回值。
*/
用处:可以做计算,汇总……
语法:
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
(total 必填;初始值或者计算结束后的返回值(后面没有初始值,默认初始值为数组的第一个数 prev的第一次等于数组的第一个数)
(currentValue 必填 当前元素)
(currentIndex 可填 当前元素的索引)
(arr 可选 当前元素所属的数组对象)
(initialValue 可选。传递给函数的初始值 不填,默认是数组的第一个数,然后从第二数开始遍历)
其余prev 都是undefined 因为没有返回 return
var arr2 = [1, 2, 3, 1, 6, 2, 3]
//ES6
consoloe.log([...new Set(arr2)])
console.log(Array.from(new Set(arr2)))
var newArray = [];
for(var i=0; i
computed:{
//computed里面的方法必须有返回值!这个return值将来在视图中被{{total}}引用
***函数 无法传入参数
total(){
var a = this.arr.reduce(function(pre, current){
console.log(pre, current)
// var total = 当前这次的 price*count + 上一次的total
var total = current.price*current.count + pre
return total
},0)
return a
}
}
函数调用了三遍,却只执行了1遍,这是computed内部方法的缓存起了作用
....
computed:{
//computed里面的方法必须有返回值!这个return值将来在视图中被{{total}}引用
total:{
get(){
console.log("total_get")
var a = this.arr.reduce(function(pre, current){
// var total = 当前这次的 price*count + 上一次的total
var total = current.price*current.count + pre
return total
},0)
return a
},
set(){
console.log("total_set")
},
}
}
...
vm.total = 3 //触发调用set方法
v-model
本质上包含了两个操作:
v-bind
绑定input元素的value属性v-on
指令绑定input元素的input事件<input type="text" v-model="textVal"/>
<input type="text" v-bind:value="textVal" v-on:input="textVal = $event.target.value"/>
语法:
// 添加数据;setItem的value值是字符串类型的数据
先对数组或对象进行字符串转换------JSON.stringify(数组或对象);
localStorage.setItem('name','张三');
// 获取数据
取值如果是对象,数组字符串的,那么要进行转换,JSON.parse
例子:
let time = localStorage.getItem('time');
console.log(JSON.parse(time));
localStorage.getItem('name'); // 张三
// 删除(time)某一项数据的值
localStorage.removeItem('time')
// 清空
localStorage.clear();
注意事项:
5M = 1024 * 5kb
语法:
// 添加数据;setItem的value值是字符串类型的数据
存储的值必须是字符串(先对数组,对象进行字符串转换,JSON.stringify)
JSON.stringify(数组或对象);
sessionStorage.setItem('name','张三');
// 获取数据
取值如果是对象,数组字符串的,那么要进行转换,JSON.parse
例子:
let time = localStorage.getItem('time');
console.log(JSON.parse(time));
sessionStorage.getItem('name'); // 张三
// 删除(time)某一项数据的值
sessionStorage.removeItem('time')
// 清空
sessionStorage.clear();
注意事项:
网站中,http请求是无状态的。也就是第一次登陆成功(发送请求),第二次请求服务器依然不知道是哪一个用户。这时候的cookie就是解决这个问题的,第一次登陆后服务器返回数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求,浏览器自动会把上次请求存储的cookie数据自动带上给服务器,服务器根据客户端的cookie来判断当前是哪一个用户。cookie存储有大小限制,不同浏览器不一样,一般是4kb,所以cookie只能存储小量数据。
token: 用户登录凭证,服务端返回给浏览器(前后端分离项目,基本都是发送ajax请求)
session和cookie的作用有点类似,也是存储用户相关信息。不同的是cookie存储在浏览器,而session存储在服务器。
语法:
过滤器 filters: {
函数 toFix2(price参数变量名) {
return price.toFixed(2)(toFixed 把数字转换为字符串,结果的小数点后有指定位数的数字)
(用法:toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。)
},
}
vue双向数据绑定原理:
借助Object.defineProperty()对数据进行劫持,并结合发布-订阅者模式,来实现双向数据绑定
vue的双向数据绑定原理是什么?
vue数据双向绑定是通过数据劫持结合“发布者-订阅者模式”的方式来实现的。
vue是通过Object.defineProperty()来实现数据劫持,其中会有getter()和setter方法;当读取属性值时,就会触发getter()方法,在view中如果数据发生了变化,就会通过Object.defineProperty()对属性设置一个setter函数,当数据改变了就会来触发这个函数;
语法:
Object.defineProperty(obj, prop, desc)
1. `obj` 需要定义属性的当前对象
2. `prop` 当前需要定义的属性名
3. `desc` 属性描述符
数据属性:
通过Object.defineProperty()为对象定义属性,有两种形式,分别为数据描述符,存取描述符,下面分别描述两者的区别:
value
表示它的默认值writable
如果为true标识可以被修改,如果为false标识不能被修改(默认值为false)configurable
描述属性是否配置,以及可否删除,可以认为是总开关 默认值 false(不可删除)enumerable
描述属性是否出现在for in 或者 Object.keys()的遍历中 默认值false(不能遍历)
例子:
var p = {};
// defineProperty(对象, 属性名, {value: 值}) === p.name = '张三'
Object.defineProperty(p, 'name', {
value: '张三',
writable: true, // writable: 设置属性(name)是否可以修改,默认值: false(不可修改),true(可以修改)
configurable: true, // configurable: 控制属性是否可以删除,默认值:false(不可以删除), true(可以删除)
enumerable: true, // enumerable: 控制属性是否可以被遍历,默认值:false(不可以遍历), true(可以遍历)
})
console.log(p.name);
p.name = '李四'//不可以直接修改,要先设置属性
console.log(p.name);
// delete p.name;//不可以直接删除,要先设置属性
// console.log(p);
for (let key in p) {
console.log(key)//不可以直接遍历,要先设置属性
}
另一种方法:
let num = 0;
// 定义p对象上age属性; get和set方法分别对age属性的获取和赋值进行拦截,get方法的返回值就是age属性对应的值
Object.defineProperty(p, 'age', {
// value: 20,
get() {//监听读取操作
获取数据的时候 本身是没有值的 值是有另外一个函数 return 出来的
get就是在读取name属性这个值触发的函数
console.log('age上的get方法')
// document.getElementById()
return num;
},
set(val) {//监听写入操作
改变数据的时候 进入的set 里面
set就是在设置name属性这个值触发的函数
num += (val + 100)
console.log('age上的set方法', val)
}
})
p.age = 30;
console.log(p.age);
除了一些内置的制定(v-model和v-show…),Vue也允许注册自定义指令。
全局自定义指令格式:
// 注册一个全局自定义指令 v-demo
Vue.directive('demo(指定名)', {
inserted: function (el, binding) {
console.log(el, binding);
},
update(el, binding){}
})
局部指令
// 组件中注册局部指令
new Vue({
el: '#app',
data: {},
directives: {
demo(指令名): {
bind(el, binding){
},
inserted: function (el, binding) {
cosnole.log(el, binding);
},
update(el, binding){
},
}
}
})
钩子函数:
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。参数:
el
:指令所绑定的元素,可以用来直接操作 DOM 。binding
:一个对象,包含以下属性:
name
:指令名,不包括 v-
前缀。value
:指令的绑定值,例如:v-demo="1 + 1"
中,绑定值为 2
。oldValue
:指令绑定的前一个值expression
:字符串形式的指令表达式。例如 v--demo="1 + 1"
中,表达式为 "1 + 1"
。modifiers
:一个包含修饰符的对象。例如:v-demo.foo.bar
中,修饰符对象为 { foo: true, bar: true }
。组件化开发的优势:可维护性高 可复用性高
组件化思想的应用开发:
通过 Vue.component
方式注册的组件,称之为全局组件。任何地方都可以使用。全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
头部 num: {{num}}
123
new Vue({
el: "#app",
components: {
// key: 组件名称, value: 组件内容
aheader: {//任意命名
//template标签------实际上 元素是被当做一个不可见的包裹元素,主要用于分组的条件判断和列表渲染。
// 只能用这个标签包裹
template : '#tmp1',
// 组件内的data必须是一个函数
data() {
return {
num: 1
}
}
}
}
})
通过Vue CLI 进行Vue全局组件
1、下载安装包
npm install -g @vue/cli
# OR
yarn global add @vue/cli
2、检查版本,并确认是否安装成功
vue --version
3、如需升级全局的 Vue CLI 包,请运行:
npm update -g @vue/cli
# 或者
yarn global upgrade --latest @vue/cli
4、创建一个项目
vue create 自定义文件名(英文)
例如
vue create hello-world
5、下载所需要的包工具
会自动识别文件package-lock 里面所需要的工具包
npm i
6、开发模式运行
npm run serve
生产模式运行
npm run build
7、删除项目
del 文件名
8、转换文件夹名
cd 文件路径
例如:
cd ./文件名
9、清空
cls 清空
组件和组件之间存在层级关系,而其中一种最重要的关系就是父子组件关系。
data属性必须是一个函数,而且函数返回一个对象 ,对象保存着数据
data() {//函数
return {//返回一个对象
title: 'zujianbiaoti'
}
}
为什么data在组件中必须是一个函数呢?
原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。
在组件中,使用选项props来声明需要从父级接收到的数据。
props的值有两种方式:
- 字符串数组,数组中的字符串就是传递时的名称。
- 对象,对象可以设置传递时的类型(String,Number,Boolean,Array, Object,Date,Function,Symbol),也可以设置默认值等。
父级
<名 :任意名 = 需要传入子级的数据对象名>名>
子级
props: {
需要传入子级的数据对象名(wrapperDate): {
type: Object,
default: {},//默认值
required:false/true //必填,一定要传值
}
三种写法,另外另种
一、
props: {
num: Number,
}
二、
props :['num']
然后子级就可以直接调用
{{wrapperDate.cancelTxt}}
子组件向父组件传递数据,通过自定义事件
子级
<div @click="函数名(submitFn)(参数)">div>
methods: {
cancelFn(参数) {
this.$emit("canceler自定义事件名" 参数);
},
},
父级
Wrapper>
methods:{
fn(){
this.clickResult = this.textObj.cancelTxt
},
},
例如祖父和孙子之间或者更多层
使用:Vue提供的更高阶方法:provide/inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
provide:Object | () => Object
inject:Array<string> | { [key: string]: string | Symbol | Object }
provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持 Symbol 和 Reflect.ownKeys 的环境下可工作。
inject 选项应该是:
一个字符串数组,或
一个对象,对象的 key 是本地的绑定名,value 是:
在可用的注入内容中搜索用的 key (字符串或 Symbol),或
一个对象,该对象的:
from property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)
default property 是降级情况下使用的 value
提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。
具体用法:
// 父级组件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子组件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
EventBus中央事件总线
1、新建一个bus.js的文件,在文件中写入
import Vue from 'vue'
export default new Vue
2、在需要进行操作的组件中,引入bus.js
import bus from "../../bus/bus"
3、传参的组件
事件
showFn() {
//用bus.$emit(第一个参数代表事件名称,第二个参数代表需要传过去的参数,第二个参数代表需要传过去的第二个参数……可以传入多个参数)
bus.$emit("showninfo", this.showin);
},
4、接收参数的组件
通过$on监听事件。
//挂载
mounted(){
bus.$on('showninfo',val=>{
console.log(val);
this.showuin = val
})
this.bus. e m i t 和 t h i s . b u s . emit 和this.bus. emit和this.bus.on来进行跨组件通信了
Vuex
提供的功能变更vuex中的数据或者状态
this.$store.commit('increment')
第一个是方法名,第二个是参数
拿到vuex中的数据
访问数据用:$store
methods: {
increment() {
this.$store.commit('increment')
console.log(this.$store.state.count)
}
}
通常放到计算属性中
computed: {
count () {
return this.$store.state.count
}
}
}
v-model 的本质就是绑定一个属性和事件
// 父组件
<aa class="abc" v-model="test" ></aa>
// aa子组件实现一:
<template>
<div>
<ul>
<li>{{'里面的值:'+ msg}}</li>
<button @click="fn2">里面改变外面</button>
</ul>
</div>
</template>
<script>
export default {
model: { // 使用model
prop: 'msg', //prop属性将msg作为该组件被使用时(此处为aa组件被父组件调用)v-model能取到的值,
event: 'cc' // event中的cc就是父组件上的自定义事件,用来更新父组件上的test值
},
props: {
msg: ''
},
methods: {
fn2 () {
this.$emit('cc', this.msg+2)
}
}
}
</script>
// aa子组件实现方法二:
<template>
<div>
<ul>
<li>{{'里面的值:'+ value}}</li> // value会被赋值为v-model绑定的test的值。
<button @click="fn2">里面改变外面</button>
</ul>
</div>
</template>
<script>
export default {
props: {
value: { // 必须要使用value
default: '',
},
},
methods: {
fn2 () {
this.$emit('input', this.value+2) // 这儿必须用input 发送数据,发送的数据会被父级v-model=“test”接受到,再被value=test传回来。
}
}
}
为什么要使用插槽?
slot翻译为插槽,组件的插槽:
如何封装合适呢?抽取共性,保留不同
如何使用slot?
就可以为子组件开启一个插槽。使用
我是插槽中的默认内容!!
被替换的内容
父组件
删除
子组件
当组件渲染的时候, 将会被替换为“a标签”(标签里面的内容)。插槽内可以包含任何模板代码,包括 HTML:
当子组件的功能复杂时,子组件的插槽可能并非是一个。
如何使用具名插槽呢?
父组件
<Todolist>
//用标签将内容包起来,并在标签上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称,向具名插槽提供内容。
<template v-slot:btn>
<a href="javascript:;" class="del" @click="reMove(idx)">删除</a>
</template>
</Todolist>
子组件
<slot name="btn"></slot>
当组件渲染的时候,name相等的 <slot></slot> 将会被替换为“a标签”(<Todolist>标签里面的内容)。插槽内可以包含任何模板代码,包括 HTML:
任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容。
注意 v-slot 只能添加在 <template> 上(有一种情况例外)
默认情况下,父组件使用子组件,插槽数据默认是拿父组件的数据,而不是子组件拿数据。
作用域插槽在父组件使用我们的子组件时, 插槽的数据从子组件中拿到数据,而不是从父组件拿到。
作用域插槽,作用:可以拿到子组件slot标签上自定义属性的集合
父组件
<Todolist>
<template v-slot:btn={idx}>
<a href="javascript:;" class="del" @click="reMove(idx)">删除</a>
</template>
</Todolist>
子组件
<template v-slot:default="{$index}">
<slot name="btn" :idx="$index"></slot>
</template>
{idx}是Es6的解构语法
$index 自定义的命名
default意思是默认,下一个子组件不需要给name属性,直接可以渲染替换<slot></slot>里面的内容
孙子组件
<slot :$index="i"></slot>
当组件渲染的时候,<slot></slot> 将会被替换为“a标签”(<Todolist>标签里面的内容)。插槽内可以包含任何模板代码,包括 HTML:
// 1、基本写法
<one-comp>
<button slot="btn" slot-scope="scope">按钮{{scope.msg}}</button>
</one-comp>
// 2、基本写法之模板写法
<one-comp>
<template slot="btn" slot-scope="scope">
<button>按钮{{scope.msg}}</button>
</template>
</one-comp>
// 3、指令写法
<one-comp v-slot:btn="scope">
<button>按钮{{scope.msg}}</button>
</one-comp>
// 4、指令写法之模板写法
<one-comp>
<template v-slot:btn="scope">
<button>按钮{{scope.msg}}</button>
</template>
</one-comp>
FileZilla是一个免费开源的FTP软件,分为客户端版本和服务器版本
Webpack(自动化 模块化 前端开发构建工具)
Gulp侧重于前端开发的 整个过程 的控制管理(像是流水线),我们可以通过给gulp配置不同的task(通过Gulp中的gulp.task()方法配置,比如启动server、sass/less预编译、文件的合并压缩等等)来让gulp实现不同的功能,从而构建整个前端开发流程。
// 执行后生成package.json文件
npm init -y
nvm install 12.10.0
nvm use 12.10.0
npm i [email protected] [email protected] -g
// 最后的参数-D是安装到package.json的开发依赖devDependencies(开发环境)对象里,也可以用 --save-dev代替
npm install webpack@4.44.1 webpack-cli@3.3.12 -D
// 全局安装webpack和webpack-cli
npm i webpack@4.44.1 webpack-cli@3.3.12 -g
// -S是--save的简写,这样安装的话,会安装到dependencies(生产环境)对象里,也可以用 --save代替
npm install jquery -S
// package.json
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.40.2",
"webpack-cli": "^3.3.9"
},
"dependencies": {
"jquery": "^3.4.1"
}
}
devDependencies与dependencies的区别:
在发布npm包的时候,本身dependencies下的模块会作为依赖,一起被下载;devDependencies下面的模块就不会自动下载了;但对于项目而言,npm install 会自动下载devDependencies和dependencies下面的模块。
`index.html`
引入js文件:<script src="./index.js"></script>
js文件
import $ from 'jquery'
$('ul li:even').css({background: 'red'})
$('ul li:odd').css({background: 'green'})
浏览器打开会出现报错,因为浏览器并不兼容import引入模块这种方式,所以我们要用到webpack打包
// 执行命令 output输出
webpack index.js -o dist/bundle.js
出现 “无法将webpack 识别为……的报错
出现这个报错,这是因为命令行执行时候会找全局的webpack,但是我们并没有安装全局的webpack,所以我们可以安装全局webpack,或者是使用脚本方式启动
执行package.json文件中添加的start命令
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack index.js -o dist/bundle.js"
},
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.40.2",
"webpack-cli": "^3.3.9"
},
"dependencies": {
"jquery": "^3.4.1"
}
}
跑一下
// 生成 dist文件夹和bundle.js文件
npm run start
然后再把index.html原来引入index.js的地方改成是通过webpack生成的bundle.js
<script src="./dist/bundle.js">script>
在根目录里,建立webpack.config.js:
const path = require('path');
module.exports = {
entry: path.join(__dirname, './index.js'), // dirname代表索引到文件所在目录
output: {
path: path.join(__dirname, './dist'),
filename: 'bundle.js'
}
}
package.json:
文件修改命令
"scripts": {
"start": "webpack --config webpack.config.js"
}
这时候如果修改index.html的背景颜色red改成是gray,会发现浏览器刷新也没有效果,需要再跑一次
npm run start
命令才有用,这时候就需要webpack-dev-server(热重载)
安装:
npm install webpack-dev-server@3.11.2 -D
package.json:
"scripts": {
"start": "webpack-dev-server --config webpack.config.js --open --port 3002 --hot"
}
// --open 自动打开浏览器
// --port 服务监听的端口 3002
// --hot 自动更新
安装:npm install [email protected] -D
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: path.join(__dirname, './index.js'),
output: {
path: path.join(__dirname, './dist'),
filename: 'bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, './index.html'),
filename: 'index.html'
})
]
}
删掉index.html文件里面的bundle.js引用,因为html-webpack-plugin会自动把打包出来的bundle自动加到我们的index.html代码里
因为webpack默认是不识别.css
文件的,需要我们通过 loader
将 .css
文件进行解释成正确的模块。
npm install css-loader@5.2.4 style-loader@2.0.0 -D
//index.css -> bundle.js -> style-loader ->
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on
在监听键盘事件时添加按键修饰符:
表单上常用的 trim
和 number
:
想去除用户输入的前后空格:
希望在input的change之后再更新输入框的值
.trim与.lazy可以合并使用:
二者没有先后顺序
{{ipt2Val}}
watch的作用可以监控一个值的变换,并调用因为变化需要执行的方法。可以通过watch动态改变关联的状态。
简单点说,就是实时监听某个数据的变化。
{{msg}}
watch: {
msg(val, oldVal){
console.log(val, oldVal)
}
}
跟data 同级
如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate属性。
跟data 同级
watch: {
监听变化的数据名
num: {
handler(val, oldVal) {
console.log(val, oldVal);
},
// 组件注入页面时就立即监听
immediate: true
}
}
immediate需要搭配handler一起使用,其在最初绑定时,调用的函数也就是这个handler函数。
当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听。
{{obj.age}}
注意:
1、如果监听的数据是一个对象,那么 immediate: true
失效;
2、一般使用于对引用类型的监听,深度监听,如监听一个Object,只要Object里面的任何一个字段发生变化都会被监听,但是比较消耗性能,根据需求使用,能不用则不用。
3、因为上面代码obj是引用数据类型,val, oldVal指向一致,导致看到的结果一样。
我们可以通过点语法获取对象中的属性,然后转为字符串,即是对深度监听的优化
{{obj.age}}
computed
watch
总结:
mixins就是定义一部分公共的方法或者计算属性,然后混入到各个组件中使用,方便管理与统一修改。同一个生命周期,混入对象会比组件的先执行。
在src下创建 mixins/index.js
,写入:
export const MixinsFn = {
created() {
console.log("这是mixins触发的created")
}
}
This is an about page
我们会发现,mixins中的created
比 about中的created
优先执行。
vue中获取页面里的某个元素(标签或组件),可以给它绑定ref属性,有点类似于给它添加id名。
//ref = '属性名'
//获取 $refs.属性名======获取到标签
{{msg}}
子组件:
{{num}}
父组件:
methods:{
fn() {
console.log(this.$refs.btn); //可以获取Dom节点----
this.$refs.btn.innerText 可以获取Dom节点的内容-----获取child组件的num
},
}
什么是生命周期: 从Vue创建、运行、到销毁期间,总是伴随着各种各样的事件,这些事件,统称为生命周期。
**生命周期钩子函数:**就是生命周期事件的别名;
**beforeCreate:**实例刚刚在内存中被创建出来,此时还没有初始化 data
和 methods
属性。
**created:**实例已经在内存中创建好,此时 data
和methods
已经创建好,此时还没有开始编译模板
和 methods、data 是同级
初始
beforeCreate() {
console.log("1.1---------------beforeCreate");
console.log(1.1, this.num, this.fn, document.getElementById("op"));
},
created() {
console.log("1.2-------------created");
console.log(1.2, this.num, this.fn, document.getElementById("op"));
},
挂载
beforeMount() {
console.log("2.1---------------beforeMount");
console.log(2.1, this.num, this.fn, document.getElementById("op"));
},
mounted() {
console.log("2.2-------------mounted");
console.log(2.2, this.num, this.fn, document.getElementById("op"));
},
更新
beforeUpdate(){ //视图跟新之前
console.log("3.1-------------beforeUpdate");
console.log(3.1, this.num, this.$refs.myp.innerHTML);
},
updated(){ //视图跟新之后
console.log("3.2-------------updated");
console.log(3.2, this.num, this.$refs.myp.innerHTML);
},
销毁
beforeDestory(){
},
destoryed(){
}
keep-alive包含的组件是不需要再重新创建(Create)
**beforeMount:**此时已经完成了模板编译,但是还没有挂载到页面中;
**mounted:**这个时候已经把编译好的模板挂载到页面指定的容器里;
beforeMount:
data
的数据可以访问和修改,而且此时的模板已经编译好了,还没有更新到页面中
mounted:
此时编译的模板更新到页面中了
**beforeUpdate:**状态更新之前执行此函数,此时的 data
中的数据是最新,但是界面上显示的还是旧的,因为此时还没有开始重新渲染DOM节点;
**updated:**实例更新完毕之后调用此函数,此时data
中的状态值和界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了;
beforeUpdate: 此时修改输入框的内容,
data
中的数据已经更新了,但是页面上显示的还是旧数据;**updated:**此时
data
的内容已经更新,页面显示的也是最新的数据。
**beforeDestroy:**实例销毁之前调用,在这一步,实例让然完全可用。
**destroyed:**实例销毁后调用,调用后,Vue实例指向的所以东西会被解绑,所有的事件监听器会被移除,所有的子实力也会被销毁。
销毁声明周期(使用keep-alive组件是不会触发销毁生命周期)
要搭配keep-alive组件来使用
// 从不活跃进入活跃状态
// activated() {
// console.log('activated');
// },
// // 从活跃进入不活跃
// deactivated() {
// console.log('deactivated')
// },
简单来说路由 就是用来跟后端服务器进行交互的一种方式,通过不同的路径,来请求不 同的资源,请求不同的页面是路由的其中一种功能。
概念:根据不同的用户的不同URL请求,发送到服务器以后返回不同的内容。
本质:是URL请求地址与服务器资源之间的对应关系。
概念:根据不同的用户事件,显示不同的页面内容
本质:用户事件与事件处理函数之间的对应关系
前后端分离阶段:
单页面应用阶段:
前端路由的核心是什么呢?
在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取; 特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。 hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com (opens new window),因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。 history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”
//写在导出模块里面 export default
export default new VueRouter({
routes,
mode: 'hash'
//还可以是 'history'
//两者区别: hash 后面跟着# 之后才是path值
//http://192.168.6.170:8080/#/me http://192.168.6.170:8080/#/cart
//history /后面才是path值
//http://192.168.6.170:8080/cate http://192.168.6.170:8080/home
})
目前前端流行的三大框架,都有自己的路由实现:
vue-router是Vue的官方路由插件,它和Vue是深度集成的,适合用于构建单页面应用 https://router.vuejs.org/zh/ 。
vue-router是基于路由和组件的,路由用于设定访问路径, 将路径和组件映射起来;在vue-router的单页面应用中, 页面的路径的改变就是组件的切换.
安装:
npm install vue-router
一般项目中建议在cli创建项目时就直接选择需要路由,并搭配history模式。
选择router 模式
在index.js文件中,明确安装模块功能(并实例化)
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
对index.js文件进行路由设置
//导入模块
import VueRouter from 'vue-router'
import Vue from 'vue'
// 导入路由对象
Vue.use(VueRouter)
// 定义路由规则
const routes = [
{
path: '/',
component: () => import('../views/index.vue') ,
children:[
//定义方法一:
{
path: '/', // 浏览器访问
//name的作用用于之后传参获取定义的名(此名是唯一)
name: 'Home',
component: Home
children:[
{
path: 'cate',
component: () => import('../views/cate.vue') // 路由懒加载
}
]
},
//定义方法二:
{
path: 'cate',
component: () => import('../views/cate.vue') // 路由懒加载
},
{
path: '/',
redirect: '/cart' // 这就是路由的重定向,重新定义跳转路径
},
{
path: 'cart', // 改成这个之后,原来的/就没有对用的组件了
component: () => import('../views/cart.vue')
},
{
path: 'me',
component: () => import('../views/Mehome.vue')
},
{
path: '*', // 匹配所有剩余的路由,只要不是上面提及的页面,全部跳转到404页面
component: () => import('@/views/Page404.vue')
}
]
},
]
// 创建路由实例 并导出
export default new VueRouter({
// 路由模式
mode: 'hash', // history hash
// 路由规则(路由映射关系)
routes,
})
懒加载的方式
// 方式一: 结合Vue的异步组件和Webpack的代码分析
const User = resolve => { require.ensure(['@/views/User.vue'], () => { resolve(require('@/views/User.vue')) }) };
// 方式二: AMD写法
const User = resolve => require(['@/views/User.vue'], resolve);
// 方式三: 在ES6中, 我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割.
const Home = () => import(/* webpackChunkName: "user" */ '../views/User.vue')
使用 router-link
标签来实现跳转
//然后通过 `router-view` 来显示页面。`router-link` 最终会被渲染为a标签。
///跳转之后,内容将替换
router-link
默认会被解析为a标签,如果想让它转换成其他的标签,就需要添加tag属性:
User
此时,router-link
就被解析为li标签。
点击跳转到指定的页面
设置点击事件 方法
jumpToAbout() {
push 在末尾添加
this.$router.push({
push里面写的是对象
// path: '/home'
name: 'Home'
name是在定义路由规则里面设置的,等同于 path的作用
})
简写:
// 简写
this.$router.push('/user')
除了push,还有
go、 整数是前进,负数是后退
forward、 前进
back、 后退
replace、替换----replace(新的访问 例如 “/name”)
这几个来触发不同情况的跳转。
router.push(location, onComplete?, onAbort?)
router.push(location).then(onComplete).catch(onAbort)
router.replace(location, onComplete?, onAbort?)
router.replace(location).then(onComplete).catch(onAbort)
router.go(n)
router.back()
router.forward()
jumpFn(){
this.$router.push({
// path:""
name:'About',
携带的参数的方法名
query:{
id:123456,
name:"张三"
}
})
},
在网页上显示:http://192.168.6.170:8080/cate?id=123456&name=张三
在子组件中可以拿到 - 通过生命周期初始函数得到
然后可以放入data 之后可以直接调用
export default {
data(){
return {
id:null,
name:null
}
},
生命周期初始函数
created(){
console.log(this.$route.query);
this.id = this.$route.query.id
this.name = this.$route.query.name
}
}
使用params传参,得到的结果与使用query传参得到的结果有以下区别:
this.$router.push({name: "User", params: {userId: 123}}) // http://localhost:8081/user/123
this.$router.push({name: "User", query: {userId: 123}}) // http://localhost:8081/?userId=123
在路由的js文件中:
'/editbrand': {
path: '/editbrand/:id', //品牌管理编辑
name: 'editbrand',
component: () =>
import(/* webpackChunkName: "editbrand" */ '../views/editbrand/Editbrand.vue'),
},
组件中:
this.$router.push({
name:'editbrand',
params:{id:row.id}
})
编程式导航中,使用name进行路径跳转,携带参数可以通过params和query,其中query会将参数携带在导航路径上,而使用path进行路径跳转,无法携带params,只能携带query。
params参数传参写法相当于在路径直接添加:
//App.vue中:
this.$router.push('/user/12');
// router/index.js中:
path: '/user/:userId',
// User.vue中:
created(){
console.log(this.$route.params.userId); // 获取到用户id12
}
const routes = [
{
path: '/',
redirect: '/home' // 这就是路由的重定向,重新定义跳转路径
},
{
path: '/home', // 改成这个之后,原来的/就没有对用的组件了
component: () => import('@/views/Home.vue')
},
... ...
{
path: '*', // 匹配所有剩余的路由,只要不是上面提及的页面,全部跳转到404页面
component: () => import('@/views/Page404.vue')
}
]
1、全局守卫: router.beforeEach
2、全局解析守卫: router.beforeResolve
3、全局后置钩子: router.afterEach
4、路由独享的守卫: beforeEnter
5、组件内的守卫: beforeRouteEnter、beforeRouteUpdate (2.2 新增)、beforeRouteLeave
router.beforeEach
全局前置守卫
导航或者路由被触发时,全局前置守卫被调用,解析执行完成之后,导航再跳转
守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
注册:全局前置守卫
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
接收三个参数
to: (去)
Route: 即将要进入的目标 路由对象
from: (来)
Route: 当前导航正要离开的路由
next
: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
一般会使用 next(): 进行管道中的下一个钩子。
next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重 置到 from 路由对应的地址。
next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给
router.onError() 注册过的回调。
router.beforeResolve
router.beforeResolve注册方法和全局前置守卫一样
这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
router.afterEach
这些钩子不会接受 next
函数也不会改变导航本身
注册:
router.afterEach((to, from) => {
// ...
})
beforeEnter
可以在路由配置上直接定义 beforeEnter 守卫:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
守卫与全局前置守卫的方法参数是一样的。
beforeRouteEnter、beforeRouteUpdate (2.2 新增)、beforeRouteLeave
可以在路由组件内直接定义以下路由导航守卫:
beforeRouteEnter
beforeRouteUpdate (2.2 新增)
beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
//可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
//beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了。
},
例如:
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
//这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。
beforeRouteLeave (to, from, next) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
beforeRouteLeave
守卫。beforeEach
守卫。beforeRouteUpdate
守卫 (2.2+)。beforeEnter
。beforeRouteEnter
。beforeResolve
守卫 (2.5+)。afterEach
钩子。beforeRouteEnter
守卫中传给 next
的回调函数,创建好的组件实例会作为回调函数的参数传入。$refs
一个对象,持有注册过 ref attribute 的所有 DOM 元素和组件实例。
dom还没有渲染完成,是不能通过ref调用dom的。
加在普通元素上,获取的是dom元素
<div ref="system">测试</div>
// 获取
mounted() {
console.log(this.$refs.system);
}
加在子组件上,获取的是组件实例,可以使用组件的所有方法
// this.$ref.xxx.方法名()
// 父组件
<contact-info ref="contactInfo"/>
import ContactInfo from './ContactInfo'
components: { ContactInfo },
mounted() {
this.$refs.contactInfo.initVal(data) // 调用子组件方法
}
// 子组件
methods: {
initVal(data){
Object.keys(this.contactInfo).forEach(val=>{
this.contactInfo[val] = data[val]
})
}
}
具体使用:
组件内(元素内)设置
<div ref="自定义名称">
</div>
this.$refs 拿到
如果是组件实例,可以使用组件的所有方法
this.$refs.方法名
$el
Vue 实例使用的根 DOM 元素。
$el读取的是组件实例挂载的dom元素
// 子组件
<template>
<div>
测试
</div>
</template>
<script>
export default {
name: 'TestComs'
};
</script>
// 父组件
<test ref="testCom" />
<div ref="test">11</div>
mounted() {
console.log(this.$refs.testCom, '组件ref'); // 获取组件实例
console.log(this.$refs.testCom.$el, '组件el'); // 获取组件实例的dom元素
//获得 测试
console.log(this.$refs.test, '元素ref'); // 获取dom元素
//获得11
console.log(this.$refs.test.$el, '元素el'); // $el用于vue组件,普通dom元素不能用
//获得undefined
},
Vue中data中变量的数据值发生改变,界面没有跟着更新,是什么原因(Vue数据双向绑定失效)
1.如果data里面定义了对象,对象里面的键值没有,getter/setter函数没法监听到属性值的数据变化,会导致此现象的发生。
解决方法:
Vue.set(obj, key, value);
// or
this.$set(obj, key, value);
使用方法:
接收三个参数
this.$set(原数组或者原对象,索引值或者键值对名,需要赋的值)
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
说得直白点,vuex就是vue.js中管理数据状态的一个库,通过创建一个集中的数据存储,供程序中所有组件访问。
一个数据只要放在了vuex中,当前项目所有的组件都可以直接访问这个数据。
npm install vuex --save
在store文件夹中的index.js中写入
//引入模块
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//导出模块
export default new Vuex.Store({
state: {
num: 0,
uerinfo:{}
},
mutations: {
increment (state,user) {
state.uerinfo = user
}
}
})
在miain.js文件中引入store
import store from './store'
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
(个人理解)存放数据的容器
vuex中的state类似于data,用于存放数据,只不过这个数据是所有组件公用的。
state: {
数据,对象key : value值的方式
num: 0,
uerinfo:{}
},
组件中
<template>
<div>
<h3>{{$store.state.num}}</h3>
</div>
</template>
也可以使用computed
computed: {
num(){
return this.$store.state.num
}
}
// 标签中
<h3>{{num}}</h3>
vuex中的getters类似于computed计算属性,getters的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
封装异步操作
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
num: 2
},
getters: {
// 这里的参数state可以让我们快速获取到仓库中的数据
doubleNum(state) {
return state.num * 2;
}
}
})
组件中
<template>
<div>
<h3>{{num}}</h3>
</div>
</template>
<script>
export default {
computed: {
num(){
return this.$store.getters.doubleNum
}
}
};
</script>
个人理解:存放方法,然后此方法被别的组件调用
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
num: 2
},
mutations: {
// payload专业名称为“载荷”,其实就是个参数
addNum(state, payload) {
state.num += payload;
}
}
})
调用的组件
<template>
<div>
<h3>{{num}}</h3>
<button @click="btnClick">累加2</button>
</div>
</template>
<script>
export default {
computed: {
num(){
return this.$store.state.num
}
},
methods: {
btnClick(){
// 使用commit来触发事件,第二个参数是要传递给payload的数据
this.$store.commit('addNum', 2)
}
}
};
</script>
如:
this.$store.commit('increment',uerRes.data)
this.$store.commit('函数方法名',传入的参数)
并且它会接受 state 作为第一个参数
Action 类似于 mutation,不同在于:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
num: 2
},
mutations: {
addNum(state, payload) {
state.num += payload;
}
},
actions: {
// context是一个对象,包含了commit和state
AsyncAddNum(context,payload) {
setTimeout(() => {
context.commit('addNum', payload)
}, 1000)
}
}
})
组件中
<template>
<div>
<h3>{{num}}</h3>
<button @click="btnClick">累加2</button>
</div>
</template>
<script>
export default {
computed: {
num(){
return this.$store.state.num
}
},
methods: {
btnClick(){
// dispatch是分发到意思,其实也是触发Actions中的方法
this.$store.dispatch('AsyncAddNum', 2)
}
}
};
</script>
当然,上面actions中的写法有点累赘,我们还可以改写:
AsyncAddNum({ commit },payload) {
setTimeout(() => {
commit('addNum', payload)
}, 1000)
}
// 如果你还想获取state中的值,可以这样:
AsyncAddNum({ commit,state },payload) {
console.log(state.num); // 2
setTimeout(() => {
commit('addNum', payload)
}, 1000)
}
把累加单独抽出来作为一个模块,在store下新建一个 add/index.js
文件:
export default {
namespaced: true, // 命名空间,为true时,可以在store中把当前模块文件夹名称(add),当作模块名使用
state: {
num: 2
},
getters: {
doubleNum(state) {
return state.num * 1;
}
},
mutations: {
addNum(state, payload) {
state.num += payload;
}
},
actions: {
AsyncAddNum({ commit }, payload) {
setTimeout(() => {
commit('addNum', payload)
}, 300)
}
}
}
把有关累加的所有内容,都移动至本文件。再到原来仓库index.js中的modules添加:
引入存放模块的文件
import add from './add'
export default new Vuex.Store({
...,
modules: {
add
}
})
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true
的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
const moduleA ={
namespaced:true, //开启namespace:true,该模块就成为命名空间模块了
state:{
count:10,
countA:888
},
getters:{...},
mutations:{...},
actions:{...}
}
可以在单个模块中通过添加namespaced:true的方式使其成为带命名空间的模块。
组件中如何获取带有命名空间moduleA中的state数据?
1、基本方式:
this.$store.state.moduleA.countA
2、mapState辅助函数方式:
...mapState({
count:state=>state.moduleB.countB
})
组件中调用命名空间模块中的getters
共有三种方式,如下:
//1.
commonGetter(){
this.$store.getters['moduleA/moduleAGetter']
},
//2.
...mapGetters('moduleA',['moduleAGetter']),此处的moduleA,不是以前缀的形式出现!!!
//3.别名状态下
...mapGetters({
paramGetter:'moduleA/moduleAGetter'
}),
组件中调用命名空间模块中的Mutations
共有三种方式,如下:
//1,3加个前缀moduleA/,都可以实现。2使用辅助函数未变名称的特殊点!!!
//1.
commonMutation(){
this.$store.commit('moduleA/moduleAMutation');
},
//2.
...mapMutations('moduleA',['moduleAMutation']),
//3.别名状态下
...mapMutations({
changeNameMutation:'moduleA/moduleAMutation'
}),
组件中调用命名空间模块中的Actions(与mutations一致)
共有三种方式,如下:
1,3加个前缀moduleA/,都可以实现。2使用辅助函数未变名称的特殊点!!!
//1.
commonAction(){
this.$store.dispatch('moduleA/moduleAAction')
},
//2.
...mapActions('moduleA',['moduleAAction']),
//3.别名状态下
...mapActions({
changeNameAction:'moduleA/moduleAAction'
})
在带命名空间的模块中,如何将action注册为全局actions
两个条件:
①添加 root: true
②并将这个 action 的定义放在函数 handler 中
//storeAction在命名空间moduleA中,但是它是一个全局actions
const moduleA = {
namespaced:true,
storeAction:{
root:true, //条件1
handler(namespacedContext, payload){//条件2:handler
//namespacedContext 上下文信息
//payload 载荷,即参数
console.log(namespacedContext)
console.log(payload)
alert("我是模块A中的全局storeAction")
}
}
}
实际上我们可以把state、getter、mutation、action和module都抽离出来,这样可以让store文件看着更加简洁。我们来将 store/index.js
进行拆分:
state.js
:
export default {
num1: 0,
title: '标题'
}
mutations.js
:
export default {
cutNum(state, payload) {
state.num1 -= payload;
}
}
actions.js
:
export default {
AsyncCutNum({ commit }, payload) {
setTimeout(() => {
commit('cutNum', payload)
}, 300)
}
}
modules.js
:
import add from './add'
export default {
add
}
最后,在 store/index.js
中:
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import modules from './modules'
Vue.use(Vuex)
export default new Vuex.Store({
state,
mutations,
actions,
modules
})
当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState
辅助函数帮助我们生成计算属性,让你少按几次键:
mapState
函数返回的是一个对象
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// 箭头函数可使代码更简练(方法一)
count: state => state.count,
// (方法二)传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// (方法三)为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState
传一个字符串数组。
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])
mapState
函数返回的是一个对象
我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed
属性。但是自从有了对象展开运算符 (opens new window),我们可以极大地简化写法:
computed: {
localComputed () { /* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}
mapGetters
辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}
}
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
缓存加载
作用:主要用于保留组件状态或避免重新渲染。
include
和 exclude
prop 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:
用在包裹着占位符,包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
max - 数字。最多可以缓存多少组件实例。
给组件起名,以此来确定是需要操作那个组件
export default {
name:"Home",
}
逗号分隔字符串
正则表达式 (使用 `v-bind`)
数组 (使用 `v-bind`)
最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。
安装
npm install axios
yarn add axios
引入模块
import axios from "axios";
import qs from "qs"
读取 JSON 数据
methods;{
Fn(){
axios.post('http://192.168.113.249:8081/cms/phoneRegin',{
params: {
ID: 12345
}
}
}.then(res=>{})
.catch(function (error) {
console.log(error);
});
}
方法二
// 直接在 URL 上添加参数 ID=12345
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
请求头
axios.get('http://192.168.113.249:8081/cms/phoneRegin',{
headers:{
'x-auth-token':' '
}
post请求
axios
.post('https://www.runoob.com/try/ajax/demo_axios_post.php')
.then(response => (this.info = response))
.catch(function (error) { // 请求失败处理
console.log(error);
});
}
post请求 传参说明
axios.post('/user', {
firstName: 'Fred', // 参数 firstName
lastName: 'Flintstone' // 参数 lastName
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
qs 是一个增加了一些安全性的查询字符串解析和序列化字符串的库。
用法之一:解析 URI 编码
qs.stringify({phone:this.username,password:this.pwd})
<style lang="less" scoped>
//
在这里可以直接写样式或者引入less文件*
scoped 样式只作用于当前组件
//
.header{
height: 50px;
background-color: #333;
}
</style>
安装初始化样式库reset-css:
npm i reset-css 或者 yarn add reset-css
安装成功后在main.js中引入即可:
import "reset-css"
发送请求:
安装axios模块:
npm i axios
尝试在app.vue中做数据请求:
import axios from "axios"
export default {
...
created(){
//get请求
axios.get("http://192.168.113.249:8081/cms/products/recommend")
.then(res=>{
console.log(res.data);
})
.catch(err=>{
console.log(err);
})
},
}
代理配置:
对 vue.config.js
进行配置:
module.exports = {
devServer: {
port: 8080,
proxy: {
'/api': {
target: "http://192.168.113.249:8081/cms",
pathRewrite: {
'^/api': ''
}
}
}
}
}
由于配置文件修改了,这里一定要记得重新 yarn serve
(跑一下)!!
在 src
下新建 request
目录 ,在request
目录下新建 request.js
request.js
中:
import axios from "axios"
const instance = axios.create({
baseURL:"http://192.168.113.249:8081/cms",
timeout:5000
})
instance.interceptors.request.use(config=>{
console.log("每一次发起请求前,都会先执行这里的代码");
console.log(config); //config本次请求的配置信息
return config
},err=>{
return Promise.reject(err)
})
=
instance.interceptors.response.use(res=>{
console.log("每一次接收到响应,都会先执行这里的代码,再去执行成功的那个回调函数then");
return res
},err=>{
return Promise.reject(err)
})
export default instance
为了更好地管理我们的这些接口,我们把所有请求都抽取出来在一个api.js中
在
request目录下新建
api.js,api.js
中:
import request from './request'
// 请求精品推荐数据
export const JingpinAPI = () => request.get('/products/recommend')
如:
request.js文件中
import axios from 'axios';
const instance = axios.create({
timeout:15000,
baseURL:'',
})
instance.interceptors.request.use((config)=>{
const token = localStorage.getItem('token')
//设置请求头
if(token){
config.headers['X-Nideshop-Token'] = token
}
return config
},(err)=>{
return Promise.reject(err)
})
instance.interceptors.response.use((reset)=>{
return reset.data;
},(err)=>{
return Promise.reject(err)
})
export default instance;
api.js文件中
import request from './request';
import qs from 'qs';
//post请求
export const getUserByToken = (data)=>request.post('http://kumanxuan1.f3322.net:8360/admin/auth/getUserByToken',qs.stringify(data))
//get请求
export const getGoods = (data)=>request.get('http://kumanxuan1.f3322.net:8360/admin/goods',{params:data})
导航栏点击之后的样式显示:
<li :class="$route.path==='/home'?'active':''">首页</li>
三元表达式:
true 执行前面的,false 执行后面的
点击之后设置路由跳转
<li @click="$router.push('/home')" :class="$route.path==='/home'?'active':''">首页</li>
插件参考:https://gitee.com/monoplasty/vue-monoplasty-slide-verify
安装插件
npm install --save vue-monoplasty-slide-verify
或者
yarn add vue-monoplasty-slide-verify
main.js入口文件引中入
import SlideVerify from 'vue-monoplasty-slide-verify' // 拼图验证码
Vue.use(SlideVerify)
在组件中使用
<template>
<slide-verify :l="42" :r="20" :w="362" :h="140" @success="onSuccess" @fail="onFail" @refresh="onRefresh" :style="{ width: '100%' }" class="slide-box" ref="slideBlock" :slider-text="msg"></slide-verify>
</template>
<script>
export default {
data() {
return {
msg: "向右滑动"
};
},
methods: {
// 拼图成功
onSuccess(times) {
let ms = (times / 1000).toFixed(1);
this.msg = "login success, 耗时 " + ms + "s";
},
// 拼图失败
onFail() {
this.onRefresh(); // 重新刷新拼图
},
// 拼图刷新
onRefresh() {
this.msg = "再试一次";
},
},
};
</script>
<style lang="less" scoped>
/deep/.slide-box {
width: 100%;
position: relative;
box-sizing: border-box;
canvas {
position: absolute;
left: 0;
top: -120px;
display: none;
width: 100%;
box-sizing: border-box;
}
.slide-verify-block{
width: 85px;
height: 136px;
}
.slide-verify-refresh-icon {
top: -120px;
display: none;
}
&:hover {
canvas {
display: block;
}
.slide-verify-refresh-icon {
display: block;
}
}
}
</style>
可以正常获取验证码的前提是:手机号格式正确
所以,点击获取验证码的逻辑如下:
1、如果校验手机号格式不正确,则return
2、滑块拼图验证不通过,则return
3、验证成功后,发起请求,获取验证码成功,则进行倒计时
【百度】结合运营商之后的手机号码的正则:
/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/
<div class="btn checkcode-btn" @click="getCode">获取验证码</div>
...
<script>
getCode(){
// 1、验证手机号是否正确
if(!/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/.test(this.phoneNum)){
alert("请输入正确的手机号");
this.$refs.phone.focus();
return
}
alert("手机号格式正确");
// 2、进行滑块验证
// 3、验证成功后,发起请求,获取验证码成功,则进行倒计时,并展示秒数
},
</script>
<div class="btn checkcode-btn" @click="getCode">
<span v-show="!isShowCount">获取验证码</span>
<span v-show="isShowCount">{{count}} s</span>
</div>
<script>
methods:{
countdown(){
// 计时的方法
// 倒计时,实际上就是每隔1秒,count减去1
// 每次点击先让count为60
this.count=60;
let timer = null;
timer = setInterval(()=>{
this.count--
if(this.count===0){
// 清除定时器
clearInterval(timer)
}
},1000);
},
getCode(){
// 1、验证手机号是否正确
/* if(!/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/.test(this.phoneNum)){
alert("请输入正确的手机号");
this.$refs.phone.focus();
return
} */
// 2、进行滑块验证
if (this.msg == "再试一次" || this.msg == "向右滑动") {
alert("请先进行滑块验证");
return
}
// 3、验证成功后,发起请求,获取验证码成功,则进行倒计时,并展示秒数
// 这里先展示秒数
this.countdown();
this.isShowCount=true;
},
}
</script>
此时连续点击倒计时会有bug,数字越跳越快,主要是重复开启倒计时造成的。
其实我们只需要把事件给到 “获取验证码” 所在的span,就可以解决
获取验证码
{{count}} s
接口上有需要我们修改请求头Content-Type字段,并使用qs.stringnify进行格式转换:
需要在请求拦截器加上:
instance.interceptors.request.use(config=>{
if (config.url === "/sendSMS" || config.url === "/wechatUsers/PCLogin") {
config.headers["Content-Type"] = "application/x-www-form-urlencoded";
}
return config
},err=>{
return Promise.reject(err)
})
安装qs模块:
npm i qs
api.js中:
import qs from "qs"
// 发送短信验证码请求
export const SendSMSAPI = params => request.post("/sendSMS",qs.stringify(params));
解题思路:利用v-for遍历数组,利用v-model获取变化的值
//先引入Vue的js文件
<script src="./vue.js"></script>
创建Vue对象
new Vue({
el:"选择器",
data:{//数据
arr:[],
val:""
//val:"A"(意思是默认值是A)
},
methods: {
//方法
}
})
//获取变化的值:用v-model 一般是用在父级元素上,获取子级元素的值的变化
//还可以用在输入框,获取输入框的值
v-model = "val"
v-model = "text"
//遍历数组---用 v-for"(item, index) in 需要遍历的数组数组"
//谁需要遍历,就谁用v-for
<div v-for='item in arr'><span>{{item.content}}</span></div>
//判断name是A还是B,分别对应给class值,控制类名
//用三元表达式
:class="item.name==='A'?'atalk':'btalk'"
//三元表达式 表达式?"等于(true)的值":"不等于(false)的值"
//点击事件,点击给数组添加值,然后再遍历
@click = '事件名'
事件中调用data的值,需要用this
methods:{
talkTxt(){
this.arr.push(`{name:${this.val},content:${this.text}}`)
this.text = ""//点击之后自动清空文本框
}
}
思路:事件点击方法,传参数,带参数,然后将参数存起来;利用三元表达式判断是否等于参数值,给类名值
@click='add(1)'
三元表达式:
:class='val===1?"current":""'
思路:用数组将内容装起来,然后遍历获取数组的item内容和index索引值,点击删除,删除对应索引值的数组内容。
用v-for ’(item,index)in arr ‘
思路:用来 v-for = “(item,index)in arr” ;v-show 显示隐藏(true;false);将索引和item值存储,然后传给下个方法;
第一种方法:isNaN()
数字返回false 字符串返回true
缺点:值有一个空串或是一个空格,isNaN将把c当作数字0来处理,所以检查不严谨。
第二种方法:正则表达式
/1+.?[0-9]* / / / 判 断 字 符 串 是 否 为 数 字 , 判 断 正 整 数 用 / [ 1 − 9 ] + [ 0 − 9 ] ∗ ] ∗ / //判断字符串是否为数字 ,判断正整数用 /^[1-9]+[0-9]*]* ///判断字符串是否为数字,判断正整数用/[1−9]+[0−9]∗]∗/
reg.test( num )
第三种方法: 利用typeof的返回值
0-9 ↩︎