vue基础(一)
箭头函数#
非常简洁的语法,使用箭头函数比普通函数少些动词,如:function或return。
() => { ... } // 零个参数用 () 表示。 x => { ... } // 一个参数可以省略 ()。 (x, y) => { ... } // 多参数不能省略 ()。 如果只有一个return,{}可以省略。 //createElement就是h,一个形参,没有具体意义,可以随意改变书写 render:(function(createElement){ return createElement(App); }) 说明:render是一个方法,自带一个形参createElement,这个参数也是一个方法,是用来创建vue 节点的,也就是html模板的,然后渲染(render)到指定的节点上
普通函数中的this
this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this指向的是window;当函数被作为某个对象的方法调用时,this就等于那个对象
var name = "the window"; var obj = { name:"my object", getNameFunc:function(){ return function(){ return this.name; } } } console.log(obj.getNameFun()()); //the window
匿名函数的执行环境是全局的,而且this只在函数内部起作用。此时的this.name在匿名函数中找不到,所以就从全局中找。
var obj = { func:function(){ console.log(this); }, say:function(){ var that = this; setTimeout(function(){ console.log(that); }); } } obj.func(); //obj obj.say(); //obj var obj = { func:function(){ console.log(this); }, say:function(){ setTimeout(()=>{ console.log(this); }); } } obj.func(); //obj obj.say(); //obj
箭头函数的this定义:箭头函数的this是在定义函数时绑定的,不是在执行过程中绑定的。简单的说,箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。
我们试着把函数的执行对象放在window下看看: function test(){ console.log(this); } test() //window对象
vue-cli构建项目如何运行#
使用vue-cli构建项目后, 在项目目录中,运行命令 npm run dev运行项目,运行成功后会返回一个地址路径。在地址栏直接输入该路径可以看到效果。
解决vue npm ERR! missing script: dev#
今天在运行Vue项目时,在运行npm run dev时报错如下图:
打开package.js文件夹,发现文件夹里的scripts有dev如下图:
可就是好不到,什么原因呢?最后一看路径不对,vue init webpack my-project时,自己又建立一个文件夹,取不到对应的package.js中的dev.
cd 到对应文件夹,重新运行npm run dev 就OK了。
还有一种情况,打开的是当前文件夹,但是文件夹package.js里的scripts确实没有dev,
输入vue init webpack 将package.json中丢失的:
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "start": "npm run dev", "build": "node build/build.js"
重新下载过来,然后在npm intall 安装依赖,最后 npm run dev即可
也适用于解决start, build丢失
创建一个vue项目#
1、vue创建 vue create hello-world
vue中this.$el 和 this.$ref#
vue组件中this和$el指向#
. . . mounted(){ console.log(this,'this'); console.log(this.$el,'$el') } 打印: VueComponent{.......} "this" <div class="el-select">....</div> "$el"
结论:
this指向组件的实例。
$el指向当前组件的DOM元素。
this.$el是在mounted中才会出现的
Vue 解决通过this.$refs来获取DOM或者组件报错问题#
1. 关于this.$refs的使用场景
如果ref属性加在普通元素上,那么this.$refs.name则指向该DOM元素
<p ref="p">hello</p> <!-- this.$refs.p 指向该DOM元素 -->
如果ref属性加在组件上,那么this.$refs.name指向该组件实例
<child-component ref="child"></child-component> <!-- this.$refs.child 指向该组件 -->
2. 为什么有时候通过this.$refs.name来获取会报错?
一个比较常见的场景:在一个弹窗打开的时候立刻通过this.$refs来获取内容就会出现xxx is undefined的错误
因为ref本身是作为渲染结果被创建的,在渲染的时候是不能访问的,因为他们还不存在!
如果此时代码是需要这样来写代码,那么你可以在DOM渲染完毕后再进行获取
this.$nextTick(() => { this.$refs.name... //DOM渲染完毕后就能正常获取了 })
this.$nextTick(function(){})#
Vue 异步执行 DOM 更新,监视所有数据改变,一次性更新DOM。如果需要拿到更新后dom中的数据 则需要通过 Vue.nextTick(callback):在DOM更新后,执行某个操作(属于DOM操作)。
this.$nextTick 一般在created钩子函数中使用,有人会问,在created 和 mounted 钩子函数中使用有什么区别呢?
这就要回到vue的生命周期了:
beforeCreated : 初始化Vue,此时 data 和 el 并未被创建 created: data被初始化但是el并未被创建 beforeMounted: data 和 el 都被创建,但是还未被挂载 mounted: data 和 el 都被创建,并且被挂载
在created 钩子函数中,数据初始化了但是页面并没有更新,而 $nextTick 是异步行为,在数据变化的同时,页面也会同步;
在mounted钩子函数中,由于页面已经加载完了,所以不需要异步操作。但是写了也不会错。。
下面看一个例子,进一步说明 $nextTick 的作用:
<div class="app"> <div ref="msgDiv">{{msg}}</div> <div v-if="msg1">Message got outside $nextTick: {{msg1}}</div> <div v-if="msg2">Message got inside $nextTick: {{msg2}}</div> <div v-if="msg3">Message got outside $nextTick: {{msg3}}</div> <button @click="changeMsg"> Change the Message </button> </div>
new Vue({ el: '.app', data: { msg: 'Hello Vue.', msg1: '', msg2: '', msg3: '' }, methods: { changeMsg() { this.msg = "Hello world." this.msg1 = this.$refs.msgDiv.innerHTML this.$nextTick(() => { this.msg2 = this.$refs.msgDiv.innerHTML }) this.msg3 = this.$refs.msgDiv.innerHTML } } })
点击前:
Hello Vue. change the Message.
点击后:
Hello world. Message got outside $nextTick:Hello Vue. Message got outside $nextTick:Hello world. Message got outside $nextTick:Hello Vue.
最后nextTick的应用场景有哪些呢?
1.在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中
2.在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。
nextTick方法的回调会在dom更新后再执行,因此可以和一些dom操作搭配一起用,如 ref。
非常好用,可以解决很多疑难杂症。
场景: 你用ref获得一个输入框,用v-model绑定。 在某个方法里改变绑定的值,在这个方法里用ref去获取dom并取值,你会发现dom的值并没有改变。 因为此时vue的方法,还没去触发dom的改变。 因此你可以把获取dom值的操作放在vm.$nextTick的回调里,就可以了。
Vue-Vue文本渲染三种方法 {{}}、v-html、v-text#
{{ }}
将元素当成纯文本输出
v-html
v-html会将元素当成HTML标签解析后输出
v-text
v-text会将元素当成纯文本输出
<div id="app"> <!-- {}}/v-text不能解析html元素,只会照样输出 --> <p>{{hello}}</p> <p v-text = 'hello'></p> <p v-html = 'hello'></p> </div>
事件修饰符#
.stop 阻止冒泡,调用 event.stopPropagation()
.prevent 阻止默认行为,调用 event.preventDefault()
.capture 添加事件侦听器时使用事件捕获模式
.self 只当事件在该元素本身(比如不是子元素)触发时,才会触发事件
.once 事件只触发一次
.self只执行直接作用在该元素身上的事件,所以它相当于忽略了 其他元素的冒泡或者捕获事件。但是这种忽略只局限于自身。
所以,为了避免一些会被冒泡事件影响,加上修饰符.self是很有用的。
指令#
样式处理 -class和style#
- 使用方式:
v-bind:class="expression" or :class="expression"
- 表达式的类型:字符串、数组、对象(重点) 语法:
<!-- 1 --> <div v-bind:class="{ active: true }"></div> ===> 解析后 <div class="active"></div> <!-- 2 --> <div :class="['active', 'text-danger']"></div> ===>解析后 <div class="active text-danger"></div> <!-- 3 --> <div v-bind:class="[{ active: true }, errorClass]"></div> ===>解析后 <div class="active text-danger"></div> --- style --- <!-- 1 --> <div v-bind:style="{ color: activeColor, 'font-size': fontSize + 'px' }"></div> <!-- 2 将多个 样式对象 应用到一个元素上--> <!-- baseStyles 和 overridingStyles 都是data中定义的对象 --> <div v-bind:style="[baseStyles, overridingStyles]"></div>
v-if 和 v-show#
- 条件渲染
- v-if:根据表达式的值的真假条件,销毁或重建元素
- v-show:根据表达式之真假值,切换元素的 display CSS 属性
<p v-show="isShow">这个元素展示出来了吗???</p> <p v-if="isShow">这个元素,在HTML结构中吗???</p>
提升性能:v-pre#
- 说明:vue会跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache标签。跳过大量没有指令的节点会加快编译。
<span v-pre>{{ this will not be compiled }}</span>
提升性能:v-once#
- 说明:vue只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
<span v-once>This will never change: {{msg}}</span>
过滤器 filter#
- 作用:文本数据格式化
- 过滤器可以用在两个地方:{{}}和 v-bind 表达式
- 两种过滤器:1 全局过滤器 2 局部过滤器
全局过滤器
- 说明:通过全局方式创建的过滤器,在任何一个vue实例中都可以使用
- 注意:使用全局过滤器的时候,需要先创建全局过滤器,再创建Vue实例
- 显示的内容由过滤器的返回值决定
Vue.filter('filterName', function (value) { // value 表示要过滤的内容 })
<div>{{ dateStr | date }}</div> <div>{{ dateStr | date('YYYY-MM-DD hh:mm:ss') }}</div> <script> Vue.filter('date', function(value, format) { // value 要过滤的字符串内容,比如:dateStr // format 过滤器的参数,比如:'YYYY-MM-DD hh:mm:ss' }) </script>
局部过滤器
- 说明:局部过滤器是在某一个vue实例的内容创建的,只在当前实例中起作用
{ data: {}, // 通过 filters 属性创建局部过滤器 // 注意:此处为 filters filters: { filterName: function(value, format) {} } }
监视数据变化 - watch#
- 概述:watch是一个对象,键是需要观察的表达式,值是对应回调函数
- 作用:当表达式的值发生变化后,会调用对应的回调函数完成响应的监视操作
- VUE $watch
<div id="app"> <input type="text" name="name2" v-model="b.age"> </div> new Vue({ data: { a: 1, b: { age: 10 } }, watch: { a: function(val, oldVal) { // val 表示当前值 // oldVal 表示旧值 console.log('当前值为:' + val, '旧值为:' + oldVal) }, // 监听对象属性的变化 b: { handler: function (val, oldVal) { /* ... */ }, // deep : true表示是否监听对象内部属性值的变化 deep: true }, // 只监视b对象中age属性的变化 'b.age': function (val, oldVal) { }, } })
计算属性 - computed#
- 说明:计算属性是基于它们的依赖进行缓存的,只有在它的依赖发生改变时才会重新求值
- 注意:Mustache语法({{}})中不要放入太多的逻辑,否则会让模板过重、难以理解和维护
- 注意:computed中的属性不能与data中的属性同名,否则会报错
- Vue computed属性原理
<div id="app"> <input type="text" name="name" v-model="firstname"><br> <input type="text" name="name1" v-model="lastname"><br> <input type="text" name="name2" v-model="fullname"> </div> var vm = new Vue({ el: '#app', data: { firstname: 'jack', lastname: 'rose' }, computed: { fullname() { return this.firstname + '.' + this.lastname } } })
computed和watch的区别
computed:不支持异步,当computed内有异步操作时无效,无法监听数据的变化
watch:支持异步,当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。这是和computed最大的区别
vue生命周期(一)#
- 生命周期钩子函数的定义:从组件被创建,到组件挂载到页面上运行,再到页面关闭组件被卸载,这三个阶段总是伴随着组件各种各样的事件,这些事件,统称为组件的生命周期函数!
- 注意:Vue在执行过程中会自动调用生命周期钩子函数,我们只需要提供这些钩子函数即可
- 注意:钩子函数的名称都是Vue中规定好的!
钩子函数 - beforeCreate()
- 说明:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用
- 注意:此时,无法获取 data中的数据、methods中的方法
钩子函数 - created()
-
注意:这是一个常用的生命周期,可以调用methods中的方法、改变data中的数据
-
实例已经创建完成之后被调用。
-
使用场景:发送请求获取数据
tip: 通常我们可以在这里对实例进行预处理。 也有一些童鞋喜欢在这里发ajax请求,值得注意的是,这个周期中是没有什么方法来对实例化过程进行拦截的。 因此假如有某些数据必须获取才允许进入页面的话,并不适合在这个页面发请求。 建议在组件路由勾子beforeRouteEnter中来完成。
钩子函数 - beforeMounted()
- 说明:在挂载开始之前被调用:相关的 render 函数首次被调用。
钩子函数 - mounted()
-
说明:此时,vue实例已经挂载到页面中,可以获取到el中的DOM元素,进行DOM操作
tip: 1.在这个周期内,对data的改变可以生效。但是要进下一轮的dom更新,dom上的数据才会更新。 2.这个周期可以获取 dom。 3.beforeRouteEnter的next的勾子比mounted触发还要靠后 4.指令的生效在mounted周期之前
钩子函数 - beforeUpdated()
- 说明:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
- 注意:此处获取的数据是更新后的数据,但是获取页面中的DOM元素是更新之前的
钩子函数 - updated()
- 说明:组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。
钩子函数 - beforeDestroy()
- 说明:实例销毁之前调用。在这一步,实例仍然完全可用。
- 使用场景:实例销毁之前,执行清理任务,比如:清除定时器等
钩子函数 - destroyed()
- 说明:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
说了这么多来点实在的,大佬一般如何在代码中合理的利用生命周期来构建代码呢?
beforeCreate () { // 进行初始化事件,进行数据的观测,可以看到在created的时候数据已经和data属性进行绑定(放在data中的属性当值发生改变的同时,视图也会改变)。注意:此时还是没有el选项 }, components: {}, data () { return { show: false, msg: "hello word", mg: "你好世界" } }, watch: { }, methods: { destory () { // 调用销毁 this.$destroy(); } }, created () { // 在这一阶段发生的事情还是比较多的。首先,会判断对象是否有el选项:如果有的话就继续向下编译,如果没有el选项,则停止编译,也就意味着停止了生命周期,直到在该vue实例上调用vm.$mount(el) }, beforeMount () { //  可以看到此时是给vue实例对象添加$el成员,并且替换掉挂在的DOM元素。因为在之前console中打印的结果可以看到beforeMount之前el上还是undefined。 }, mounted () { this.intervalID = setInterval(() => { console.log("++++++-------++++"); this.show = !this.show; }, 1000); // 在mounted之前p中还是通过{{message}}进行占位的,因为此时还没有挂在到页面上,还是JavaScript中的虚拟DOM形式存在的。在mounted之后可以看到h1中的内容发生了变化。 }, beforeUpdate () { // 当vue发现data中的数据发生了改变,会触发对应组件的重新渲染,先后调用beforeUpdate和updated钩子函数。 }, updated () { // 在beforeUpdate可以监听到data的变化,但是view层没有被重新渲染,view层的数据没有变化。等到updated的时候,view层才被重新渲染,数据更新。 }, beforeDestory () { clearInterval(this.intervalID); //  beforeDestroy钩子函数在实例销毁之前调用。在这一步,实例仍然完全可用,可以用于清除定时器 }, destory () { //调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 },
全局路由钩子#
作用于所有路由切换,一般在main.js里面定义
router.beforeEach
示例 router.beforeEach((to, from, next) => { console.log('路由全局勾子:beforeEach -- 有next方法') next() })
一般在这个勾子的回调中,对路由进行拦截。
比如,未登录的用户,直接进入了需要登录才可见的页面,那么可以用next(false)来拦截,使其跳回原页面。
值得注意的是,如果没有调用next方法,那么页面将卡在那。
next的四种用法 1.next() 跳入下一个页面 2.next('/path') 改变路由的跳转方向,使其跳到另一个路由 3.next(false) 返回原来的页面 4.next((vm)=>{}) 仅在beforeRouteEnter中可用,vm是组件实例。
router.afterEach
示例 router.afterEach((to, from) => { console.log('路由全局勾子:afterEach --- 没有next方法') })
在所有路由跳转结束的时候调用,和beforeEach是类似的,但是它没有next方法
组件路由勾子#
和全局勾子不同的是,它仅仅作用于某个组件,一般在.vue文件中去定义。
beforeRouteEnter
示例 beforeRouteEnter (to, from, next) { console.log(this) //undefined,不能用this来获取vue实例 console.log('组件路由勾子:beforeRouteEnter') next(vm => { console.log(vm) //vm为vue的实例 console.log('组件路由勾子beforeRouteEnter的next') }) }
这个是一个很不同的勾子。因为beforeRouterEnter在组件创建之前调用,所以它无法直接用this来访问组件实例。
为了弥补这一点,vue-router开发人员,给他的next方法加了特技,可以传一个回调,回调的第一个参数即是组件实例。
一般我们可以利用这点,对实例上的数据进行修改,调用实例上的方法。
我们可以在这个方法去请求数据,在数据获取到之后,再调用next就能保证你进页面的时候,数据已经获取到了。没错,这里next有阻塞的效果。你没调用的话,就会一直卡在那
tip: next(vm=>{console.log('next') }) 这个里面的代码是很晚执行的,在组件mounted周期之后。没错,这是一个坑。你要注意。 beforeRouteEnter的代码时很早执行的,在组件beforeCreate之前; 但是next里面回调的执行,很晚,在mounted之后,可以说是目前我找到的,离dom渲染最近的一个周期。
beforeRouteLeave
beforeRouteLeave (to, from, next) { console.log(this) //可以访问vue实例 console.log('组件路由勾子:beforeRouteLeave') next() },
在离开路由时调用。可以用this来访问组件实例。但是next中不能传回调。
beforeRouteUpdate
这个方法是vue-router2.2版本加上的。因为原来的版本中,如果一个在两个子路由之间跳转,是不触发beforeRouteLeave的。这会导致某些重置操作,没地方触发。在之前,我们都是用watch $route来hack的。但是通过这个勾子,我们有了更好的方式。
vue生命周期(二)#
那么进入某个路由对应的组件的时候,我们会触发哪些类型的周期呢?
- 根实例的加载相关的生命周期(beforeCreate、created、beforeMount、mounted)
- 组件实例的加载相关的生命周期(beforeCreate、created、beforeMount、mounted)
- 全局路由勾子(router.beforeEach)
- 组件路由勾子(beforeRouteEnter)
- 组件路由勾子的next里的回调(beforeRouteEnter)
- 指令的周期(bind,inserted)
- nextTick方法的回调
接下来,让我们用vue-cli简单改造后的项目,做一个测试,看看各个声明周期的触发顺序是怎样的
main.js:
router.beforeEach((to, from, next) => { console.log('路由全局勾子:beforeEach') next() }) router.afterEach((to, from) => { console.log('路由全局勾子:afterEach') }) new Vue({ beforeCreate () { console.log('根组件:beforeCreate') }, created () { console.log('根组件:created') }, beforeMount () { console.log('根组件:beforeMount') }, mounted () { console.log('根组件:mounted') } el: '#app', router, template: '<App/>', components: { App } })
test.vue
<template> <h1 v-ooo @click = "$router.push('/')">test</h1> </template> <script> export default { beforeRouteEnter (to, from, next) { console.log('组件路由勾子:beforeRouteEnter') next(vm => { console.log('组件路由勾子beforeRouteEnter的next') }) }, beforeCreate () { console.log('组件:beforeCreate') }, created () { this.$nextTick(() => { console.log('nextTick') }) console.log('组件:created') }, beforeMount () { console.log('组件:beforeMount') }, mounted () { console.log('组件:mounted') }, directives: { ooo: { bind (el, binding, vnode) { console.log('指令binding') }, inserted (el, binding, vnode) { console.log('指令inserted') } } } } </script>
接下来,直接进入test.vue对应的路由。在控制台,我们看到如下的输出
我们看到执行的顺序为
- 路由勾子 (beforeEach、beforeRouteEnter、afterEach)
- 根组件 (beforeCreate、created、beforeMount)
- 组件 (beforeCreate、created、beforeMount)
- 指令 (bind、inserted)
- 组件 mounted
- 根组件 mounted
- beforeRouteEnter的next的回调
- nextTick
结论#
路由勾子执行周期非常早,甚至在根实例的渲染之前
具体的顺序 router.beforeEach > beforeRouteEnter > router.afterEach
tip:在进行路由拦截的时候要避免使用实例内部的方法或属性。 在开发项目时候,我们脑门一拍把,具体拦截的程序,写在了根实例的方法上了,到beforeEach去调用。 结果导致整个拦截的周期,推迟到实例渲染的之后。 因此对于一些路由组件的beforeRouteEnter里的请求并无法拦截,页面看上去好像已经拦截下来了。 实际上请求依然发了出去,beforeRouteEnter内的函数依然执行了。
指令的绑定在组件mounted之前,组件的beforeMount之后
不得不提的, beforeRouteEnter的next勾子
beforeRouteEnter的执行顺序是如此靠前,而其中next的回调勾子的函数,执行则非常靠后,在mounted之后!!
我们通常是在beforeRouteEnter中加载一些首屏用数据,待数据收到后,再调用next勾子,通过回调的参数vm将数据绑定到实例上。
因此,请注意next的勾子是非常靠后的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?