在此之前也做了好多个vue3项目,这次通过coderwhy老师的视频,系统的学习一下vue3。
项目一:仿知乎项目 github.com/zhang-glitc…
项目二: 数据大屏项目: github.com/zhang-glitc…
项目三: 自己构建的个人blog: github.com/zhang-glitc…
如何使用Vue呢?
Vue的本质,就是一个JavaScript的库。
- 方式一:在页面中通过CDN的方式来引入;
<script src="https://unpkg.com/vue@next"></script>
- 方式二:下载Vue的JavaScript文件,并且自己手动引入;
- 方式三:通过npm包管理工具安装使用它;
- 方式四:直接通过Vue CLI创建项目,并且使用它; 简单使用 我们可以调用
Vue.createApp()
来创建一个应用实例,并通过mount
将其挂载到指定的dom上。
<div id="app"></div> <script src="https://unpkg.com/vue@next"></script> <script> const options = { template: '<h2>Hello Vue3</h2>' } const app = Vue.createApp(options); app.mount("#app") </script>
模板语法
React的开发模式:
- React使用的jsx,所以对应的代码都是编写的类似于js的一种语法;
- 之后通过Babel将jsx编译成 React.createElement 函数调用;
Vue也支持jsx的开发模式:
- 但是大多数情况下,使用基于HTML的模板语法;
- 在模板中,允许开发者以声明式的方式将DOM和底层组件实例的数据绑定在一起;
- 在底层的实现中,Vue将模板编译成虚拟DOM渲染函数。
Mustache双大括号语法
如果我们希望把数据显示到模板(template)中,使用最多的语法是 “Mustache”语法 (双大括号) 的文本插值。
- 并且我们前端提到过,data返回的对象是有添加到Vue的响应式系统中;
- 当data中的数据发生改变时,对应的内容也会发生更新。
- 当然,Mustache中不仅仅可以是data中的属性,也可以是一个JavaScript的表达式。
指令
渲染内容相关指令
v-once
用于指定元素或者组件只渲染一次:
- 当数据发生变化时,元素或者组件以及其所有的子元素将视为静态内容并且跳过
- 该指令可以用于性能优化;
- 如果是子节点,也是只会渲染一次。即使用v-once的标签中的内容都只会渲染一次。
v-text
用于更新元素的 textContent
- 等价于
{{}}
。
- 并且他会覆盖标签中的任何内容。
v-html
用于将html字符串当做html渲染到页面,这个指令一般在个人blog渲染文章用的比较多。
- 我们通过
{{}}
展示html字符串时,vue并不会对其进行特殊的解析。仍然渲染成html字符串。
- 如果我们希望这个内容被Vue可以解析出来,那么可以使用 v-html 来展示;
v-pre
用于跳过元素和它的子元素的编译过程,显示原始的Mustache标签:
- 跳过不需要编译的节点,加快编译的速度;
属性相关指令
v-bind
动态地绑定一个或多个 attribute,或一个组件 prop 到表达式。
- 缩写
:
- 修饰符
.camel
将 kebab-case attribute 名转换为 camelCase。
.prop
- 将一个绑定强制设置为一个 DOM property。
.attr
- 将一个绑定强制设置为一个 DOM attribute。
- 绑定class有两种方式:
- 对象语法: 传入一个对象作为class的值。key为class属性值,value为一个boolean,看key是否绑定到该元素的class。如果想使用data中定义的变量作为class值,我们需要使用动态属性绑定
[]
,下面的title将被看作是一个变量,而不是一个字符串。
<div :class="{active: isActive, [title]: true}">对象形式添加class</div>
- 数组语法: 传入一个数组作为class的值。数组中的每个元素作为class属性值。如果遇到元素是表达式或者对象,那么就看其值是否是true。就被添加到class上。注意如果是元素值不加上引号,那么他将会去定义的data中查找是否有该变量值。例如下面的title
<div :class="['abc', title, isActive ? 'active': '', {active: isActive}]"> 数组形式添加class </div>
- 绑定style:某些样式我们需要根据数据动态来决定。
- CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名
- 对象语法:等同于写内联的css样式。只不过属性必须是字符串。如果不是字符串,那么他将被当做成变量,然后将去data中查找是否有该变量。
<div :style="{color: finalColor, 'font-size': '30px'}">对象形式</div>
- 数组语法:基本不用。就是将键值对的样式对象当做元素放在数组中。
<div :style="[style1Obj, style2Obj]">数组形式</div> data() { return { message: "Hello World", style1Obj: { color: 'red', fontSize: '30px' }, style2Obj: { textDecoration: "underline" } } }
- 动态绑定自定义属性。通过
:[自定义属性]
的形式。
<div :[name]="value">动态绑定自定义属性</div> data() { return { name: "cba", value: "kobe" } }
- 将对象数据映射到dom元素的属性。这个一般用于将
inheritAttrs: false
后,将父元素传入的非props属性挂载到指定的dom上。v-bind="$attrs"
// 这里的info将被写到到div上,成为div的一个个属性 <div v-bind="info">将对象数据映射到dom元素的属性</div> <div :="info">将对象数据映射到dom元素的属性</div> data() { return { info: { name: "zh", age: 20 } } }
事件指令
v-on
: 用于绑定事件监听。
- 简写:
@
- 修饰符
.stop
- 调用 event.stopPropagation()。
.prevent
- 调用 event.preventDefault()。
.capture
- 添加事件侦听器时使用 capture 模式。
.self
- 只当事件是从侦听器绑定的元素本身触发时才触发回调。
.{keyAlias}
- 仅当事件是从特定键触发时才触发回调。
.once
- 只触发一次回调。
.lef
- 只当点击鼠标左键时触发。
.right
- 只当点击鼠标右键时触发。
.middle
- 只当点击鼠标中键时触发。
.passive
- { passive: true } 模式添加侦听器
- 开发时基本上都是绑定一个function,但是如果需要绑定多个函数,我们就需要传入一个对象。
<div v-on="{click: btn1Click, mousemove: mouseMove}"></div> <div @="{click: btn1Click, mousemove: mouseMove}"></div>
- 当通过methods中定义方法,以供@click调用时,需要注意参数问题:
- 情况一:如果该方法不需要额外参数,那么方法后的()可以不添加。
- 但是注意:如果方法本身中有一个参数,那么会默认将原生事件event参数传递进去
- 情况二:如果需要同时传入某个参数,同时需要event时,可以通过
$event
传入事件。
条件渲染相关指令
v-if
- v-if是惰性的。
- 当条件为false时,其判断的内容完全不会被渲染或者会被销毁掉。
- 当条件为true时,才会真正渲染条件块中的内容。
- 如果想要多个dom同时显示或者隐藏,我们可以将v-if写在
template
标签上,并且让其包裹该多个dom元素。
v-else
(配合v-if使用)
v-else-if
(配合v-if使用)
v-show
- v-show是不能添加在
template
标签上
- v-show不可以和v-else一起使用。
- 本质是通过设置css的
display
的属性值来显示或者隐藏元素的。
列表渲染指令
v-for
- 它既可以遍历对象也可以遍历数组
- 格式:
"value in object / Array / Number"
;
"(value, key) in object / Array / Number"
;
"(value, key, index) in object"
;
- v-for同时也支持数字的遍历。
- 可以使用template来对多个元素进行包裹,而不是使用div来完成。
- 需要结合key来使用。 v-for中的key是什么作用?
- key属性主要用在Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes。
- 如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。
- 而使用key时,它会基于key的变化重新排列元素顺序,并且会移除/销毁key不存在的元素。
VNode是什么?
VNode的全称是Virtual Node,也就是虚拟节点。事实上,无论是组件还是元素,它们最终在Vue中表示出来的都是一个个VNode,VNode的本质是一个JavaScript的对象。
虚拟DOM?
如果我们不只是一个简单的div,而是有一大堆的元素,那么它们应该会形成一个VNode Tree。然后就组成了虚拟DOM。
下面我们来看一个小案例
vue中的diff算法
没有添加key的处理过程
添加key的处理过程
表单指令
v-model
: 用于表单数据和提供的数据双向绑定。
- 在表单
<input>
、<textarea>
及<select>
元素上创建双向数据绑定。
- 它会根据控件类型自动选取正确的方法来更新元素。
- 它负责监听用户的输入事件来更新数据,并在某种极端场景下进行一些特殊处理。
- 他的本质就是监听
input
事件,并且通过事件对象将值赋值给提供的数据。
- 修饰符
.lazy
: 将v-model的事件绑定从input转变为change事件。
.number
: 将v-model绑定的值转化为数字
.trim
: 将v-model绑定的值两边的空格去除。
- 如果是复选框和多选框,
v-model
将给选中的值加入到绑定的数组中。并且每个选项都必须设置value
属性。
<div id="app"></div> <template id="my-app"> <!-- 1.绑定textarea --> <label for="intro"> 自我介绍 <textarea name="intro" id="intro" cols="30" rows="10" v-model="intro"></textarea> </label> <h2>intro: {{intro}}</h2> <!-- 2.checkbox --> <!-- 2.1.单选框 --> <label for="agree"> <input id="agree" type="checkbox" v-model="isAgree"> 同意协议 </label> <h2>isAgree: {{isAgree}}</h2> <!-- 2.2.多选框 --> <span>你的爱好: </span> <label for="basketball"> <input id="basketball" type="checkbox" v-model="hobbies" value="basketball"> 篮球 </label> <label for="football"> <input id="football" type="checkbox" v-model="hobbies" value="football"> 足球 </label> <label for="tennis"> <input id="tennis" type="checkbox" v-model="hobbies" value="tennis"> 网球 </label> <h2>hobbies: {{hobbies}}</h2> <!-- 3.radio --> <span>你的爱好: </span> <label for="male"> <input id="male" type="radio" v-model="gender" value="male">男 </label> <label for="female"> <input id="female" type="radio" v-model="gender" value="female">女 </label> <h2>gender: {{gender}}</h2> <!-- 4.select --> <span>喜欢的水果: </span> <select v-model="fruit" multiple size="2"> <option value="apple">苹果</option> <option value="orange">橘子</option> <option value="banana">香蕉</option> </select> <h2>fruit: {{fruit}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { intro: "Hello World", isAgree: false, hobbies: ["basketball"], gender: "", fruit: "orange" } } } Vue.createApp(App).mount('#app'); </script>
在组件中使用v-model指令
我们在表单元素中很容易的使用v-model来做双向绑定。他的原理是通过v-bind:value
的数据绑定和@input的事件监听
如果我们想要在自定义组件中使用v-model呢?该如何实现呢?
<!-- 组件上使用v-model --> <hy-input v-model="message"></hy-input> // 等加入上面 <hy-input :modelValue="message" @update:model-value="message = $event"></hy-input>
其实在组件中使用v-model,默认情况下其实就是在组件中提供modelValue
props, 并且定义update:modelValue
事件。
如果我们想要在表单元素上使用v-model来代替上面的input事件中的属性操作。我们可以借助computed来实现,并且提供getter, setter方法。
// 通过原生的双向绑定实现 <input v-model="updateModelValue"> props: { modelValue: String }, emits: ["update:modelValue"], computed: { updateModelValue: { // 当改变modelValue时,就是调用setter方法 set(value) { this.$emit("update:modelValue", value); }, get() { return this.modelValue; } } },
如果我们想要自定义props来实现在组件上使用v-model,我们需要给v-model传递自定义属性名。
<hy-input v-model:title="title"></hy-input> data() { return { title: "title" } }
// 这里绑定的computed提供的属性 <input v-model="updateTitle"> props: { title: String }, emits: ["update:title"], computed: { updateTitle: { set(value) { this.$emit("update:title", value); }, get() { return this.title; } } }
当我们想在自定义组件中绑定多个属性(即使用多个v-model)时,我们就需要通过上面自定义props绑定名来实现了。
<hy-input v-model="message" v-model:title="title"></hy-input> data() { return { message: "message", title: "title" } }
<input v-model="updateModelValue"> <input v-model="updateTitle"> props: { modelValue: String, title: String }, emits: ["update:modelValue", "update:title"], computed: { updateModelValue: { set(value) { this.$emit("update:modelValue", value); }, get() { return this.modelValue; } }, updateTitle: { set(value) { this.$emit("update:title", value); }, get() { return this.title; } } }
optionsAPI
computed计算属性
我们知道,在模板中可以直接通过插值语法显示一些data中的数据。但是在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示。
- 需要对多个data数据进行运算、三元运算符来决定结果、数据进行某种转化后显示
- 在模板中使用表达式,可以非常方便的实现,但是设计它们的初衷是用于简单的运算,在模板中放入太多的逻辑会让模板过重和难以维护。所以需要使用计算属性。
- 如果多个地方都使用到,那么会有大量重复的代码,将它抽离到计算属性中,可以得到重用。 其实,我们也可以通过methods来实现这些逻辑,那为什么要用计算属性呢?他们有什么区别呢?
- 调用逻辑函数的时候,计算属性不需要写
()
, 但是methods需要写()
- 计算属性方法多次使用会有缓存,只会执行一次,再调用就会使用缓存的结果。当引用的数据发生变化他会重新结算结果,并缓存。但是methods方法不会存在缓存,每次调用对应的方法,都会重新执行一遍。 计算属性的
getter
和setter
方法
计算属性在大多数情况下,只需要一个getter方法即可,所以我们会将计算属性直接写成一个函数。但是,如果我们确实想设置计算属性的值呢? 这个时候我们也可以给计算属性设置一个setter的方法,并且调用计算属性函数时,可以传入值。
methods: { handleName() { // 改变计算属性值,然后值传递到set方法中作为参数 this.test = "llm zh" } }, computed: { test: { get() { return this.name }, set(value) { // console.log(value) this.name = value } } }
Vue内部是如何对我们传入的是一个getter,还是说是一个包含setter和getter的对象进行处理的呢?
事实上非常的简单,Vue源码内部只是做了一个逻辑判断而已