-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.json
More file actions
1 lines (1 loc) · 132 KB
/
search.json
File metadata and controls
1 lines (1 loc) · 132 KB
1
[{"title":"Vue工具包","url":"/2022/12/02/Vue%E5%B7%A5%E5%85%B7%E5%8C%85/","content":"全选反选<script>computed:{ 'isAll':{ get () { // 数组方法every:查询数组中'不符合条件'的元素,如果有一个,立即返回false return this.list.every(obj => obj.checked) }, set (value) { // 让所有的小选框变成value this.list.forEach(obj => obj.checked = value) } }}</script>\n\n本地存储<script> watch:{ list:{ handler(){ // 进行本地存储 \tlocalStorage.setItem('TASK_LIST',JSON.stringify(this.list))\t\t},\t deep:true }}</script>\n\n路由router文件夹下的index.js\n// 路由设置的基本配置步骤import Vue from 'vue'// 1. (固定)安装路由依赖 npm i vue-router@3.5.1// 2. (固定)在main.js中导入路由import VueRouter from 'vue-router'// 3. (固定)通过 Vue构造函数 use() 静态方法使用路由Vue.use(VueRouter)// 4. 创建路由规则数组// 4.1 引入页面组件import Find from '../views/Find.vue'import My from '../views/My.vue'import Part from '../views/Part.vue'import NotFound from "../views/NotFound.vue";import Ranking from '../views/second/Ranking.vue'import Recommend from '../views/second/Recommend.vue'import SongList from '../views/second/SongList.vue'// 4.2 创建规则const routes = [ // 路由对象:在其中建立路由与组件的对应关系 { path:'/', // 默认根路由 redirect: '/find' // 重定向属性,当网页路由为 / 时, 会自动跳转到 /find 路由上 }, { path:'*', // * 表示的是,除其他规则外的所有地址 component: NotFound }, { path:'/find', // 路由 component: Find, // 组件 name:'find', // 编程式导航的name children:[ // 二级路由数组 { path:'recommend', component:Recommend }, { path:'ranking', component:Ranking }, { path:'songList', component:SongList }, ] }, { path:'/my', // 路由 component: My, // 组件 name:'my' // 编程式导航的name }, { path:'/part', // 路由 component: Part, // 组件 name:'part' // 编程式导航的name }, { path:'/part/:username', // 路由 component: Part // 组件 },]// 4.5 可以过滤掉路由抛出的错误异常// 编程式导航会遇到一个小问题,当处在A路由上时,想用编程式导航跳转到A路由,会报出错误阻止你重复的跳转const originalPush = VueRouter.prototype.pushVueRouter.prototype.push = function push (location) { return originalPush.call(this, location).catch(err => err)}// 5. (固定)创建实例化路由对象 - 传入规则const router = new VueRouter({ // 解构赋值 routes, /* 关于路由模式分为2种 1. hash 模式(默认): 网址中带有 # 号 2. history模式: 网址中不带 # 号 history模式的路由形式 与 接口请求形式是一致的, 后端在不做任何配置的情况下是无法分别究竟是路由跳转还是接口请求, 所以如果需要在项目中做history模式的路由跳转时,需要后端同事的支持 */ // mode:'history'})// 导出routerexport default router\n\nmain.js:\n// 5.5 接收路由模块中的router实例import router from './router/index'new Vue({ // 6. 关联vue对象 router, render: h => h(App), // 渲染函数,告诉vue 要渲染那个组件 => app.vue 根组件}).$mount('#app') // 确定将这个vue实例挂载到哪里? => id为app的盒子中\n\naxios请求在utils文件夹下的request.js中:\n// 全局配置接口请求模块// 安装axios// 1. 引入axiosimport axios from 'axios'// 2. 创建并导出请求基本地址export const request1 = axios.create({ baseURL: 'https://www.xxxx.cn'})export const request2 = axios.create({ baseURL: 'https://www.xxxx.com'})// 以上通过axios.create创建基本地址的写法,// 代替了axios.defaults.baseURL的写法// 可以实现有多个基本地址的配置\n\n在api文件下的分类模块.js中:\nGET请求:\n// 相关接口模块// /api/goodsimport { request1 } from '../utils/request.js';export const GetGoodsList = () => request1({ method:'GET', url:'/api/goods'})\n\nPOST请求:\n// 用户接口模块import { request2 } from '../utils/request.js';export const login = data => request2({ method:'POST', url:'/api/goods', data})export const GetSms = params => request2({ method:'POST', url:'/api/goods', params})\n\nAPI接口统一出口文件:api/index.js:(方便引入,只需引入一个index文件)\n// api接口的统一出口// 作用:引入各个功能模块中的接口方法,再次统一导出import { GetGoodsList } from './goods.js'import { login, GetSms } from './user.js'export const GetGoodsListAPI = GetGoodsListexport const loginAPI = loginexport const GetSmsAPI = GetSms\n\n需要请求数据的vue文件中使用:\nimport { GetGoodsListAPI, loginAPI } from './api/index.js'export default { created() { this.loadInfo() }, methods:{ // 请求数据 async loadInfo() { try { let res1 = await GetGoodsListAPI() let res2 = await loginAPI({ userName: this.form.userName, passWord: this.form.passWord }) console.log(res1, res2) } catch (err) { console.log('请求失败', err) } } },}\n\n通过CDN引入public/index.html下引入对应链接:\n<!DOCTYPE html><html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <title><%= webpackConfig.name %></title> </head> <body> <noscript> <strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> <% if(process.env.NODE_ENV!=='development'){ %> <!-- script本质是给window添加变量 --> <script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/dayjs/1.10.8/dayjs.min.js" type="application/javascript"></script> <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/xlsx/0.14.5/xlsx.min.js" type="application/javascript"></script> <script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js" type="application/javascript"></script> <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/element-ui/2.13.2/index.js" type="application/javascript"></script> <link href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/element-ui/2.13.2/theme-chalk/index.css" type="text/css" rel="stylesheet" /> <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/echarts/5.3.0/echarts.min.js" type="application/javascript"></script> <% } %> </body></html>\n\nvue.config.js文件下配置:\nconst path = require('path')function resolve (dir) { return path.join(__dirname, dir)}module.exports = { publicPath: './', lintOnSave: process.env.NODE_ENV === 'development', configureWebpack: { // provide the app's title in webpack's name field, so that // it can be accessed in index.html to inject the correct title. name: '头条', // 配置显示name resolve: { alias: { '@': resolve('src') } }, externals: process.env.NODE_ENV !== 'development' ? { dayjs: 'window.dayjs', xlsx: 'XLSX', vue: 'Vue', 'element-ui': 'ELEMENT', echarts: 'echarts' } : {} }}\n\n定制富文本编辑器下载安装\nnpm install vue-quill-editor (@3.0.6)\n// 导入样式import 'quill/dist/quill.core.css'import 'quill/dist/quill.snow.css'import 'quill/dist/quill.bubble.css'// 导入组件import { quillEditor } from 'vue-quill-editor'export default { components: { quillEditor },}\n\n使用:\n<quill-editor v-model="form.content" :options="editorOption"></quill-editor>// 富文本配置对象data(){\treturn {\t\teditorOption: {\t\t\t// 占位配置 placeholder: '', modules: { // 配置工具栏 toolbar: [ ['bold', 'italic', 'underline', 'strike'], ['blockquote', 'code-block'], [{ header: 1 }, { header: 2 }], [{ list: 'ordered' }, { list: 'bullet' }], [{ indent: '-1' }, { indent: '+1' }], ['image'] ] \t\t\t} }\t}}\n\n\n内容高度 写死\n工具栏间距 紧凑\n\n<style scoped lang='less'>// 在父组件中去设置子组件的样式,加上/deep/ 实现穿透功能.publish-container /deep/ .ql-editor{ height: 300px;}.publish-container /deep/ .ql-toolbar.ql-snow{ padding: 0 8px;}</style>\n\n\n\n\n\n较为完整配置:\nvar toolbarOptions = [ ['bold', 'italic', 'underline', 'strike'], // toggled buttons ['blockquote', 'code-block'], [{ 'header': 1 }, { 'header': 2 }], // custom button values [{ 'list': 'ordered'}, { 'list': 'bullet' }], [{ 'script': 'sub'}, { 'script': 'super' }], // superscript/subscript [{ 'indent': '-1'}, { 'indent': '+1' }], // outdent/indent [{ 'direction': 'rtl' }], // text direction [{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown [{ 'header': [1, 2, 3, 4, 5, 6, false] }], [{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme [{ 'font': [] }], [{ 'align': [] }], ['clean'] // remove formatting button];// 初始化插件var quill = new Quill('#editor', { modules: { toolbar: toolbarOptions }, theme: 'snow'});\n\n"},{"title":"Vue2","url":"/2022/11/22/Vue2/","content":"接近Vue入口文件的特点 要想要某个页面或文件在项目中生效,一定会直接或间接的与入口文件产生联系\n\n引入vue依赖 => 如果引入的是第三方依赖,那么路径不必写成相对或绝对路径import Vue from 'vue'\n引入根组件import App from './APP.vue'\n\nvue项目的一个控制台打印提示,默认关闭Vue.config.productionTip = false\n\n\n实例化vue对象,并且挂载到唯一html页面上new Vue({ render: h => h(App), // 渲染函数,告诉vue要渲染的组件}).$mount('#app') // 确定vue实例挂载的盒子\n\n\n自定义vue中开发服务器配置及关闭eslint检查自定义服务器配置在vue.config.js文件中,moudule.exports添加devServer:{ // 自定义开发服务器配置 prot: 3000 // 自定义端口号}\n全局关闭eslint检查lintOnSave:false // \n局部关闭eslint检查 直接添加//注释即可\n// 只能忽略下一行代码的eslint检测:// eslint-disable-next-linelet a = 10// 只能忽略当前行代码的eslint检测:let b = 20// eslint-disable-line// 忽略后续代码的eslint检测:/* eslint-disable *//* eslint-disable */let c = 30 let d = 40let e = 50// 开启后续代码的eslint检测:/* eslint-enable *//* eslint-enable */let f = 60\n\n关于.vue文件中的三个标签\ntemplate标签标签模板 => 对应记忆对照html文件中的<html>标签结构vue模板标签中,只能有一个根标签!\nscript标签// js相关的代码// 在js中 有一个 export default {} 结构, 在这个结构中需要遵循vue的代码规则\nstyle标签/* 当前组件样式表 - 期望每个组件中的style 标签只能影响当前组件中的标签内容 *//* 只需要在style 标签中添加一个属性 - scoped 就可以让样式只作用于当前组件的标签 *//* 原理: scoped 属性,会为每一个样式表自动添加当前组件中所被设置的自定义属性(data-v-hash随机数),形成一个复合选择器,而能够被选中的标签均存在于当前组件中,所以可以被区分开 *//* 规律/要求: 只要组件需要设置style样式,就在style标签中添加 scoped 属性 */\n\nMVVM模型的理解 设计模式: 是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。\n\nMVVM,一种软件架构模式,决定了写代码的思想和层次\nM: model数据模型 (data里定义) \nV: view视图 (html页面)\nVM: ViewModel视图模型 (vue.js源码)\n\n\n\n\nMVVM通过数据双向绑定让数据自动地双向同步 不再需要操作DOM\nV(修改视图) -> M(数据自动同步)\nM(修改数据) -> V(视图自动同步)\n\n\n\n\n总结:\n vue使用的mvvm设计模式。MVVM是Model-View-ViewModel缩写,也就是把MVC中的Controller演变成ViewModel。Model层代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。\n在MVVM模型之前使用的MVC模型:\n\n MVC允许在不改变视图的情况下改变视图对用户输入的响应方式,用户对View的操作交给了Controller处理,在Controller中响应View的事件调用Model的接口对数据进行操作,一旦Model发生变化便通知相关视图进行更新。\n原生的HTML + JS就是使用的这个模型\n\n将html看成view;js看成controller,负责处理用户与应用的交互,响应对view的操作(对事件的监听),调用Model对数据进行操作,完成model与view的同步(根据model的改变,通过选择器对view进行操作);将js的ajax当做Model,也就是数据层,通过ajax从服务器获取数据。\n\n\n\n\n\n\n关于Vue中的属性v-bind动态属性详解语法: v-bind:属性名=”vue中定义的变量/表达式”\n简化的语法: 将v-bind:属性名 转化为 => :属性名\n<a v-bind:href="baiduUrl">{{ url }}</a>\n\nexport default { name:'DemoIndex2', data(){ return{ baiduUrl:'http://www.baidu.com', url:'www.baidu.com', } }}\n\n注意点:\n关于v-bind动态属性作用于图片时尤其需要注意:v-bind 会直接把资源路径认定为字符串\n解决方式:直接在v-bind动态属性后面传递实际的资源\n方法1:通过import引入资源文件,在data中进行定义,最后在动态属性中使用\n<img v-bind:src="img1" alt="">\n\n// 引入importimport img from '../assets/logo.png'export default { name:'DemoIndex2', data(){ return{ // 在data中设置路径,引入 img1:img, } }}\n\n方法2:通过require 引入资源文件,直接在动态属性中使用\n<img v-bind:src="img2" alt="">\n\nexport default { name:'DemoIndex2', data(){ return{ // 通过require 引入资源文件,直接在动态属性中使用 img2:require('../assets/logo.png') } }}\n\n开发中一般会使用方法2,优势: require引入资源的方式是按需的,这样一来,页面初始更新效率会更高\nv-on事件绑定语法:\nv-on:事件名="简单的函数执行体" => 只有一行代码v-on:事件名="methods中定义的方法"v-on:事件名="methods中定义的方法(实际参数)"简写: v-on: => @ @事件名="methods中定义的方法"\n\n使用:\n<p>{{ count }}</p><button v-on:click="count++">点我加1</button><button v-on:click="addCount2">点我加2并打印</button><button @click="addCountN(7)">点我加7</button>\n\nexport default { name: "DemoIndex03", data() { return { count: 0, }; }, // vue组件的方法定义在methods对象中 methods: { // 点击加2并打印 addCount2() { this.count += 2; console.log(this.count); }, // 点击加按钮传递过来的数字 addCountN(num) { this.count += num; }, },};\n\nv-on获取事件对象\nvue事件处理函数中, 拿到事件对象\n\n语法:\n\n无传参, 通过形参直接接收\n传参, 通过$event指代事件对象传给事件处理函数\n\n<template><div> <a @click="one" href="http://www.baidu.com">百度</a> <hr /> <a @click="two(10, $event)" href="http://www.taobao.com">淘宝</a></div></template><script> export default { methods: { // 1. 事件触发, 无传值, 可以直接获取事件对象是 one(e){ console.log(e) e.preventDefault() }, // 2. 事件触发, 传值, 需要手动传入$event two(num, e){ console.log(e) e.preventDefault() } } };</script>\n\n如何不用e这个事件对象而阻止默认行为呢?↓ 事件修饰符\nv-on修饰符语法:\n\n@事件名.修饰符=”methods里函数”\n.stop - 阻止事件冒泡\n.prevent - 阻止默认行为\n.once - 程序运行期间, 只触发一次事件处理函数\n\n\n\n<template><div> <div @click="fatherFn"> <p @click.stop="oneFn">.stop - 阻止事件冒泡</p> <a href="http://www.baidu.com" @click.prevent.stop>去百度</a> <p @click.once="twoFn">点击观察事件处理函数执行几次</p> </div></div></template><script> export default { methods: { fatherFn(){ console.log("fahter-触发click事件"); }, oneFn(){ console.log("p标签点击了"); }, twoFn(){ console.log("p标签被点击了"); } } }</script>\n\nv-on按键修饰符\n给键盘事件, 添加修饰符\n\n语法:\n\n@keyup.enter - 监测回车按键\n@keyup.esc - 监测返回按键\n更多修饰符\n\n<template><div> <!-- 1. 绑定键盘按下事件.enter-回车 --> <input type="text" @keydown.enter="enterFn"> <!-- 2. .esc修饰符 - 取消键 --> <hr> <input type="text" @keydown.esc="escFn"></div></template><script> export default { methods: { enterFn(){ console.log("用户按下的回车"); }, escFn(){ console.log("用户按下esc键"); } } }</script>\n\nv-model\n把value属性和vue数据变量, 双向绑定到一起\n\n基础用法\n语法: v-model=”vue数据变量”\n双向数据绑定\n数据变化 -> 视图自动同步\n视图变化 -> 数据自动同步\n\n\n\n<template> <div> <!-- v-model 这个指令可以实现表单控件中数据与视图的双向绑定 1. 数据改变 => 视图自动同步 2. 视图改变 => 数据自动同步 --> <!-- 1. input type='test' 将 value 属性替换成 v-model --> <input type="text" v-model="inputValue"> <!-- 2. 下拉选择框 select 将name属性替换成 v-model --> <select v-model="city"> <option value="北京">北京</option> <option value="上海">上海</option> <option value="深圳">深圳</option> </select> <!-- 3. 单选框 将 name 属性替换成 v-model --> <input type="radio" value="男" v-model="sex"> 男 <input type="radio" value="女" v-model="sex"> 女 <!-- 4. 复选框 将 name 属性替换成 v-model => v-model中绑定一个数组\t\t选中项就是数组中的每一个元素 将 name 属性替换成 v-model => v-model中绑定的是一个非数组\t\t当切换选择状态时会以布尔值的形式来做状态的切换 --> <input type="checkbox" value="唱" v-model="hobby"> 唱 <input type="checkbox" value="跳" v-model="hobby"> 跳 <input type="checkbox" value="rap" v-model="hobby"> rap <input type="checkbox" value="唱" v-model="hobby1"> 唱 <input type="checkbox" value="跳" v-model="hobby2"> 跳 <input type="checkbox" value="rap" v-model="hobby3"> rap <!-- 5. 文本域 将双标签中的内容 替换为 标签内的 v-model指令 --> <textarea v-model="textarea"></textarea> </div></template><script>export default { name:'DemoIndex05', data () { return { inputValue:'123', city:'', sex:'', hobby:[], hobby1:'', hobby2:false, hobby3:0, textarea:'333' } },}</script>\n\n原理:当一个Vue实现创建时,Vue会遍历data选项的属性,用Object.defineProperty将它们转化为getter/setter并且在内部追踪相关依赖,在属性被访问拒绝和修改时通知变化。每个组件实例都有相应的watcher程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。\nv-model 的修饰符语法: v-model.修饰符="vue数据变量"\n\n.number 以parseFloat转成数字类型(会自动清除非数字及以后部分)\n.trim (常用) 去除首尾空白字符\n.lazy 在change时触发而非input时(一切可以触发change事件的情况,如失焦)\n\n<template> <div> <span>转化为数字</span> <input type="text" v-model.number="newNumber"> <hr> <span>取消前后空格</span> <input type="text" v-model.trim="newTrim"> <hr> <span>懒更新</span> <input type="text" v-model.lazy="newLazy"> </div></template><script>export default { name:'DemoIndex', data () { return { newNumber:'', newTrim:'', newLazy:'' } }}</script>\n\nv-text和v-html\n作用: 更新DOM对象的innerText/innerHTML\n注意: v-text或v-html会覆盖标签内的所有子元素\n\n语法:\n\nv-text=”vue数据变量” \nv-html=”vue数据变量” \n\n<template> <div> <!-- v-text => 代替了webapi中的 innerText v-html => 代替了webapi中的 innerHTML v-text 使用时不允许当前标签内有任何子元素 原因: 本来当前元素中的内容就会被v-text中的内容覆盖,\t 所以当前标签中的子元素显得毫无意义甚至影响页面的渲染效率 --> <p v-text="text"></p> <p v-html="text"></p> </div></template><script>export default { name:'DemoIndex07', data () { return { text:'<span style="color: red;">这是一个字符串</span>' } },}</script>\n\nv-show和v-if\nv-show控制标签的显示和隐藏,v-if控制标签在dom中是否存在\n\n\n语法:\nv-show=”vue变量” \nv-if=”vue变量” \n\n\n原理\nv-show 用的display:none隐藏 (频繁切换使用)\nv-if 直接从DOM树上移除\n\n\n高级\nv-else使用\n\n\n使用场景及区别\nv-show 一般用于单个没有子元素或者子元素都是静态元素的标签\nv-if 常用于弹窗表单\n\n\n\n<template> <div> <!-- 通过变量的boolean值隐式转化,来判断是true或者是false\t为true时显示,为false时隐藏 => 1. 变量是否必须是布尔值?\t => 不必须是布尔值,只需要查看它的隐式转化结果 隐式转化为false的情况: 0 / '' / null / undefined / NaN v-show="变量" 原理: 给当前标签设置一个display: none; \t来进行隐藏操作,原来的这个标签元素还存在于dom数中 v-if="变量" 原理: 直接将需要隐藏的元素从dom树中删除 还可以和 v-else-if 和 v-else进行连用,\t它的判断逻辑与 js中 if else 完全相同 当使用v-if/v-else-if/v-else 时\t他们之间必须是连续的兄弟标签 *** 选择v-show和v-if的直接条件: \t如果当前元素及内部子元素全是静态数据,那么使用v-show\t除此以外全用v-if --> <div v-show="flag">v-show</div> <div v-if="flag">{{ msg }}</div> <div v-if="flag1 === 0">显示为0</div> <div v-else-if="flag1 === 1">显示为1</div> <div v-else>显示为其他</div> </div></template><script>export default { name: "DemoIndex08", data() { return { flag: true, msg: "动态", flag1: 0, }; },};</script>\n\nv-for循环\n渲染列表数据时,直接在标签结构中进行数组遍历\n循环时需要给v-for当前标签加上一个key属性,用来表现每个被循环出来的标签结构唯一且不同\n\n\n语法 (类比forEach)\n\nv-for=”(值, 索引) in 目标结构”\nv-for=”值 in 目标结构”\n\n\n目标结构:\n\n可以遍历数组 / 对象 / 数字 / 字符串 (可遍历结构)\n\n\n注意:\nv-for的临时变量名不能用到v-for范围外\n\n\n<template> <div> <!-- v-for 数据循环指令 语法: 与forEach的使用方式进行联合记忆\t\t它的每次循环得到的数据和索引的用法与forEach完全一样 1. v-for="(value, index) in 目标数据结构" 2. v-for="value in 目标数据结构" 在v-for循环体中\t\t必须为每一个循环标签加上一个key属性来表示当前标签的唯一性 forEach((value, index) => {}) forEach(value => {}) --> <!-- 1. 循环简单数组 √ --> <div> <p v-for="(value, index) in arr" :key="index"> {{ value }} ----- {{ index }} \t</p> </div> <!-- 2. 循环复杂数组 => 数组对象 √ --> <div> <p v-for="value in arr2" :key="value.id"> {{ value.name }} ----- {{ value.age }} \t</p> </div> <!-- 3. 循环对象 => 循环的就是对象的属性名和属性值 √ --> <!-- 参数1: 属性值 参数2: 属性名 --> <div> <p v-for="(value, key) in obj" :key="key"> {{ value }} ----- {{ key }} \t</p> </div> <!-- 4. 循环数字 --> <!-- 参数1: 从1开始计数到最后的自然数 参数2: 下标索引从0开始计数 --> <div> <p v-for="(value, index) in 10" :key="index"> {{ value }} ----- {{ index }} \t</p> </div> <!-- 5. 循环字符串 --> <!-- 参数1: 字符串的每一个字符 参数2: 字符所在的下标索引,从0开始计数 --> <div> <p v-for="(value, index) in 'abcdefg'" :key="index"> {{ value }} ----- {{ index }} \t</p> </div> </div></template><script>export default { name:'DemoIndex10', data() { return { arr:['a', 'b', 'c', 'd'], arr2:[ { name:'cxk', age:24, id:1}, { name:'lbw', age:30, id:2} ], obj:{ name:'cxk', age:111, sex:'不详' }, arr3:[1, 2, 3, 4, 5, 6] } },};</script>\n\n注意: 避免v-for和v-if同时使用\n 当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级。这意味着 v-if 将分别重复运行于 每个 v-for 循环中,即先运行 v-for 的循环,然后在每一个 v-for 的循环中,再进行 v-if 的条件对比,会造成性能问题,影响速度\n解决方法:在v-for前对数组进行筛选\n<!-- v-for 会比 v-if 有更高的优先级,那么数据渲染出来以后才会进行v-if判断,一增一删毫无意义,所以不希望在同一个标签中使用v-if 解决方案: 直接把v-if中的逻辑判断放到filter的函数执行体中即可--><p v-for="(value, index) in arr3.filter(value => value >= 3)" :key="index"> {{ value }} ----- {{ index }}</p>\n\nv-for更新监测\n原因: 当v-for遍历的目标结构改变, Vue触发v-for的更新\n\n<div> <div v-for="(value, index) in arr1" :key="index"> {{ value }} </div> <hr /> <!-- 数组的翻转 --> <button @click="reverseBtn">点击翻转数组</button> <!-- 截取数组的前3个 --> <button @click="sliceBtn">点击截取数组</button> <!-- 将第一个元素改成1000 --> <button @click="changeFirstBtn">点击修改第一个元素</button></div>\n\nexport default { name: "DemoIndex01", data() { return { arr1: [5, 3, 2, 1, 9], }; }, methods: { // 翻转数组的方法 reverseBtn() { this.arr1.reverse(); }, // 截取数组的前三位 sliceBtn() { // 发现使用slice进行数组截取,页面上不会发生变化 // 其原因是slice不会改变原数组,所以需要重新赋值 // this.arr1 = this.arr1.slice(0, 3) // 标准修改方法 ↓ this.arr1.splice(3, 2); }, // 修改第一个元素 changeFirstBtn() { // 这种直接通过索引改变数组某个元素的方式不是标准修改形式 // 通过length进行数组元素的增加和删除的方式也不是标准修改形式 this.arr1[0] = 1000; // 发现通过索引到的方式无法让页面监听到数组元素的改变 // 通过 vue 提供的内置方法来强制刷新 => this.$set // this.$set(更新的目标结构, 更新的位置, 更新的数据) // 如果某个修改数据的方法需要用到this.$set才可以实现数据监听时, // 那么这个方法一定不是一个标准化的规范修改过程,一定有与之替换的标准化解决方案, // 所以去用标准方案吧 this.$set(this.arr1, 0, 1000); // splice 是修改数组中某个元素的标准化解决方案 // this.arr1.splice(0, 1, 1000) }, },};\n\n\n数组变更方法, 就会导致v-for更新, 页面更新\n\n数组非变更方法, 返回新数组, 就不会导致v-for更新, 可采用覆盖数组或this.$set()\n\n\nv-for 立即更新\nv-for 的默认行为会尝试原地修改元素而不是移动它们。\n简单来说就是不删除原来的dom元素,而是尽量去修改各循环元素内部的值\n\n\n<div> <ul> <li v-for="(val, ind) in arr" :key="ind"> {{ val }} </li> </ul> <button @click="btn">下标1位置插入新来的</button></div>\n\nexport default { data(){ return { arr: ['老大', "老二", '老三'] } }, methods: { btn(){ // 索引为1的位置插入一个元素 this.arr.splice(1, 0, '新来的') } }}\n\n自定义指令\n自定义指令的目的是为标签添加额外的功能\n\n除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。 v-xxx \nhtml+css的复用的主要形式是组件\n你需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令\n注册\n目标: 获取标签, 扩展额外的功能\n\n全局注册在main.js用 Vue.directive()方法来进行注册, 以后随便哪个.vue文件里都可以直接用v-gfocus指令\n// 全局指令 - 到处"直接"使用Vue.directive("gfocus", { inserted(el) { // 当绑定元素插入到父元素时调用 el.focus() // 触发标签的事件方法 }, update(el) { // 值或模板更新时,触发此函数 }})\n\n局部注册只能在当前组件.vue文件中使用\n<script>// 目标: 创建 "自定义指令", 让输入框自动聚焦// 1. 创建自定义指令// 全局 / 局部// 2. 在标签上使用自定义指令 v-指令名export default { directives: { focus: { inserted(el){ // 当绑定元素插入到父元素时调用 el.focus() } } }}</script>\n\n实际使用\n 目标: 创建 “自定义指令”, 让输入框自动聚焦\n\n自定义指令的两个事件:\n\ninserted方法 - 指令所在标签, 被插入到网页上触发(一次)\nupdate方法 - 指令对应数据/标签更新时, 此方法执行\n\n07_UseDirective.vue\n<template> <div> <!-- <input type="text" v-gfocus> --> <input type="text" v-focus> </div></template><script>// 目标: 创建 "自定义指令", 让输入框自动聚焦// 1. 创建自定义指令// 全局 / 局部// 2. 在标签上使用自定义指令 v-指令名// 注意:// inserted方法 - 指令所在标签, 被插入到网页上触发(一次)// update方法 - 指令对应数据/标签更新时, 此方法执行export default { data(){ return { colorStr: 'red' } }, directives: { focus: { inserted(el){ el.focus() } } }}</script>\n\n自定义指令传参\n目标: 使用自定义指令, 传入一个值\n\n语法:\n// 目标: 自定义指令传值Vue.directive('自定义指令名', { inserted(el, binding) { // 当绑定元素插入到父元素时调用 }, update(el, binding) { // 值或模板更新时,触发此函数 }})\n\n需求: 定义color指令-传入一个颜色, 给标签设置文字颜色\nmain.js定义处修改一下\n// 目标: 自定义指令传值Vue.directive('color', { inserted(el, binding) { el.style.color = binding.value }, update(el, binding) { el.style.color = binding.value }})\n\nUserDirect.vue处更改一下\n<p v-color="colorStr" @click="changeColor">修改文字颜色</p><script> data() { return { colorStr: "red", }; }, methods: { changeColor() { this.colorStr = 'blue'; }, },</script>\n\nVue 过滤器filter定义使用\n目的: 转换格式, 过滤器就是一个函数, 传入值返回处理后的值\n简单理解就是在数据渲染到页面之前的过程中,进行一次包装处理\n\n注意:过滤器只能用在, 插值表达式和v-bind表达式\nVue中的过滤器场景:\n\n字母转大写, 输入”hello”, 输出”HELLO”\n字符串翻转, “输入hello, world”, 输出”dlrow ,olleh”\n\n语法: \n\nVue.filter(“过滤器名”, (值) => {return “返回处理后的值”})\nfilters: {过滤器名字: (值) => {return “返回处理后的值”}\n\n注意:一定要有返回值\n例子:\n\n全局定义字母都大写的过滤器\n局部定义字符串翻转的过滤器\n\n<div> <p>原来的样子: {{ msg }}</p> <!-- 2. 过滤器使用 语法: {{ 值 | 过滤器名字 }} --> <p>使用翻转过滤器: {{ msg | reverse }}</p> <p>{{ msg | toUp }}</p></div>\n\nexport default { data(){ return { msg: 'Hello, Vue' } }, // 方式2: 局部 - 过滤器 // 只能在当前vue文件内使用 /* 语法: filters: { 过滤器名字 (val) { return 处理后的值 } } */ filters: { toUp (val) { return val.toUpperCase() } }}\n\nmain.js\n// 过滤器接参数Vue.filter("reverse", (val, s) => { return val.split("").reverse().join(s || '')})\n\n传参和多过滤器\n可同时使用多个过滤器, 或者给过滤器传参\n从左往右依次过滤\n\n语法:\n\n过滤器传参: vue变量 | 过滤器(实参) \n多个过滤器: vue变量 | 过滤器1 | 过滤器2\n\n<div> <p>原来的样子: {{ msg }}</p> <!-- 1. 给过滤器传值 语法: vue变量 | 过滤器名(值) --> <p>使用翻转过滤器: {{ msg | reverse('|') }}</p> <!-- 2. 多个过滤利使用 语法: vue变量 | 过滤器1 | 过滤器2 --> <p>{{ msg | toUp | reverse('|') }}</p> </div>\n\nVue 计算属性computed\n场景: 页面上某一个变量是由其他两个或多个变量通过一定的逻辑运算后得到的时候,可以用到计算属性进行简化\n\n基础语法语法:\ncomputed: { "计算属性名" () { return "值" }}\n\n需求:求2个数的和显示到页面上\n<div> <p>{{ num }}</p></div>\n\nexport default { data(){ return { a: 10, b: 20 } }, // 计算属性: // 场景: 一个变量的值, 需要用另外变量计算而得来 /* 语法: computed: { 计算属性名 () { return 值 } } */ // 注意: 计算属性和data属性都是变量-不能重名 // 注意2: 函数内变量变化, 会自动重新计算结果返回 computed: { num(){ return this.a + this.b } }}\n\n总结:\n\n使用场景: 当变量的值, 需要通过别人计算而得来\n特点:函数内使用的变量改变, 重新计算结果返回\n注意事项:计算属性名和data里名字不能重复\n\ncomputed缓存\n目标: 计算属性是基于它们的依赖项的值结果进行缓存的,只要依赖的变量不变, 都直接从缓存取结果\n以上也是计算属性和方法的区别\n\n场景: 通过函数与计算属性做对比\n<div> <p>{{ reverseMessage }}</p> <p>{{ reverseMessage }}</p> <p>{{ reverseMessage }}</p> <p>{{ getMessage() }}</p> <p>{{ getMessage() }}</p> <p>{{ getMessage() }}</p></div>\n\n\n\nexport default { data(){ return { msg: "Hello, Vue" } }, // 计算属性优势: // 带缓存 // 计算属性对应函数执行后, 会把return值缓存起来 // 依赖项不变, 多次调用都是从缓存取值 // 依赖项值-变化, 函数会"自动"重新执行-并缓存新的值 computed: { reverseMessage(){ console.log("计算属性执行了"); return this.msg.split("").reverse().join("") } }, methods: { getMessage(){ console.log("函数执行了"); return this.msg.split("").reverse().join("") } }}\n\n小结:\n\n计算属性优势:\n带缓存 \n依赖项不变, 直接从缓存取\n依赖项改变, 函数自动执行并重新缓存\n\n\n 因为要消耗内存,所以也是计算属性的缺点\n\ncomputed的完整写法\n计算属性也是变量, 如果想要直接赋值, 需要使用完整写法\n场景: 当计算属性变量作为表单的值时\n注意事项:在实际开发过程中,几乎(99.9%)使用完整写法的计算属性,只有用在全选反选上\n\n语法:\ncomputed: { "属性名": { set(值){ }, get() { return "值" } }}\n\n需求:让计算属性给v-model使用\n<div> <span>姓名:</span> <input type="text" v-model="full"></div>\n\ncomputed: { data () { return { num1: 10, num2: 20, num3: 0 } }, full: { // 给full赋值触发set方法 set(val){ this.num3 = val }, // 使用full的值触发get方法 get(){ return this.num1 + this.num2 } }}\n\n小结:\n\n当需要给计算属性赋值时,会用到计算属性,但这种情况非常少。\nset函数和get函数的执行时机\nset接收要赋予的值\nget里要返回给这个计算属性具体值\n\n\n\nVue 侦听器watch\n可以侦听data/computed属性值改变\n\n 一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个 property。\n基本语法语法:\nwatch: { "被侦听的属性名" (newVal, oldVal){ }}\n\n例子:\n<div> <input type="text" v-model="name"> <input type="text" v-model="obj.name"></div>\n\nexport default { data(){ return { name: "", obj: { name: '' } } }, // 目标: 侦听到name值的改变 /* 语法: watch: { 变量名 (newVal, oldVal){ // 变量名对应值改变这里自动触发 } } */ watch: { // newVal: 当前最新值 // oldVal: 上一刻值 name(newVal, oldVal){ console.log(newVal, oldVal); }, // 监听引用类型 obj(newVal, oldVal) { console.log(newVal, oldVal) } }}\n\n发现,引用类型的对象无法监听到,该怎么办?\n深度侦听和立即执行\n侦听引用数据类型, 或者立即执行侦听函数\n\n语法:\nwatch: { "要侦听的属性名": { immediate: true, // 立即执行 deep: true, // 深度侦听复杂类型内变化 handler (newVal, oldVal) { } }}\n\n例子:\n<div> <input type="text" v-model="user.name"> <input type="text" v-model="user.age"></div>\n\nexport default { data(){ return { user: { name: "", age: 0 } } }, // 目标: 侦听对象 /* 语法: watch: { 变量名 (newVal, oldVal){ // 变量名对应值改变这里自动触发 }, 变量名: { handler(newVal, oldVal){ }, deep: true, // 深度侦听(对象里面层的值改变) immediate: true // 立即侦听(网页打开handler执行一次) } } */ watch: { user: { handler(newVal, oldVal){ // user里的对象 console.log(newVal, oldVal); }, deep: true, immediate: true } }}\n\n\n总结: immediate立即侦听, deep深度侦听, handler固定方法触发\n\n补充: 另一种监听对象特定属性的方式 通过设置key为对象属性的索引,value为监听回调方法的形式\nwatch: { 'user.name': (newVal, oldVal) => { console.log(newVal, oldVal) } }\n\nVue组件为什么要用组件?同样的功能如果要复用的话,以前的方法是直接CV多份,显得代码非常的冗余和重复。\n使用vue提供的单vue文件 - 将某一小块会重复使用的代码封装到单独的vue文件中,以后通过直接使用这个文件来实现功能复用\n概念\n组件是可复用的 VUE 实例, 封装标签(HTML), 样式(CSS)和JS\n\n组件化 :封装的思想,把页面上 可重用的部分 封装为 组件,从而方便项目的 开发 和 维护\n为什么说工作中写代码就是一个借鉴的过程呢: 一个页面, 可以拆分成一个个组件,一个组件就是一个整体, 每个组件可以有自己独立的 结构 样式 和 行为(html, css和js)\n\n基础使用\n目标: 每个组件都是一个独立的个体, 代码里体现为一个独立的.vue文件\n\n\n组件内template只能有一个根标签\n组件内data必须是一个函数, 独立作用域\n\n步骤:\n\n创建组件 components/Pannel.vue\n\n封装标签+样式+js - 组件都是独立的, 为了复用\n\n<div> <div class="title"> <h4>芙蓉楼送辛渐</h4> <span class="btn" @click="isShow = !isShow"> {{ isShow ? "收起" : "展开" }} </span> </div> <div class="container" v-show="isShow"> <p>寒雨连江夜入吴,</p> <p>平明送客楚山孤。</p> <p>洛阳亲友如相问,</p> <p>一片冰心在玉壶。</p> </div></div>\n\nexport default { data() { return { isShow: false, }; },};\n\n<style scoped>.title { display: flex; justify-content: space-between; align-items: center; border: 1px solid #ccc; padding: 0 1em;}.title h4 { line-height: 2; margin: 0;}.container { border: 1px solid #ccc; padding: 0 1em;}.btn { /* 鼠标改成手的形状 */ cursor: pointer;}</style>\n注册组件:创建后需要注册后再使用\n\n全局注册\n全局入口在main.js, 在new Vue之上注册\n// 目标: 全局注册 (一处定义到处使用)// 1. 创建组件 - 文件名.vue// 2. 引入组件import Pannel from './components/Pannel'// 3. 全局 - 注册组件/* 语法: Vue.component("组件名", 组件对象)*/Vue.component("PannelG", Pannel)\n\n全局注册PannelG组件名后, 就可以当做标签在任意Vue文件中template里用\n单双标签都可以或者小写加-形式, 运行后, 会把这个自定义标签当做组件解析, 使用\n==组件里封装的标签替换到这个位置==\n以下写法均可:\n<PannelG></PannelG><PannelG/><pannel-g></pannel-g>\n\n注意:\n\n如果组件在全局注册时,启动项目后初始化网页时就会立即加载该组件资源(无论是否在当前页面进行使用),那么这种情况是会导致网页开启缓慢的性能问题的,所以请在往后的开发过程中不要使用全局注册的方式\n关于引用组件的标签写法,这里建议如果import组件时采用的是驼峰命名法拼接的多个单词(如:PannelG),那么在创建标签的时候请用全小写并短横线隔开(如:pannel-g)\n\n\n局部 - 注册使用\n任意需要引用组件的 vue文件中中引入, 注册, 使用\n<div id="app"> <h3>案例:折叠面板</h3> <!-- 4. 组件名当做标签使用 --> <!-- <组件名></组件名> --> <PannelG></PannelG> <PannelL></PannelL></div>\n\n// 目标: 局部注册 (用的多)// 1. 创建组件 - 文件名.vue// 2. 引入组件import Pannel from './components/Pannel_1'export default { // 3. 局部 - 注册组件 /* 语法: components: { "组件名": 组件对象 } */ components: { PannelL: Pannel }, /* 当标签名和import引入组件名称相同是可以简写 */ components: { Pannel }}\n\n注意: 以后尽量将引入时(import)时的明明与当前注册组件时(components)的命名保持一致\n\n\n\n\n组件使用总结:\n\n(创建)封装html+css+vue到独立的.vue文件中\n(引入注册)组件文件 => 得到组件配置对象\n(使用)当前页面当做标签使用\n\nCSS中的scoped作用\n作用: 解决多个组件样式名相同, 冲突问题\n\n在style上加入scoped属性, 就会在此组件的标签上加上一个随机生成的data-v-hash开头的属性。保证了必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到\n<style scoped></style>\n\n\n解决了组件内部的css样式影响外部文件的问题\n总结: style上加scoped, 组件内的样式只在当前vue组件生效\nvue组件通信父传子 - props步骤:\n\n创建组件 components/MyProduct.vue\n\n<div class="my-product"> <h3>标题: ? </h3> <p>价格: ? 元</p> <p>描述: ?</p></div>\n\n.my-product { width: 400px; padding: 20px; border: 2px solid #000; border-radius: 5px; margin: 10px;}\n\n\n在父组件中注册MyProduct.vue子组件, 在使用时传入具体的数据\n\n<div> <!-- 目标: 父(App.vue) -> 子(MyProduct.vue) 分别传值进入 需求: 每次组件显示不同的数据信息 步骤(口诀): 1. 子组件 - props - 变量 (准备接收) 2. 父组件 - 传值进去 --> <Product></Product></div>\n\n// 1. 创建组件 (.vue文件)// 2. 引入组件import Product from './components/MyProduct'export default { data(){ return { str: "好贵啊, 快来啊, 好吃" } }, // 3. 注册组件 components: { // Product: Product // key和value变量名同名 - 简写 Product }}\n\n\n子组件内在props属性中定义变量, 用于接收外部传入的值\n\n<div class="my-product"> <h3>标题: {{ title }}</h3> <p>价格: {{ price }}元</p> <p>{{ intro }}</p></div>\n\nexport default { props: ['title', 'price', 'intro']}\n\n\n父组件中的组件标签中传入不同的值\n注意: 传入的值,可以是固定值,也可以是变量\n\n\n<div> <!-- 目标: 父(App.vue) -> 子(MyProduct.vue) 分别传值进入 需求: 每次组件显示不同的数据信息 步骤(口诀): 1. 子组件 - props - 变量 (准备接收) 2. 父组件 - 传值进去 --> <Product title="好吃的口水鸡" price="50" intro="开业大酬宾, 全场8折"></Product> <Product title="好可爱的可爱多" price="20" intro="老板不在家, 全场1折"></Product> <Product title="好贵的北京烤鸭" price="290" :intro="str"></Product></div>\n\n\n总结: 父传子的流程\n\n子组件内, props定义变量, 在子组件使用变量\n父组件内, 使用子组件, 属性方式给props变量传值\n\n\n要传的数据量较多时,也可以用v-for遍历循环,标签传递\n单项数据流概念: 从父到子的数据流向,叫做单项数据流\n在vue中需要遵循单向数据流原则\n1. 父组件的数据发生了改变,子组件会自动跟着变\n\n2. 子组件不能直接修改父组件传递过来的props props是只读的\n(这里的只读,是栈内存中的数据只读,可以改堆)\n父组件传给子组件的是一个对象,子组件修改对象的属性,是不会报错的,\n对象是引用类型, 互相更新\n\n<template> <div class="my-product"> <h3>标题: {{ title }}</h3> <p>价格: {{ price }}元</p> <p>{{ intro }}</p> <button @click="() => {price--}">宝刀-砍1元</button> </div></template>\n\n出现报错\n\n总结: props的值不能重新赋值, 对象引用关系属性值改变, 互相影响\n子传父 - $emit\n目标: 从子组件把值传出来给外面使用\n\n需求: 实现砍价功能, 子组件点击实现随机砍价-1功能\n语法:\n\n父: @自定义事件名=”父methods函数”\n<MyProduct v-for="(obj, i) in list" :key="obj.id" :title="obj.proname" :price="obj.proprice" :intro="obj.info" @subprice="fn"></MyProduct>\n\nmethods: { fn(){ // 逻辑代码 - 修改价格 }}\n子: this.$emit(“自定义事件名”, 传值) - 执行父methods里函数代码\n<div class="my-product"> <h3>标题: {{ title }}</h3> <p>价格: {{ price }}元</p> <p>{{ intro }}</p> <button @click="subFn">宝刀-砍1元</button></div>\n\nimport eventBus from '../EventBus'export default { props: ['index', 'title', 'price', 'intro'], methods: { subFn(){ this.$emit('subprice') // 子向父 } }}\n如何知道是循环中的那个组件触发的砍价呢?\n\n为循环中的每一个组件设置它在数据列表中的索引号\n\n<MyProduct v-for="(obj, i) in list" :key="obj.id" :title="obj.proname" :price="obj.proprice" :intro="obj.info" :index="i" @subprice="fn"></MyProduct>\n子组件调用父组件方法时,传入当前的索引\nexport default { props: ['index', 'title', 'price', 'intro'], methods: { subFn(){ this.$emit('subprice', this.index) // 子向父 } }}\n父组件实现砍价的js逻辑\nfn(inde){ // 逻辑代码 this.list[inde].proprice > 1 && (this.list[inde].proprice = (this.list[inde].proprice - 1).toFixed(2))}\n\n\n总结: 父自定义事件和方法, 等待子组件触发事件给方法传值\n\n跨组件传参 - EventBus\n如果两个没有任何引入关系的组件,如何进行数据通信?\n\n例如:左侧为组件A,右侧为组件B,左右两侧的组件没人直接关联\n目标:如何听过左侧的砍价,修改右侧的组件中的值\n\n两个组件的关系非常的复杂,通过父子组件通讯是非常麻烦的。这时候可以使用通用的组件通讯方案:事件总线(event-bus)\n\n核心语法:\n\n设置EventBus/index.js- 定义事件总线bus对象\nimport Vue from 'vue'// 导出空白vue对象export default new Vue()\nList.vue注册事件 - 等待接收要砍价的值 - 准备兄弟页面\n<ul class="my-product"> <li v-for="(item, index) in arr" :key="index"> <span>{{ item.proname }}</span> <span>{{ item.proprice }}</span> </li> <li>{{num}}</li></ul>\n\n// 目标: 跨组件传值// 1. 引入空白vue对象(EventBus)// 2. 接收方 - $on监听事件import eventBus from "../EventBus";export default { props: ["arr"], data() { return { num: 0 } },};\n\n\n\n.my-product { width: 400px; padding: 20px; border: 2px solid #000; border-radius: 5px; margin: 10px;}\ncomponents/MyProduct_sub.vue\n<div class="my-product"> <h3>标题: {{ title }}</h3> <p>价格: {{ price }}元</p> <p>{{ intro }}</p> <button @click="subFn">宝刀-砍1元</button></div>\n\nimport eventBus from '../EventBus'export default { props: ['index', 'title', 'price', 'intro'], methods: { subFn(){ this.$emit('subprice', this.index, 1) // 子向父 } }}\n\n.my-product { width: 400px; padding: 20px; border: 2px solid #000; border-radius: 5px; margin: 10px;}\n创建父组件,分别挂载以上两个组件\n\n发现组件A改变数据后,B的数据也会改变,为什么?\n\n<div style="overflow: hidden;"> <div style="float: left;"> <MyProduct v-for="(obj, i) in list" :key="obj.id" :title="obj.proname" :price="obj.proprice" :intro="obj.info" :index="i" @subprice="fn" ></MyProduct> </div> <div style="float: left;"> <List :arr="list"></List> </div></div>\n\nimport MyProduct from "./components/MyProduct_sub";import List from "./components/List";export default { data() { return { list: [ { id: 1, proname: "超级好吃的棒棒糖", proprice: 18.8, info: "开业大酬宾, 全场8折", }, { id: 2, proname: "超级好吃的大鸡腿", proprice: 34.2, info: "好吃不腻, 快来买啊", }, { id: 3, proname: "超级无敌的冰激凌", proprice: 14.2, info: "炎热的夏天, 来个冰激凌了", }, ], }; }, components: { MyProduct, List, }, methods: { fn(i, price) { this.list[i].proprice > 1 && (this.list[i].proprice = (this.list[i].proprice - price).toFixed( 2 )); }, },}; \n编写eventBus触发方 - MyProduct_sub.vue\nsubFn(){ this.$emit('subprice', this.index, 1) // 子向父 eventBus.$emit("send", this.index, 1) // 跨组件}\n编写eventBus接收方 - List.vue\n// 3. 组件创建完毕, 监听send事件created() { eventBus.$on("send", (index, price) => { this.arr[index].proprice > 1 && (this.arr[index].proprice = (this.arr[index].proprice - price).toFixed(2)); this.num++ });}\n\n\n 总结: eventBus是空的Vue对象, 只负责$on注册事件, $emit触发事件, 一定要确保$on先执行\n\n扩展 props的进阶用法说明:props也可以作为一个对象,并且可以指定数据类型,必要性,默认值\n语法:\nexport default {\tprops: { a: { type: String, // 传参类型 required: true, // 是否必传(一般也不写,了解) default: 'string' // 默认值 }, b: { type: Array, default: () => [] }, c: { type: Object, default: () => ({}) } }};\n\n动态组件\n目标: 多个组件使用同一个挂载点,并动态切换,这就是动态组件\n\n<template> <div> <button>账号密码填写</button> <button>个人信息填写</button> <p>下面显示注册组件-动态切换:</p> <div style="border: 1px solid red;"> <component :is="comName"></component> </div> </div></template><script>// 目标: 动态组件 - 切换组件显示// 场景: 同一个挂载点要切换 不同组件 显示// 1. 创建要被切换的组件 - 标签+样式// 2. 引入到要展示的vue文件内, 注册// 3. 变量-承载要显示的组件名// 4. 设置挂载点<component :is="变量"></component>// 5. 点击按钮-切换comName的值为要显示的组件名import UserName from '../components/01/UserName'import UserInfo from '../components/01/UserInfo'export default { data(){ return { comName: "UserName" // 这个值一定要是我们的组件名 } }, components: { UserName, UserInfo }}</script>\n\n总结:vue内置component组件,配合is属性,设置要显示的组件名称\n组件缓存\n目标: 组件切换会导致组件被频繁销毁和重新创建, 性能不好\n\n使用Vue内置的keep-alive组件, 可以让包裹的组件保存在内存中不被销毁\n演示1: 可以先给UserName.vue和UserInfo.vue 注册created和destroyed生命周期事件, 观察创建和销毁过程\n每一次切换,都会创建A,并销毁B\nApp.vue\n<hr><h1>2. 组件缓存</h1><UseDynamic2></UseDynamic2>\n\nUserName.vue\n<script>export default { created(){ console.log("02-UserName-创建"); }, destroyed(){ console.log("02-UserName-销毁"); },}</script>\n\n\n\n演示2: 使用keep-alive内置的vue组件, 让动态组件缓存而不是销毁\n除了第一次开启组件时会创建,切换的过程当中不会重复创建和销毁的过程\n语法:\n Vue内置的keep-alive组件 包起来要频繁切换的组件\n02_UseDynamic.vue\n<div style="border: 1px solid red;"> <!-- Vue内置keep-alive组件, 把包起来的组件缓存起来 --> <keep-alive> <component :is="comName"></component> </keep-alive></div>\n\nkeep-alive相关钩子函数\n目标: 被缓存的组件不再创建和销毁, 而是激活和非激活\n\n补充生命周期:\n\nactivated - 激活\ndeactivated - 失去激活状态\n\n\n总结: keep-alive可以提高组件的性能, 内部包裹的标签不会被销毁和重新创建, 触发激活和非激活的生命周期方法\n\nUserName.vue\n<script>export default { created(){ console.log("02-UserName-创建"); }, destroyed(){ console.log("02-UserName-销毁"); }, // 组件缓存下 - 多了2个钩子函数 activated(){ console.log("02-UserName-激活"); }, deactivated(){ console.log("02-UserName-失去激活"); }}</script>\n\n组件插槽\n给组件传递动态的参数时,可以使用props,那如果给组件传递动态的标签结构呢?\n\n基本用法\n目标: 用于实现组件的内容分发, 通过 slot 标签, 可以接收到写在组件标签内的内容\n\nvue提供组件插槽能力, 允许开发者在封装组件时,把不确定的部分定义为插槽\n需求: 折叠面板案例, 想要实现不同内容显示, 我们把折叠面板里的Pannel组件, 添加组件插槽方式\n\n语法口诀: \n\n组件内用<slot></slot>占位\n使用组件时<Pannel></Pannel>夹着的地方, 传入标签替换slot\n\n03/Pannel.vue - 组件\n<template> <div> <!-- 按钮标题 --> <div class="title"> <h4>芙蓉楼送辛渐</h4> <span class="btn" @click="isShow = !isShow"> {{ isShow ? "收起" : "展开" }} </span> </div> <!-- 下拉内容 --> <div class="container" v-show="isShow"> <p>寒雨连江夜入吴,</p> <p>平明送客楚山孤。</p> <p>洛阳亲友如相问,</p> <p>一片冰心在玉壶。</p> </div> </div></template>\n\n\n\nexport default { data() { return { isShow: false, }; },};\n\nh3 { text-align: center;}.title { display: flex; justify-content: space-between; align-items: center; border: 1px solid #ccc; padding: 0 1em;}.title h4 { line-height: 2; margin: 0;}.container { border: 1px solid #ccc; padding: 0 1em;}.btn { /* 鼠标改成手的形状 */ cursor: pointer;}img { width: 50%;}\n\n步骤:\n\n在 views/03_UserSlot.vue 中使用组件\n03_UserSlot.vue\n<div id="container"> <div id="app"> <h3>案例:折叠面板</h3> <Pannel></Pannel> <Pannel></Pannel> <Pannel></Pannel> </div></div>\n\nimport Pannel from "../components/03/Pannel";export default { components: { Pannel, },};\n\n#app { width: 400px; margin: 20px auto; background-color: #fff; border: 4px solid blueviolet; border-radius: 1em; box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5); padding: 1em 2em 2em;}\n给Pannel.vue设置插槽\n<template> <div> <!-- 按钮标题 --> <div class="title"> <h4>芙蓉楼送辛渐</h4> <span class="btn" @click="isShow = !isShow"> {{ isShow ? "收起" : "展开" }} </span> </div> <!-- 下拉内容 --> <div class="container" v-show="isShow"> <!-- 插槽设置位置,这里可以插入html结构 --> <slot></slot> </div> </div></template>\n```html\n\n \n \n 案例:折叠面板\n \n \n 我是内容\n \n \n 寒雨连江夜入吴,\n 平明送客楚山孤。\n 洛阳亲友如相问,\n 一片冰心在玉壶。\n \n \n \n \n\n### 插槽默认内容> 目标: 如果外面不给传, 想给个默认显示内容口诀: `<slot>`夹着内容默认显示内容, 如果不给插槽slot传东西, 则使用`<slot>`夹着的内容在原地显示Pannel.vue```html<template> <div> <!-- 按钮标题 --> <div class="title"> <h4>芙蓉楼送辛渐</h4> <span class="btn" @click="isShow = !isShow"> {{ isShow ? "收起" : "展开" }} </span> </div> <!-- 下拉内容 --> <div class="container" v-show="isShow"> <!-- 插槽设置位置,这里可以插入html结构 --> <slot>默认显示的内容</slot> </div> </div></template>\n\n如果有两个同地方都需要插入HTML结构呢?该怎么办?\n具名插槽\n目标: 当一个组件内有2处以上需要外部传入标签的地方,传入的标签可以分别派发给不同的slot位置\n\n语法:\n\nslot使用name属性区分插槽名称\nPannel.vue\n<template> <div> <!-- 按钮标题 --> <div class="title"> <slot name="title"></slot> <span class="btn" @click="isShow = !isShow"> {{ isShow ? "收起" : "展开" }} </span> </div> <!-- 下拉内容 --> <div class="container" v-show="isShow"> <slot name="content"></slot> </div> </div></template>\ntemplate配合v-slot: 来分别对应插槽标签\n注意:\n\nv-slot一般用跟template标签使用 (template是html5新出标签内容模板元素, 不会渲染到页面上, 一般被vue解析内部标签)\nv-slot的简写是#\n\nviews/04_UseSlot.vue使用\n<template> <div id="container"> <div id="app"> <h3>案例:折叠面板</h3> <Pannel> <!-- v-slot插槽名称 --> <template v-slot:title> <h4>芙蓉楼送辛渐</h4> </template> <template v-slot:content> <img src="../assets/mm.gif" alt=""> <span>我是内容</span> </template> </Pannel> <Pannel> <!-- 简写方式 --> <template #title> <span style="color: red;">我是标题</span> </template> <template #content> <p>寒雨连江夜入吴,</p> <p>平明送客楚山孤。</p> <p>洛阳亲友如相问,</p> <p>一片冰心在玉壶。</p> </template> </Pannel> </div> </div></template>\n\nimport Pannel from "../components/04/Pannel";export default { components: { Pannel, },};\n\n在使用插槽时,是否可以使用子组件中的变量呢?\n作用域插槽 - 接收 prop 的具名插槽\n目标: 子组件里值, 在给插槽赋值时在父组件环境下使用\n\n口诀: \n\n子组件, 在slot上绑定属性和子组件内的值\n使用组件, 传入自定义标签, 用template和v-slot=”自定义变量名” \nscope变量名自动绑定slot上所有属性和值\n\n场景: 插槽有显示默认内容,但使用时,不使用外部传参,只用组件本身的内容去修改页面内容\n需求:在该组件内,不改动代码的基础上,能否使用defaultTwo替换插值中的默认值\ncomponents/05/Pannel.vue \n<template> <div> <!-- 按钮标题 --> <div class="title"> <h4>芙蓉楼送辛渐</h4> <span class="btn" @click="isShow = !isShow"> {{ isShow ? "收起" : "展开" }} </span> </div> <!-- 下拉内容 --> <div class="container" v-show="isShow"> <!-- 这里!!!!!!!!!!!! --> <slot :row="defaultObj" name="pannel"> {{ defaultObj.defaultOne }} </slot> </div> </div></template>\n\n// 目标: 作用域插槽// 场景: 使用插槽, 使用组件内的变量// 1. slot标签, 自定义属性和内变量关联// 2. 使用组件, template配合v-slot="变量名"// 变量名会收集slot身上属性和值形成对象export default { data() { return { isShow: false, defaultObj: { defaultOne: "无名氏", defaultTwo: "咕咕" } }; },};\n\n在调用子组件处05_UseSlot.vue引用子组件中solt上的设置的属性\n\n<template> <div id="container"> <div id="app"> <h3>案例:折叠面板</h3> <Pannel> <!-- 需求: 插槽时, 使用组件内变量 --> <!-- scope变量: {row: defaultObj} --> <template v-slot:pannel="scope"> <p>{{ scope.row.defaultTwo }}</p> </template> </Pannel> </div> </div></template><script>import Pannel from "../components/05/Pannel";export default { components: { Pannel, },};</script>\n\n\n总结: 组件内变量绑定在slot上, 然后使用组件v-slot=”变量” 变量上就会绑定slot身上属性和值\n\nVue生命周期生命周期\n一个vue组件从 创建 到 销毁 的整个过程就是生命周期\n\n\n钩子函数\n目标: Vue 框架内置函数,随着组件的生命周期阶段,自动执行\n\n作用: 特定的时间点,执行特定的操作\n场景: 组件创建完毕后,可以在created 生命周期函数中发起Ajax 请求,从而初始化 data 数据\n分类: 4大阶段8个方法\n\n初始化\n挂载\n更新\n销毁\n\n\n\n\n阶段\n方法名\n方法名\n\n\n\n初始化\nbeforeCreate\ncreated\n\n\n挂载\nbeforeMount\nmounted\n\n\n更新\nbeforeUpdate\nupdated\n\n\n销毁\nbeforeDestroy\ndestroyed\n\n\n\n初始化阶段含义讲解:\n1.new Vue() – Vue实例化(组件也是一个小的Vue实例)\n2.Init Events & Lifecycle – 初始化事件和生命周期函数\n3.beforeCreate – 生命周期钩子函数被执行\n4.Init injections&reactivity – Vue内部添加data和methods等\n5.created – 生命周期钩子函数被执行, 实例创建\n6.接下来是编译模板阶段 –开始分析\nmain.js\nimport Vue from 'vue'import App from './App.vue'Vue.config.productionTip = falsenew Vue({ // el: "#app", // vue实例编译后的模板挂载到index.html的id叫app的标签上 render: h => h(App),}).$mount("#app")\n\n创建components/Life.vue文件,并且在App.vue里引入\n<div> <h1>1. 生命周期</h1> <Life></Life></div>\n\nimport Life from './components/Life'export default { components: { Life }}\n\n设置components/Life.vue文件内容\n<div> <p>学习生命周期 - 看控制台打印</p> <p id="myP">{{ msg }}</p></div>\n\n\n\nexport default { data(){ return { msg: "hello, Vue", } }, // 一. 初始化 // new Vue()以后, vue内部给实例对象添加了一些属性和方法, // data和methods初始化"之前" beforeCreate(){ console.log("beforeCreate -- 执行"); console.log(this.msg); // undefined } // data和methods初始化以后 // 场景: 网络请求, 注册全局事件 created(){ console.log("created -- 执行"); console.log(this.msg); // hello, Vue } }\n\n\n挂载阶段1.template选项检查\n 有 - 编译template返回render渲染函数\n 无 – 编译el选项对应标签作为template(要渲染的模板)(非脚手架)\n2.虚拟DOM挂载成真实DOM之前\n3.beforeMount – 生命周期钩子函数被执行\n4.Create … – 把虚拟DOM和渲染的数据一并挂到真实DOM上\n5.真实DOM挂载完毕\n6.mounted – 生命周期钩子函数被执行\ncomponents/Life.vue 中设置beforeMount和mounted\n<div> <p>学习生命周期 - 看控制台打印</p> <p id="myP">{{ msg }}</p></div>\n\n\n\nexport default { // ...省略其他代码 // 二. 挂载 // 真实DOM挂载之前 // 场景: 预处理data, 不会触发updated钩子函数 beforeMount(){ console.log("beforeMount -- 执行"); console.log(document.getElementById("myP")); // null this.msg = "重新值" }, // 真实DOM挂载以后 // 场景: 挂载后真实DOM mounted(){ console.log("mounted -- 执行"); console.log(document.getElementById("myP")); // p }}\n\n\n注意:\n\n一般工作中主要会用到created和mounted这两个生命周期。\n可以在created中修改data中的数据,以及注册全局事件,不可以去查询DOM元素\n可以在mounted中做所有created中可以执行的事件,也可以在这个时期去查询DOM元素\n以上 - 可以直接只用mounted确保万无一失\n\n\n\n更新阶段含义讲解:\n1.当data里数据改变, 更新DOM之前\n2.beforeUpdate – 生命周期钩子函数被执行\n3.Virtual DOM…… – 虚拟DOM重新渲染, 打补丁到真实DOM\n4.updated – 生命周期钩子函数被执行\n5.当有data数据改变 – 重复这个循环\ncomponents/Life.vue 中设置beforeUpdate和updated\n准备ul+li循环, 按钮添加元素, 触发data改变->导致更新周期开始\n<div> <p>学习生命周期 - 看控制台打印</p> <p id="myP">{{ msg }}</p> <ul id="myUL"> <li v-for="(val, index) in arr" :key="index"> {{ val }} </li> </ul> <button @click="arr.push(1000)">点击末尾加值</button></div>\n\n\n\nexport default { data(){ return { msg: "hello, Vue", arr: [5, 8, 2, 1] } }, // ...省略其他代码 // 三. 更新 // 前提: data数据改变才执行 // 更新之前 beforeUpdate() { console.log("beforeUpdate -- 执行"); console.log(document.querySelectorAll("#myUL>li")[4]); // undefined }, // 更新之后 // 场景: 获取更新后的真实DOM updated() { console.log("updated -- 执行"); console.log(document.querySelectorAll("#myUL>li")[4]); // li },}\n\n\n问:\n\n什么时候执行updated钩子函数?\n当数据发生变化并更新页面后\n\n在哪可以获取更新后的DOM?\n在updated钩子函数里\n\n\n注意:\n\n更新阶段有这样一种情景:如果在update更新阶段时对数据做了新的处理,可能又会触发update更新阶段,以此往复会出现修改一次数据触发多次更新阶段逻辑的过程,这样会出现项目代码执行效率低下,在开发过程中,尽量不要去使用update的两个生命周期,如果需要监听某个值的改变应该用到watch!\n\n销毁阶段含义讲解:\n1.当$destroy()被调用 – 比如组件DOM被移除(例v-if)\n2.beforeDestroy – 生命周期钩子函数被执行\n3.拆卸数据监视器、子组件和事件侦听器\n4.实例销毁后, 最后触发一个钩子函数\n5.destroyed – 生命周期钩子函数被执行\ncomponents/Life.vue - 准备生命周期方法(Life组件即将要被删除)\nexport default { // ...省略其他代码 created() { // ...省略其他代码 this.timer = setInterval(() => { console.log("哈哈哈"); }, 1000) }, // 四. 销毁 // 前提: v-if="false" 销毁Vue实例 // 场景: 移除全局事件, 移除当前组件, 计时器, 定时器 beforeDestroy(){ // console.log('beforeDestroy -- 执行'); clearInterval(this.timer) }, destroyed(){ // console.log("destroyed -- 执行"); }}\n\nApp.vue - 点击按钮让Life组件从DOM上移除 -> 导致Life组件进入销毁阶段\n<div> <h1>1. 生命周期</h1> <Life v-if="show"></Life> <button @click="show = false">销毁组件</button></div>\n\n\n\nimport Life from './components/Life'export default { data(){ return { show: true } },}\n\n注意:\n\n销毁阶段一般是拿来销毁一些全局事件, 移除当前组件, 计时器, 定时器,特别注意!如果子组件中涉及到定时器setInterval时,一定注意当组件销毁时这个定时器是否关闭clearInterval\n\n总结常用的生命周期钩子:\n1.mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。 2.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。 \n关于销毁Vue实例 \n1.销毁后借助Vue开发者工具看不到任何信息。 \n2.销毁后自定义事件会失效,但原生DOM事件依然有效。 \n3.一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。\nVue实例的方法$refs - 获取DOM元素\n目标: 利用 ref 和 $refs 可以用于获取 dom 元素\n\n步骤:\n\n创建components/More.vue,设置基本结构\n<div> <p>1. 获取原生DOM元素</p> <h1 id="h">我是一个孤独可怜又能吃的h1</h1></div>\n\n// 目标: 获取组件对象export default {}\nApp.vue引入 组件\n<div> <h1>1. 生命周期</h1> <Life v-if="show"></Life> <button @click="show = false">销毁组件</button> <hr> <h1>2. axios使用</h1> <UseAxios></UseAxios> <hr> <h1>3. $refs的使用</h1> <More></More></div>\n\nimport More from './components/More'export default { components: { More }}\n给标签设置ref属性\n<div> <p>1. 获取原生DOM元素</p> <h1 id="h" ref="myH">我是一个孤独可怜又能吃的h1</h1></div>\n\n\n\n// 目标: 获取组件对象 // 组件起别名ref export default { }\n获取组件对象\n<div> <p>1. 获取原生DOM元素</p> <h1 id="h" ref="myH">我是一个孤独可怜又能吃的h1</h1></div>\n\n// 目标: 获取组件对象// 组件起别名ref// 恰当时机, 获取组件对象 - mountedexport default { mounted(){ console.log(document.getElementById("h")); // h1 console.log(this.$refs.myH); // h1\t},}\n\n$refs - 获取组件对象\n通过$refs 可以获取整个组件对象,并且使用该组件中的数据与方法\n\n步骤:\n\n创建components/Child/Demo.vue,组件\n<div> <p>我是Demo组件</p></div>\n\nexport default { methods: { fn(){ console.log("demo组件内的方法被调用了"); } }}\n在More.vue中 - 获取组件对象并设置ref属性\n<div> <p>1. 获取原生DOM元素</p> <h1 id="h" ref="myH">我是一个孤独可怜又能吃的h1</h1> <p>2. 获取组件对象 - 可调用组件内一切</p> <Demo ref="de"></Demo></div>\n\n// 目标: 获取组件对象// 1. 创建组件/引入组件/注册组件/使用组件// 2. 组件起别名ref// 3. 恰当时机, 获取组件对象import Demo from './Child/Demo'export default { mounted(){ console.log(document.getElementById("h")); // h1 console.log(this.$refs.myH); // h1 }, components: { Demo }}\n使用$refs调用组件中的方法\n// 目标: 获取组件对象// 1. 创建组件/引入组件/注册组件/使用组件// 2. 组件起别名ref// 3. 恰当时机, 获取组件对象import Demo from './Child/Demo'export default { mounted(){ console.log(document.getElementById("h")); // h1 console.log(this.$refs.myH); // h1 let demoObj = this.$refs.de; demoObj.fn() }, components: { Demo }}\n\n\n总结: ref定义值, 通过$refs.值 来获取组件对象, 就能继续调用组件内的变量\n\n$nextTick使用\nVue更新DOM-异步的!(数据与页面有延时)\n\n\n案例:点击count++, 马上通过”原生DOM”拿标签内容, 无法拿到新值\n\n步骤:\n\n在 components/Move.vue 中添加以下代码\n<div> <p>3. vue更新DOM是异步的</p> <p ref="myP">{{ count }}</p> <button @click="btn">点击count+1, 马上提取p标签内容</button></div>\n\nimport Demo from './Child/Demo'export default { methods: { btn(){ this.count++; // vue监测数据更新, 开启一个DOM更新队列(异步任务) console.log(this.$refs.myP.innerHTML); // 0 } }}\n\n发现: 页面数据更改了,但从页面上拿到的dom元素中的值还是更改前的数据\n\n使用$nextTick()\n等DOM更新后,触发$nextTick()的函数体执行\n<div> <p>3. vue更新DOM是异步的</p> <p ref="myP">{{ count }}</p> <button @click="btn">点击count+1, 马上提取p标签内容</button></div>\n\nimport Demo from './Child/Demo'export default { methods: { btn(){ this.count++; // vue监测数据更新, 开启一个DOM更新队列(异步任务) // console.log(this.$refs.myP.innerHTML); // 0 // 原因: Vue更新DOM异步 // 解决: this.$nextTick() // 过程: DOM更新完会挨个触发$nextTick里的函数体 this.$nextTick(() => { console.log(this.$refs.myP.innerHTML); // 1 }) } }}\n\nVue路由路由是一对一的映射关系,vue中的路由是路径和组件的映射关系,通过路径的不同在页面上展示不同的组件\n为什么使用路由\n目标: 在一个页面里, 切换业务场景,不会刷新\n\n具体使用示例: 网易云音乐 https://music.163.com/\n单页面应用(SPA): 所有功能在一个html页面上实现\n前端路由作用: 实现业务场景切换\n优点:\n\n整体不刷新页面,用户体验更好\n\n数据传递容易, 开发效率高\n\n\n缺点:\n\n首次加载会比较慢一点。不利于seo(搜索引擎优化)\n\n总结:\n\n单页面应用: 所有业务都在一个页面上编写,只有一个html\n通过路由来切换单页面的业务场景\n\nvue-router介绍\n目标: 如何在Vue项目中集成路由\n\n官网: https://router.vuejs.org/zh/\n\nvue-router模块包\n\n它和 Vue.js 深度集成\n可以定义 - 视图表(映射规则)\n\n\n模块化的\n\n提供2个内置全局组件(router-link,router-view)\n\n声明式导航自动激活的 CSS class 的链接\n\n\n组件分类\n目标: .vue文件分2类, 一个是页面组件, 一个是复用组件\n\nsrc/views(或pages) 文件夹 和 src/components文件夹\n\n页面组件 - 页面展示 - 配合路由用\n复用组件 - 展示数据/常用于复用\n\n\n\n总结: views下的页面组件, 配合路由切换, components下的一般引入到views下的vue中复用展示数据\n\nvue-router使用\n需求: 通过切换不同的路由链接,来实现内容页面的切换\nApp.vue - 页面标签和样式准备\n分析:\n\n下载vue-router模块到当前项目\n在main.js中引入VueRouter函数\n在Vue.use()上,添加全局RouterLink和RouterView组件\n创建路由规则数组(路由结构) - 保证路径和组件名称一一对应\n利用规则生产路由对象\n把路由对象注入到new Vue实例中\n用router-view作为挂载点,切换不同的路由页面\nvue-router文档\n\n步骤:\n\n安装\n// 1. 下载vue-routeryarn add vue-router@3.5.1\n在main.js 中导入路由\n// 2. 引入import VueRouter from 'vue-router'\n使用路由插件\n// 在vue中,使用使用vue的插件,都需要调用Vue.use()// 3. 注册全局组件Vue.use(VueRouter)\n创建路由规则数组\nimport Find from './views/Find.vue' // @是src的绝对地址import My from './views/My.vue'import Part from './views/Part.vue'// 4. 规则数组const routes = [ { path: "/find", // 路由 component: Find // 组件名称 }, { path: "/my", component: My }, { path: "/part", component: Part }]\n\nFind.vue\n<template> <div> <p>推荐</p> <p>排行榜</p> <p>歌单</p> </div></template>\n\nMy.vue\n<template> <div> <p>我的收藏</p> <p>我的历史记录</p> </div></template>\n\nPart.vue\n<template> <div> <p>关注明星</p> <p>发现精彩</p> <p>寻找伙伴</p> <p>加入我们</p> </div></template>\n创建路由对象 - 传入规则\n// 5. 生成路由对象(传入配置对象)const router = new VueRouter({ routes,// routes是固定key(传入规则数组)})\n关联到vue实例\nnew Vue({ router})\n设置App.vue引入挂载路由组件\n<template> <div> <div class="footer_wrap"> <a href="#/find">发现音乐</a> <a href="#/my">我的音乐</a> <a href="#/part">朋友</a> </div> <div class="top"> <!-- 7. 设置挂载点-当url的hash值路径切换, 显示规则里对应的组件到这 --> <router-view></router-view> </div> </div></template><script>export default {};</script><style scoped>.footer_wrap { position: fixed; left: 0; top: 0; display: flex; width: 100%; text-align: center; background-color: #333; color: #ccc;}.footer_wrap a { flex: 1; text-decoration: none; padding: 20px 0; line-height: 20px; background-color: #333; color: #ccc; border: 1px solid black;}.footer_wrap a:hover { background-color: #555;}.top { padding-top: 62px;}</style>\n\n\n总结: \n\n下载路由模块, 编写对应规则注入到vue实例上, 使用router-view挂载点显示切换的路由\n\n一切都围绕着hash值变化为准,切换url上的hash值,开始匹配规则,对应组件展示到router-view位置\n\n\n\n声明式导航基础用法\n可用全局组件router-link来替代a标签\n\n\n vue-router提供了一个全局组件 router-link\n router-link实质上最终会渲染成a链接 to属性等价于提供 href属性**(to无需#)**\n router-link提供了声明式导航高亮的功能(自带类名)\n\n\n<template> <div> <div class="footer_wrap"> <router-link to="/find">发现音乐</router-link> <router-link to="/my">我的音乐</router-link> <router-link to="/part">朋友</router-link> </div> <div class="top"> <router-view></router-view> </div> </div></template><script> // 目标: 声明式导航 - 基础使用 // 本质: vue-router提供的全局组件 "router-link"替代a标签 // 1. router-link 替代a标签 // 2. to属性 替代href属性 // 好处: router-link自带高亮的类名(激活时类名) // 3. 对激活的类名做出样式的编写 export default {};</script><style scoped>/* 省略了 其他样式 */.footer_wrap .router-link-active{ color: white; background: black;}</style>\n\n\n 总结: \n\nrouter-link是全局组成的组件,本质就是a标签\n当使用router-link时,必须传入to属性,指定路由路径值\n自带类名\n\n router-link没啥大用\n\n跳转传参\n目标: 在跳转路由时, 可以给路由对应的组件内传值\n\n在router-link上的to属性传值, 语法格式如下\n\n/path?参数名=值\n/path/值 – 需要路由对象提前配置 path: “/path/参数名”\n\n对应页面组件接收传递过来的值\n\n$route.query.参数名\n$route.params.参数名\n\n步骤:\n\n路由定义\n{ path: "/part", component: Part},{ path: "/part/:username", // 有:的路径代表要接收具体的值 component: Part},\n修改Part.vue中接受的路由参数\n<template> <div> <p>关注明星</p> <p>发现精彩</p> <p>寻找伙伴</p> <p>加入我们</p> <p>人名: {{ $route.query.name }} -- {{ $route.params.username }}</p> </div></template><script>// 目标: 声明式导航 - 传值// 方式1:// to="/path?参数名=值"// 接收: $route.query.参数名// 方式2:// (1): 路由规则path上 定义 /path/:参数名// (2): to="/path/值"// 接收: $route.params.参数名export default {};</script>\n导航跳转, 传值给Part.vue组件\nApp.vue\n<router-link to="/part?name=张三">朋友-张三</router-link><router-link to="/part/李四">朋友-李四</router-link>\n\n\n总结: \n\n?key=value 用$route.query.key 取值\n\n/值 提前在路由规则/path/:key 用$route.params.key 取值 (可读性很差,建议不要使用)\n\n\n\n声明导航 - 类名区别\n目标: router-link自带的2个类名的区别是什么\n\n观察路由嵌套导航的样式\n\nrouter-link-exact-active (精确匹配) url中hash值路径, 与href属性值完全相同, 设置此类名\n\nrouter-link-active (模糊匹配) url中hash值, 包含href属性值这个路径\n\n\n\n路由重定向和模式修改重定向场景:首次进入页面时,没有任何路由的hash值,页面元素会显示空白,如何解决?\n\n目标: 匹配path后, 强制切换到目标path上\n\n\n网页打开url默认hash值是/路径\nredirect是设置要重定向到哪个路由路径\n\nconst routes = [ { path: "/", // 默认hash值路径 redirect: "/find" // 重定向到/find // 浏览器url中#后的路径被改变成/find-重新匹配数组规则 }]\n\n\n总结: 强制重定向后, 还会重新来数组里匹配一次规则\n\n实际作用:\n\n当某个路由在使用过程中废弃掉之后,将原来路由地址重定向到其他地址\n当满足某个条件时(如:登录信息过期),可以重定向到某个指定位置\n\n路由 - 404页面\n目标: 如果路由hash值, 没有和数组里规则匹配\n\n默认给一个404页面\n\n语法: 路由最后, path匹配*(任意路径) – 前面不匹配就命中最后这个, 显示对应组件页面\n步骤:\n\n创建NotFound页面\n<template> <img src="../assets/404.png" alt=""></template><script>export default {}</script><style scoped> img{ width: 100%; }</style>\n在main.js - 修改路由配置\nimport NotFound from '@/views/NotFound'const routes = [ // ...省略了其他配置 // 404在最后(规则是从前往后逐个比较path) { path: "*", component: NotFound }]\n\n\n总结: 如果路由未命中任何规则, 给出一个兜底的404页面\n\n路由模式修改\n目标: 修改路由在地址栏的模式\n\nhash路由例如: http://localhost:8080/#/home\nhistory路由例如: http://localhost:8080/home (以后上线需要服务器端支持, 否则找的是文件夹)\nconst router = new VueRouter({ routes, mode: "history" // 打包上线后需要后台支持, 模式是hash})\n\n\n唯一区别,是路由后缀有无#号\n\n编程式导航基础用法语法:\npath和name二选一!\nthis.$router.push({ path: "路由路径", // 都去 router/index.js定义 name: "路由名"})\n\n\nmain.js - 路由数组里, 给路由起名字\n代码规范:一般情况下,path除去/以外的部分和name需要保持一致\n{ path: "/find", name: "Find", component: Find},{ path: "/my", name: "My", component: My},{ path: "/part", name: "Part", component: Part},\nApp.vue - 换成span 配合js的编程式导航跳转\n<template> <div> <div class="footer_wrap"> <span @click="btn('/find', 'Find')">发现音乐</span> <span @click="btn('/my', 'My')">我的音乐</span> <span @click="btn('/part', 'Part')">朋友</span> </div> <div class="top"> <router-view></router-view> </div> </div></template><script>// 目标: 编程式导航 - js方式跳转路由// 语法:// this.$router.push({path: "路由路径"})// this.$router.push({name: "路由名"})// 注意:// 虽然用name跳转, 但是url的hash值还是切换path路径值// 场景:// 方便修改: name路由名(在页面上看不见随便定义)// path可以在url的hash值看到(尽量符合组内规范)export default { methods: { btn(targetPath, targetName){ // 方式1: path跳转 this.$router.push({ // path: targetPath, name: targetName }) } }};</script><style scoped>.footer_wrap { position: fixed; left: 0; top: 0; display: flex; width: 100%; text-align: center; background-color: #333; color: #ccc;}.footer_wrap span { flex: 1; text-decoration: none; padding: 20px 0; line-height: 20px; background-color: #333; color: #ccc; border: 1px solid black;}.footer_wrap span:hover { background-color: #555;}.top { padding-top: 62px;}/*激活时样式 */.footer_wrap .router-link-active{ color: white; background: black;}</style>\n\n跳转传参\n目标: JS跳转路由, 传参\n\n语法 query / params 任选 一个\nthis.$router.push({ path: "路由路径" name: "路由名", query: { \t"参数名": 值 } params: {\t\t"参数名": 值 }})// 方式1:// params => $route.params.参数名// 方式2:// query => $route.query.参数名\n\n格外注意: 使用path的话,只能和query一起使用,不可以使用params(会自动忽略)\nApp.vue\n<template> <div> <div class="footer_wrap"> <span @click="btn('/find', 'Find')">发现音乐</span> <span @click="btn('/my', 'My')">我的音乐</span> <span @click="oneBtn">朋友-张三</span> <span @click="twoBtn">朋友-李四</span> </div> <div class="top"> <router-view></router-view> </div> </div></template><script>// 目标: 编程式导航 - 跳转路由传参// 方式1:// params => $route.params.参数名// 方式2:// query => $route.query.参数名// 重要: path会自动忽略params// 注意: 如果当前url上"hash值和?参数"与你要跳转到的"hash值和?参数"一致, // 爆出冗余导航的问题, 不会跳转路由export default { methods: { btn(targetPath, targetName){ // 方式1: path跳转 this.$router.push({ path: targetPath, params: { username: '东东' } }) }, oneBtn(){ this.$router.push({ // 方式2:name跳转 name: 'Part', params: { username: '张三' } }) }, twoBtn(){ this.$router.push({ name: 'Part', query: { name: '李四' } }) } }};</script><style scoped>.footer_wrap { position: fixed; left: 0; top: 0; display: flex; width: 100%; text-align: center; background-color: #333; color: #ccc;}.footer_wrap span { flex: 1; text-decoration: none; padding: 20px 0; line-height: 20px; background-color: #333; color: #ccc; border: 1px solid black;}.footer_wrap span:hover { background-color: #555;}.top { padding-top: 62px;}/*激活时样式 */.footer_wrap .router-link-active{ color: white; background: black;}</style>\n\n路由嵌套\n目标: 在现有的一级路由下, 再嵌套二级路由\n\n二级路由示例-网易云音乐-发现音乐下\n\nrouter-view嵌套架构图\n\n创建所有组件\n\n\nmain.js– 继续配置2级路由\n一级路由path从/开始定义\n二级路由往后path直接写名字, 无需/开头\n嵌套路由在上级路由的children数组里编写路由信息对象\nimport Find from './views/Find.vue' // @是src的绝对地址import My from './views/My.vue'import Part from './views/Part.vue'import NotFound from './views/NotFound.vue'import Recommend from './views/Second/Recommend.vue'import Ranking from './views/Second/Ranking.vue'import SongList from './views/Second/SongList.vue'const routes = [ { path: "/", // 默认hash值路径 redirect: "/find" // 重定向到/find // 浏览器url中#后的路径被改变成/find-重新匹配规则 }, { path: "/find", name: "Find", component: Find, children: [ { path: "recommend", component: Recommend }, { path: "ranking", component: Ranking }, { path: "songlist", component: SongList } ] }]\n配置router-view\n\nApp.vue的router-view负责发现音乐和我的音乐页面, 切换\n<template> <div> <div class="footer_wrap"> <router-link to="/find">发现音乐</router-link> <router-link to="/my">我的音乐</router-link> </div> <div class="top"> <router-view></router-view> </div> </div></template>\n\nFind.vue的的router-view负责发现音乐下的, 三个页面, 切换\n<template> <div> <p>推荐</p> <p>排行榜</p> <p>歌单</p> <div class="nav_main"> <router-link to="/find/recommend">推荐</router-link> <router-link to="/find/ranking">排行榜</router-link> <router-link to="/find/songlist">歌单</router-link> </div> <div style="1px solid red;"> <router-view></router-view> </div> </div></template>\n运行 - 点击导航观察嵌套路由在哪里展示\n\n\n\n总结: \n\n嵌套路由, 找准在哪个页面里写router-view和对应规则里写children\n页面跳转时,多级路由需要把每个阶段的路由地址拼接起来,如: /find/songlist\n写非一级路由时,path里的内容不能有/\n\n\n全局前置守卫beforeEach\n目的: 路由跳转之前, 先执行一次前置守卫函数, 判断是否可以正常跳转\n\n场景:在跳转路由前, 判断用户登陆了才能去<我的音乐>页面, 未登录弹窗提示回到发现音乐页面\n\n在路由对象上使用固定方法beforeEach\n// 目标: 路由守卫// 场景: 当你要对路由权限判断时// 语法: router.beforeEach((to, from, next)=>{//路由跳转"之前"先执行这里, 决定是否跳转})// 参数1 to: 要跳转到的路由 (路由对象信息) 目标// 参数2 from: 从哪里跳转的路由 (路由对象信息) 来源// 参数3 next: 函数体 - next()才会让路由正常的跳转切换, next(false)在原地停留, next("强制修改到另一个路由路径上")// 注意: 如果不调用next, 页面留在原地// 例子: 判断用户是否登录, 是否决定去"我的音乐"/myconst isLogin = true; // 登录状态(未登录)router.beforeEach((to, from, next) => { if (to.path === "/my" && isLogin === false) { alert("请登录") next(false) // 阻止路由跳转 // next('/part') // 跳转别的地方 } else { next() // 正常放行 }})\n\n\n总结: \n\nnext()放行\nnext(false)留在原地不跳转路由\nnext(path路径)强制换成对应path路径跳转\n\n\n扩展:afterEach\n添加一个导航钩子,在每次导航后执行。返回一个删除注册钩子的函数。\n\n语法:\nrouter.afterEach((to,from)=>{}) \n\n注意:该钩子一般用于跳转到某个页面后,调用某个身份验证接口获取权限\n组件内的独享路由钩子\nbeforeRouteEnter - 在进入到该组件之前,触发\nbeforeRouteUpdate - 仅是当前组件内部的路由发生变化(主路由地址没有改变)时,触发\nbeforeRouteLeave - 离开该组件之前,触发\n\n<script> export default { beforeRouteEnter(to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 `this` // 因为当守卫执行前,组件实例还没被创建 console.log('进find') next() }, beforeRouteUpdate(to, from, next) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 `this` console.log('被复用') next() }, beforeRouteLeave(to, from, next) { // 导航离开该组件的对应路由时调用 // 可以访问组件实例 `this` console.log('离开') next() } };</script>\n"},{"title":"Vue3","url":"/2022/12/13/Vue3/","content":"Vue3带来了什么1.性能的提升\n打包大小减少41%\n\n初次渲染快55%, 更新渲染快133%\n\n内存减少54%\n……\n\n\n2.源码的升级\n使用Proxy代替defineProperty实现响应式\n\n重写虚拟DOM的实现和Tree-Shaking\n……\n\n\n3.拥抱TypeScript\nVue3可以更好的支持TypeScript\n\n4.新的特性\nComposition API(组合API)\n\nsetup配置\nref与reactive\nwatch与watchEffect\nprovide与inject\n……\n\n\n新的内置组件\n\nFragment \nTeleport\nSuspense\n\n\n其他改变\n\n新的生命周期钩子\ndata 选项应始终被声明为一个函数\n移除keyCode支持作为 v-on 的修饰符\n……\n\n\n\n常用的Composition API1.setup\n理解:Vue3.0中一个新的配置项,值为一个函数。\nsetup是所有Composition API(组合API)“ 表演的舞台 ”。\n组件中所用到的:数据、方法等等,均要配置在setup中。\nsetup函数的两种返回值:\n若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)\n若返回一个渲染函数:则可以自定义渲染内容。(了解)\n\n\n注意点:\n尽量不要与Vue2.x配置混用\nVue2.x配置(data、methos、computed…)中可以访问到setup中的属性、方法。\n但在setup中不能访问到Vue2.x配置(data、methos、computed…)。\n如果有重名, setup优先。\n\n\nsetup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)\n\n\n\n<template>\t<h1>一个人的信息</h1>\t<h2>姓名:{{name}}</h2>\t<h2>年龄:{{age}}</h2>\t<h2>性别:{{sex}}</h2>\t<h2>a的值是:{{a}}</h2>\t<button @click="sayHello">说话(Vue3所配置的——sayHello)</button>\t<br>\t<button @click="sayWelcome">说话(Vue2所配置的——sayWelcome)</button>\t<br>\t<button @click="test1">测试一下在Vue2的配置中去读取Vue3中的数据、方法</button>\t<br>\t<button @click="test2">测试一下在Vue3的setup配置中去读取Vue2中的数据、方法</button></template>\n\n对比:\nvue2中是这样配置的:\n\nvue3中是这样配置的:\n(此处只是测试一下setup,暂时不考虑响应式的问题)\n\n注意点:\n\nsetup执行的时机\n\n在beforeCreate之前执行一次,this是undefined。\n\n\nsetup的参数\n\nprops:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。\ncontext:上下文对象\nattrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs。\nslots: 收到的插槽内容, 相当于 this.$slots。\nemit: 分发自定义事件的函数, 相当于 this.$emit。\n\n\n\n\n\n\n2.ref函数\n作用: 定义一个响应式的数据\n语法: const xxx = ref(initValue) \n创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。\nJS中操作数据: xxx.value\n模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>\n\n\n备注:\n接收的数据可以是:基本类型、也可以是对象类型。\n基本类型的数据:响应式依然是靠Object.defineProperty()的get与set完成的。\n对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。\n\n\n\n<template>\t<h1>一个人的信息</h1>\t<h2>姓名:{{name}}</h2>\t<h2>年龄:{{age}}</h2>\t<h3>工作种类:{{job.type}}</h3>\t<h3>工作薪水:{{job.salary}}</h3>\t<button @click="changeInfo">修改人的信息</button></template><script>\timport {ref} from 'vue'\texport default {\t\tname: 'App',\t\tsetup(){\t\t\t//数据\t\t\tlet name = ref('张三')\t\t\tlet age = ref(18)\t\t\tlet job = ref({\t\t\t\ttype:'前端工程师',\t\t\t\tsalary:'30K'\t\t\t})\t\t\t//方法\t\t\tfunction changeInfo(){\t\t\t\t// name.value = '李四'\t\t\t\t// age.value = 48\t\t\t\tconsole.log(job.value)\t\t\t\t// job.value.type = 'UI设计师'\t\t\t\t// job.value.salary = '60K'\t\t\t\t// console.log(name,age)\t\t\t}\t\t\t//返回一个对象(常用)\t\t\treturn {\t\t\t\tname,\t\t\t\tage,\t\t\t\tjob,\t\t\t\tchangeInfo\t\t\t}\t\t}\t}</script>\n\n\n3.reactive函数\n作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)\n语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)\nreactive定义的响应式数据是“深层次的”。\n内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。\n\n<template>\t<h1>一个人的信息</h1>\t<h2>姓名:{{person.name}}</h2>\t<h2>年龄:{{person.age}}</h2>\t<h3>工作种类:{{person.job.type}}</h3>\t<h3>工作薪水:{{person.job.salary}}</h3>\t<h3>爱好:{{person.hobby}}</h3>\t<h3>测试的数据c:{{person.job.a.b.c}}</h3>\t<button @click="changeInfo">修改人的信息</button></template><script>\timport {reactive} from 'vue'\texport default {\t\tname: 'App',\t\tsetup(){\t\t\t//数据\t\t\tlet person = reactive({\t\t\t\tname:'张三',\t\t\t\tage:18,\t\t\t\tjob:{\t\t\t\t\ttype:'前端工程师',\t\t\t\t\tsalary:'30K',\t\t\t\t\ta:{\t\t\t\t\t\tb:{\t\t\t\t\t\t\tc:666\t\t\t\t\t\t}\t\t\t\t\t}\t\t\t\t},\t\t\t\thobby:['抽烟','喝酒','烫头']\t\t\t})\t\t\t//方法\t\t\tfunction changeInfo(){\t\t\t\tperson.name = '李四'\t\t\t\tperson.age = 48\t\t\t\tperson.job.type = 'UI设计师'\t\t\t\tperson.job.salary = '60K'\t\t\t\tperson.job.a.b.c = 999\t\t\t\tperson.hobby[0] = '学习'\t\t\t}\t\t\t//返回一个对象(常用)\t\t\treturn {\t\t\t\tperson,\t\t\t\tchangeInfo\t\t\t}\t\t}\t}</script>\n\n\n4.reactive对比ref\n从定义数据角度对比:\n ref用来定义:基本类型数据。\n reactive用来定义:对象(或数组)类型数据。\n 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象。\n\n\n从原理角度对比:\n ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)。\n reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。\n\n\n从使用角度对比:\n ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value。\n reactive定义的数据:操作数据与读取数据:均不需要.value。\n\n\n\n\nVue3.0中的响应式原理vue2.x的响应式\n实现原理:\n\n对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。\n\n数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。\nObject.defineProperty(data, 'count', { get () {}, set () {}})\n\n\n存在问题:\n\n新增属性、删除属性, 界面不会更新。\n直接通过下标修改数组, 界面不会自动更新。\n\n\n\n模拟Vue2的响应式:\nlet p = {}Object.defineProperty(p,'name',{\tconfigurable:true,\tget(){ //有人读取name时调用\t\treturn person.name\t},\tset(value){ //有人修改name时调用\t\tconsole.log('有人修改了name属性,我发现了,我要去更新界面!')\t\tperson.name = value\t}})Object.defineProperty(p,'age',{\tget(){ //有人读取age时调用\t\treturn person.age\t},\tset(value){ //有人修改age时调用\t\tconsole.log('有人修改了age属性,我发现了,我要去更新界面!')\t\tperson.age = value\t}})\n\n\n\nVue3.0的响应式\n实现原理: \n\n通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。\n\n通过Reflect(反射): 对源对象的属性进行操作。\n\nMDN文档中描述的Proxy与Reflect:\n\nProxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy\n\nReflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect\nnew Proxy(data, {\t// 拦截读取属性值 get (target, prop) { \treturn Reflect.get(target, prop) }, // 拦截设置属性值或添加新属性 set (target, prop, value) { \treturn Reflect.set(target, prop, value) }, // 拦截删除属性 deleteProperty (target, prop) { \treturn Reflect.deleteProperty(target, prop) }})proxy.name = 'tom' \n\n\n\n\n\n模拟Vue3的响应式:\n//模拟Vue3中实现响应式const p = new Proxy(person,{\t//有人读取p的某个属性时调用\tget(target,propName){\t\tconsole.log(`有人读取了p身上的${propName}属性`)\t\treturn Reflect.get(target,propName)\t},\t//有人修改p的某个属性、或给p追加某个属性时调用\tset(target,propName,value){\t\tconsole.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`)\t\tReflect.set(target,propName,value)\t},\t//有人删除p的某个属性时调用\tdeleteProperty(target,propName){\t\tconsole.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)\t\treturn Reflect.deleteProperty(target,propName)\t}})\n\n\n计算属性与监视1.computed函数\n与Vue2.x中computed配置功能一致\n\n写法\nimport {computed} from 'vue'setup(){ ...\t//计算属性——简写 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] } })}\n\n2.watch函数\n与Vue2.x中watch配置功能一致\n\n两个小“坑”:\n\n监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。\n监视reactive定义的响应式数据中某个属性时:deep配置有效。\n\n//情况一:监视ref定义的响应式数据watch(sum,(newValue,oldValue)=>{\tconsole.log('sum变化了',newValue,oldValue)},{immediate:true})//情况二:监视多个ref定义的响应式数据watch([sum,msg],(newValue,oldValue)=>{\tconsole.log('sum或msg变化了',newValue,oldValue)}) /* 情况三:监视reactive定义的响应式数据\t\t\t若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!\t\t\t若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 */watch(person,(newValue,oldValue)=>{\tconsole.log('person变化了',newValue,oldValue)},{immediate:true,deep:false}) //此处的deep配置不再奏效//情况四:监视reactive定义的响应式数据中的某个属性watch(()=>person.job,(newValue,oldValue)=>{\tconsole.log('person的job变化了',newValue,oldValue)},{immediate:true,deep:true}) //情况五:监视reactive定义的响应式数据中的某些属性watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{\tconsole.log('person的job变化了',newValue,oldValue)},{immediate:true,deep:true})//特殊情况watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue)},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效\n\n3.watchEffect函数\nwatch的套路是:既要指明监视的属性,也要指明监视的回调。\n\nwatchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。\n\nwatchEffect有点像computed:\n\n但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。\n而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。\n\n//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。watchEffect(()=>{ const x1 = sum.value const x2 = person.age console.log('watchEffect配置的回调执行了')})\n\n\n生命周期\n\nVue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:\n\nbeforeDestroy改名为 beforeUnmount\ndestroyed改名为 unmounted\n\n\nVue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:\n\nbeforeCreate===>setup()\ncreated=======>setup()\nbeforeMount ===>onBeforeMount\nmounted=======>onMounted\nbeforeUpdate===>onBeforeUpdate\nupdated =======>onUpdated\nbeforeUnmount ==>onBeforeUnmount\nunmounted =====>onUnmounted\n\n<script>import{ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'\texport default {\t\tname: 'Demo',\t\t\t\tsetup(){\t\t\tconsole.log('---setup---')\t\t\t//数据\t\t\tlet sum = ref(0)\t\t\t//通过组合式API的形式去使用生命周期钩子\t\t\tonBeforeMount(()=>{\t\t\t\tconsole.log('---onBeforeMount---')\t\t\t})\t\t\tonMounted(()=>{\t\t\t\tconsole.log('---onMounted---')\t\t\t})\t\t\tonBeforeUpdate(()=>{\t\t\t\tconsole.log('---onBeforeUpdate---')\t\t\t})\t\t\tonUpdated(()=>{\t\t\t\tconsole.log('---onUpdated---')\t\t\t})\t\t\tonBeforeUnmount(()=>{\t\t\t\tconsole.log('---onBeforeUnmount---')\t\t\t})\t\t\tonUnmounted(()=>{\t\t\t\tconsole.log('---onUnmounted---')\t\t\t})\t\t\t//返回一个对象(常用)\t\t\treturn {sum}\t\t},\t\t//通过配置项的形式使用生命周期钩子\t\tbeforeCreate() {\t\t\tconsole.log('---beforeCreate---')\t\t},\t\tcreated() {\t\t\tconsole.log('---created---')\t\t},\t\tbeforeMount() {\t\t\tconsole.log('---beforeMount---')\t\t},\t\tmounted() {\t\t\tconsole.log('---mounted---')\t\t},\t\tbeforeUpdate(){\t\t\tconsole.log('---beforeUpdate---')\t\t},\t\tupdated() {\t\t\tconsole.log('---updated---')\t\t},\t\tbeforeUnmount() {\t\t\tconsole.log('---beforeUnmount---')\t\t},\t\tunmounted() {\t\t\tconsole.log('---unmounted---')\t\t},\t}</script>\n\n自定义hook函数\n什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。\n\n类似于vue2.x中的mixin。\n\n自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。\n\n\ntoRef与toRefs\n作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。\n语法:const name = toRef(person,'name')\n应用: 要将响应式对象中的某个属性单独提供给外部使用时。\n\n\n扩展:toRefs 与toRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)\n\n\n其它 Composition API1.shallowReactive 与 shallowRef\nshallowReactive:只处理对象最外层属性的响应式(浅响应式)。\n\nshallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。\n\n什么时候使用?\n\n 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。\n 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。\n\n\n\n2.readonly 与 shallowReadonly\nreadonly: 让一个响应式数据变为只读的(深只读)。\nshallowReadonly:让一个响应式数据变为只读的(浅只读)。\n应用场景: 不希望数据被修改时。\n\n3.toRaw 与 markRaw\ntoRaw:\n作用:将一个由reactive生成的响应式对象转为普通对象。\n使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。\n\n\nmarkRaw:\n作用:标记一个对象,使其永远不会再成为响应式对象。\n应用场景:\n有些值不应被设置为响应式的,例如复杂的第三方类库等。\n当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。\n\n\n\n\n\n4.customRef\n作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。\n\n实现防抖效果:\n<template>\t<input type="text" v-model="keyword">\t<h3>{{keyword}}</h3></template><script>\timport {ref,customRef} from 'vue'\texport default {\t\tname:'Demo',\t\tsetup(){\t\t\t// let keyword = ref('hello') //使用Vue准备好的内置ref\t\t\t//自定义一个myRef\t\t\tfunction myRef(value,delay){\t\t\t\tlet timer\t\t\t\t//通过customRef去实现自定义\t\t\t\treturn customRef((track,trigger)=>{\t\t\t\t\treturn{\t\t\t\t\t\tget(){\t\t\t\t\t\t\ttrack() //告诉Vue这个value值是需要被“追踪”的\t\t\t\t\t\t\treturn value\t\t\t\t\t\t},\t\t\t\t\t\tset(newValue){\t\t\t\t\t\t\tclearTimeout(timer)\t\t\t\t\t\t\ttimer = setTimeout(()=>{\t\t\t\t\t\t\t\tvalue = newValue\t\t\t\t\t\t\t\ttrigger() //告诉Vue去更新界面\t\t\t\t\t\t\t},delay)\t\t\t\t\t\t}\t\t\t\t\t}\t\t\t\t})\t\t\t}\t\t\tlet keyword = myRef('hello',500) //使用程序员自定义的ref\t\t\treturn {\t\t\t\tkeyword\t\t\t}\t\t}\t}</script>\n\n5.provide 与 inject\n\n\n作用:实现祖与后代组件间通信\n\n套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据\n\n具体写法:\n\n祖组件中:\nsetup(){\t...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) ......}\n后代组件中:\nsetup(props,context){\t...... const car = inject('car') return {car}\t......}\n\n\n\n6.响应式数据的判断\nisRef: 检查一个值是否为一个 ref 对象\nisReactive: 检查一个对象是否是由 reactive 创建的响应式代理\nisReadonly: 检查一个对象是否是由 readonly 创建的只读代理\nisProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理\n\n\nComposition API 的优势Options API 存在的问题使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。\n\nComposition API 的优势我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。\n\n\n\n新的组件1.Fragment\n在Vue2中: 组件必须有一个根标签\n在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中\n好处: 减少标签层级, 减小内存占用\n\n2.Teleport\n什么是Teleport?—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。\n<teleport to="移动位置">\t<div v-if="isShow" class="mask">\t\t<div class="dialog">\t\t\t<h3>我是一个弹窗</h3>\t\t\t<button @click="isShow = false">关闭弹窗</button>\t\t</div>\t</div></teleport>\n\n3.Suspense\n等待异步组件时渲染一些额外内容,让应用有更好的用户体验\n\n使用步骤:\n\n异步引入组件\nimport {defineAsyncComponent} from 'vue'const Child = defineAsyncComponent(()=>import('./components/Child.vue'))\n使用Suspense包裹组件,并配置好default 与 fallback\n<template>\t<div class="app">\t\t<h3>我是App组件</h3>\t\t<Suspense>\t\t\t<template v-slot:default>\t\t\t\t<Child/>\t\t\t</template>\t\t\t<template v-slot:fallback>\t\t\t\t<h3>加载中.....</h3>\t\t\t</template>\t\t</Suspense>\t</div></template>\n\n\n\n\n其他全局API的转移\nVue 2.x 有许多全局 API 和配置。\n\n例如:注册全局组件、注册全局指令等。\n//注册全局组件Vue.component('MyButton', { data: () => ({ count: 0 }), template: '<button @click="count++">Clicked {{ count }} times.</button>'})//注册全局指令Vue.directive('focus', { inserted: el => el.focus()}\n\n\nVue3.0中对这些API做出了调整:\n\n将全局的API,即:Vue.xxx调整到应用实例(app)上\n\n\n\n2.x 全局 API(Vue)\n3.x 实例 API (app)\n\n\n\nVue.config.xxxx\napp.config.xxxx\n\n\nVue.config.productionTip\n移除\n\n\nVue.component\napp.component\n\n\nVue.directive\napp.directive\n\n\nVue.mixin\napp.mixin\n\n\nVue.use\napp.use\n\n\nVue.prototype\napp.config.globalProperties\n\n\n\n\n\n\n其他改变\ndata选项应始终被声明为一个函数。\n\n过度类名的更改:\n\nVue2.x写法\n.v-enter,.v-leave-to { opacity: 0;}.v-leave,.v-enter-to { opacity: 1;}\nVue3.x写法\n.v-enter-from,.v-leave-to { opacity: 0;}.v-leave-from,.v-enter-to { opacity: 1;}\n\n\n移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes\n\n移除v-on.native修饰符\n\n父组件中绑定事件\n<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent"/>\n子组件中声明自定义事件\n<script> export default { emits: ['close'] }</script>\n\n\n移除过滤器(filter)\n\n过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。\n\n\n……\n\n\n"}]