vue

Vue

Vue包:构建数据驱动的Web界面的渐进式框架。响应式的更新机制,数据改变之后,视图会自动刷新。

创建项目

vite

真正的按需编译,不再等待整个应用编译完成。开发环境中,无需打包操作,可快速的冷启动。

  1. npm init vite-app <project-name> 创建项目
  2. cd <project-name> 跳转到项目文件夹里
  3. npm install 安装依赖
  4. npm run dev 运行项目

vue-cli

  1. cnpm install -g @vue/cli 全局安装 vue-cli
  2. vue create myapp 创建项目,初学选default,项目名小写
  3. cd myapp 跳转到项目文件夹里,执行 cnpm install,自动下载安装所需的依赖
  4. npm run serve 运行项目
  5. 浏览器输入 http://localhost:8080/
  6. 在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

  • 后端路由:

    1. 理解:value 是 function,用于处理客户端提交的请求
    2. 工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
  • 前端路由:

    1. 理解:value 是 component,用于展示页面内容
    2. 工作过程:当浏览器的路径改变时,对应的组件就会显示
// 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
<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')

应用: 要将响应式对象中的某个属性单独提供给外部使用时。

扩展:toRefstoRef功能一致,但可以批量创建多个 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:
    • 作用:标记一个对象,使其永远不会再成为响应式对象。
    • 应用场景:
      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

provide 与 inject

  • 作用:实现祖与后代组件间通信

  • 套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据

  • 具体写法:

    1. 祖组件中:

      setup(){
      	......
          let car = reactive({name:'奔驰',price:'40万'})
          provide('car',car)
          ......
      }
      
    2. 后代组件中:

      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

等待异步组件时渲染一些额外内容,让应用有更好的用户体验

  1. 异步引入组件

    import {defineAsyncComponent} from 'vue'
    const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
    
  2. 使用Suspense包裹组件,并配置好defaultfallback

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

参考

https://blog.csdn.net/qq_55593227/article/details/119717498

https://www.bilibili.com/video/BV1Zy4y1K7SH?p=118

posted @ 2022-04-12 00:22  咕咚麦当  阅读(220)  评论(0)    收藏  举报