vue2-3版本笔记
vue2
第一类引入方式
就是直接把vue的js代码库运行到html环境中
1.自己引入cdn
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
2.自己把vue.js文件放在项目文件夹中引入项目 然后webpack打包
import Vue from './vue.js' new Vue({el: "#app",data: {msg: "hello",age:"18"} })
3.编辑器直接生成cdn的方式
HbuilderX 创建项目 选择 vue项目普通模式
第二类引入方式
就是直接把vue的代码环境集成了 然后开始在vue的代码环境中写项目,最终才经过webpack打包生成第一类方式
自己构建vue的脚手架(面试): 用npm下载vue 引入到项目中 然后webpack打包
//第1.1种 // var Vue = require("vue") //第1.2种 自己构建vue的脚手架 //这种方式需要vue的加载器去解析.vue文件,参考网址:https://blog.csdn.net/weixin_44867717/article/details/104447943 //index.js入口文件 import Vue from 'vue' import MyCom from './App.vue' new Vue({render(h){return h(MyCom)} }).$mount('#app') //先下载:npm install -D vue-loader vue-template-compiler //webpack.config.js打包配置文件: const webpack= require('webpack') const VueLoaderPlugin = require('vue-loader/lib/plugin') //引入vue加载器 rules: [{test: /\.vue$/,loader: 'vue-loader'}]//loader plugins:[new VueLoaderPlugin()]//插件 //项目配置文件 pakage.json文件中: //scripts:{ //"dev":"webpack-dev-server --open --inline --progress --config webpack.config.js" //} //然后就可以启动了:npm run dev
2.使用官方脚手架的方式来构建项目环境(面试)
1. cnpm install @vue/cli -g //下载官方脚手架 2. vue create app1 //项目名称 3. 接下来让你选择一些默认要生成的工具,不管直接回车 4. 进入项目文件夹: cd app1 //进入项目文件夹,也可以直接在项目文件夹中打开终端 5. 启动: npm run serve //生成的打包文件在内存中不会写入磁盘用于开发阶段 或者 npm run build //生成的打包文件在dist中 用于项目上线
3.可视化项目管理方式(面试)
1. cnpm install @vue/cli -g 2. vue ui 3. 打开的界面 中 4.创建项目==>填写项目名字,选择项目创建的目录 点击创建 5.等待它下载所有配置文件完毕 6.任务中serve启动以后想当与启动了热更新服务器:默认localhost:8080(也可以点启动app直接帮我们打开localhost:8080) 7.如果把项目做完以后要上线,就启用build相当于webpack的打包,生成dist文件,注意:打包的路径是相对路径要改一下配置中的公共路径为: ./ 然后点保存修改
4.编辑器直接生成脚手架环境的方式 (做项目推荐)
HbuilderX 创建项目 选择 vue项目 vue-cli默认模板 然后要:npm i 再:npm run serve
//注意:代码中回车或者空格导致的报错 禁用eslint启用严格模式来加载我们的代码,项目下新建vue.config.js文件:
module.exports = {lintOnSave: false }
//注意:npm run build打包后 生成的引入文件的路径不对 解决方案:
//第1种.手动把路径的斜杠删除
//第2种.在根目录下新建vue.config.js
复制代码:
module.exports = {/** 区分打包环境与开发环境* process.env.NODE_ENV==='production' (打包环境)* process.env.NODE_ENV==='development' (开发环境)* baseUrl: process.env.NODE_ENV==='production'?"https://cdn.didabisai.com/front/":'front/',*/ // 项目部署的基础路径 // 我们默认假设你的应用将会部署在域名的根部, // 例如 https://www.my-app.com/ // 如果你的应用部署在一个子路径下,那么你需要在这里 // 指定子路径。比如将你的应用部署在 // https://www.foobar.com/my-app/ // 那么将这个值改为 '/my-app/' baseUrl: "./", // 构建好的文件输出到哪里 outputDir: "dist", // where to put static assets (js/css/img/font/...) // 是否在保存时使用‘eslint-loader’进行检查 // 有效值: true | false | 'error' // 当设置为‘error’时,检查出的错误会触发编译失败 lintOnSave: false, // 使用带有浏览器内编译器的完整构建版本 // https://vuejs.org/v2/guide/installation.html#Runtime-Compiler-vs-Runtime-only runtimeCompiler: false, // babel-loader默认会跳过`node_modules`依赖. // 通过这个选项可以显示转译一个依赖 transpileDependencies: [/* string or regex */], // 是否为生产环境构建生成sourceMap? productionSourceMap: false, // 调整内部的webpack配置. // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md chainWebpack: () => {}, configureWebpack: () => {}, // CSS 相关选项 css: {// 将组件内部的css提取到一个单独的css文件(只用在生产环境) // 也可以是传递给 extract-text-webpack-plugin 的选项对象 extract: true, // 允许生成 CSS source maps? sourceMap: false, // pass custom options to pre-processor loaders. e.g. to pass options to // sass-loader, use { sass: { ... } } loaderOptions: {}, // Enable CSS modules for all css / pre-processor files. // This option does not affect *.vue files. modules: false}, // use thread-loader for babel & TS in production build // enabled by default if the machine has more than 1 cores parallel: require("os").cpus().length > 1, // PWA 插件相关配置 // see https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa pwa: {}, // configure webpack-dev-server behavior devServer: {open: process.platform === "darwin", disableHostCheck: false, host: "192.168.2.57", port: 9000, https: false, hotOnly: false, // See https://github.com/vuejs/vue-cli/blob/dev/docs/cli-service.md#configuring-proxy proxy: null // string | Object // before: app => {}}, // 第三方插件配置 pluginOptions: {// ...} };
npm 下载指令 -S -D
//下载到项目依赖中 以后项目上线时 这个模块的代码也在项目中起作用(vue jquery等) npm i xx1 xx2 --save npm i xx1 xx2 --S//--save的简写
//下载到项目依赖中 以后项目上线时 这个模块的代码不在项目中(webpack css-loader vue/cli等) npm i xx1 xx2 --save-dev npm i xx1 xx2 --D//--save-dev的简写
VM对象和基础指令(面试)
1.普通插值表达式插入数据: 在标签尖括号中使用{{}}插入js表达式:变量,函数调用,三目运算等等 ,插值表达式中的标识符 代表vue对象中的data的属性名或者methods中的方法名
<h1>{{msg+"666"}}</h1> <p>{{fn()}}</p> <p>{{ count==10?"hellohqyj":"hqyjhello" }}</p>
2.文本指令:(面试) v-html ==>相当于innerHTML v-text==>相当于innerText v-pre==>插件表达式就被识别为文本,而不是js表达式 v-cloak==>加上这个属性的标签相当于在构建虚拟节点的时候就会有这个属性,等data的数据生成的时候,这个标签会自动去掉这个属性,可以利用这个特性来在css中把这个元素在加载初期写样式(隐藏)
示例: <h1>{{msg+"666"}}</h1> <p v-cloak>{{fn()}}</p> <p v-cloak>{{ count==10?"hellohqyj":"hqyjhello" }}</p> <div v-html="p_html"></div> <div v-text="p_text"></div> <p v-pre>{{p_pre}}</p>
3.考点:如何解决vue第一次加载的时候 页面上使用的数据会闪烁?(面试)
3.1.界面加载的时候会把节点直接挂载到文档树中,导致{{msg+"666"}}这个字符串会显示一下 3.2.vue对象生成data数据时候 回去刷新界面 把{{msg+"666"}}字符串替换成结果字符串 3.3.导致界面第一次加载的时候会闪屏 3.4.解决方案:使用v-html,v-text指令操作,或者css中加[v-cloak] {display:none};
4.给元素绑定属性 所有标签中属性绑定js中变量: 标准写法: v-bind:src="变量" 简写形式: :src="[10,20,30]"
示例: <a v-bind:href="link">baidu</a> <a :href="link">baidu</a> <img v-bind:src="myimg"> <img :src="myimg">
事件
1.方法的写法 在methods中写方法,供事件或者别的方法内部调用 方法的写法:由于是做了es6语法处理的 所以学过的所有方式的写法都行
function fn4 () {console.log("fn4")} var fn5=()=>{console.log("fn5")} var fn6=function(){console.log("fn6")} new Vue({el:"#app",data:{}, methods:{fn1(){console.log("fn1")},fn2:function(){console.log("fn2")},fn3:()=>{console.log("fn3")},fn4,fn5,fn6 } })
2.事件绑定
//v-on: 和 @ 都是绑定事件的指令 //指令后面跟事件类型,值就是methds中的方法,可以加小括号也可以不加 <button v-on:click="fn1()">点击事件1</button> <button @click="fn2">点击事件2</button>
3.事件修饰符(面试)
.stop 阻止冒泡,阻止从当前元素经过的所有冒泡行为
.prevent 阻止默认事件
.capture 添加事件侦听器时让事件在捕获阶段触发
.self 其他元素的事件触发时 事件链经过它,无论是捕获还是冒泡阶段都不会触发它的事件,只有它自己是精准对象才会触发事件, 虽然它自己不会被别人影响,但是它自己的事件触发的时候还是会生成事件链经过其他元素,并不阻止继续冒泡到其他元素
.once 事件只触发一次,触发完之后,事件就解绑
多修饰符一起使用:连点
<div class="box" @click="divBoxHandler"><input type="button" @click.stop="btnHandler" value="戳他"> </div> <a v-on:click.prevent.once="doThat">阻止点击跳转,但是只会阻止第一次</a>
4.事件中的this与数据操作(面试)
1. 方法中的this代表vm对象 a.方法和ES5的函数中的this是vm对象 b.ES6的箭头函数中的this就不是vm==>因此推荐事件的函数采用ES6的对象的方法写方法 这种写法2. 操作数据: this.xx="新值" //这里的修改会执行两步操作: //a.修改内存data容器中的数据 //b.刷新UI==>重新设置innerHTML//后面学的差不多了回头补充:高薪面试题 底层设计:js的语法 definexxx 设计模式:发布订阅
样式绑定
(1) 对class 属性进行绑定
<!--对象语法,v-bind:class 指令也可以与普通的 class 属性共存--> <div class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }"> </div> <div v-bind:class="classObject"></div> 对应js 中的data: data: {isActive: true,hasError: false,classObject: {active: true,'text-danger': false} } <!--数组语法,这样写将始终添加 errorClass,但是只有在 isActive 是真值时才添加 activeClass--> <div v-bind:class="[isActive ? activeClass : '', errorClass]"></div> <div v-bind:class="[{ active: isActive }, errorClass]"></div>
(2)对style 进行绑定
<!--对象语法--> <div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> <div v-bind:style="styleObject"></div> 对应js 中的data: data: {activeColor: 'red',fontSize: 30,red1:"red",styleObject: {color: 'red',fontSize: '13px'} } <!--数组语法,可以将多个样式对象应用到同一个元素上--> <div v-bind:style="[baseStyles, overridingStyles,{color:red1}]"></div>
数据绑定(面试)
响应式数据:只能由代码改变UI或者只能由UI改变代码 双向数据绑定:代码改变UI,UI也能改变代码
双向数据绑定的实现: 2种方式 1.自己实现,vue可以自己实现(没必要) 微信开发可以自己实现(只能自己实现) 利用input事件,用户交互的时候,获取用户输入的值,然后把值绑定到data容器中
2.系统指令:v-model
//vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。//利用 Object.defineProperty 自己实现双向数据绑定 <body><div id="app"><input type="text" id="txt"><p id="show"></p></div> </body> <script type="text/javascript">var obj = {}Object.defineProperty(obj, 'txt', {get: function () {return obj},set: function (newValue) {document.getElementById('txt').value = newValuedocument.getElementById('show').innerHTML = newValue}})document.addEventListener('keyup', function (e) {obj.txt = e.target.value}) </script>
vue简介
Vue.js是什么? 是阿里的尤雨溪出的框架
Vue.js(读音 /vjuː/, 类似于 view)是目前最流行的一个框架,React是最流行的一个框架。
Vue.js是前端的主流框架之一,和Angular.js、React.js并称为前端三大主流框架。
数据驱动
vue.js 数据驱动和组件化开发,轻量级一些,分层渐进式框架;spa React.js 数据驱动和组件化开发,灵活性很高,需要什么都得自己构建逻辑自己写;app Angular.js 数据驱动 1.0 ,加上了组件化开发2.0,重量级框架;大型企业OA办公
Vue.js 是一套构建用户界面的 渐进式框架。与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计。
Vue 的核心库只关注视图层,并且非常容易学习,非常容易与其它库或已有项目整合。另一方面,Vue 完全有能力驱动采用单文件组件和Vue生态系统支持的库开发的复杂单页应用。
Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。
特点:(面试) 1、三大主流框架的区别?设计思想和使用场景
2、vue.js 渐进式框架(分层的设计模式)是什么意思?五层设计
3、vue.js 的核心是什么?数据驱动(响应式数据)和组件化开发
//面试题: 渐进增强 优雅降级
MVC和MVVM(面试)
MVC与MVVM之间的区别
(1)MVC
在实际应用开发场景中,开发者常用的一种设计模式是MVC(eg:node(后端)中的MVC设计模式):
M(Model):数据模型层。是应用程序中用于处理应用程序数据逻辑的部分,模型对象负责在数据库中存取数据。
V(View):视图层。是应用程序中处理数据显示的部分,视图是依据模型数据创建的。
C(Controller):控制层。是应用程序中处理用户交互的部分,控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。
(2)MVVM
M(Model):模型层。就是业务逻辑相关的数据对象,通常从数据库映射而来,我们可以说是与数据库对应的model。
V(View):视图层。就是展现出来的用户界面。
VM(ViewModel):视图模型层。就是与界面(view)对应的Model。因为,数据库结构往往是不能直接跟界面控件一一对应上的,所以,需要再定义一个数据对象专门对应view上的控件。而ViewModel的职责就是把model对象封装成可以显示和接受输入的界面数据对象。
条件渲染(面试)
v-if /v-else 或者 v-show
<div id="app"><div v-if="flag">666</div><div v-show="flag">777</div></div><script type="text/javascript">new Vue({el: "#app",data: {flag:false}}) //1.使用的变量为true就显示,false就隐藏 //2.在业务中常常可以通过操作if或者show使用的变量,来达到操作元素显示和隐藏的效果 //3.v-if的做法是删除节点,v-show做法是操作css的display:none visibility: hidden; 不脱离文档流的 display:none 脱离文档流 v-if 删除节点 v-show display:none //4.这个两个谁好?(面试) //根据它们底层的设计不一样有各自的使用场景 // v-if具有较高的 切换消耗,常常用在用户不常切换的模块 // v-show具有较高的性能消耗,常常用在频繁切换的模块中
循环渲染(面试)
1.for和if放在了同一个标签中 没有先后顺序的要求,但是先执行for
渲染过程为:对arr每一项先做map循环判断v-if给出的条件,再做一遍for 循环渲染
这样引起的问题是:arr 数组新增一项数据时,会对每一项再做一遍v-if 循环,然后for 循环渲染
2.解决方案把for弄到最外层(面试)
如果if和for套在一层,数据容器发生变化时,if会重新判断一遍 嵌套的写法 数据容器变化时 if只判断新增的数据 这样当arr 数组某一项数据发生变化时,只对新增的数据进行v-if 判断,节约渲染效率
这样又会产生新的问题:外层for的div会也创建一个挂载到DOM中 解决方案:wx采用的是block元素 vue呢? template 其实就是dom操作中的fragment
<div v-for="(item,index) in arr2" :key="item.id"><div class="box" v-if="item.age>=18"><p>{{item.name}}</p><p>{{item.age}}</p><p>{{item.info}}</p></div> </div>
3.key的意义(面试)
data中for循环的容器数据个数发生变化时,会跟for中的vm节点个数作比较
如果数据多了,会在vm节点后面增加对应数量的节点,并不会重新创建所有节点,然后vm去更新对应的DOM
然后就去刷新数据到界面: 按照for的数据容器中的数据顺序来渲染如果用户以前操作过旧节点,那么新数据顺序可能会出现跟旧节点顺序不匹配的效果(旧节点跟旧数据没有对应起来)
解决方案: for循环时把数据跟创建的节点利用给元素绑定唯一key值
简答:因为vue在刷新页面组件时,会把旧节点跟新vm节点做比较,如果要增加节点,并不会删除旧节点,而是复用 这样会导致节点跟数据没有绑定关系而重新渲染,用key可以将数据与节点绑定起来
<div id="app"><h1>活动:1分钟内从1万件商品中选择你喜欢10件送给你</h1><button type="button" @click="add">加载新的商品</button><button>提交</button><div><div v-for=" (item,index) in arr" :key="item.id"><input type="checkbox" name="goods"/> {{item.name}}=={{item.info}}</div></div></div><script type="text/javascript">new Vue({el:"#app",data:{arr:[{id:10001,name:"鞋子1",info:"这个鞋子好1"},{id:10002,name:"鞋子2",info:"这个鞋子好2"},{id:10003,name:"鞋子3",info:"这个鞋子好3"},{id:10004,name:"鞋子4",info:"这个鞋子好4"}],count:5},methods:{add(){//网络请求 然后把新商品添加到arr中//假装网络请求了新数据 var result=this.count++ this.arr.unshift({id:result,name:"鞋子"+result,info:"这个鞋子好"+result})}}})</script>
过滤器(面试)
filter主要用于数据展示之前的处理 过滤器只能用在v-bind或者插值表达式中 语法:
<div id="app"><div v-for="(item) in sinaArr" class="box"><p v-html="item.user.name"></p><img :src="item.user.img" ><p v-text="item.text"></p><!-- <p>发表时间:{{item.create_At|timerTool}}</p> --><!-- 下面这种写法是不行的:过滤器只能应在v-bind或者插值表达式中 --><p v-html="item.create_At|timerTool">发表时间:</p></div> </div>new Vue({el:"#app",data:{sinaArr:[{create_At:"Thu Apr 16 14:08:18 +0800 2020",text:"内容1",user:{name:"karen1",img:"1.jpg"}},{create_At:"Thu Apr 16 14:01:18 +0800 2020",text:"内容2",user:{name:"karen2",img:"2.jpg"}},{create_At:"Thu Apr 16 10:39:18 +0800 2020",text:"内容3",user:{name:"karen3",img:"3.jpg"}},{create_At:"4.jpg"}}]},filters:{timerFormat(arg){var dt1=new Date(arg)var dt2=new Date()var abstime=dt2-dt1if(0<=abstime&&abstime<1000*60){return "刚刚"}else if(1000*60<=abstime&&abstime<1000*60*60){var m=new Date(abstime).getMinutes()+""return `${m}分钟前`}else if(1000*60*60<=abstime&&dt1.getDate()==dt2.getDate()){var m=dt1.getMinutes()+""var h=dt1.getHours()return `今天 ${h}:${m.padStart(2,"0")}`}else if((dt1.getDate()+1)==dt2.getDate()){var m=dt1.getMinutes()+""var h=dt1.getHours()return `昨天 ${h}:${m.padStart(2,"0")}`}else if((dt1.getDate()+2)==dt2.getDate()){var m=dt1.getMinutes()+""var h=dt1.getHours()return `前天 ${h}:${m.padStart(2,"0")}`}}}}
计算属性(面试)
把computed中的方法当做属性使用,会返回一个数据供使用:
new Vue({el:"",//关联界面元素data:{},//vm的数据源methods:{},//方法filters:{qq(){}},//过滤器computed:{xx(){}} //xx就是一个计算属性})
使用示例:
<div id="app"><p>{{msg}}</p><p>方法获取的年龄:{{getAge()}}</p><p>计算属性获取的年龄:{{getAge_computed}}</p><button @click="change">改变birth的值看看年龄变不变</button></div> new Vue({el: "#app",data: {msg: "hello",birth: "1995-02-03"},methods: {getAge() {var age = new Date().getFullYear() - new Date(this.birth).getFullYear()return age + "岁"},change() {this.birth = "1996-02-03"}},computed:{//计算属性第一种用法getAge_computed(){var age = new Date().getFullYear() - new Date(this.birth).getFullYear()return age + "岁"}//计算属性第二种用法xx:{set(oldvalue){},get(){}}}})
计算属性和方法的区别:(面试) 计算属性会把使用到的data中的属性缓存起来,防止页面发生大量重复计算, 提升js 运行效率,如果计算属性中使用到的data中那部分数据变了才会重新调用计算属性 methods方法没有计算结果缓存起来,data任何数据发生改变,方法都会被重新调用一遍 方法常常是作用的事件使用,计算属性常常是动态计算结果时使用
补充:关于计算属性 函数什么情况下调用
//计算属性使用时当做属性使用 //计算属性设计时当做函数设计(就像es6中的属性) //当计算属性的函数中使用到的data中的数据发生变化时,计算属性就会重新执行并刷新UI //1.如果是修改了data中监听的某个的属性值 计算属性就会运行 //2.如果是修改了data中监听的某个属性值内部的数据,计算属性就不会重新运行 //比如:计算属性使用的是data中的一个数组,某个交互把数组内部的某个下标的值改了,但是这个数组没有改变,就不会触发计算属性 //3.解决2的办法1:把修改后的数组重新赋值给data,让引用发生变化,来触发计算属性 //3.解决2的办法2:赋值 JSON.parse(JSON.stringfy(data)) methods:{ change(arg,index1){this.arr[index1]=argthis.arr=[...this.arr]} } computed:{total(){//eval(this.arr.join("+"))// eval("100+200+88")==>把字符串当做代码运行 产生运算结果return eval(this.arr.join("+"))}}
属性监听器(面试)
watch:{x(){}}中的方法名必须跟要监听的data中的属性名一样,才代表监听指定属性
当侦听器监听的属性发生变化时,就会调用watch中对应的方法
侦听器属性,比计算属性计算效率消耗大
new Vue({el:"",//关联界面元素data:{x:12},//vm的数据源methods:{},//方法filter:{},//过滤器computed:{xx(){}}, //xx就是一个计算属性watch:{x(){}} //x就是监听了data中的x属性的一个监听器 })
自定义指令(面试)
除了默认设置的核心指令( v-model 和 v-show 等),Vue 也允许注册自定义指令。
在Vue里,代码复用的主要形式和抽象是组件。
然而,有的情况下,仍然需要对纯 DOM 元素进行底层操作,这时候就会用到自定义指令 。
以一个input元素自动获得焦点为例,当页面加载时,使用autofocuse可以让元素将获得焦点 ,但是autofocuse在移动版Safari上不工作,现在注册一个使元素自动获取焦点的指令。
指令注册类似于组件注册,包括全局指令和局部指令两种。
1)全局指令
// 注册一个全局自定义指令 v-focus Vue.directive('focus', {// 当绑定元素插入到 DOM 中。inserted: function (el) {// 聚焦元素el.focus()} })
2)局部指令
var vm = new Vue({el: '#app',directives:{focus:{inserted: function (el) {el.focus()} }} })
在模板中任何元素上使用新的 v-focus 属性
<div id="app"><input v-focus> </div>
3)钩子函数
指令定义函数提供了几个钩子函数(可选) 。
【bind】
只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
【inserted】
被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
【update】
所在组件的 VNode 更新时调用,但是可能发生在其孩子的 VNode 更新之前。指令的值可能发生了改变也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新。
【componentUpdated】
所在组件的 VNode 及其孩子的 VNode 全部更新时调用。
【unbind】
只调用一次, 指令与元素解绑时调用。
注意区别:
bind与inserted:bind时父节点为null,inserted时父节点存在;
update与componentUpdated:update是数据更新前,componentUpdated是数据更新后。
4)钩子函数参数
【el】
指令所绑定的元素,可以用来直接操作 DOM。
【binding】
一个对象,包含指令名称及该指令所绑定的表达式信息。
【vnode】
Vue 编译生成的虚拟节点。
【oldVnode】
上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
注意:除了 el 之外,其它参数都是只读的,尽量不要修改他们。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
5)函数简写
大多数情况下,可能想在bind和update钩子上做重复动作,并且不想关心其它的钩子函数。可以这样写:
<div v-demo="{ color: 'white', text: 'hello!' }"></div>Vue.directive('demo', function (el, binding) {console.log(binding.value.color) // => "white"console.log(binding.value.text) // => "hello!" })
for写一个新浪微博
<style type="text/css">.box{position: relative;width: 970px;min-height: 200px;background-color: rosybrown;border-radius: 10px;/* margin-top: 10px; */margin: 10px auto;}</style><div id="app"><button @click="fn">加载</button><div class="box" v-for="(item) in arr"><img :src="item.user.profile_image_url" ><h1>{{item.user.name}}</h1><p>{{item.created_at|timerFormat}}发表</p><p>{{item.text}}</p><div><img v-for="(imgObj) in item.pic_urls" :src="imgObj.thumbnail_pic"></div> <div class="bottom">评论:({{item.comments_count}})</div></div></div><script type="text/javascript">new Vue({el:"#app",data:{arr:[]},methods:{fn(){// console.log($)// let url="https://api.weibo.com/2/statuses/home_timeline.json?access_token=2.00ZmCkcDlel8HDd856f9b9ccsVwsYD&callback=?"// $.getJSON(url,(result)=>{// console.log(result)// }) // var result=sinadataconsole.log(sinadata)this.arr=sinadata.statuses}},filters:{timerFormat(arg){console.log(arg)var sinaTimer=new Date(arg)var nowTime=new Date()var absTimer=nowTime-sinaTimerif(0<=absTimer&&absTimer<=1000*60){return "刚刚"}else if(1000*60<absTimer&&absTimer<=1000*60*60){var dt=new Date(absTimer)return dt.getMinutes()+"分钟前"}else if(1000*60*60<absTimer&&absTimer<=1000*60*60*24){return sinaTimer.getHours()+":"+sinaTimer.getMinutes()}else if(1000*60*60*24<absTimer&&absTimer<=1000*60*60*24*2){return "昨天--"+sinaTimer.getHours()+":"+sinaTimer.getMinutes()}}}})</script>
网络
1.具体的网络请求的工具的用法见node阶段视频
注: egg后端的缓存,要求前端的网络请求运行携带缓存cookie数据,axios网络请求携带参数下面两种方式:
①axios.defaults.withCredentials=true;//让ajax携带cookie
②axios.post(url,{pwd:123},{ withCredentials: true },(res)=>{})
axios.get(url,{params:{pwd:123},{ withCredentials: true },(res)=>{})
后端egg中配置:
//设置允许哪些源可以跨域访问当前服务器
// credentials: true 跨域服务的页面去做cookie缓存
config.cors = {
origin: 'http://ip:8080',
credentials: true//后端会给去前端返回缓存数据包 告诉浏览器 去做缓存
}
2.网络请求工具的二次封装:
2.1 原理: 把用到的axios等网络请求工具封装成自己的工具 在项目中使用,防止axios出问题时,快速的更换工具 不会影响项目代码
2.2.设计:
xx.prototype.myaxios=axios
xx.myaxios.post=axios.post
xx.myaxios.get=axios.get
xx.myaxios.put=axios.put
3.代理
//main.js module.exports={lintOnSave:false,devServer:{port:"10086",host:"localhost",proxy:{"/":{target:"http://192.168.2.105:7001",changeOrigin:true,pathRewrite:{"^/":""}}}} }
生命周期函数(面试)
生命周期: vm对象从创建到销毁的过程
vm对象的生命周期函数(钩子函数,生命钩子)
相当于是一种特殊事件,当vm实例在整个运行的过程中,会在不同的时期去执行特定的函数,这样的函数就是vue的生命周期函数 beforeCreate created beforeMount mounted destory/x这些钩子都只执行一次 beforeUpdate updated第一次构建不会调用,以后每次data被更新了就会调用 beforeDestroy destroyed 销毁的方式有两种:用户关闭和代码this.$destroy()//销毁vm实例
为什么要用生命周期函数? 0.把整个运行期间的业务区分的很明显 1.能够更好的帮助我们把产品的业务逻辑实现了 2.更有利于我们维护产品 和 修改需求 3.能够让我们写出更高质量的产品的代码 网络请求应该在什么钩子中,为什么? 可以放在data生成以后更新数据之前 的所有钩子中 具体的更具业务需求来 常见的放在created或者mounted中 因为网络请求下来的数据 常常会存在data容器中 created:因为有时候我们希望异步的网络请求和vm的挂载同时进行 体现出CPU多核的优势 mounted:因为有时候我们希望本地的UI骨架已经加载完毕以后再去请求数据刷新UI 你用销毁钩子做过什么? 常常去把一些运行着的代码停下来 本地或者网络请求来记录用户的配置信息或者偏好设置
//vm实例创建中//1.data都还没有构建出来数据//2.方法也还没有构建出来beforeCreate() {console.log("vm实例创建中")// console.log(this.msg)//这时候data都还没有构建出来数据// this.fn()},//vm实例创建完毕//data 方法 等等工具已经构建完毕//但是vm还没有挂载到界面上去,可以在这个钩子中做页面第一次加载时的网络请求created() {console.log("vm实例创建完毕")// console.log(this.msg)// this.fn()},//挂载到DOM之前//data等vm对象的工具构建完毕 正在挂载到DOM中beforeMount() {console.log("挂载到DOM之前")},//挂载到DOM上了//有点像window.onload//这里也可以做网络请求,页面加载了之后的所有业务,都可以在这里//这个函数执行标志着 vm和dom成功关联==>随意操作vm来间接的操作dommounted() {console.log("挂载到DOM上了")},//更新数据之前beforeUpdate() {alert("更新数据之前")debugger//卡住页面},//更新数据完毕//data.xx=100//xxx.innerHTML=data.xx//更新的是data,这时候还没有刷新UI,它会找一个合适的时机去刷新UI//这个钩子调用之后才会刷新UIupdated() {alert("更新数据完毕")},//销毁实例之前//常常去把一些运行着的代码停下来//本地或者网络请求来记录用户的配置信息或者偏好设置beforeDestroy() {console.log("销毁实例之前",this.msg)},//实例销毁了//钩子调用后 才销毁 做最后的挽救//从技术的角度来说 可以在这个方法打开其他程序,然后销毁这个程序destroyed() {console.log("实例销毁了",this.msg)}
组件(面试)
组件是可复用的 Vue 实例,主要用于开发中 具有相同特征不同数据的模块 把它集成为一个组件 供重复利用
组件基础(面试)
1.全局组件: 组件的属性不能用大写字母
组件的名字可以用驼峰命名法,但是使用的时候必须用连字符 全局注册的组件使用时不能使用单标签(不会报错,但是只能使用一次 多次使用只显示第一个)
注册的组件不要跟系统标签同名
2.局部组件: 一个vm实例可以有多个局部组件,但是只能供当前vm实例使用
3.单文件组件:
引入:@1官方脚手架 @2挂载vm对象 @3组件引入并渲染到vm中
单文件组件也有全局组件和局部组件 只是把一个组件单独写在一个.vue文件中,供别的组件引入然后注册 引入文件时:一般使用相对路径 上一下用../ 同级使用./ 下级使用/ @ 代表src文件夹
<div id="app"><nav1 son-proprety="子组件使用时属性传进去的值"></nav1> <content1 img2src="./img/2.png"></content1></div><div id="app2"><nav1 son-proprety="子组件使用时属性传进去的值2"></nav1><content1 img2src="./img/2.png"></content1> </div><script type="text/javascript">//祖册全局组件Vue.component("nav1",{data(){return {sondata:"子组件的数据"}},template:`<div><h1>{{sondata}}</h1><p>{{sonProprety}}</p></div>`,props:["sonProprety"]})// let content1={// data(){return {img1:"./img/1.png"}},// template:`<div><img :src="img1"/><img :src="img2src"/></div>`,// props:["img2src"]// }new Vue({el:"#app",data:{},components:{// content1//注册局部组件content1:{data(){return {img1:"./img/1.png"}},template:`<div><img :src="img1"/><img :src="img2src"/></div>`,props:["img2src"]}}})new Vue({el:"#app2",data:{} })</script>
4.组件的属性 属性有两种写法:简单声明和详细描述:
4.1.简单声明 props:["prop1","prop2"]
4.2对属性做详细的描述
props: {propA: Number, // 基础的类型检查 (`null` 匹配任何类型)propB: [String, Number], // 多个可能的类型propC: { type: String,required: true // 必填的字符串},propD: { type: Number,default: 100 // 带有默认值的数字},propE: { type: Object, // 带有默认值的对象或者数组填Arraydefault: function () { // 不建议直接填对象(因为对象直接量会一直占用内存),一般使用工厂函数,调用时才创建对象节省资源(面试)return { message: 'hello' }}},propF: {validator: function (value) {// 自定义验证函数返回为true就代表数据符合我们规定return ['success', 'warning', 'danger'].indexOf(value) !== -1}}}
5.v-slot: 插槽, 具名插槽 slot,slot-scope过时了 2.6.0使用v-slot 语法:v-slot:插槽名 语法糖:#插槽名 没有指定插槽名就是默认插入到插槽,不给插槽插入数据的话,就会使用组件的slot中的数据 插槽名不用使用引号引起来,直接写变量名 插入的内容必须是template标签或者组件 不能是原生的元素
//设计组件: .vue组件文件中 <template> <div class="content1"><slot></slot><slot name="slot1"></slot><h1>{{contentData.title}}</h1><h2>{{contentData.dt}}</h2><slot name="slot3">你不给我数据到3号插槽中 我就会默认显示出来</slot><p>{{contentData.text}}</p><slot name="slot2"></slot></div> </template><script>export default{props:{contentData:{type:Object,default:()=>{return {title:"0",dt:"0",text:"0"}}}} } </script>//使用组件: .vue页面文件中 <template> <div> <content2 :contentData="arr[1]"><template #slot1><img src="../assets/28.jpg"></template><template #slot2><p>我在外部插入插槽的数据,不是子组件中的数据,也不是属性传进去的数据</p></template><template v-slot:slot3>666</template> <p>我并没有指定插入到哪里</p></content2> </div> </template>
6.组件的嵌套: .vue文件既可以是一个页面,也可以是一个组件 它可以被别人.vue文件引入 然后作为组件使用
补充:组件的自定义事件和原生事件
//1.在原生组件(就是html标签)中 事件是由系统来设计触发条件的: <div @click="fn">点我</div>//2.在自定义组件中,事件是由自己来设计什么时候触发的://绑定事件: <mydiv @myclick="fn">点我</mydiv>//事件设计: //在mydiv组件内部,你可以在你想要的条件下去触发事件 this.$emit("myclick","要给触发的事件的函数传入值")//这个代码放在你想触发自定义事件的地方//3.如果希望组件绑定原生事件(事件的触发条件设计由系统设计) //给事件绑定事件修饰符 .native <mydiv @click.native="fn">点我</mydiv>//事件名必须是系统存在的事件//4.自定义事件的sync 事件名和属性名相同时可以简写<MySon :title="msg" @update:title="fn"></MySon><MySon :title.sync="msg" ></MySon>
7.面试题(组件基础)
组件中的基础语法常见的面试题方向:
7.1 @ 是一个关键字,在引入的文件路径中 它代表src目录
7.2 template:组件的模板中只能有一个根节点
7.3 v-slot: 插槽, 具名插槽 slot,slot-scope过时了 2.6.0使用v-slot 语法:v-slot:插槽名 语法糖:#插槽名 没有指定插槽名就是默认插入到插槽,不给插槽插入数据的话,就会使用组件的slot标签的尖括号中的数据 插槽名不用使用引号引起来,直接写变量名 插入的内容必须是template标签或者组件 不能是原生的元素
7.4 组件的data为什么是个函数然后返回对象,以前使用vm时都是个对象
组件和挂载到界面的vm对象的区别,vm挂载到页面上时,触发了钩子函数的,data生成了,页面上使用的数据就是data容器中渲染上去的,而且页面只有一个vm对象,所以的vm生成完毕(mouted)时data必须存在
组件是引入和注册以后不一定使用的,比如for循环0次就是组件对象生成了的,但是使用0次,所以组件对象并没有使用自己的data容器去渲染数据,造成资源浪费,解决方案就是懒加载:当使用data时去调用,才生成data对象
组件的data 设计成function的用义:组件可以多次使用,每使用一次,函数被调用一次则创建出不同的数据对象,实现同名组件的数据可以相互独立
7.5 scoped: style标签的scoped="scoped" 生成css的使用作用域只有当前组件内部的选择器生效
组件高级(面试)
7.传值
7.1父组件通过属性给子组件传值: 子组件的props接受数据
在页面模板中 使用变量名:属性 data 计算属性(重点)
//注意属性传值是单向的
7.2 子组件通过调用父组件的方法给父组件传值:子组件的自定义事件中,用$emit触发事件调用父组件方法给父组件传值 (重点)
因为通过属性传值是单向的,有时候我们需要子组件的data 数据需要交给父组件使用: 通过在子组件上定义自定义事件,在子组件中通过$emit 来触发事件;子组件的事件被触发并传参,事件处理函数可以接收到子组件的数据;事件绑定的事件处理函数在父节点上,故可在事件处理函数中用到子组件的数据值来修改父节点的数据。
//父组件中: <my-search @myevent="handleEvent"></my-search> //myevent是事子组件的自定义事件 //handleEvent是绑定的父组件的方法子组件中: 在任意业务中触发事件:this.$emit("myevent","要给父组件传的数据")
3多层组件传值:$listeners/$attrs(了解)
在不用状态管理vuex的时候,如何让GrandFather与Son通信,我们可以用可以emit一层一层的传递:会显得冗余。 vue2.4之后,提出$attrs、$listeners ,可以实现跨级组件通信。 $listeners官网解说:事件传递 $attrs官网解说:属性传递
在组件中绑定 可以把当前组件的自定义属性和方法传给子元素使用:
one组件:<two v-bind:xx="100" v-on:twoEvent="fn"></two> two组件中:<three v-bind="$attrs" v-on="$listeners"></three> three组件:可以访问two的 属性和触发事件: {{this.$attrs.xx}} this.$emit("twoEvent",20)
4 $ parent/$root、$children/$refs(了解)
这些功能都是有劣势或危险的场景的,官方建议我们尽量避开它们,但是高级点的面试题中会出现
$root: 访问根组件vm对象,所有的子组件都可以将这个实例作为一个全局 store 来访问或使用,现在有更好的技术vuex代替。$parent:访问父组件对象,直接操作父组件的data数据,不需要再使用属性传值,但是容易出现渲染混乱之后只渲染一个的情况$children:访问子组件对象数组,不能保证顺序,也不是响应式的$refs:只会在组件渲染完成之后生效,并且它们不是响应式的。你应该避免在模板或计算属性中访问 $refs。//在组件或者原生元素绑定ref属性(类似于id):<myinput ref="myInput1"></myinput><input ref="myInput2"></input>//在父组件中可以通过 this.$refs访问到它:methods: {focus: function () {this.$refs.myInput2.focus()}}
5 中央事件总线bus(了解) event bus
通过创建一个新的vm对象,专门统一注册事件,供所有组件共同操作,达到所有组件随意隔代传值的效果
//vue-bus.js文件 const install = function (Vue) {const Bus = new Vue({methods: {emit(event, ...args) {this.$emit(event, ...args);},on(event, callback) {this.$on(event, callback);},off(event, callback) {this.$off(event, callback);}}});Vue.prototype.$bus=Bus;//由于这个新的vm放在与界面绑定的那个vm的原型上,因此页面上的所有组件都能通过this.$bus访问这个新vm对象 }; export default install;//main.js文件 import VueBus from './vue-bus' Vue.use(VueBus);//组件文件中: 任意业务中都可以通过调用来绑定事件,触发事件并传值,和销毁事件 this.$bus.on(event,callback) this.$bus.off(event,callback) this.$bus.emit(event, ...args)示例: 组件1:this.$bus.on('changedFormObject',(val) =>{//接受并处理传过来的值:valthis.msg = val;});组件2: this.$bus.emit('changedFormObject',this.inputValue);//把组件2的data中的给inputValue值传给组件1
7.6 Vue 依赖注入 - Provide/Inject(重点)
通常情况下,父组件向孙组件传递数据,可以采用父子props
层层传递,也可以使用bus
和Vuex
直接交互。在Vue2.2.0之后,Vue还提供了provide/inject
选项
官网不建议在应用中直接使用该办法,理由很直接:他怕你"管不好"
//爷爷 <template><div><p>{{ title }}</p><son></son></div> </template> <script>import Son from "./son"export default {name: 'Father',components: { Son },// provide选项提供变量provide: {message: 'provided by father'},data () {return {title: '父组件'}},methods: { ... }} </script>//爸爸 <template><div><p>{{ title }}</p><grand-son></grand-son></div> </template> <script> import grandSon from "./grandSon " export default {name: "Son",components: { grandSon },data () {return {title: '子组件'}}, }; </script> //孙子 <template><div><p>message:{{ message }}</p></div> </template> <script> export default {name: "GrandSon",inject: [ "message" ],data () {return {title: '孙组件'}},methods: { ... } }; </script>
7仓库vuex(重点)
后面讲
8.动态组件
有的时候,我们希望页面中的某个地方,在不同组件之间进行动态切换,这时候除了条件渲染,还可以使用动态组件
component 标签的 is属性语法:is后跟组件的变量名决定使用哪个组件来渲染
<component is="Sinabox"></component>==><Sinabox/>
<component v-bind:is="mycomponent"></component> 注意: is是组件名 :is是data中的变量中保存的组件名
<template><div id="app"><button @click="fn">xx</button><components :is="com"></components></div> </template> <script> import HelloWorld from './components/HelloWorld.vue' import HelloWorld2 from './components/HelloWorld2.vue' export default {name: 'app',data(){return{flag:true,com:"HelloWorld",}},components: {HelloWorld,"HelloWorld2"},methods:{fn(){this.flag=!this.flagthis.com=this.flag?"HelloWorld":"HelloWorld2"}} } </script>
9.缓存组件keep-alive 动态组件的切换,切换后是不会缓存之前被切换掉的组件的,每次切换新组件的时候,Vue 都创建了一个新的组件对象。 有时候我们希望在A组件时用户做了一些操作,切换B组件时做了一些操作,当切回A组件时希望记住A的操作,不要重新创建A组件,keep-alive可以缓存动态切换的组件 <!-- 失活的组件将会被缓存!--> <keep-alive> <component v-bind:is="currentTabComponent"></component> </keep-alive>
<!-- 提供有include 和exclude 属性决定哪些组件可以被缓存(字符串或正则表达式)。!-->
<keep-alive :include="/a|b/"> <component v-bind:is="currentTabComponent"></component> </keep-alive> //include表示a,b组件被缓存,其他组件不缓存,exclude代表除了xx组件其他的组件缓存
匹配首先检查组件自身的 name 选项,匿名组件不能被匹配。
<template><div id="app"><button @click="fn">xx</button><keep-alive><components :is="com"></components></keep-alive></div> </template> <script> import HelloWorld from './components/HelloWorld.vue' import HelloWorld2 from './components/HelloWorld2.vue' export default {name: 'app',data(){return{flag:true,com:"HelloWorld",}},components: {HelloWorld,"HelloWorld2"},methods:{fn(){this.flag=!this.flagthis.com=this.flag?"HelloWorld":"HelloWorld2"}} } </script>
10.异步组件(高薪意向)
vue开发过程中,我们会做出特别多特别多的组件,包括login,header,footer,main等等。这样使整个网站看起来就十分的庞大,当我们在打开网页的时候,突然一下子把这些所有的组件加载上来,这么多的请求全部同时开始请求,势必会造成网页打开很慢,使客户得到的是非常差劲的体验。
SPA: single page app
在单页应用中,如果没有用懒加载,运用webpack打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,延时过长,不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时
异步加载并缓存组件:
1、 异步加载组件:用不到的组件不会加载,因此网页打开速度会很快,当你用到这个组件的时候,才会通过异步请求进行加载;
官方解释:Vue允许将组件定义为一个异步解析(加载)组件定义的工厂函数,即Vue只在实际需要渲染组件时,才会触发调用工厂函数,并且将结果缓存起来,用于将来再次渲染。
2、 组件缓存起来:通过异步加载的组件会缓存起来,当你下一次再用到这个组件时,丝毫不会有任何的疑迟,组件很快会从缓存中加载出来。
作业:import和require的区别
方法一:通过webpack2.0的按需加载
//1 全局:Vue.component('component-name',function(resolve){//require 语法告诉 webpack自动将编译后的代码分割成不同的块//这些块将通过 Ajax 请求自动下载require(['./my-async-componnet'],resolve)})//注册全局组件名,但只有一个名字,没有实体,相当于空的 //当需要这个组件时,调用上面的工厂函数,触发webpack的异步加载模块方法 //然后异步请求一个模块,请求成功后,这个模块内容即为组件实体部分,并对应地方渲染,加载内容也缓存下来。//2 局部new Vue({components: {'component-name':function(resolve) {require(['./my-component'], resolve)}}})
方法二:通过webpack2+es2015返回一个promise(主流 )
//1 全局: Vue.component('component-name',()=> import('./my-async-componnet')//这个 `import` 函数会返回一个 `Promise` 对象,不要问我为什么,人家webpack这样设计的。) //2 局部:new Vue({components: {'component-name': () => import('./my-async-componnet')//这个 `import` 函数会返回一个 `Promise` 对象。}})
方法三:高级异步组件(可以处理加载状态) 常常用在路由中使用,vue 2.3.0+ 新增终极解决方案,要求路由2.4.0+
//工厂对象可以返回一个如下对象,对象里面的一些配置参数 const AsyncComponent = () => ({// 需要加载的组件 (这个 `import` 函数会返回一个 `Promise` 对象。)component: import('./MyComponent.vue'),// 异步组件加载时使用的组件loading: LoadingComponent,// 加载失败时使用的组件error: ErrorComponent,// 展示加载时组件的延时时间。默认值是 200 (毫秒)delay: 200,// 如果提供了超时时间且组件加载也超时了,// 则使用加载失败时使用的组件。默认值是:`Infinity`timeout: 3000 })
示例:
<template><div id="app"><button @click="fn">xx</button><keep-alive><components :is="com"></components></keep-alive><!-- <components :is="com"></components> --></div></template><script>import HelloWorld from './components/HelloWorld.vue'export default {name: 'app',data() {return {flag: true,com: "HelloWorld",}},components: {HelloWorld,// HelloWorld2,// "HelloWorld2":() => import('./components/HelloWorld2.vue')"HelloWorld2": () => ({// 需要加载的组件 (是一个 `Promise` 对象)component: import('./components/HelloWorld2.vue'),// 异步组件加载时使用的组件loading: import('./components/Palcebox.vue'),// 加载失败时使用的组件error: import('./components/Palcebox.vue'),// 展示加载时组件的延时时间。默认值是 200 (毫秒)delay: 200,// 如果提供了超时时间且组件加载也超时了,// 则使用加载失败时使用的组件。默认值是:`Infinity`timeout: 3000})},methods: {fn() {this.flag = !this.flagthis.com = this.flag ? "HelloWorld" : "HelloWorld2"}}}</script><style></style>
组件总结
模板上用的代码: {{}} v-text v-html v-pre v-clock v-bind(:) v-on(@) 事件修饰符 .stop .once .capture .self .native v-xxx directive v-if/v-else v-show v-for v-model $attr $listener 新:v-slot 旧:slot
组件内的代码 data:()=>{} props:[], comments:{}, directives:{}, filters:{}, watch:{}, computed:{}, beforeCreate(){}, created(){}, beforeMount(){}, mounted(){}, beforeUpdate(){}, updated(){}
this可以访问哪些东西? 属性 方法 data $destroy $ emit=>自定义事件
路由
1.相关认识 后端路由:对于前端的网络请求,不同的pathname,去执行后端的不同业务 前端路由:不同的网址对应各自的页面 vue的前端路由:SPA应用要做出路由效果,就得判断当前网址,然后切换组件 vue-router就是专门做切换组件的功能,它是一个单独的技术,依赖vue 就像jQuery和dom操作一样
2.引入 2.1.cdn自己网上随便看看
2.2 自己npm下载引入使用
//安装到项目中:npm i vue-router --save-dev//注意:这种下载不好,因为打包之后容易出问题npm i vue-router --save或者 npm i vue-router --S在main.js入口文件中引入import Vue from "vue"import VueRouter from "vue-router"//引入路由工具import App from "./App.vue"Vue.use(VueRouter)//注入路由,就是运行路由的相关函数和给vue绑定东西:比如$router//创建路由工具const router=new VueRouter({//routes路由器 生成很多网址对应的切换组件routes:[{path:"/home",component:()=>import("./home.vue")},{path:"/about",component:()=>import("./about.vue")}]})new Vue({router,//把路由挂载到页面render(h){return h(App)}}).$mount("#app")//在App.vue中写 <router-view></router-view>
2.3 cli安装 vue create xxapp 空格键选择router然后回车 自定就会把路由安装好
3.router-view和router-link 标签
router-view:相当于 路由网址匹配到的组件 会渲染到当前组件的这个标签上(学不学的会路由 就看能不能理解这个)
router-link:相当于a标签,给我们提供跳转到某个路由的功能,如果没有匹配到路由就会跳转失败:
<router-link to="/xx"> xx<router-link > 编程式跳转: this.$router.push({path:"路由"})
路由传参
两种方式:
1、 在router-link 标签的to 属性路由值里添问号”?参数名=参数值“传参,在js 中使用”$route.query.参数名“接收参数值;
2、 在路由规则里定义路由规则”{path:'/路由/:参数名'}“
在router-link 标签的to 属性路由值里添斜杠”/参数值“传参,在js 中使用”$route.params.参数名“接收参数值;
html: <div id="app" ><h1>Hello App!</h1><p><router-link to="/">首页</router-link><br><router-link to="/list?id=1">新闻部</router-link><br><router-link to="/list?id=2">编辑部</router-link><br><router-link to="/list?id=3">人事部</router-link><br><router-link to="/about/1">关于我们</router-link></p><router-view></router-view></div> 部分js: 路由规则-- const myroutes = [{ path: '/', component: main },{ path: '/list', component: list },{ path: '/about/:mydata', component: about } ] 组件定义内,取参用参-- const list = { template: `<div>列表页面内容{{$route.query.id}}` } const about = { template: '<div>关于我们页面{{$route.params.mydata}}</div>' }
3、编程式导航
通过js 触发路由切换:
router.push('home') // 字符串,路由规则中定义的name名 router.push({ path: 'home' }) // 对象 router.push({ name: 'user', params: { userId: 123 }}) // 命名的路由 router.push({ path: '/list', query: { plan: 'private' }}) router.replace(location)://跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。 router.go(n);//参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)。 router.go(1) // 在浏览器记录中前进一步,等同于 history.forward() router.go(-1) // 后退一步记录,等同于 history.back()
5.路由嵌套--子路由跳转 *代表所有匹配都通过 redirect代表重定向 //注意:子路由中的匹配地址不要在前面加/ 否则就会认为是根路由
//语法:一个路由对象的设计中中包含: path(路由匹配的路径) component(匹配路径时对应渲染的组件) redirect(匹配路径时重新匹配另外的路径) children(子路由们的数组) name(路由的名字,方便直接通过名字来匹配路由): //routes=[{path,component,redirect,children,name},{path,component,redirect,children,name}] const routes = [{path: '/',component: () => import("../views/root.vue"),redirect:"/goods_rank",// 访问/时默认重新访问/goods_rankchildren: [{name:"goods_rank",path: "goods_rank",component: () => import("../views/root/goods_rank.vue"),//goods_rank组件加载的条件是路由匹配上了: localhost:8080/goods_rankchildren: [{name:"goods_rank_all",path: "goods_rank_all",component: () => import("../views/root/goods_rank/goods_rank_all.vue")//goods_rank_all组件加载的条件是路由匹配上了: localhost:8080/goods_rank/goods_rank_all},{path: "goods_rank_highpraise",name:"goods_rank_highpraise",component: () => import("../views/root/goods_rank/goods_rank_highpraise.vue")},{path: "goods_rank_middlepraise",name:"goods_rank_middlepraise",component: () => import("../views/root/goods_rank/goods_rank_middlepraise.vue")},{path: "goods_rank_badepraise",name:"goods_rank_badepraise",component: () => import("../views/root/goods_rank/goods_rank_badepraise.vue")},{path: "*",component: () => import("../views/root/goods_rank/goods_rank_all.vue")}] },{path: "goods_inventory",name:"goods_inventory",component: () => import("../views/root/goods_inventory.vue")},{path: "goods_new",name:"goods_new",component: () => import("../views/root/goods_new.vue")},{path: "goods_set",name:"goods_set",component: () => import("../views/root/goods_set.vue")}] }]
//跳转总结:
注册路由时,路由路径不要在前面加 斜杠
跳转路由时 1.path里写的路径必须前面加斜杠 从根路由路径开始写 2.不用path 直接使用name
6.路由模式--hash&history(面试)
hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取; 特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。 hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
history模式:history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。 history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。
const router=new VueRouter({mode:"hash",//"history"routes:[{path:"/home",component:()=>import("./home.vue")},{path:"/about",component:()=>import("./about.vue")}]})
7.VueRouter router route区别 VueRouter是一个nodejs识别的模块包 route是路由匹配时,携带了一些信息的对象,包括path,params,hash,query等等信息 router是路由实例对象,包含了路由的跳转方法,钩子函数等等
8.路由守卫
8.1全局守卫
全局前置钩子router.beforeEach(fn),导航被触发----一般登录验证
全局解析钩子router.beforeResolve(fn),组件初始化 全局后置钩子router.afterEach(fn),没有next,导航被确认,一般路由跳转以后用window把窗口调上去
//全局前置守卫 router.beforeEach((to, from, next) => {//用户未登录只能访问首页、登录注册页面if (to.path == "/" || to.path == "/login" || to.path == "/register") {next();} else {//去其他页面判断是否登录let flag = window.localStorage.getItem("email");//登录过直接放行if (flag) {next()} else {//未登录则跳转到首页Message({message: '您尚未登录哦,请先登录!',type: 'warning',duration: 1500});next("/");}} }) //全局后置守卫 router.afterEach((to, from) => {window.scrollTo(0,0) })
8.2路由独享的守卫
路由独享的守卫beforeEnter(to,from,next),路由初始化(组件未初始化)----
a,路由鉴权-----用户体验:界面,功能,bug,效率,权限
b,组件异步加载情景中(插件配置:syntax-dynamic-import)
routes:[{path:"/test",component:()=>import("../components/Test.vue"),beforeEnter(to,from,next){if(to.path==="/test"){alert("请登录");next(false); // 禁止跳转}else{next()}} }]
3)组件内部生命周期守卫
beforeRouteLeave 从该组件离开
beforeRouteEnter(to,from,next),组件被激活,使用不了this,故构造指定该next 可以接收一个回调函数接收当前vm 实例----路由传参获取参数,得到初始化数据
beforeRouteUpdate(to,from,next),组件重用时被调用----路由传参获取参数,避免增添watch 开销
导航守卫执行顺序:beforeRouteLeave < beforeEach < beforeRouteUpdate < beforeEnter < beforeRouteEnter < beforeResolve < afterEach
出发路由,预备从当前组件离开,判断路由变化,判断组件是否重用,判断新路由初始化,判断组件初始化,路由与组件初始化完毕,路由组件重定向完毕
仓库vuex
Vuex简介
1.为了方便实现组件之间的数据共享,Vuex是他们团队借鉴了redux,用来实现vue组件全局状态(数据)管理的一种机制.
2.特点(面试)
能够在vuex中集中管理共享的数据,易于开发和后期维护
能够高效地实现组件之间的数据共享, 提高开发效率
存储在 vuex中的数据都是响应式的,能够实时保持数据与页面的同步
一般情况下,只有组件之间共享的数据,才有必要存储到vuex中;
对于组件中的私有数据,依旧存储在组件自身的data中即可.
3.使用场景
如果你实在不知道开发中什么数据才应该保存到Vuex中,那你就先不用Vuex,需要的时候自然就会想到它
引入方式
1.自己下载引入
2.cli脚手架引入
1.安装:
npm i vuex --save导入:import Vuex from "vuex"Vue.use(Vuex)
创建仓库:
const store=new Vuex.Store({state:{msg:"我就是所有共享的数据"}})
把仓库挂载到vm对象:
new Vue({render(h){return h(app)},router,store//挂载以后 所有的组件就可以直接从store中获取全局数据}).$mount( "#app")
2.vue create app
选择配置vuex
State
1.创建state状态,状态就是那个存数据的对象
const store=new Vuex.store({state:{msg:"我就是所有共享的数据"}})
2.组件中访问数据
this.$store.state.msg
Getter
getter就就像是store的计算属性,它会传入state对象供我们操作 //1.设计
const store = new Vuex.Store({state: {arr: [{ id: 1, text: '...', birth: "1997-02-03" },{ id: 2, text: '...', birth: "2019-10-03" }]},getters: {bigMans(state) {return state.arr.filter((man)=>{let manY=new Date(man.birth).getFullYear()let nowY=new Date().getFullYear()return (nowY-manY)>=18})}} })
//2.使用
this.$store.getters.bigMans
Mutation
组件中希望更改 Vuex 的 store 中的状态(数据)的唯一方法是提交 mutation 不要用赋值表达式直接在组件中给store设置新数据 这样设计的原因是,只有通过mutation来更新数据,它才会去帮我们通知所有使用数据的组件更新数据 刷新UI
//1.设计
const store = new Vuex.Store({state: {count: 1},mutations: {//默认传第一个参数传stateincrement (state,obj) {// 变更状态state.count=obj.n}} })
//2.组件中使用 //2.1 直接触发并传值(提交载荷)
this.$store.commit('increment',{n:100})
//2.2 可以以一个对象的形式传入 //传入对象时,当相于把整个对象作为了第二个参数传入mutations
this.$store.commit({type: 'increment',n:100 })
注意:一条重要的原则就是要记住 mutation 必须是同步处理 下面是错误的方式:
mutations: {increment (state,obj) {//如果fnAsync函数是一个异步业务函数,就会导致更新失败fnAsync(() => {state.count=obj.n})} }
Action
Action 提交的是 mutation,而不是直接变更状态。 Action 可以包含任意异步操作。
//设计
const store = new Vuex.Store({state: {count: 0},mutations: {increment (state,obj) {state.count=obj.n}},actions: {//默认第一个参数传一个跟store一样的对象increment (context,obj) {//假设fnAsync是一个异步业务函数fnAsync(() => {context.commit('increment',obj)}) }} })
//使用也是两种: //1.直接分发
this.$store.dispatch('increment',{n:100})
//2.以对象形式分发
this.$store.dispatch({type: 'increment',n:100 })
Module
可用于业务分块开发: 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。 为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter
//分块设计: const moduleA = {namespaced: true,//局部命名空间(让state的中变量与其他模块中的同名变量不冲突) state: { msg:1 },mutations: { change(state,n){state.msg=n} },actions: { change(context,n){context.commit("change",n)} },getters: { x(state){return state.msg} } } const moduleB = {namespaced: true,//局部命名空间 state: { msg:1 },mutations: { change(state,n){state.msg=n} },actions: { change(context,n){context.commit("change",n)} },getters: { x(state){return state.msg} } } const store = new Vuex.Store({modules: {a: moduleA,b: moduleB} }) //组件中的使用: this.$store.state.a.msg // -> moduleA 的状态 this.$store.state.b.msg // -> moduleB 的状态 this.$store.commit("a/change",100)-> moduleA 的mutations this.$store.commit("b/change",200)-> moduleB 的mutations this.$store.getters["a/x"]-> moduleA 的getters this.$store.getters["b/x"]-> moduleB 的getters this.$store.dispatch("a/change",100)-> moduleA 的actions this.$store.dispatch("b/change",200)-> moduleB 的actions
技术厉害的同学拓展:mapState,mapGetters,mapMutation,mapAction和使用常量替代 Mutation 事件类型,
使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然
用不用常量取决于你——在需要多人协作的大型项目中,这会很有帮助。但如果你不喜欢,你完全可以不这样做。
element等等UI框架
1.安装:
npm i element-ui -S
2.引入
import Vue from 'vue'; import ElementUI from 'element-ui';//引入组件库 import 'element-ui/lib/theme-chalk/index.css';//全局的css样式 import App from './App.vue';Vue.use(ElementUI);//注册全局组件==>Vue.component("xxxx",{})new Vue({el: '#app',render: h => h(App) });
Vue3快速上手
1.Vue3简介
2020年9月18日,Vue.js发布3.0版本,代号:One Piece(海贼王)
耗时两年多,2600+次提交,30+个RFC,600+次PR,99位贡献者
github上的tags地址:Release v3.0.0 One Piece · vuejs/core · GitHub
2.Vue3带来了什么
2.1 性能的提升
打包大小减少41%
初次渲染块55%,更新渲染快133%
内存减少54%
......
2.2 源码的升级
使用Proxy代替defineProperty实现响应式
重写虚拟DOM的实现和Tree-Shaking
3.拥抱TypeScript
vue3.0更好的支持TypeScript
4.新的特性
4.1 Composition API (组合API)
setup配置
ref和reactive
watch和watchEffect
provide和inject
4.2 新的内置组件
Fragment
Teleport
Suspence
4.3 其他改变
新的生命周期钩子
data选项应该始终被声明为一个函数
移除keyCode支持做为v-on的修饰符
.....
Vue3.0环境集成
1.使用vue-cli创建
## 1.查看@vue/cli的版本,确保@vue/cli版本在4.5.0以上 vue --version ## 2.安装或者升级你的@vue-cli ## 3.创建 vue create vueproject # 4.启动 cd vueproject npm run serve
2.使用vite创建
官方文档:快速上手 | Vue.js
vite官网:https://vitejs.com
什么是vite? -------- 新一代的前端工具
优势如下:
开发环境中,无需打包,可快速的冷启动
轻量快速的热加载
## 1.创建 npm init vite vapp -- --template vue 生成一个模板页面 npm init vite vapp 可以选择集成的插件(路由 仓库 ts 等等)
常用的Composition API(组合API)
官方文档:组合式 API 常见问答 | Vue.js
1.setup
理解:Vue3.0中一个新的配置向,值为一个函数
setup是所有Composition API(组合API) ”表演的舞台“
组件中所有用到的:数据、方法等等,均要配置在setup中
setup函数的两种返回值:
若返回一个对象,则对象中的数据、方法、在模板中均可直接使用(重点)
<template><div class="home"><h1>显示vue3.0的数据和方法</h1><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><button @click="sayName()">点击测试vue3.0的方法</button></div> </template> <script> export default {setup() {// 现在暂时还不考虑响应式数据的问题// 数据 let name = "李国栋";let age = 18; // 方法let sayName = function () {console.log(`我的名字是${name},我的年龄是${age}`);} return {name, age, sayName}} } </script>
若返回一个渲染函数,则可以自定义渲染内容(了解)
//页面中会显示一个h1标签,标签的内容是"vue3返回渲染函数" <script> import {h} from 'vue' export default {setup() {// 返回一个渲染函数// h相当于document.CreateElement()return ()=>{return h("h1","vue3返回渲染函数")}} } </script>
注意点
尽量不要和Vue2.x配置混用
Vue2.x配置(data,methods,computed......)中可以访问setup中的属性,方法
但在setup中不能访问到Vue2.x配置(data,methods,computed......)
如有重名,setup优先
setup不能是一个async函数,因为返回值不再是return的对象,而是Promise,模板看不到return对象中的属性
2.ref函数
作用:定义一个响应式的数据
语法:const xxx = ref("value")
创建一个包含响应式的数据的引用对象(reference对象)
js中操作数据:xxx.value
模板中读取数据不需要.value,直接<div>{{xxx}}</div>
注意
接收的数据类型可以是基本数据类型也可以是引用数据类型
基本类型的数据:响应式依然是靠Object.defineProperty()的get和set完成的
对象类型的数据:内部“求助”了Vue3.0的一个新的函数------reactive函数
<template><div class="home"><h1>显示vue3.0的数据和方法</h1><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><h2>工作:{{more.job}}</h2><h2>薪资:{{more.salary}}</h2><button @click="changeMsg()">点击测试vue3.0的基本数据</button><button @click="changeMore()">点击测试vue3.0的引用数据</button></div> </template> <script> import { ref } from "vue" export default {setup() {// 1.基本数据类型let name = ref("李国栋");let age = ref(18);let more = ref({job: "前端开发工程师",salary: "10k"}) // 方法let changeMsg = function () {name.value = "余梦佳";age.value = 19;console.log(name, age);} let changeMore = function () {more.value.salary = "20K";console.log(more.value);} return {name, age, changeMsg, more, changeMore}} } </script>
3.reactive函数
作用:定义一个对象类型的响应式数据(基本数据类型别用它,用ref函数)
语法:const 代理一个对象 = reactive(被代理的对象) 接收一个对象(或数组),返回一个代理器对象(proxy对象)
reactive定义的响应式数据是“深层次的”
内部基于ES6的Proxy实现,通过代理对象内部的数据都是响应式的
<template><div class="home"><h1>显示vue3.0的数据和方法</h1><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><h2>工作:{{more.job}}</h2><h2>薪资:{{more.salary}}</h2><h2>c:{{more.a.b.c}}</h2><button @click="changeMsg()">点击测试vue3.0的基本数据</button><button @click="changeMore()">点击测试vue3.0的引用数据</button></div> </template> <script> //1.引入 import {ref,reactive} from "vue" export default {setup() {let name = ref("李国栋");let age = ref(18);let more = reactive({job: "前端开发工程师",salary: "10k",a:{b:{c:"ccccccccccccc"}}}) // 方法let changeMsg = function () {name.value = "余梦佳";age.value = 19;console.log(name, age);} //2.修改 let changeMore = function () {more.salary = "20K";more.a.b.c = "55555555555555555555"} return {name, age, changeMsg, more, changeMore}} } </script>
4.Vue3.0中的响应式原理
vue2.0的响应式
实现原理
对象类型:通过Object.definedProperty()对属性的读取、修改进行拦截(数据劫持)
数组类型:通过重写更新数据的一系列方法来实现拦截。(对数组的方法进行了包裹)
Object.defineProperty(data,"count",{get(){},set(){} })
存在问题:
新增属性,删除属性都不会刷新界面
直接通过下标修改数组,界面不会自动更新
let person = {name:"李国栋",age:18} let p = {}; Object.defineProperty(p,"name",{get(){console.log("有人读取数据时调用");return person.name}, set(value){console.log("有人修改了数据,我要去更新页面");person.name = value}})
vue3.0的响应式
实现原理
通过Proxy(代理):拦截对象中任意属性的变化,包括:属性的读写,属性的添加,属性的删除等
通过Reflect(反射):对被代理对象的属性进行操作
MDN文档中描述的Proxy与Reflect:
Proxy:Proxy - JavaScript | MDN
Reflect:Reflect - JavaScript | MDN
let person = {name:"李国栋",age:18} let p = new Proxy(person,{// 读取get(target,proname){// target表示原对象 proname表示对象名console.log("有人读取了person上的属性",target);return target[proname]},// 修改或者增加set(target,proname,value){console.log("有人修改了person上的属性,我要去更新了");target[proname] = value},// 删除deleteProperty(target,proname){console.log("有人删除了person上面的属性,我要去调用了");return delete target[proname]},});
5.计算属性
与vue2.x中computed配置功能一致
写法
<template><div class="home"><h1>vue2.0的计算属性--------{{total1}}</h1> <div v-for="(el,index) in arr2" :key="index"><span>{{el.title}}---</span><span>{{el.price}}--</span><button @click="jianshao(index)">-</button><span>{{el.count}}</span><button>+</button></div> <h1>vue3.0的计算属性----------{{total2}}</h1></div> </template> <script> // 注意:vue2.x和vue3.x不要混用 // 1.引入 import { reactive, computed } from "vue" export default { data() {return {arr: [{title: "鱼香肉丝",price: 10,count: 1},{title: "麻婆豆腐",price: 10,count: 2},{title: "米饭",price: 1,count: 3}],};},computed: {total1() {return this.arr.reduce((a, b) => {return a + b.price * b.count}, 0)}}, setup() { let arr2 = reactive([{ title: "鱼香肉丝", price: 18, count: 1 },{ title: "鱼香肉丝2", price: 20, count: 2 },{ title: "鱼香肉丝3", price: 30, count: 4 }]) // 2.使用let total2 = computed(() => {return arr2.reduce((n1, n2) => (n1 + n2.price * n2.count), 0)}) // 减少let jianshao = function(index){arr2[index].count--}return { arr2, total2,jianshao } } } </script>
6.监听属性
与vue2.x中的watch配置功能一致
注意
监视reactive定义的响应式数据时,oldvalue无法正确获取,强制开始了深度监视(deep的配置失效)
监视reactive定义的响应式数据的某一个值时:deep配置有效
<template><div class="home"><h1>{{num}}</h1><h1>{{msg}}</h1> <button @click="num++">点击num++</button><button @click="addmsg()">点击msg+!</button></div> </template> <script> // 注意:vue2.x和vue3.x不要混用 // 1.引入 import { watch, ref,reactive } from "vue" export default { data() {return { }},computed: {}, setup() { let num = ref(0);let msg = ref("hello");let person = reactive({name:"李国栋",age:18,more:{job:"前端开发工程师",salary:"12k"}}) // 1.监听ref定义的响应式数据watch(num, (newvalue, oldvalue) => {console.log("num变量", newvalue, oldvalue);}) // 2.监听多个ref定义的响应式数据watch([num, msg], (newvalue, oldvalue) => {console.log("msg数据变化了", newvalue, oldvalue);}) let addmsg = () => {msg.value += "!"}//3.监听reactive所定义的一个响应式数据的全部属性//注意:此处无法正确的获取oldvalue//注意:并且强制开启了深度监视(deep配置无效)watch(person, (newvalue, oldvalue) => {console.log("person数据变化了", newvalue, oldvalue);},{deep:false})//4.监听reactive所定义的一个响应式数据的某一个属性watch(()=>person.name, (newvalue, oldvalue) => {console.log("person数据变化了", newvalue, oldvalue);},{deep:false}) return { num, msg, addmsg } } } </script>
7.Vue3生命周期
什么是生命周期
Vue中每个组件都是独立的,每个组件都有一个属于它的生命周期,从一个组件创建、数据初始化、挂载、更新、销毁、这就是一个组件所谓的生命中周期
Vue2.x中的生命周期
beforeCreate created beforeMount mounted beforeUpdate updated beforeDestroy destroyed activated deactivated errorCaptured
Vue3.x的生命周期
在Vue3.x中,新增了一个setup生命周期函数,setup执行的时机是在beforeCreate生命函数之前执行,因为在这个函数中不能通过this来获取实例的;同时为了命名的统一,将beforeDestory改名为beforeUnmount,destoryed改名为unmounted
beforeCreate(建议使用setup代替)created(建议使用setup代替) setup beforeMount mounted beforeUpdate updated beforeUnmount unmounted
vue3新增了生命周期钩子,我们可以通过在生命周期函数前加on来访问组件的生命周期
Composition API 形式的生命周期钩子
onBeforeMount onMounted onBeforeUpdate onUpdated onBeforeUnmount onUnmounted onErrorCaptured onRenderTracked onRenderTriggered
<script> import {onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted,ref } from 'vue'export default {setup () {// 其他的生命周期onBeforeMount (() => {console.log("App ===> 相当于 vue2.x 中 beforeMount")})onMounted (() => {console.log("App ===> 相当于 vue2.x 中 mounted")})// 注意,onBeforeUpdate 和 onUpdated 里面不要修改值onBeforeUpdate (() => {console.log("App ===> 相当于 vue2.x 中 beforeUpdate")})onUpdated (() => {console.log("App ===> 相当于 vue2.x 中 updated")})onBeforeUnmount (() => {console.log("App ===> 相当于 vue2.x 中 beforeDestroy")})onUnmounted (() => {console.log("App ===> 相当于 vue2.x 中 destroyed")})return {}} } </script>
8.Teleport
Vue 鼓励我们通过将 UI 和相关行为封装到组件中来构建 UI。我们可以将它们嵌套在另一个内部,以构建一个组成应用程序 UI 的树。
然而,有时组件模板的一部分逻辑上属于该组件,而从技术角度来看,最好将模板的这一部分移动到 DOM 中 Vue app 之外的其他位置
模态弹框
to属性:放到指定位置
<template><div class="reson"><button @click="showModel">点击弹出模态框</button> <teleport to="body"><div class="mask" v-show="isShow"><div class="box"><h1>我是模态框</h1><button @click="closeModel">点击关闭模态框</button></div></div></teleport> </div> </template> <script> import { ref } from "vue" export default {name: 'Reson', setup() {let isShow = ref(false)//显示let showModel = function () {isShow.value = true}//隐藏let closeModel = function () {isShow.value = false}return { isShow, showModel, closeModel }} }; </script> <style lang="scss" scoped> .reson {background-color: pink; } .mask {width: 100%;height: 100%;position: absolute;top: 0;left: 0;background-color: rgba(0, 0, 0, 0.5); } .box {width: 300px;height: 300px;background-color: aqua;position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%); } </style>
9.属性
组件内部 还是可以用2.0 做项目时愿意用2.0的同学语法不变
<script> export default {pops:["msg"],// pops:{msg:String}, setup(){} } < /script> <template><div>{{msg}}</div> </template>父组件:<Box :msg="n"></Box>
2.vue3组件内部组合式API setup中取属性值
<script> export default {pops:["msg","count"],// pops:{msg:String}, setup(props){let fn=()=>{console.log(props.msg,props.count)}//必须在组件中注册属性不然setup函数收不到数据return {fn}} } < /script> <template><div><p> {{msg}}</p><button @click="fn">look</button></div> </template>父组件:<Box :msg="n" :count="200"></Box>
3.setup语法糖中获取属性值
<script setup>import {defineProps} from "./Box1.vue"//注意3.2之后不用引入可以直接使用let obj=defineProps(["msg","count"])let fn=()=>{console.log(obj.msg,obj.count)} } < /script> <template><div><p> {{obj.msg}}</p><button @click="fn">look</button></div> </template>父组件:<Box :msg="n" :count="200"></Box>
10.自定义事件
事件名
与组件和 prop 一样,事件名提供了自动的大小写转换。如果在子组件中触发一个以 camelCase (驼峰式命名) 命名的事件,你将可以在父组件中添加一个 kebab-case (短横线分隔命名) 的监听器。
//父组件HomeView <template><div><MyBox1 @myclick="fn" @my-event="fm"></MyBox1></div> </template> <script setup> import MyBox1 from "../components/MyBox1.vue" let fn = (arg) => {console.log(6666, arg) } let fm = (arg) => {console.log(77777, arg); } </script> //子组件MyBox1 <template> <div @mousemove="change1"><button @click="change">点击触发myevent事件</button></div> </template> <script > export default { methods: {change() {this.$emit('myclick', "helo666");},change1() {this.$emit('myEvent', "121212"); }}, } </script>
定义自定义事件
可以通过 emits
选项在组件上定义发出的事件
//父组件 <HomeView @my-event="fn" @my-submit="fn1"></HomeView> //子组件 <template><div @mouseenter="fm"><button @click="fn">点我触发mybtn组件内部的myclick事件</button> </div> </template> <script>export default {emits:["myEvent","myclick"],methods:{fn(){this.$emit("myClick",200)},fm(){this.$emit("click",300)}}} </script>
v-model
参数
//父组件 <HomeView v-model:title="bookTitle"></HomeView> //子组件 //1.接收参数 props:["title"] //2.定义事件 emits: ['update:title'] //3.触发事件 this.$emit("update:title","子组件传递给父组件的值")
多个
v-model
绑定
//2.0这样写会有矛盾 <MyVmodel v-model="msg" v-model="msg2" v-model="msg3"></MyVmodel> //2.0应该这样写: <MyVmodel v-model="msg" :msg2="msg2" @changemsg2="changemsg2" :msg3="msg3" @changemsg3="changemsg3"> </MyVmodel> js: changemsg2(arg){this.msg2=arg} changemsg3(arg){this.msg3=arg}//3.0 <MyVmodel v-model="msg" v-model:msg2="msg2" v-model:msg3="msg3"></MyVmodel> //父组件 <user-namev-model:msg1="msg1"v-model:msg2="msg2" ></user-name> //子组件 //1.接收参数 props:["msg1","msg2"] //2.定义事件 emits: ['update:msg1','update:msg2'] //3.触发事件 this.$emit('update:msg1',"子组件传递给父组件的值1") this.$emit('update:msg2',"子组件传递给父组件的值2")
11.状态驱动的动态 CSS
单文件组件的 <style>
标签可以通过 v-bind
这一 CSS 函数将 CSS 的值关联到动态的组件状态上
<template><div class="box1">hello</div><button @click="changcolor">修改box1的样式</button><div class="box2">hello66666</div><button @click="changother">修改box2的样式</button> </template> <script setup> import { ref, reactive } from "vue" let color = ref("red") let changcolor = () => {color.value = "blue" } let other = reactive({width: "200px",height: "100px" }) let changother = () => {other.width = "400px" } </script> <style lang="scss" scoped> .box1 {color: v-bind('color'); } .box2 {background-color: yellow;width: v-bind('other.width');height: v-bind('other.height'); } </style>
12.注册组件
组件内部
<script>import Box1 from "./Box1.vue"export defult{components:{Box1},setup(){}} </script> <template><Box1></Box1> < /template>
2.vue3组件内部组合式API setup语法糖中注册组件
<script setup>import Box1 from "./Box1.vue" //只需要导入 不用写注册代码 会在打包的时候自动帮我们注册 </script> <template><Box1></Box1> < /template>
3.注册全局组件
//main.js文件: import { createApp} from 'vue' import App from './App.vue' const app=createApp(App) import Box1 from "./Box5.vue" app.component(Box1.name,Box1) app.mount('#app')//注意 一定要在app.mount之前注册全局组件 否则使用不了 //App.vue文件: <template><Box1></Box1> < /template>
4.定义同步组件:
//Box1.vue文件: <script>import {defineComponent} from "vue"export default defineComponent({data(){return {}},methods:{}, setup(){} }); </script>
5.1定义局部异步组件:
组件内部
<script>import {defineAsyncComponent} from "vue"let Box1 = defineAsyncComponent(() => import("./Box1.vue")) //注意3.2之后不用引入defineAsyncComponentexport default {components: {Box1},setup() {}} </script>
setup语法糖:
<script setup>import Box1 from "./Box1.vue"import Box2 from "./Box2.vue"import {defineAsyncComponent} from "vue"let Box3=defineAsyncComponent(()=>import("./Box3.vue"))//注意3.2之后不用引入defineAsyncComponent,而且这个变量名直接就是注册的组件名(打包时自动注册的) </script>
5.2定义全局异步组件:
//main.js文件: import { createApp,defineAsyncComponent} from 'vue' import App from './App.vue' const app=createApp(App) let Box1=defineAsyncComponent(()=>import("./Box4.vue")) app.component("Box1",Box1) app.mount('#app')//注意 一定要在app.mount之前注册全局组件 否则使用不了
13.Suspense
等待异步组件时渲染一些额外的内容,让应用有更好的用户体验
<suspense>
组件有两个插槽。它们都只接收一个直接子节点。default
插槽里的节点会尽可能展示出来。如果不能,则展示fallback
插槽里的节点。<template><div class="fa"><h1>父组件HomeView</h1> <!-- <Helloworld></Helloworld> --><suspense><template #default><MyChild></MyChild></template><template #fallback><div><h1> 正在加载中Loading...</h1></div></template></suspense></div> </template> <script setup> import { defineAsyncComponent } from "vue" // import Helloworld from "../components/HelloWorld.vue" //静态引用 let MyChild = defineAsyncComponent(() => import("../components/HelloWorld.vue")) //异步引入 </script> <style lang="scss" scoped> .fa {height: 300px;background-color: #ccc; } </style>
14. isRef toRef toRefs readonly
isRef 判断是否为响应性变量
<script setup> import {ref,isRef} from "vue" let msg=ref(0) function fn(){console.log(isRef(msg)) //判断是否为响应性变量 msg.value++ }< /script>
toRef 把引用数据的属性值设置为变量 并且关联和设置为响应性变量
import {ref,isRef,reactive,toRef,toRefs} from "vue" let obj=reactive({x:20,y:20}) // obj.x // obj.y // let {x,y}=obj//let x=obj.x,y=obj.y // function fm(){ // x=10000 // } // function look(){ // console.log(x) // } let x=toRef(obj,"x")// 隐式 let x=ref(obj.x);并且响应到obj对象去 function fm(){x.value=10000 } function look(){console.log(x,obj.x)//10000 10000 }
toRefs 引用数据响应式解构
import {isRef,reactive,toRef,toRefs} from "vue" //1.引用数据普通解构 let obj1=reactive({x:100,y:200}) let {x,y}=obj1//x,y变量不具备响应功能 //2.引用数据响应式解构 let obj2=reactive({a:10,b:20}) let {a,b}=toRefs(obj2)//a,b变量具备响应功能 function change2 () {a.value++b.value++ } function look2 () {console.log(a.value,obj2.a,isRef(a))// 11 11 true } </script>
readonly 把数据变为只读功能
import {isRef,reactive,toRef,toRefs,readonly} from "vue" let obj=readonly({})
公共数据配置
因为v2使用公共配置时一般绑定在原型上无论是否使用都在每一个组件的this链上,这样的设计不太友好,v3提供了专门公共数据配置的方式: globalProperties getCurrentInstance
//main.js const app=createApp(App) app.config.globalProperties.$hqyj=公共数据 //组件中钩子中: //getCurrentInstance()获取当前组件实例对象 //当前组件的实例对象解构的proxy对象中就有$hqyj //注意点是这个函数要在组件钩子中使用,不要在普通函数中使用 <script setup>import {onBeforeMount,getCurrentInstance,effect} from "vue"const { proxy } = getCurrentInstance();//正常使用:语法糖环境默认为setup钩子onBeforeMount(()=>{console.log(getCurrentInstance())//正常使用:标准钩子})effect(()=>{console.log(getCurrentInstance())//正常使用:副作用钩子}) let fn=()=>{console.log(getCurrentInstance())//错误使用:获取null,自定义函数} </script>
网络配置
代理配置:
//vite环境: export default defineConfig({plugins: [vue()],server: {// port:"8080",// hostproxy: {'/api': {target: 'http://127.0.0.1:7001', // 代理的目标地址rewrite: (path) => path.replace(/^\/api/, '/'), // 路径重写changeOrigin: true,// secure: true, // target是否https接口// ws: true, // target是否代理websockets }}} }) //webpack环境不变: module.exports={lintOnSave:false,devServer:{port:"10086",host:"localhost",proxy:{"/":{target:"http://127.0.0.1:7001",changeOrigin:true,pathRewrite:{"^/":""}}}} }
网络配置:
//mian.js import axios from "axios" const app=createApp(App) axios.defaults.baseURL="http://127.0.0.1:5173/api" app.config.globalProperties.$axios=axios app.config.globalProperties.hqyj=666 app.mount('#app') //组件.vue <script setup>import {ref,reactive,computed,onBeforeMount,getCurrentInstance,effect} from "vue" const { proxy } = getCurrentInstance();//注意在组件钩子中获取,不要在事件中let fn=()=>{proxy.$axios("/test")}
app.use
插件配置
同2.0一样use函数接受一个函数或者对象(对象有install函数) 然后会调用这个传入的回调函数 给它传参app对象,以此来实现第三方插件
//main.js文件 import { createApp} from 'vue' import App from './App.vue' const app=createApp(App) import $hqyj from "./http/$hqyj.js" app.use($hqyj) import $axios from "./http/$axios.js" app.use($axios) app.mount('#app') //$hqyj.js文件 function $hqyj(app){app.config.globalProperties.$hqyj="5000e" } export default $hqyj; //$axios文件 import axios from "axios" function $axios(app){axios.defaults.baseURL="http://127.0.0.1:5173/api"app.config.globalProperties.$axios=axios } export default $axios; //组件中就可通过获取实例对象 来获取公共属性中的$hqyj的"5000e" $axios的网络工具 //组件内: //1.钩子函数中获取实例对象: //2.获取公共属性中的数据 <script setup>import {onMounted,getCurrentInstance} from "vue"// import axios from "axios"let {proxy}=getCurrentInstance()onMounted(async ()=>{//axios.defaults.baseURL="http://127.0.0.1:5173/api" let res=await proxy.$axios('/test')//"http://127.0.0.1:5173/api/test"==>"http://127.0.0.1:7001/test"console.log(res,proxy.$hqyj,11111111111)})
路由配置
使用思路跟之前差不多 语法略微有变化
npm i vue-router
//路由文件 import { createRouter, createWebHistory } from 'vue-router' const routes = [{path: '/',name: 'home',component: () => import('../views/Home.vue'),//独享守卫beforeEnter(to,from,next){next()}},{path: '/car',name: 'car',component: () => import('../views/Car.vue'),children:[{path: '/car/son1',name: 'son1',component: ()=>import("../views/Son1.vue") }]} ] const router = createRouter({history: createWebHistory(),routes }) //全局守卫 router.beforeEach((to,from,next)=>{console.log()next() }) router.beforeResolve((to,from,next)=>{next() }) router.afterEach((to,from)=>{}) export default router //main.js文件 import router from "./router/index.js" app.use(router)//记得在mount之前调用 //组件内部的使用 import {onBeforeRouteLeave,useRouter,useRoute} from "vue-router"let router=useRouter() let fn=()=>{let route=useRoute() console.log(route.query)router.push({path:"/car/son1",query:{id:123}})}//组件内的守卫钩子onBeforeRouteLeave((to,from,next)=>{console.log(66666,"离开")next()}) router-view和router-link组件不变
数据仓库配置
vuex
vuex3.0适用于vue2.0
vuex4.0适用于vue3.0和2.0
Pinia 适用于3.0 为了兼容2.0也能使用 主要是给3.0的组合式API设计的
官方中文网: Pinia 中文文档
1.安装
npm install pinia
2.写一个store文件
// store/index.js文件 import { defineStore } from 'pinia' //这里官网是单独导出 是可以写成默认导出的 官方的解释为大家一起约定仓库用use打头的单词 固定统一小仓库的名字不易混乱 const useStore = defineStore('storeId', {// 这里易错语法:如果箭头函数直接指向返回值的同学 记得把返回的对象用小括号括起来state: () => {return {msg: 'hello'}} }) //main.js文件 import { createPinia } from 'pinia' let app=createApp(App) app.use(createPinia())
3.组件中的使用
<script setup>import useStore from "../store/index.js"let store=useStore()console.log(store,msg,111) </script>
4.修改数据
//修改仓库状态1: <script setup>import useStore from "../store/index.js"let store=useStore()let fm=()=>{store.msg="666修改成功"}//store是一个响应性对象 因此可以修改并刷新let {msg}=storelet fn=()=>{ msg="6666修改失败"} //解构之后修改是不行的,因为解构后msg为非响应式变量 同toRefs知识点//解决方案:利用toRefs把响应式对象的属性解构为响应性的变量import {toRefs} from "vue"import useStore from "../store/index.js"let store=useStore()let {msg}=toRefs(store)let fn=()=>{msg.value="6666修改成功啦"} //一定不要忘了value </script> //修改仓库状态2: store.$reset()//将状态 重置 到其初始值 //修改仓库状态3: store.$patch({msg:"666修改成功",count:store.count+100, }) store.$patch((state)=>{state.msg="666修改成功";state.count= state.count+100, }) //问:$patch整体修改个单条修改的区别? store.msg="666修改成功" store.count=store.count+100 //答:状态刷新一次,可以批量修改
5.订阅修改
//可以通过 store 的 $subscribe() 方法查看状态及其变化,通过patch修改状态时就会触发一次 store.$subscribe((mutation, state) => { // 每当它发生变化时,将整个状态持久化到本地存储localStorage.setItem('test', JSON.stringify(state)) })
6.Getter
Getter 完全等同于 Store 状态的 计算值。 它们可以用 defineStore()
中的 getters
属性定义。 他们接收“状态”作为第一个参数以鼓励箭头函数的使用:(ps:虽然鼓励但是依然提供了非es6玩家的使用方式 内部可以用this代表state)
//store/index.js文件 export const useStore = defineStore('main', {state: () => ({counter: 0,}),getters: {doubleCount: (state) => state.counter * 2,}, }) //组件中直接使用: <p>Double count is {{ store.doubleCount }}</p>
7.Actions
在pinia中没有提供mutaion 因为Actions就够了(它可以异步同步的统一修改状态)
之所以提供这个功能 就是为了项目中的公共修改状态的业务统一
export const useStore = defineStore('main', {state: () => ({counter: 0,}),actions: {increment() {this.counter++//1.有this},randomizeCounter(state) {//2.有参数 想用哪个用哪个state.counter = Math.round(100 * Math.random())},randomizeCounter(state) {//异步函数axios("/test").then(res=>{state.counter = res.data.length})}}, }) //组件中的使用:setup() {const store = useStore()store.increment()store.randomizeCounter()}
8.模块化
一个文件一个小仓库,仓库之间可以相互访问数据 不同组件也可以访问任意小仓库数据
//store/car.js文件 export const car = defineStore('car', {state: () => ({counter: 0,}),actions: {} }) //store/userinfo.js文件 export const userinfo = defineStore('userinfo', {state: () => ({counter: 0,}),actions: {fn(state){state.counter=100}} }) //A组件可以用car,userinfo两个仓库的数据 import {car} from "@/store/car.js" import {userinfo} from "@/store/userinfo.js" let store1=car() store1.counter let store2=userinfo() store2.counter //car仓库使用userinfo仓库跟组件一样的使用方式 import {userinfo} from "@/store/userinfo.js" let store2=userinfo() store2.counter store2.fn()
vue2-3版本笔记相关推荐
- vue2.x版本+element-ui2.15+版本实现只能输入数字的ip输入框,功能样式借鉴windows,与父组件双向绑定
目录 1.双向绑定 2.:oninput动态绑定和@input事件处理 3.主要功能 4.自定义组件代码 1.双向绑定 实现双向绑定在vue2.x版本中是使用v-model实现的,本文子类中的主要代码 ...
- 安装Vue2.6版本
卸载原来版本: npm install -g vue-cli 安装vue2.6版本: npm install -g vue-cli@2.6
- Vue2.0学习笔记一 :各种表达式
#,过滤器 #,在Vue2.x中,过滤器只能在mustache绑定中使用,为了在指令帮定中实现同样的行为,你应该使用计算属性: #,过滤器可以串联 {{ message | filterA | ...
- vue2.x学习笔记
Vue.config.productionTip=false //阳止 vue在启动时生成生产提示. BootCDN MOMENT.JS 日期处理库 dayjs 解构赋值连续写法 {query:{id ...
- vue2.x 学习笔记for
文章目录 Vue指令之`v-for`和`key`属性 v-for循环普通数组 v-for循环对象数组 v-for循环对象 v-for迭代数字 v-for循环中key属性的使用 Vue指令之v-for和 ...
- vs 更换cuda版本笔记
换了一台电脑,vs编译项目时,需要更换cuda版本, vs直接打开项目会报错,解决方法测试成功. 更换cuda方法,需要三个步骤: 在project_xxx.vcxproj中更换cuda版本 1.比如 ...
- vue2.0读书笔记2-进阶
一.深入响应式原理 二.过渡效果 三.过渡状态 四.Render函数 五.自定义指令 六.混合 七.插件 八.单文件组件 九.生产环境 十.路由 vue-router: http://router.v ...
- Tensorflow2.0版本 笔记
文章目录 Tensorflow笔记 1 常用函数 1.1 tf.where() 1.2 np.mgrid() 1.3 tf.nn.softmax_cross_entropy_with_logits() ...
- vue2.0版本指令v-if与v-show的区别
v-if: 判断是否加载,可以减轻服务器的压力,在需要时加载. v-show:调整css dispaly属性,可以使客户端操作更加流畅. v-if示例: <!DOCTYPE html> & ...
最新文章
- 对PostgreSQL中 index only scan 的初步理解
- DBCC CHECKDB 数据库或表修复
- python中import random_python import random
- CentOS 6.3安装MPlayer
- 软件测试的流程结构图
- springboot-shiro-cas-redis集成session共享,权限共享
- ArcBlock 问答 | 扎实打造影响整个区块链生态的产品
- js html监听ctrl v,js监听组合按键
- 今天是我的生日,也是我的离职日!
- call和apply详解-利用借充电宝模拟用法
- Java核心技术第一周学习总结
- 微信撤回服务器保存吗,收藏!微信消息被撤回?闪照无法保存?这样一步完美解决...
- 最新大数据可视化展示效果,别误会了,这才是可视化该有的样子
- Selenium基础 — 多窗口操作
- 适用于渗透测试不同阶段的工具收集整理
- Jmeter做压力测试
- 猫耳FM音频转换成MP3格式
- 打印服务Print Spooler开启后自动关闭
- linux dns劫持转发,linux的dns被劫持(解决方案)
- java邮件开发--电子邮件基础
热门文章
- ips细胞最新发现:科学家开发出了一套评估干细胞的全能性的新标准
- git@gitlab.com: Permission denied报错
- 2018提高组模拟13
- 利用Py-Socket模块做的一个不登陆windows服务器自动实现替换或者调用自动拨号功能...
- 超级壁纸android,超级壁纸大全app下载-超级壁纸大全 安卓版v1.1.0-PC6安卓网
- bzoj1671 Knights of Ni 骑士 BFS
- 自定义Android桌面小部件
- 国企的判断标准是什么 怎么查企业是私企还是国企
- word中图片为嵌入式格式时显示不全_图片在word中显示不全怎么处理_word图片显示不全怎么办-win7之家...
- vue-admin-elem对接地图报错Unable to preventDefault inside passive event listener invocation.