计算属性 computed
计算属性: 一个特殊属性,值依赖于另外一些数据动态计算出来
注意点:
- 计算属性必须定义在computed节点中
- 计算属性必须是一个function,计算属性必须有返回值
- 计算属性不能被当作方法来调用,要作为属性来使用
- 计算属性也是属性,所以不要和data里的属性重名,用起来也和data类似
计算属性缓存特性
- 计算属性基于依赖项的值进行缓存,依赖的变量不变,就直接从缓存中去结果,依赖的变量改变,则重新计算
- 计算属性如果被多次使用,性能极高
简写语法:
<template>
<div>
<p>和为:{{ getSum }}</p>
</div>
</template>
computed: {
// '计算属性名'(){ return '值' }
getSum(){
return 100
}
}
计算属性的设置问题--完整写法
- 计算属性默认情况下只能获取,不能修改
- 如果需要给计算属性赋值,就必须写计算属性的完成写法!
computed: {
getSum: {
get(){
...
},
// value是改变的值
set(value){
...
}
}
}
- get函数:获取计算属性的值会调用get,get会缓存,不一定每次都执行
- set函数:修改计算属性的时候会触发set,第一个参数就是修改的值
计算属性案例
<template>
<div>
全选:<input type="checkbox" v-model="isAll"> <button>反选</button>
<ul>
<li v-for="item in students" :key="item.name">
<input type="checkbox" v-model="item.checked">{{ item.name }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
students: [
{
name: "猪八戒",
checked: false,
},
{
name: "孙悟空",
checked: false,
},
{
name: "唐僧",
checked: false,
},
{
name: "沙师弟",
checked: false,
},
]
}
},
computed: {
isAll: {
// 读取计算属性
get(){
let all; // undefined
this.students.forEach(item => {
// 如果有一个没有选中,那么全选就是false
if(item.checked === false){
all = false
}
});
// 如果all还是undefined,代表所有选项都选中了
if(all === undefined){
return true
}else {
return false
}
},
// 修改计算属性
set(value){
this.students.forEach(item => {
item.checked = value
})
}
}
}
}
</script>
<style>
</style>
侦听器 watch
作用:watch:可以侦听到data/computed属性值的改变
简写语法:
<template>
<div>
<input type="text" v-model="name">
</div>
</template>
watch: {
// '被侦听的属性名'(newVal,oldVal){}
name(newVal,oldVal){ // 当name变量的值改变触发此函数
console.log(newVal,oldVal)
}
}
深度侦听和立即执行--完整写法
如果监听的是复杂数据类型,需要深度监听,需要指定deep为true, 需要用到监听的完整的写法
watch: {
//"要侦听的属性名":{immediate:true,deep:true,handler(newVal,oldVal){}}
student: {
immediate: true, // 立即执行,开始监听的时候,带着当前的值触发一次handler
deep: true, // 深度侦听复杂类型内变化
// 当数据变化,会调用handler,他有两个参数
// 引用类型的新值和旧值指向的都是同一个地址,所以新值和旧值相等
handler(newVal, oldVal){
....
}
}
}
created
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
比如 created 钩子可以用来在一个实例被创建之后执行代码:
// 组件渲染,需要先创建实例
// 组件创建后,会调用created函数
created () {
// 在这里可以做一些数据初始化工作
// 读取本地缓存的数据
const locList = localStorage.getItem('list')
// 判断本地是否有数据
if(locList !== null){
// 把本地存储的字符串还原成数组
this.list = JSON.parse(locList)
}
},
组件
为什么使用组件
- 提高了 复用性和灵活性
- 提升了 开发效率 和 后期可维护性
什么是组件化开发
- 组件是可复用的 Vue 实例, 封装标签, 样式和JS代码
- 组件化 :封装的思想,把页面上
可重用的部分封装为组件,从而方便项目的 开发 和 维护 - 一个页面, 可以拆分成一个个组件,一个组件就是一个整体
- 每个组件可以有自己独立的 结构 样式 和 行为(html, css和js)
- 例如:www.ibootstrap.cn/ 所展示的效果,就契合了组件化开发的思想。
组件的注册使用
App.vue 是根组件, 这个比较特殊, 是最大的一个根组件。其实里面还可以注册使用其他小组件。
使用组件的四步:
-
创建组件:写一个vue文件
-
引入组件
-
注册组件
- 全局注册 – main.js中
- 局部注册 – 某.vue文件内 – 可以注册多个组件
-
使用组件: 用注册的名字作为标签名,写出来
注意:组件不能和内置的html名同名
局部注册组件语法
import 组件对象 from 'vue文件路径'
export default {
components: {
"组件名": 组件对象
}
}
全局注册组件语法
import Vue from 'vue'
import 组件对象 from 'vue文件路径'
Vue.component("组件名", 组件对象)
组件名的命名规范
- 在进行组件的注册时,定义组件名的方式有两种:
-
注册使用短横线命名法,例如 hm-header 和 hm-main
- Vue.component('hm-button', HmButton)
- 使用时
<hm-button> </hm-button>
-
注册使用大驼峰命名法,例如 HmHeader 和 HmMain
- Vue.component('HmButton', HmButton)
- 使用时
<HmButton> </HmButton>和<hm-button> </hm-button>都可以
推荐1: 定义组件名时, 用大驼峰命名法, 更加方便
推荐2: 使用组件时,遵循html5规范, 小写横杠隔开(可选)
建议:组件名建议使用多个单词,用横线分隔,组件名不建议使用大写
通过 name 注册组件 (了解)
组件在 开发者工具中 显示的名字,可以通过name进行修改:在注册组件期间,除了可以直接提供组件的注册名称之外,还可以把组件的 name 属性作为注册后组件的名称
import HmButton from './components/HmButton/index.vue'
Vue.component(HmButton.name, HmButton)
// 等价于 Vue.component('HmButton', HmButton)
export default {
name: 'HmButton'
}
组件样式冲突 scoped
默认情况下,写在组件中的样式会 全局生效,因此很容易造成多个组件之间的样式冲突问题。
全局样式: 默认组件中的样式会作用到全局局部样式: 可以给组件加上 scoped 属性, 可以让样式只作用于当前组件
scoped原理?
- (1)当前组件内标签都被添加 data-v-hash值 的属性
- (2)css选择器都被添加 [data-v-hash值] 的属性选择器
最终效果: 必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到
组件通信
组件通信 - 父传子 - 语法
-
父组件通过给子组件加属性传值
-
子组件中, 通过props属性接收 props: [ 'data' ]
单向数据流
-
在vue中需要遵循单向数据流原则: (从父到子的单向数据流动, 叫单向数据流)
- 父组件的数据变化了,会自动向下流动影响到子组件
- 子组件不能直接修改父组件传递过来的 props, props是只读的!
组件通信 - 子传父 - 语法
子传父是指,子组件给父组件发事件
-
子组件可以通过
this.$emit('事件名', 参数1, 参数2, ...)触发事件的同时传参的this.$emit("update", 10)
-
父组件可以给子组件注册对应的自定义事件
<com @update="fn" />
props校验
-
说明:props 是父传子, 传递给子组件的数据, 为了提高 子组件被使用时 的稳定性, 可以进行props校验, 验证传递的数据是否符合要求
-
默认的数组形式, 不会进行校验, 如果希望校验, 需要提供对象形式的 props
-
props提供了多种数据验证方案,例如
- 基础的类型检查 Number
- 多个可能的类型[String, Number]
- 必须项校验 required: true
- 默认值 default: 100
- 自定义验证函数
export default {
// 要求父组件age参数必须是数字
// props: { 参数名: 参数类型 }
props: {
// 属性名是参数名,值是参数类型
age: Number
}
}
v-model 语法糖
语法糖: v-model本质上是 value属性和input事件的一层包装
v-model的作用:提供数据的双向绑定
①数据发生了改变,页面会自动变 v-bind:value
②页面输入改变 , 数据会自动变化 v-on:input
v-model给组件使用
我们经常遇到一种场景:
- 父组件提供一个数据给子组件使用(父传子)
- 子组件又需要修改父组件传过来的这个数据,所以需要子传父把值传给父组件。(子传父)
这种场景可以使用v-model进行简写。
<template>
<div>
<good-count v-model="num"></good-count>
</div>
</template>
<script>
import GoodCount from './components/good-count.vue'
export default {
data(){
return {
num: 100
}
},
components: {
"good-count": GoodCount
}
}
</script>
<style>
</style>
<template>
<div>
<h3>计数:{{ value }}</h3>
<button @click="fn">改成500</button>
</div>
</template>
<script>
export default {
props: ['value'],
methods: {
fn(){
this.$emit('input', 500)
}
}
}
</script>
<style>
</style>
ref 和 $refs
作用: 利用 ref 和 $refs 可以用于 获取 dom 元素, 或者组件实例
-
获取原生DOM标签
- 1.目标标签 – 添加 ref 属性
-
<h1 ref="myH1" id="h">ref/id获取原生dom</h1> - 2.恰当时机, 通过 this.$refs.xxx, 获取目标标签
-
console.log(this.$refs.myH1)
-
通过ref属性获取组件对象
-
1.给Demo组件目标组件, 添加ref属性-名字随意
-
<Demo ref="de"></Demo>- 2.恰当时机, 通过 this.$refs.xxx 获取组件对象, 可调用组件对象里方法等
-
this.$refs.de.fn()
$nextTick 的使用
需求:点击改data的数据, 获取原生DOM内容
-
创建标签显示数据
<p ref="a">数字:{{ count }}</p> <button>点击+1</button> -
点击+1, 马上获取原生DOM内容 (拿到的是更新前的dom?)
methods: { btn() { this.count++; console.log(this.$refs.a.innerHTML) } }
原因: Vue更新DOM是异步的,如果想要获取DOM更新后的内容,需要使用$nextTick
$nextTick等DOM更新后, 才会触发执行此方法里的函数体
- 语法:this.$nextTick(函数体)
-
methods: { btn() { this.count++; this.$nextTick(() => { console.log("DOM更新后触发$nextTick函数"); console.log(this.$refs.a.innerHTML) }) } }
获取DOM案例
<template>
<div>
<button @click="replay">回复</button>
<br>
<input type="text" placeholder="请输入评论" v-if="showInput" ref="comment">
</div>
</template>
<script>
export default {
data(){
return {
showInput: false
}
},
methods: {
replay(){
this.showInput = true
// 获取input的dom,获取焦点
// $nextTick
this.$nextTick(() => {
// input.focus是获取焦点
// this.$refs.comment可以获取到指定了ref的comment名字的DOM元素
this.$refs.comment.focus()
})
}
}
}
</script>
<style>
</style>
dynamic 动态组件
动态组件是什么?
- 是 可以改变 的 组件
动态组件能解决什么需求呢 ?
- 解决多组件同一位置, 切换显示的需求
基本语法:
-
component 组件(位置) + is 属性 (哪个组件)
-
<component :is="变量"> //是组件名,字符串,当你需要切换组件,就修改变量成对应的组件名
-
-
修改 is 属性绑定的值 => 切换组件
dynamic 动态组件- 怎么用(案例)
- 准备被切换的 2个组件, 并引入注册
- 准备变量来承载要显示的"组件名"
- 设置挂载点, is属性设置要显示的组件 ( component + is )
- 点击按钮 – 修改comName变量里的"组件名" ( 修改 is 的值)
<template>
<div>
<button @click="comName = 'little-red'">小红</button>
<button @click="comName = 'little-blue'">小蓝</button>
<button @click="comName = 'little-white'">小白</button>
<component :is="comName"></component>
</div>
</template>
<script>
import LittleRed from './components/little-red.vue'
import LittleBlue from './components/little-blue.vue'
import LittleWhite from './components/little-white.vue'
export default {
data(){
return {
comName: 'little-red'
}
},
components: {
'little-red': LittleRed,
'little-blue': LittleBlue,
'little-white': LittleWhite
}
}
</script>
<style>
</style>
插槽
作用:组件部分内容允许调用者动态传入
1.插槽 - 默认插槽
插槽基本语法:
- 组件内用占位
- 使用组件时夹着的地方, 传入标签替换slot
2.插槽 - 后备内容(默认值)
插槽后备内容:封装组件时,可以为预留的 <slot> 插槽提供后备内容(默认内容)。
-
语法: 在标签内放置内容, 作为默认显示内容
-
效果:
- 外部使用组件时,不传东西,则slot会显示后备内容 (slot标签内的结构)
- 外部使用组件时,传东西了,则slot整体会被换掉
3.插槽 - 具名插槽
需求:一个组件内有多处,需要外部传入标签,进行定制
-
语法:
- 1.多个slot使用name属性区分名字
- 2.template配合v-slot:名字来分发对应标签
- v-slot:可以简化成#
4.插槽 - 作用域插槽
作用域插槽: 定义 slot 插槽的同时, 是可以传值的。给插槽上可以绑定数据,将来使用组件时可以用。
基本使用步骤:
- 给 slot 标签, 以 添加属性的方式传值
- 所有添加的属性, 都会被收集到一个对象中
- 在template中, 通过
v-slot:插槽名= "obj"接收
自定义指令
自定义指令:自己定义指令, 封装dom操作,扩展额外功能
- 全局注册 - 语法 - 在main.js中注册
// 全局注册自定义指令
Vue.directive("指令名",{
inserted(el){
// 可以对el标签扩展额外功能
}
})
- 局部注册 - 语法 - 在App.vue中注册
export default {
directives: {
"指令名": {
inserted(el){
// 对el进行操作
}
}
}
};
-
自定义指令 - 指令的值
- 需求:定义color指令-传入一个颜色, 给标签设置文字颜色
- 语法:在绑定指令时,可以通过“等号”的形式为指令绑定具体的参数值
-
<template> <div> <h3 v-color>Hello, World</h3> <h3 v-color="textColor">Hello, World</h3> <button @click="textColor = 'purple'">变变变</button> <h3 v-color="pink">Hello, World</h3> <h3 v-bigger>大字体</h3> </div> </template> <script> export default { data() { return { textColor: 'green', pink: 'pink' }; }, directives: { bigger: { inserted(el){ el.style.fontSize = '50px' } } } }; </script> - 通过 binding.value 可以拿到指令值,指令值修改会触发 update 函数。
-
// 全局注册自定义指令 Vue.directive("color",{ // inserted是指令所在的标签被插入到父节点之后触发 // 在inserted里,可以拿到指令所在标签的DOM inserted(el,binding){ console.log('dom', el) // binding有个属性value,value就是指令传的值 el.style.color = binding.value || 'red' }, // update会在指令的值变化的时候触发 update (el, binding) { // 当变量变化,再修改一下颜色 el.style.color = binding.value } })
生命周期
生命周期基本认知
vue组件生命周期:从创建 到 销毁 的整个过程就是 – Vue实例的 - 生命周期
生命周期-钩子函数概述
生命周期函数:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。
-
作用: 特定的时间点,执行特定的操作
-
比如: 组件创建完毕后,可以在created 生命周期函数中发起Ajax 请求,从而初始化 data 数据
-
分类: 三大阶段,8个方法
- 组件
初始化阶段的生命周期函数 - 组件
运行阶段的生命周期函数 - 组件
销毁阶段的生命周期函数
- 组件
生命周期钩子-详解
八大生命周期钩子函数:
- beforeCreate:data数据初始化之前,组件还没有数据
- created: data数据初始化之后,可以获取到组件的数据
- beforeMount:DOM渲染之前,DOM还没渲染
- mounted:DOM渲染之后,可以操作DOM了
- beforeUpdate: 数据更新,DOM更新前
- updated: 数据更新,DOM更新后
- beforeDestroy: 组件销毁前
- destroyed: 组件销毁后
路由基础
单页应用程序: SPA - Single Page Application
-
单页面应用(SPA): 所有功能在一个html页面上实现 (多页面应用程序MPA)
-
优点:
- 不整个刷新页面,每次请求仅获取需要的部分,用户体验更好
- 数据传递容易, 开发效率高
-
缺点:
- 开发成本高(需要学习专门知识 - 路由)
- 首次加载会比较慢一点。不利于seo
客户端路由
- 浏览器端实现页面切换
- 路由是路径和页面组件的对应关系
Vue的路由:vue-router
vue-router: vue-router本质是 vue 官方的一个路由插件,是一个第三方包
- 官网: router.vuejs.org/zh/
- lvue-router集成了 路径 和 组件的切换匹配处理,我们只需要配置规则即可。
组件分类: .vue文件分2类, 一个是页面组件, 一个是复用组件
-
vue文件本质无区别, 方便大家学习和理解, 总结的一个经验
-
src/views文件夹
- 页面组件 - 页面展示 - 配合路由用
-
src/components文件夹
- 复用组件 - 展示数据 - 常用于复用
vue-router的基本使用(5 + 2)
-
Vue2使用3.x的路由版本
-
Vue3使用4.x的路由版本
-
5 个 基础步骤
-
下载vue-router模块到当前工程,版本3.5.3
- npm i vue-router@3
-
在main.js中引入VueRouter函数
- import VueRouter from 'vue-router'
-
添加到Vue.use()身上 – 帮你注册全局RouterLink和RouterView组件
- Vue.use(VueRouter)
-
创建路由对象
- { routes: [{path:'路径', component: 组件变量}] }
-
将路由对象注入到new Vue实例中
- new Vue({ router })
-
-
2 个 核心步骤:
- 配置路由规则
- 指定路由出口 router-view
-
配置路由页面放在哪里
- App.vue-> router-view
路由模块在企业开发中一般抽离成单独的模块
- 在src目录下新建router文件目录,并在改目录下新建index.js文件,在index.js文件中配置,src/router/ -----> index.js
- 好处:拆分模块,利于维护
声明式导航 - 导航链接
-
组件router-link 替代 a标签,能跳转,能高亮
- 1.vue-router提供了一个全局组件 router-link
- 2.router-link实质上最终会渲染成a链接 to属性等价于提供 href属性(to无需#)
- 3.router-link提供了声明式导航高亮的功能(自带类名)
<template>
<div>
<!-- router-link跳转页面,如果携带参数,to可以绑定 -->
<!-- to对象有两个属性:path,跳转的路径;query属性 -->
<router-link :to="'/foo'">Foo</router-link>
|
<router-link :to="`/bar">Bar</router-link>
<router-view></router-view>
</div>
</template>
声明式导航 - 两个类名
-
你在哪个页面,那个页面的router-link会有两个类
-
lrouter-link-active: 激活的导航链接 模糊匹配
- to="/my" 可以匹配 /my /my/a /my/b ....
-
lrouter-link-exact-active: 激活的导航链接 精确匹配
-
to="/my" 仅可以匹配 /my
-
可以修改默认高亮的类名
-
const router = new VueRouter({ linkActiveClass: 'aa', linkExactActiveClass: 'aa', // route: 一条规则 })
声明式导航 - 跳转传参
-
query
-
问号后面的参数 /foo?age=18
-
:to="{ path: "路径", query: {参数名: 参数值} }"
-
接收:this.$route
- this.$route代表当前的路由信息
- this.$route.query接收query参数
-
-
params
-
内嵌在地址里的参数 /foo/123
-
使用
- 1.路由配置声明参数 /foo/:id
- 2.跳转的时候传递参数 <router-link :to="
/foo/${id}" - 3.接收参数 this.$route.params.id
-
-
代码演示
// 主组件App.vue
<template>
<div>
<!-- router-link跳转页面,如果携带参数,to可以绑定 -->
<!-- to对象有两个属性:path,跳转的路径;query属性 -->
<router-link :to="{ path: '/foo', query: { name: 'xuee', age: 18 } }">Foo</router-link>
|
<router-link :to="`/bar/${productId}`">Bar</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {
data(){
return {
productId: 666
}
}
}
</script>
<style scoped>
.router-link-active {
color: red;
}
</style>
// 子组件 bar.vue
<template>
<div>
这是bar组件
</div>
</template>
<script>
export default {
created(){
console.log(this.$route);
console.log(this.$route.params);
console.log(this.$route.params.id);
}
}
</script>
<style>
</style>
// 子组件 foo.vue
<template>
<div>
这是foo组件
</div>
</template>
<script>
export default {
created () {
console.log(this.$route);
console.log(this.$route.query.name);
console.log(this.$route.query.age);
}
}
</script>
<style>
</style>
vue路由 - 重定向
重定向:匹配path后, 强制跳转path路径
- 网页打开url默认hash值是/路径
- redirect是设置要重定向到哪个路由路径
// new一个VueRouter
const router = new VueRouter({
// routes代表路由配置
routes: [
{
path: "/", // 匹配的路径
redirect: "/foo", // 匹配到上面的路径后重定向到这个路径
},
{
path: "/foo",
component: Foo,
},
{
path: "/bar/:id",
component: Bar,
}
],
});
vue路由 - 404
- 404:当找不到路径匹配时,给个提示页面
- 路由最后, path匹配*(任意路径) – 前面不匹配就命中最后这个
import NotFound from '../views/NotFound.vue'
// new一个VueRouter
const router = new VueRouter({
// routes代表路由配置
routes: [
{
path: "/", // 匹配的路径
redirect: "/foo", // 匹配到上面的路径后重定向到这个路径
},
{
path: "/foo",
component: Foo,
},
{
path: "/bar/:id",
component: Bar,
},
{
path: '*',
component: NotFound,
}
],
});
vue路由 - 模式设置 - 修改路由,在地址栏的模式
- hash路由例如: http://localhost:8080/#/home
- history路由例如: http://localhost:8080/home (以后上线需要服务器端支持)
- 在实例化路由对象时, 传入mode选项和值修改
const router = new VueRouter({
routes,
mode: "history"
})
编程式导航 - 基本跳转
编程式导航:用JS代码来进行跳转
- 语法: path或者name任选一个
// html标签中写法
<button @click="$router.push({ path: '/foo'})">Foo</button>
// script中写法
methods: {
fn(){
this.$router.push({
path: "/foo"
})
}
}
编程式导航 - 路由传参
- 语法: query或params都可以传参
query传:$route.query.xxx 接收
params传:$route.params.xxx 接收
区别:
- params传参:是在内存中传参,刷新会丢失
- query传参:是在地址栏传参,刷新还在
注意: 使用path会忽略params
<template>
<div>
<!-- router-link跳转页面,如果携带参数,to可以绑定 -->
<!-- to对象有两个属性:path,跳转的路径;query属性 -->
<router-link :to="{ path: '/foo', query: { name: 'xuee', age: 18 } }">Foo</router-link>
|
<router-link :to="`/bar/${productId}`">Bar</router-link>
<br>
<!-- <button @click="$router.push('/foo')">Foo</button> -->
<button @click="$router.push({ path: '/foo', query: { age: 18 } })">Foo</button>
<!-- <button @click="$router.push(`/bar/${productId}`)">Bar</button> -->
<!-- path和params不能一起使用,params要和name属性搭配使用 -->
<button @click="$router.push({name: 'xxx', params: { id: 123 } })">Bar</button>
<router-view></router-view>
</div>
</template>
<script>
export default {
data(){
return {
productId: 666
}
}
}
</script>
<style scoped>
.router-link-active {
color: red;
}
</style>
// 引入VueRouter函数
import VueRouter from "vue-router";
import Vue from "vue";
import Foo from "../views/foo.vue";
import Bar from "../views/bar.vue";
import NotFound from '../views/NotFound.vue'
// 注册VueRouter的功能
Vue.use(VueRouter);
// new一个VueRouter
const router = new VueRouter({
// routes代表路由配置
routes: [
{
path: "/", // 匹配的路径
redirect: "/foo", // 匹配到上面的路径后重定向到这个路径
},
{
path: "/foo",
component: Foo,
},
{
name: 'xxx',
path: "/bar/:id",
component: Bar,
},
{
path: '*',
component: NotFound,
}
],
});
// 导出router对象
export default router;