vue
Vue
Vue包:构建数据驱动的Web界面的渐进式框架。响应式的更新机制,数据改变之后,视图会自动刷新。
创建项目
vite
真正的按需编译,不再等待整个应用编译完成。开发环境中,无需打包操作,可快速的冷启动。
npm init vite-app <project-name>
创建项目cd <project-name>
跳转到项目文件夹里npm install
安装依赖npm run dev
运行项目
vue-cli
cnpm install -g @vue/cli
全局安装 vue-clivue create myapp
创建项目,初学选default,项目名小写cd myapp
跳转到项目文件夹里,执行cnpm install
,自动下载安装所需的依赖npm run serve
运行项目- 浏览器输入
http://localhost:8080/
- 在cmd中输入两边
ctrl+c
关闭
项目文件的含义:
- views(文件夹):实际界面
- router.js:路由信息
.文件目录
├── node_modules 放第三方包
├── public 公共资源
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源。图片
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ └── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件。ES6与ES5的转换
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json: 包版本控制文件
└── vue.config.js:设置代理与跨域
package.json:添加包
"dependencies": {
"express": "^4.17.3",
"mysql": "",
"body-parser": ""
}
main.js:该文件是整个项目的入口文件
//引入Vue,这个是残缺版的vue
import Vue from 'vue'
//引入汇总组件App
import App from './App.vue'
//关闭vue的生产提示
Vue.config.productionTip = false
/*
关于不同版本的Vue:
1.vue.js与vue.runtime.xxx.js的区别:
(1).vue.js是完整版的Vue,包含:核心功能+模板解析器。
(2).vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
2.因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。
*/
//创建Vue实例对象---vm
new Vue({
el:'#app',
//render将App组件放入容器app中
render: h => h(App)
}).$mount('#app') //如果没写 el ,那就要写$mount来指定
index.html :主页面
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<!-- 针对IE浏览器的一个特殊配置,含义是让IE浏览器以最高的渲染级别渲染页面,但其实没啥用开启移动端的理想视口 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 开启移动端的理想视口 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 配置页签图标。<%= BASE_URL %>就代表public目录,导入同级文件不用写./写这个了 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- Webpack的一个插件,会自动去package.json文件里找到name当做标题 -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- 当浏览器不支持js时noscript中的元素就会被渲染 -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
</body>
</html>
vue.config.js :自定义配置文件,它和package.json在同一级下,那么它会被 @vue/cli-service
自动加载
//其实修改的是webpack的配置,webpack底层为nodejs,所以用commonjs导入导出
module.exports = {
/* 设置代理 */
// 方式一。这种代理写法,会优先匹配前端已有的资源,当请求了前端不存在的资源时,才会将请求转发给服务器
devServer:{
proxy:"http://localhost:5000"
},
//方式二。这种代理写法可以配置多个代理,且可以灵活的控制请求是否走代理。
devServer: {
proxy: {
'/api': { //如果请求前缀是/api就走代理,代理服务器帮你转发请求。这个前缀是跟在端口号后面的,这么写请求的url http://localhost:8887/api
target: 'http://localhost:8887', // 目标服务器。代理服务器帮你向该服务器发送请求
ws: true, //用于支持websocket
changeOrigin: true, // 请求目标服务器时,目标服务器会询问代理服务器来自哪里。为true是说谎,为false是实话实说自己来自哪。在本地会创建一个虚拟服务端,然后发送请求的数据,并同时接收请求的数据,这样服务端和服务端进行数据的交互就不会有跨域问题
pathRewrite: { // 重写请求
'^/api': '' // 代理服务器转发请求时,只会有/api后面的东西,因为目标服务器没有/api这层路径,所以要重写。
}
}
}
},
lintOnSave: false //关闭语法检查
}
Vue实例的生命周期函数
创建期间的生命周期函数
- beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性
- created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板。我们可以在这里进行Ajax请求。
- beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中
- mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示。(mounted之后,表示真实DOM渲染完了,可以操作DOM了)
运行期间的生命周期函数
- beforeUpdate:状态更新之前执行此函数。此时 data 中的状态值是最新的,但是界面上显示的数据还是旧的,因为此时还没有开始重新渲染DOM节点
- updated:实例更新完毕之后调用此函数。此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了。
销毁期间的生命周期函数
- beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。可以在这里清除定时器、或清除事件绑定
- destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
Vue实例
单文件版引用Vue和项目版Vue对比
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--1、导入Vue的包-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
</head>
<body>
<!--这个div区域就是MVVM中的 View-->
<div id="div1">
{{name}} <!--插值表达式用双花括号 -->
</div>
</body>
<script>
/* 全局配置api接口的url地址 */
Vue.http.options.root = 'http://smyhvae/' //配置过全局请求路径了,后面写Ajax请求时,只用写相对路径就好了,如'api/getprodlist',开头不写/
Vue.http.options.emulateJSON = true // 全局启用 emulateJSON 选项
/* 自定义全局按键修饰符 */
Vue.config.keyCodes.f2 = 113; //定义f2键的修饰名为113
/* 自定义全局过滤器 */
//Vue.filter中的第一个参数是过滤器的名称,第二个参数是具体的过滤器函数
Vue.filter('myFilter', function (myMsg, arg2, arg3) { //第一个参数为过滤对象,是 | 左边的内容,第二三参数为修改成什么
return myMsg.replace(/单纯/g, arg2+arg3)
})
/* 自定义全局指令 */
//color为自定义指令名,调用指令的时候要加上v-,如v-focus、v-color
Vue.directive('color', {
//以下每个函数中,第一个参数,永远是 el ,相当于Vue实例中的el
bind: function (el, binding) {
// 每当指令绑定到元素上的时候,会立即执行这个 bind 函数,【只执行一次】
// 元素刚绑定指令,还没有插入到DOM中去,所以这时候调用 focus 方法(el.focus())没有作用
// 可用来绑定css
binding.name //指令名
binding.value //赋给指令的值,v-color="'green'",那么就是green
binding.expression //赋给指令的表达式,'green'
el.style.color=binding.value //设置css内联样式
},
inserted: function (el) { // inserted 表示元素 插入到DOM中的时候,会执行 inserted 函数【触发1次】。和JS行为有关的操作,最好在 inserted 中去执行,防止JS行为不生效
el.focus()
},
updated: function (el) { // 当VNode更新的时候,会执行 updated, 【可能会触发多次】
}
})
//方式二:不需要调用inserted的时候,可以简写成这样
Vue.directive('color', function (el, binding) { //注意,这个function等同于把代码写到了 bind 和 update 中去
el.style.color = binding.value
})
/* 创建一个Vue的实例 */
//new出来的对象就是MVVM中的 View Module(调度者)
var myVue = new Vue({
//用逗号分隔每个属性
el: '#div1', //当前vue对象将接管上面的div1区域
data: {//data就是MVVM中的 module。
name: 'smyhvae' //div里引用的内容,在控制台输入myVue.$data.name = 'haha',页面会自动更新name的值,因为它会监听自己的 data 中所有数据的改变,不用再去操作DOM了。
change: null //相当于全局定义一个变量,到时候再赋值
myAccount: {username: '', userpwd: ''} //用于下面的v-model进行双向数据绑定,在页面修改用户名密码,这里的数据也会自动更新
},
//watch监听器
watch: {
//要监听data里哪个变量,就写哪个,这里监听的是name,newFirstName为固定参数,name的值一旦变了,就会执行该函数
name: function(newFirstName){
return this.name+"!"
}
},
//computed计算属性,里面也写方法,但插值调用computed里的方法只需{{fullName}}就好,不用写成函数调用fullName()。性能比methods好,有缓存,值不变的情况下只用调取一次
computed: {
fullName: function(){
return this.name+"!"
}
},
//methods是Vue中自带的属性,定义了当前Vue实例所有可用的方法。调用方法需要用函数调用的写法{{change()}}
methods: {
change: function() {
this.name += '1';//在Vue的实例中,如果想要获取data里的属性、methods里面的方法,都要通过this来访问
}
},
//用filters关键字定义在Vue里面的过滤器,为局部过滤器,只在el指定区域起作用
filters: {
myFilter: function (Mymsg) {} //myFilter为过滤器名
},
//用directives关键字,自定义私有指令。可以这么写,也可以简写。
directives: {
'fontweight': {
bind: function (el, binding) {
el.style.fontWeight = binding.value;
}
}
}
});
</script>
</html>
Vue语法
<!-- {{}}插值表达式 -->
<p>{{ a*2 }}</p> <!-- 可进行简单的运算,字符串拼接等 -->
<p>{{ msg | myFilter }}</p> <!-- 把msg用自定义的msgFormat过滤器过滤 -->
<p>{{ msg | myFilter('你好','ya') }}</p> <!-- 如果过滤器函数添加了第二三个参数,那么可这样写,表示过滤成你好... -->
<p>{{ msg | myFilter1 | myFilter2}}</p> <!-- 可定义多个过滤器,将过滤器1处理完的结果放到过滤器2去处理 -->
<!-- v-once -->
<div v-once>{{count}}</div> <!-- v-once首次渲染后,不再随着数据的改变而重新渲染 -->
<!-- v-cloak -->
<!-- 插值表达式{{}}会有闪烁问题:css样式会先加载完成,内容要等Vue实例化完毕才会显示,这视觉上会让人觉得加载慢。v-cloak属性会在Vue实例化完毕以后自动消失,所以给标签添加v-cloak属性,然后css设置含这个标签的隐藏,等vue实例化后,这个css就失效了 -->
<style>
[v-cloak] { /* 选中所有含v-cloak属性的标签 */
display: none;
}
</style>
<!-- v-text -->
<span v-text="name">----</span> <!-- 同<span>{{name}}</span>作用,但没有闪烁的问题。且没解析到该属性之前会显示----,解析到了以后就会用name值替换---- -->
<!-- v-html -->
<!-- 会被解析成html元素,它很容易导致 XSS(跨站脚本)攻击,所以不用 -->
<!-- v-bind -->
<!-- 用于绑定属性,这样属性值就可以用变量了 -->
<div v-bind:style="{ fontSize: size + 'px' }"></div> <!-- size是Vue实例里面的变量 -->
<div :style="{ fontSize: size + 'px' }"></div> <!-- 写法二 -->
<a v-bind="{href:'http://www.baidu.com/'+path}">超链接</a> <!-- 写法三 -->
<h1 :class="['my-red', 'my-thin']">我</h1> <!-- 绑定多个class属性 -->
<h1 :style="[ styleObj1, styleObj2 ]">我</h1> <!-- 绑定多个data中定义的样式对象 -->
<h1 :class="[flag?'my-red':'']">我</h1> <!-- 还可用三元表达式 -->
<h1 :class="[ {'my-red':flag} ]">我</h1> <!-- 用对象来替代三元表达式,一个意思 -->
<h1 :class="{style1:true, style2:false}">我</h1> <!-- 这种写法样式名不能有中划线,如my-red是不能用的。表示用样式1,不用样式2 -->
<h1 :class="classObj">我</h1> <!-- 也可以将上面那中对象格式的放入data中,然后引用下对象,data:{classObj:{style1:true, style2:false}} -->
<!-- v-on -->
<!-- 绑定事件用的,可绑定click、keydown、keyup、mousedown、mouseover、submit等等事件 -->
<div>
<button v-on:click="change">改变name的值</button> <!-- v-on一定要写在div区域里,否则点击事件不生效 -->
<button @click="change">改变name的值</button> <!-- 写法二。方法需要传一个参数的,但没传,默认会把事件对象作为参数传给方法。要传事件对象的这么写change($event) -->
<!-- v-on的事件修饰符 -->
<!-- 可绑定多个。如 .once事件只触发一次 .{keyCode | keyAlias}当事件是从侦听器绑定的元素本身触发时才会回调 .native监听组件根元素的原生事件 -->
<button @click.stop="doThis"></button> <!-- 阻止冒泡,doThis是method里自建的函数名 -->
<div class="father" @click.self="fatherClick"> <!-- .self只有当事件在该元素本身(比如不是子元素)触发时,才会触发回调,在父标签中设置可用来阻止冒泡 -->
<div class="father" @click.capture="fatherClick"> <!-- 想采用捕获方式,可在父标签上加事件修饰符.capture -->
<a href="http://www.baidu.com" @click.prevent="linkClick">百度一下</a> <!-- .prevent阻止默认事件,这里是阻止跳转 -->
<a href="" @click.prevent="flag=true">登录</a> <!-- 双引号里不仅可以写函数名,也可以写语句。点击登录,flag就变为true了 -->
<form @submit.prevent action="http://www.baidu.com"> <!-- 阻止表单提交到action属性所指页面中去 -->
<!-- v-on的按键修饰符 -->
<!-- 监听键盘输入的事件 -->
<input type="text" v-model="formData.name" @keyup.enter="addData"> <!-- 按enter键后,执行addData()方法。keyup是监听按键抬起,.enter是enter键,结合起来就这意思。等价于v-on:key.enter="addData" -->
</div>
<!-- v-model -->
<!-- v-model双向数据绑定,即修改<input>标签里的内容,要求data里的数据自动更新。只能用于表单元素,或者用于自定义组件 -->
<!-- v-model.lazy="" 只有敲完回车或者鼠标失去焦点时才绑定内容。v-model.number=""绑定的内容一定为数字类型,不然默认会转换为str类型。v-model.trim=""绑定的内容会自动去掉前后的空格 -->
<input type="password" id="pwd" v-model="myAccount.userpwd">
<!-- v-for -->
<li v-for="item in arr1">{{item}}</li> <!-- list是data中定义的数组data{arr1: [2, 3, 4]},v-for会对其值进行遍历,生成3行无序列表 -->
<li v-for="(item,index) in arr1">值{{item}}---索引{{index}}</li>
<li v-for="(item, index) in arrObj1">姓名:{{item.name}} --- 年龄:{{item.age}} --- 索引:{{index}}</li> <!-- 对象数组遍历,data{arrObj1: [{name:'a',age:1},{name:'b',age:2}]} -->
<li v-for="(value,key,index) in obj1">值{{value}}---键{{key}}---index{{index}}</li> <!-- 对象遍历 -->
<li v-for="myCount in 10">这是第{{myCount}}次循环</li> <!-- 遍历数字,从1到10 -->
<tr v-for="item in arrObj1"> <!-- 列表行用for循环,就可以只写一份,它会生成多个列表行的 -->
<td>{{item.name}}</td>
<td><a href="javascript:void(0)">删除</a></td> <!-- "javascript:void(0)"是让超链接去执行一个js函数,而不是去跳转到一个地址,void(0)表示一个空的方法,也就是不执行js函数 -->
</tr>
<!-- v-for生成的,悬停时只改变悬停的那个的颜色 -->
<div v-for='tag in tags' :key="tag.text" @mouseover="Mouseover(tag.text)" @mouseleave="Mouseleave(tag.text)" :class="{ys:istrue===tag.text}"> <!-- 让两个鼠标方法控制istrue的值,再用这个语句判断class是否有ys,最后style设置下有ys的颜色 -->
<p>{{ tag.text }}</p>
</div>
data(){return{istrue: ''}}
Mouseover(index) { // 鼠标"悬停"触发此方法
this.istrue=index
},
Mouseleave(index) { // 鼠标"离开"触发此方法
this.istrue=""
}
.ys{
fill:#fede0e;
}
<!-- 复用-->
<!-- 当在同一个盒子里,做增加、删除、多选一(如v-if/v-else)渲染时,会产生复用问题,不要复用的话在循环处或输出处添加key,key必须使用v-bind的形式绑定,可以是data里的值,也可以是为了区分不同小框的自定义值。 -->
<!-- 这里key的值默认是index,页面渲染的时候比对key,一样的会就地复用,不一样的会重新渲染。例如删除,[0,1,2]数组下标是0,1,2;删除数据1变为[0,2]下标是0,1;页面渲染的时候比对的是下标,下标少了2,其余的就地复用,页面上显示的就是[0,1],就错了,所以key要取一些不变化且与数据唯一对应的值,如id。 -->
<!-- 但增加数据不一样,[1,2]数组开头增加数据0,变为[0,1,2],同个下标的数据进行比较,都不一样,都重新渲染,但之前在数据1前打钩的,重新渲染后会在数据0前打钩,因为它只记录了在哪个下标处打钩,所以也要加key,这样只会重新渲染特定的一条数据。 -->
<p v-for="item in arrObj1" :key="item.id">
<input type="checkbox">{{item.name}}---{{item.age}}
</p>
<!-- v-if -->
<!-- 根据表达式的值的真假条件,来决定是否渲染元素,如果为false则不渲染(达到隐藏元素的目的),如果为true则渲染。每次都会重新添加/删除DOM元素,适合永久隐藏该盒子的情况 -->
<div v-if="isShow">我是盒子</div> <!-- isShow在data里是true -->
<!-- v-show -->
<!-- 根据表达式的真假条件,来切换元素的 display 属性。如果为false,则在元素上添加 display:none属性;否则移除display:none属性。每次不会重新进行DOM的添加/删除操作,适合用在频繁切换盒子显隐的情况 -->
<div v-show="arrObj1.length == 0">我是盒子</div> <!-- true则显示盒子,false则不显示 -->
<!-- 调用自定义全局指令 -->
<input type="text" id="search" v-focus> <!-- 调用了自定义的focus指令 -->
<input type="text" id="search" v-color="'green'"> <!-- 调用了自定义的color指令,如果green加单引号是个字符串,不加是个变量 -->
动态添加和删除数据
<head>
<title>VueJs 在线编辑器</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js">
</script>
</head>
<body>
<div id="app">
<div>
<label>
Id:
<input type="text" v-model="id">
</label>
<label>
Name:
<input type="text" v-model="name">
</label>
<input type="button" value="添加" @click="add">
</div>
<li v-for="(item, i) in list" :key="item.id" @click="del(i)">
{{item.id}} --- {{item.name}}
</li>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
id: '',
name: '',
list: [
{ id: 1, name: '赵高' },
{ id: 2, name: '秦桧' },
{ id: 3, name: '严嵩' },
{ id: 4, name: '魏忠贤' }
]
},
/* 数组的响应式 */
//vue不能检测用索引赋值而引起的数组变动。但数组的push() pop() shift() unshift() splice() sort() reverse()方法是响应式的。this.list[1]='x' 不是响应性的,这么修改就是响应式的了Vue.set(vm.list,1,'x')或this.list.splice(1,1,'x')。但数组里的对象的属性可以直接改this.list[i].id=10
methods: {
add() {
this.list.push({ id: this.id, name: this.name })
this.id = this.name = ''
},
del(i) {
this.list.splice(i, 1);
}
}
});
</script>
</body>
请求
axios
/* 全局配置请求 */
//设置一些所有请求的默认参数
axios.defaults.url='/user' //请求地址
axios.defaults.method='get' //请求类型
axios.defaults.baseURL = 'http://123.207.32.32:8000' //请求根路径
axios.defaults.timeout= 5000 //超时
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded' //报错显示跨域了或参数格式错误,有可能是axios不支持老版本后台接口的post请求头,需要这么设置,还需要将参数转为拼接方式:key1=value1&key2=value2,一般使用第三库qs来转换
/* 创建axios实例 */
//不创建的话,使用的是默认实例
const instance1 = axios.create({
baseURL:'http://123.207.32.32:8000',
timeout:5000,
method:'get',
data:{ post请求参数 }, //data: Qs.stringify({username: 'zxk'})这是遇到跨域问题了,使用qs库转换的post请求参数
params:{ get请求参数 },
headers: {'Content-type': 'application/x-www-form-urlencoded'} //老版本的post请求头
})
instance1({
url:'/home/multidata'
}).then(res=>{
console.log(res)
})
/* get请求 */
//第二个参数为对象类型 { params:{get参数对象} }
axios.get('https://autumnfish.cn/api/joke/list', {
params: {
num: 10
}
}).then(res => {
//请求成功
console.log(res.data);
this.name=res.data //只有箭头函数或用async才能获得vue data里的数据,普通函数不行,解决办法是先将this赋值一下,再使用var slef=this,这个写在methods里
}).catch(err => {
//请求失败,可省略
console.log(err.message);
})
/* post请求 */
//第二个参数是对象类型 { post参数对象 }
axios.post('http://ttapi.research.itcast.cn/mp/v1_0/authorizations', {
mobile: '18801185985',
code: '246810'
}).then(res => {
//请求成功
console.log(res);
})
/* 并发请求 */
//使用axios.all,可以放入多个请求的数组
//axios.all([])返回的结果是一个数组,使用 axios.spread可将数组[res1,res2]展开为res1,res2
axios.all([
axios({
url:'/home/multidata'
}),
axios({
url:'/home/data',
params:{
type:'sell',
page:2
}
})
]).then(axios.spread((res1,res2)=>{
console.log(res1);
console.log(res2);
}))
/* axios封装 */
import axios from 'axios'
export function request(config){
const instance1 = axios.create({
baseURL:'http://123.207.32.32:8000',
timeout:5000
})
return instance1(config) //返回的是个promise
}
//在任意一个文件就可以导入并调用封装函数,request是封装名
import {request} from '自己的路径'
request({
url:'/home/multidata'
}).then(res=>{
console.log(res);
}).catch(err=>{
console.log(err);
})
/* axios的拦截器 */
export function request(config) {
const instance1 = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
// 请求拦截
instance1.interceptors.request.use(config =>{
// 如果config中的一些信息不符合要求 就可以使用拦截改变config;比如发送网络请求是,都希望在界面中显示一个图标;某些网络请求(比如登录(token)),必须携带一些信息
console.log(config);
return config // 被拦截的config需要重新返回
},err=>{
console.log(err); //请求错误返回这个
})
// 响应拦截
instance1.interceptors.response.use(res=>{
console.log(res);
return res.data //被拦截后需要把响应的data返回回去
},err=>{
console.log(err);
})
// 发送网络请求
return instance1(config)
}
Vue-resource
用于发送Ajax请求
引入包<script src="../vue-resource121.js"></script>
之前要先引用vue包
get请求
new Vue({
el :'#app',
data:{
list:[]
},
// Vue对象实例创建成功以后就会自动调用这个方法,表示程序一加载,就马上在created这个函数里执行getlist()方法
created:function(){
this.getlist();
},
methods:{
getlist:function(){
this.$http.get(url)
.then(function (response) { //通过 .then 来设置成功的回调函数
if(response.body.status != 0){
this.list = response.body.message; // 这里是json格式的数据,正常那么status键值为0;还会有message键,里面装着数据
}
})
}
}
})
post请求
new Vue({
el: '#app',
data: {
pname: '', //这个 pname 是我在输入框里添加的数据。我们要把这个传给服务器
list: []
},
// Vue对象实例创建成功以后就会自动调用这个方法
created: function () {
this.getlist();
},
methods: {
//ajax请求:添加数据。methods里面的方法名都是自定义的,除了一些局部私有的关键字。
adddata: function () {
var url = 'http://vueapi.ittun.com/api/addproduct';
var postData = { name: this.pname }; //【重要】键`name`是json中约定好的字段。我们把这个字段传给服务器
var options = { emulateJSON: true };
this.$http.post(url, postData, options).then(function (response) {
if (response.body.status != 0) {
alert(response.body.message);
return;
}
this.pname = '';
// 3、添加完成后,只需要手动再调用一次getlist(将列表数据重新加载一次),即可刷新页面上的数据
this.getlist();
});
},
//ajax请求:获取数据
getlist: function () {
this.$http.get('http://vueapi.ittun.com/api/getprodlist')
.then(function (response) {
// 1、处理服务器异常信息提示
if (response.body.status != 0) {
alert(response.body.message);
return;
}
// 2、处理正常的数据逻辑
this.list = response.body.message;
console.log(this.list);
});
}
// ajax请求:删除数据
deldata: function (id) {
this.$http.get('http://vueapi.ittun.com/api/delproduct/' + id)
.then(function (response) {
if (response.body.status != 0) {
alert(response.body.message);
return;
}
// 刷新列表
this.getlist();
});
}
}
});
jsonp请求
JSONP:是种数据获取方式。通过动态创建script标签的形式,用script标签的src属性代表api接口的url(因为script标签不存在跨域限制),就能跨域获取数据了。【注意:根据JSONP的实现原理,知晓,JSONP只支持Get请求】
// vue-resource中的jsonp方法,url后面不需要再跟callback=fn这个参数了,jsonp方法会自动加上
this.$http.jsonp('http://vuecms.ittun.com/api/getlunbo?id=1')
.then(function (response) {
console.log(JSON.stringify(response.body));
}, function (err) {
//err是异常数据
});
动画
Vue动画
<style>
/* v-enter 【这是一个时间点】 是进入之前,元素的起始状态,此时还没有开始进入 */
/* v-leave-to 【这是一个时间点】 是动画离开之后,离开的终止状态,此时,元素 动画已经结束了 */
.v-enter, /* 正常是长这样,如果transition标签设置过name后,v-enter等的前缀可写成my-enter */
.v-leave-to {
opacity: 0;
transform: translateX(80px); /* 设置v-enter表示进入前在80坐标处,然后入场结束是默认位置没动过在0坐标处,然后开始离场,离场后设置的是80坐标处。就意味着从右向左淡入,从左向右淡出 */
}
/* v-enter-active 【入场动画的时间段】 */
/* v-leave-active 【离场动画的时间段】 */
.v-enter-active,
.v-leave-active {
transition: all 1s ease; /*期间,设置过渡的属性:all表示所有的属性,时间为1秒、过渡的状态*/
}
/* 下面的 .v-move 和 .v-leave-active 配合使用,能够实现列表后续的元素,渐渐地漂上来的效果 */
.v-move {
transition: all 0.6s ease;
}
.v-leave-active {
position: absolute;
}
</style>
<body>
<!-- transition 标签,是 Vue 官方提供的,把需要被动画控制的元素,用它包起来。name是自定义前缀名,在css中用,用来区分执行不同动画的 -->
<transition name="my">
<h3 v-if="flag">这是一个H3</h3>
</transition>
<!-- v-for循环渲染出来的要用transition-group包裹,意为给当前的li做动画效果,transition会给所有的li做动画效果。只渲染一条li,所以一定要写key。给 ransition-group 添加 appear 属性,页面刚展出时或刷新时,会有入场效果。用transition-group包裹的元素,会被默认套上一层<span>,可用tag修改为ul,只是为了更符合正常的规范 -->
<transition-group appear tag="ul">
<li v-for="(item, index) in list" :key="item.id">
{{item.id}} --- {{item.name}}
</li>
</transition-group>
</body>
animate.css库(动画)
<!-- 引入库 -->
<script src="vue2.5.16.js"></script>
<link rel="stylesheet" href="animate3.6.0.css">
<body>
<!-- 写法一,固定格式,bounceIn是入场,bounceOut是离场 -->
<transition enter-active-class="animated bounceIn" leave-active-class="animated bounceOut" :duration="500"> <!-- 入场和离场的动画时长都为500ms -->
<h3 v-if="flag">这是一个H3</h3>
</transition>
<!-- 写法二,固定格式 -->
<transition enter-active-class="bounceIn" leave-active-class="bounceOut" :duration="{ enter: 1000, leave: 300 }"> <!-- 入场时长1s,离场时长300ms -->
<h3 v-if="flag" class="animated">这是一个H3</h3>
</transition>
</body>
钩子函数实现半场动画
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
</transition>
<body>
<div id="app">
<transition @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter">
<div class="ball" v-show="flag"></div>
</transition>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
flag: false
},
methods: {
//以下所有函数第一个参数都必须是el
beforeEnter(el) {
el.style.transform = "translate(0, 0)" //// 设置小球开始动画之前的起始位置
},
enter(el, done) {
el.offsetWidth// 【注意1】el.offsetWidth 这句话,没有实际的作用,但是,如果不写,出不来动画效果。可以认为 el.offsetWidth 会强制动画刷新。也可以是别的包含offset的属性
el.style.transform = "translate(150px, 300px)" // enter 可以设置小球完成动画之后的结束状态
el.style.transition = 'all 1s ease' //设置过渡
done()// 【注意2】表示立即执行后面的afterEnter()函数;如果没有这个done,则会延迟执行后面的afterEnter()函数。在enter和leave中必须使用done进行回调。否则,它们将被同步调用,过渡会立即完成。
},
afterEnter(el) {
// 进入动画完成之后,会调用 afterEnter
this.flag = !this.flag // 动画结束后,让小球消失(直接让 flag 取反即可),因为最开始的时候,小球就是处于消失的状态,这行代码可以让小球的动画重新开始
}
}
});
</script>
</body>
组件
定义组件
//Vue.extend()是用来定义组件的,Vue.extend()这几个字母可省略。VueComponent(注册组件)是Vue.extend(创建组件)生成的。所以用的时候直接写定义的标签名<school></school>,Vue会自动帮我们注册的
const school = Vue.extend({
name:'school', //其实name和函数名写一个就好
template:`<div><test>学校名称:{{name}}</test></div>`,
data(){
return {
name:'尚硅谷'
}
},
methods: {
showName(){
console.log('showName',this)
}
},
components:{test} //局部注册子组件。test是个组件
})
/* vue实例,绑定组件 */
全局注册组件
<body>
<div id="app">
<!-- 使用组件。直接把组件的名称account,以 HTML 标签的形式,引入到页面中即可 -->
<!-- 编译作用域:Vue实例作用在id为app的div中,那么这个div中调用的data变量,都是vue实例中的变量,而不是子组件中的data变量,哪怕它调用了子组件的模板 -->
<account>
<template> <button>按钮</button> </template> <!-- 标签里写的内容,会插入到slot插槽位置处,实现diy -->
<template v-slot:left> <button>按钮</button> </template> <!-- 会插入到slot name="left"的插槽处 -->
<template #left>按钮</template> <!-- 具名插槽缩写 -->
<template v-slot="scope">{{scope.info}}</template><!-- 作用域插槽:父组件使用子组件中的数据。scope是自定义的名字,相当于props里自定义名,调用的是模板里slot中定义的info数据。如果slot既有作用域插槽又有具名插槽,可写成v-slot:left="scope"或简写#left="scope" -->
</account>
</div>
<!-- 1. 定义模板 -->
<!-- 编译作用域:模板中用的是子组件中的data数据 -->
<template id="myAccount">
<!-- 一定要用一个大的根元素(例如<div>)包裹起来-->
<div>
<h2>登录页面</h2>
<h3>注册页面</h3>
<slot> <button>按钮</button> </slot> <!-- 插槽,使组件具有扩展性(预留空间diy)。slot标签里的内容,是插槽默认内容,如果调用模板时有自定义内容,那将会替换默认值显示。 -->
<slot name="left"> <button>按钮</button> </slot> <!-- 给插槽定义了名字,那调用标签的slot属性要指定该插槽名,才能修改该插槽中的内容 -->
<slot :info="name"> <button>name</button> </slot> <!-- info是自定义名,name是子组件data中的变量 -->
</div>
</template>
<script>
/* 2. 全局注册组件 */
//第一个参数account是组件的名称(标签名),如果它是驼峰命名如myAbc,调用时要写成my-abc
Vue.component('account', {
template: '#myAccount', //是上面创建出来的模板对象。也可以直接写template: '<div><h2>登录页面</h2> <h3>注册页面</h3></div>'
//在组件中添加的data和methods,作用域只限于account组件里
//【注意】组件中的data,不再是对象,而是一个方法(否则报错),这样多次调用组件data里的数据能各自独立;而且这个方法内部,还必须返回一个对象才行。组件中的data 数据,使用方式,和实例中的 data 使用方式完全一样
data: function () {
return {
myData: 'smyhvae'
}
},
//组件中的 method
methods: {
login: function () {
alert('login操作');
}
}
});
/* vue实例 */
new Vue({
el: '#app'
});
</script>
</body>
局部注册组件
<body>
<div id="app">
<!-- 使用Vue实例内部的私有组件 -->
<my-login></my-login>
</div>
<!-- 定义模板 -->
<template id="loginTmp">
<h3>这是私有的login组件</h3>
</template>
<script>
new Vue({
el: '#app',
data: {},
components: { // 定义、注册Vue实例内部的私有组件
myLogin: { //myLogin标签名,这里驼峰命名,所以调用时变形。
template: '#loginTmp' //模板
}
}
});
</script>
</body>
组件的切换
<body>
<div id="app">
<!-- 方式一 -->
<!-- 点击登录或注册,以此来切换flag的值 -->
<a href="" @click.prevent="flag=true">登录</a>
<a href="" @click.prevent="flag=false">注册</a>
<!-- 登录组件/注册组件,同时只显示一个 -->
<login v-if="flag"></login> <!-- 如果v-if为true则执行,否则执行v-else -->
<register v-else="flag"></register>
<!-- 方式二 -->
<!-- 点击登录或注册,以此来切换comName的值 -->
<a href="" @click.prevent="comName='login'">登录</a>
<a href="" @click.prevent="comName='register'">注册</a>
<!-- component 是一个占位符, :is 属性可以用来指定要展示的组件的名称,此处的comName是变量,变量值为组件名称 -->
<!-- 用transition给多个组件切换时设置动画,mode="out-in"模式表示第一个组件消失之后,第二个组件才会出现 -->
<transition mode="out-in">
<component :is="comName"></component>
</transition>
</div>
<script>
Vue.component('login', {
template: '<h3>登录组件</h3>'
})
Vue.component('register', {
template: '<h3>注册组件</h3>'
})
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {
flag: false,
comName: 'login'
},
methods: {}
});
</script>
</body>
组件之间的传值
父组件向子组件传值
<body>
<div id="app">
<!-- component1是子组件标签,子组件想要引用父组件vue实例里的数据,要用v-bind:【props里定义的属性名】=【父组件data里的变量名】的方式 -->
<!-- 子组件想要引用父组件vue实例里的方法,要用v-on:【emit里定义的属性名】=【父组件methods里的函数名】的方式 -->
<component1 v-bind:parent-msg="msg" @parent-show='show'></component1>
</div>
<!-- 定义子组件的模板 -->
<template id="myTemplate">
<h2 @click="childClick">父组件中的数据{{ parentMsg }}</h2> <!-- parentMsg:子组件模板里的父组件数据,直接写props里定义的属性名就好。childClick:子组件模板里的父组件方法,还是写子组件里定义的methods函数名 -->
</template>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: '父组件中的数据123'
a:null
},
methods: {
show: function (arg1, arg2) { // 定义父组件的show方法。arg存的是子组件传过来的两个参数
console.log('父组件提供的方法');
this.a = arg2 //将传过来的子组件data,赋值给父组件data中的变量
}
},
components: {
//子组件的data和method都是独立的
component1: { //子组件标签名
template: '#myTemplate',
data() { //子组件自己本身的数据是可读可写的
return {
title: '子组件私有的数据 title',
content: '子组件私有的数据 content'
}
},
//props接收父组件里的data
//方式一
props: ['parentMsg','name'], // 自定义一个属性名parentMsg,给上面的子标签调用的。props里定义的属性名只读,无法重新赋值,如this.parentMsg=1是错误的。v-model不要直接绑定props里的值
//方式二
props:{
name:String,
age:Number
}, //接收的同时对数据进行类型限制
//方式三
props:{
name:{
type:String,
default:'jxm',
required:true,
}
}, //接收的同时对数据进行类型限制 + 指定默认值 + 限制必要性
directives: {},
filters: {},
components: {},
methods: {
childClick() {
//通过emit能让子组件用父组件中的methods,并传值给父组件
this.$emit('parent-show', 'child 123', this.title) //parent-show为自己定义的名字,给上面的子标签调用的。后面都是传递给父组件的参数
}
}
}
}
});
</script>
</body>
ref 获取DOM元素
<!-- 在标签中给 DOM 元素设置 ref 属性 -->
<h3 id="myH3" ref="myTitle"> 今天天气太好了</h3>
<!-- 在Vue实例中,调用$refs来就能获取设置了ref的DOM元素 -->
this.$refs.myTitle.innerText <!-- 原生js获取DOM元素,相当于document.getElementById('myH3').innerText -->
<login-component ref="loginTemplate"></login-component> <!-- 子组件标签中设置ref -->
this.$refs.loginTemplate.myData <!-- 在父组件中就能通过ref获取整个子组件,进而获取子组件data里的myData变量 -->
this.$refs.loginTemplate.showMethod() <!-- 在父组件中就能通过ref获取整个子组件,进而获取子组件methods里的showMethod方法 -->
单文件组件
<v回车
快捷创建书写框架
<!-- School.vue -->
<!-- 定义School组件 -->
<template>
<div class="demo">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<h2>性别:{{sex}}</h2>
<h2>年龄:{{age+1}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
//要暴露
export default {
name:'School', //最好和vue文件一个名,这就是之后调用的标签名
data(){
return {
name:'尚硅谷',
address:'北京昌平'
}
},
methods: {
showName(){
alert(this.name)
}
},
props:['age','sex'] //让组件接收外部传过来的数据
}
</script>
<style>
.demo{
background-color: orange;
}
</style>
必须写App.Vue(汇总所有组件)
<!-- App.vue -->
<!-- 最终模板 -->
<template>
<div>
<School sex="男" :age="20"></School> <!-- 就相当于给组件标签添加属性,在定义组件School时添加props接收,就能达到给组件传值的效果。age需要v-bind方式传,是因为它在模板里用了表达式 -->
<Student></Student>
</div>
</template>
<script>
//引入组件。这个只能在<script>标签下写,不然语法不对
import School from './School.vue'
import Student from './Student.vue'
export default {
name:'App',
components:{
School,
Student
}
}
</script>
必须写main.js,因为这是创建vue实例的地方
import App from './App.vue'
new Vue({
el:'#root',
template:`<App></App>`, //vue实例里面的模板会自动用在el指定标签里
components:{App}, //注册组件
})
必须写index.html,因为这是使用vue实例的文件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>练习一下单文件组件的语法</title>
</head>
<body>
<!-- 准备一个容器 -->
<div id="root"></div>
<script type="text/javascript" src="../js/vue.js"></script> <!-- 要写在root这个div下面,因为要先解析出这个,vue再引入才能解析到。且引入vue也要在自定义文件前面 -->
<script type="text/javascript" src="./main.js"></script>
</body>
</html>
mixin混入
可以把多个组件共用的配置提取成一个混入对象,多个组件引入共享或全局共享
- 如果混入对象和组件有同名的生命周期钩子,那它们将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用
- 组件和混入对象有同名数据时,会进行混合,同名的数据用组件的
/* 局部混入 */
// src/mixin.js,定义mixin
export const mixin = {
methods: {
showName() {
alert(this.name)
}
},
mounted() {
console.log("你好呀~")
}
}
// src/components/School.vue
//引入混入
import {mixin} from '../mixin'
export default {
name:'School',
data() {
return {
name:'尚硅谷',
}
},
mixins:[mixin]
}
/* 全局混入 */
// src/main.js
import {mixin} from './mixin'
Vue.mixin(mixin)
plugin插件
用于增强Vue
// src/plugin.js
export default {
//install为固定写法,第一个参数要写Vue,第二个以后的参数是插件使用者传递的数据
install(Vue,x,y,z){
// 1. 添加全局过滤器
Vue.filter(....)
// 2. 添加全局指令
Vue.directive(....)
// 3. 配置全局混入
Vue.mixin(....)
//给Vue原型上添加一个方法(vm和vc就都能用了)
Vue.prototype.hello = ()=>{alert('你好啊')}
}
}
// src/main.js
import plugin from './plugin'
//使用插件
Vue.use(plugin,1,2,3)
Vue-router路由
vue-router:vue 的一个插件库,专门用来实现SPA 应用(单页应用)
路由:一个路由就是一组映射关系(key - value)。key 为路径,value 可能是 function 或 componen
-
后端路由:
- 理解:value 是 function,用于处理客户端提交的请求
- 工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
-
前端路由:
- 理解:value 是 component,用于展示页面内容
- 工作过程:当浏览器的路径改变时,对应的组件就会显示
// src/router/index.js
import VueRouter from "vue-router";
//引入组件
import News from '../pages/News'
import Message from '../pages/Message'
import Home from '../pages/Home'
import About from '../pages/About' //路由组件一般放在pages文件夹里
//创建并暴露一个路由器
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home,
children:[ //通过children配置子级路由
{
path:'news',
component:News
},
{
path:'message',
component:Message
}
]
}
]
})
// src/main.js
import router from './router'
Vue.use(VueRouter)
new Vue({
el:"#app",
render: h => h(App),
router
})
<!-- src/App.vue -->
<!-- 这是个单页面,要实现点击不同的按钮,在展示区展示不同的内容 -->
<!-- 设置组件切换的超链接 -->
<a href="./home.html">Home</a> <!-- 组件模板里,原本用a标签实现页面跳转 -->
<router-link active-class="active" to="/home">Home</router-link> <!-- 现在可借助router-link标签实现路由的切换。active-class可配置高亮样式 -->
<!-- 在展示区插入该代码,切换的Home或About组件内容就会在这里展示 -->
<router-view></router-view>
<!-- src/pages/Home.vue -->
<!-- 多级路由跳转的时候这么写 -->
<router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
Vue3.0
main.js
//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数。构造函数一般首字母大写,用new创建实例。而工厂函数可小写,不用new创建。
import { createApp } from 'vue'
import App from './App.vue'
const app=createApp(App)
app.mount('#app')
/* vue2.0中是这样的
const vm = new Vue({
render: h => h(App)
})
vm. $mount('#app') */
Composition API
setup
setup不能是一个async函数,因为它的返回值不是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
setup执行的时机
- 在beforeCreate之前执行一次,this是undefined。
setup的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
- context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
this.$attrs
。 - slots: 收到的插槽内容, 相当于
this.$slots
。 - emit: 分发自定义事件的函数, 相当于
this.$emit
。
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
<script>
import {h} from 'vue'
export default{
name: 'App',
//vue3中新的东西,里面可以写data、methods等等,需要返回。不要混用。即写了vue2的data之类又写setup。
setup(){
//数据,类似vue2里的data
let name = '张三'
let age = 18
//方法,类似vue2里的methods
function sayHello(){
alert(`我叫${name},我${age}岁了,你好啊!`)
}
//返回对象(常用)
return {
name,
age,
sayHello
}
//返回一个函数(渲染函数),将不会显示template里的东西,只会显示h1是尚硅谷。
return ()=>h('h1','尚硅谷')
}
}
</script>
响应式原理
vue2.0响应式原理
-
对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。 -
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
Object.defineProperty(data, 'count', { get () {}, set () {} })
-
存在问题:
-
新增属性、删除属性, 界面不会更新。
-
直接通过下标修改数组, 界面不会自动更新。
-
vue3.0响应式原理
-
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
-
通过Reflect(反射): 对源对象的属性进行操作。
new Proxy(data, { // 拦截读取属性值 get (target, prop) { return Reflect.get(target, prop) }, // 拦截设置属性值或添加新属性 set (target, prop, value) { return Reflect.set(target, prop, value) }, // 拦截删除属性 deleteProperty (target, prop) { return Reflect.deleteProperty(target, prop) } }) proxy.name = 'tom'
响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
ref & reactive
ref
语法: const xxx = ref(initValue)
接收基本类型、也可以是对象类型(因为它会自动调用reactive转为代理对象)
reactive
语法:const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
computed
import {computed} from 'vue'
setup(){
...
//计算属性——简写
let fullName = computed(()=>{
return person.firstName + '-' + person.lastName
})
//计算属性——完整
let fullName = computed({
get(){
return person.firstName + '-' + person.lastName
},
set(value){
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
}
watch & watchEffect
两个小“坑”:
- 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
- 监视reactive定义的响应式数据中某个属性时:deep配置有效。
//情况一:监视ref定义的响应式数据
watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
},{immediate:true})
//情况二:监视多个ref定义的响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg变化了',newValue,oldValue)
})
/* 情况三:监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
*/
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效
//情况四:监视reactive定义的响应式数据中的某个属性
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
//情况五:监视reactive定义的响应式数据中的某些属性
watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
//特殊情况
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
watchEffect
-
watch的套路是:既要指明监视的属性,也要指明监视的回调。
-
watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
-
watchEffect有点像computed:
- 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。 watchEffect(()=>{ const x1 = sum.value const x2 = person.age console.log('watchEffect配置的回调执行了') })
生命周期
beforeCreate
===>setup()
created
===>setup()
beforeMount
===>onBeforeMount
mounted
===>onMounted
beforeUpdate
===>onBeforeUpdate
updated
===>onUpdated
beforeUnmount
===>onBeforeUnmount
unmounted
===>onUnmounted
hook
类似于vue2.x中的mixin。 复用代码。
toRef
作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
语法:const name = toRef(person,'name')
应用: 要将响应式对象中的某个属性单独提供给外部使用时。
扩展:toRefs
与toRef
功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
customRef
-
作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
-
实现防抖效果:
<template> <input type="text" v-model="keyword"> <h3>{{keyword}}</h3> </template> <script> import {ref,customRef} from 'vue' export default { name:'Demo', setup(){ // let keyword = ref('hello') //使用Vue准备好的内置ref //自定义一个myRef function myRef(value,delay){ let timer //通过customRef去实现自定义 return customRef((track,trigger)=>{ return{ get(){ track() //告诉Vue这个value值是需要被“追踪”的 return value }, set(newValue){ clearTimeout(timer) timer = setTimeout(()=>{ value = newValue trigger() //告诉Vue去更新界面 },delay) } } }) } let keyword = myRef('hello',500) //使用程序员自定义的ref return { keyword } } } </script>
其他对比
shallowReactive 与 shallowRef
- shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
- shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
- 什么时候使用?
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
readonly 与 shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读)。
- 应用场景: 不希望数据被修改时。
toRaw 与 markRaw
- toRaw:
- 作用:将一个由
reactive
生成的响应式对象转为普通对象。 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- 作用:将一个由
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象。
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
provide 与 inject
-
作用:实现祖与后代组件间通信
-
套路:父组件有一个
provide
选项来提供数据,后代组件有一个inject
选项来开始使用这些数据 -
具体写法:
-
祖组件中:
setup(){ ...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) ...... }
-
后代组件中:
setup(props,context){ ...... const car = inject('car') return {car} ...... }
-
新组件标签
Fragment
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
Teleport
能将组件html结构移动到指定位置的技术
<teleport to="移动位置">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>
Suspense
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
-
异步引入组件
import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
-
使用
Suspense
包裹组件,并配置好default
与fallback
<template> <div class="app"> <h3>我是App组件</h3> <Suspense> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>加载中.....</h3> </template> </Suspense> </div> </template>
全局API
将全局的API,即:Vue.xxx
调整到应用实例(app
)上
2.x 全局 API(Vue ) |
3.x 实例 API (app ) |
---|---|
Vue.config.xxxx | app.config.xxxx |
Vue.config.productionTip | 移除 |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
其他改变
-
data选项应始终被声明为一个函数。
-
过度类名的更改:
-
Vue2.x写法
.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
-
Vue3.x写法
.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
-
-
移除keyCode作为 v-on 的修饰符,同时也不再支持
config.keyCodes
-
移除
v-on.native
修饰符-
父组件中绑定事件
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
-
子组件中声明自定义事件
<script> export default { emits: ['close'] } </script>
-
-
移除过滤器(filter)