目录
计算属性computed
侦听器watch
对象监听
组件
注册全局组件
注册局部组件
Vue-CLI脚手架
安装和使用
.browserslistrc
main.js
jsconfig.json
组件通讯
父组件传递给子组件
props基础
非prop的attribute
子组件传递给父组件
插槽slot
基础使用
具名插槽
动态插槽名
作用域插槽
计算属性computed
如果我得到了socre,希望根据这个判断是否及格,可以这么做:
<div>{{score>=60?'及格':'不及格'}}</div>
但是这么做的弊端是,需要在插值语法中放置过于复杂的逻辑,不易于维护
有一种方法是在methods中放置函数:
<body>
<div class="app">
<div>{{gett()}}</div>
</div>
<script src="./lib/vue.js"></script>
<script>
const app = Vue.createApp({
data:function(){
return {
score:80
}
},
methods:{
gett(){
return this.score>=60?'及格':'不及格'
}
}
})
app.mount(".app")
</script>
</body>
但是我们希望不要调用函数,而是直接用变量名,就可以使用计算属性computed
对于任何包含响应式数据的复杂逻辑,都应该使用计算属性
<body>
<div class="app">
<!-- 在调用时直接写函数的名字,而不用写() -->
<div>{{gett}}</div>
</div>
<script src="./lib/vue.js"></script>
<script>
const app = Vue.createApp({
data:function(){
return {
score:80
}
},
computed:{
gett(){
return this.score>=60?'及格':'不及格'
}
}
})
app.mount(".app")
</script>
</body>
在调用时直接写函数的名字,而不用写()
计算属性的优势
- 计算属性直接放函数名字,比较简洁
- 计算属性具有缓存
<div class="app"> <div>{{gett}}</div> <div>{{gett}}</div> <div>{{gett}}</div> <div>{{gett1()}}</div> <div>{{gett1()}}</div> <div>{{gett1()}}</div> </div>
methods:{ gett1(){ console.log('11--------11'); return this.score>=60?'及格':'不及格' } }, computed:{ gett(){ console.log('22--------22'); return this.score>=60?'及格':'不及格' } }
结果是
也就是说计算属性执行一次后会将结构放在缓存中,下次再使用时不用重新调用
侦听器watch
可以侦听数据的改变
<body>
<div class="app">
<div>{{gett}}</div>
<button @click="change"></button>
</div>
<script src="./lib/vue.js"></script>
<script>
const app = Vue.createApp({
data:function(){
return {
score:80
}
},
methods:{
change(){
this.score = 90
}
},
watch:{
score(newvalue,old){
console.log('score has been changed');
console.log('new:'+newvalue);
console.log('old:'+old);
}
}
})
app.mount(".app")
</script>
</body>
watch里用变量的名字定义了一个函数,定义了这个变量发生变化时,执行哪些操作
这个函数可以调用此变量的 新旧值
对象监听
这个方法对于对象也可以监听,但是只能监听对象整体赋值
methods:{
change(){
this.score = {name:'1232'};
}
},
watch:{
score(newvalue,old){
console.log('score has been changed');
},
上面这个可以监听到
methods:{
change(){
this.score.name = '1233'
}
},
watch:{
score(newvalue,old){
console.log('score has been changed');
},
上面这个不行,因为watch默认不会进行深度监听、
如果要进行深度监听:
watch:{
score:{
handler(newvalue,oldvalue){
console.log('score has been changed')
},
deep:true
}
}
如果在最开始渲染界面时就想运行这个监听函数,使用 immediate:true
watch:{
score:{
handler(newvalue,oldvalue){
console.log('score has been changed')
},
deep:true,
immediate:true
}
}
做一个小案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0
}
ul{
width: -moz-fit-content;
width: fit-content;
}
li {
display: inline-block;
list-style-type: none;
width: 100px;
height: 40px;
border: 1px solid black;
text-align: center;
}
.title li {
background-color: gray;
}
.red{
color: red;
}
</style>
</head>
<body>
<div class="app">
<ul class='title'>
<li>序号</li>
<li>书籍名称</li>
<li>出版日期</li>
<li>价格</li>
<li>购买数量</li>
<li>操作</li>
</ul>
<ul v-for="(item,index) in products" @click="light(index)" :class="{red:index==indexchoice}">
<li>{{index+1}}</li>
<li v-for="(value,key) in item">{{value}}</li>
<li><button @click="sub(index)">-</button>{{counter[index]}}<button @click="add(index)">+</button></li>
<li><button @click='deleteProduct(index)'>移除</button></li>
</ul>
<h1>总价:¥{{sum}}</h1>
<h1>{{alert}}</h1>
</div>
<script src="./lib/vue.js"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
counter: [],
alert: '',
indexchoice:-1,
products: [{ name: '《算法导论》', data: '2006-9', price: 85 }, { name: '《代码大全》', data: '2006-9', price: 25 }, { name: '《编程》', data: '2006-9', price: 85 }]
}
},
methods: {
sub(index) {
this.counter[index] = this.counter[index] > 1 ? this.counter[index] - 1 : 1
},
add(index) {
this.counter[index]++
},
deleteProduct(index) {
this.products.splice(index, 1);
this.counter.splice(index, 1);
},
light(index){
this.indexchoice = index
}
},
computed: {
sum() {
let summ = 0
for (let i = 0; i < this.products.length; i++) {
summ += this.products[i].price * this.counter[i]
}
if (summ == 0) {
this.alert = '购物车为空'
}
else { this.alert = '' }
return summ
}
},
beforeMount() {
for (let i = 0; i < this.products.length; i++) {
this.counter.push(1)
}
}
})
app.mount(".app")
</script>
</body>
</html>
</html>
组件
注册全局组件
这里分三步创造全局组件product-item,在使用时直接使用即可使用模板内容
<body>
<div class="app">
<product-item></product-item>
<product-item></product-item>
<product-item></product-item>
</div>
<script src="./lib/vue.js"></script>
<script>
//1.组件:App组件(根组件)
const App = {}
//2.创建app
const app = Vue.createApp(App)
//3.注册一个全局组件
app.component('product-item', {
template: `
<div>
<h2>商品</h2>
<div>图片</div>
<h3>价格</h3>
</div>
`
})
app.mount(".app")
</script>
</body>
template写在这里感觉不是很方便,也可以写在上面再用id引入,并且在app中,之前所有的方法一样适用
当然也可以注册多个全局组件
<body>
<div class="app">
<product-item></product-item>
<product-item></product-item>
<product-item></product-item>
</div>
<template id="products">
<div>
<h2>{{name}}</h2>
<div>图片</div>
<h3>价格</h3>
</div>
<button @click="fn">hhh</button>
</template>
<template id="navs">
<h2>hhhh</h2>
</template>
<script src="./lib/vue.js"></script>
<script>
//1.组件:App组件(根组件)
const App = {}
//2.创建app
const app = Vue.createApp(App)
//3.注册一个全局组件
app.component('product-item', {
template: '#products',
data(){
return{
name:'aaaa'
}
},
methods:{
fn(){
console.log(111);
}
}
})
app.component('nav-item',{
template:"#navs"
})
app.mount(".app")
</script>
</body>
这里定义了两个全局组件 product-item 和 nav-item
全局组件可以在任意组件的template中使用
例如下面这种写法是允许的:
<template id="navs">
<product-item></product-item>
<h2>hhhh</h2>
</template>
注册局部组件
一般开发时很少注册全局组件
局部组件是在app中采用components api设置:
<body>
<div class="app">
<product-item></product-item>
<nav-item></nav-item>
</div>
<template id="products">
<div>
<h2>{{name}}</h2>
<div>图片</div>
<h3>价格</h3>
</div>
</template>
<template id="navs">
<h2>hhhh</h2>
</template>
<script src="./lib/vue.js"></script>
<script>
const app = Vue.createApp({
data(){
return{
}
},
methods:{},
components:{
'product-item':{
template:'#products',
data(){
return{
name:'hhhh'
}
}
},
'nav-item':{
template:'#navs'
}
}
})
app.mount(".app")
</script>
</body>
局部组件不可以在任意组件的template中使用
Vue-CLI脚手架
但是上述的开发模式很繁琐,将js vue css html都写在一起
一般使用脚手架搭建项目模板
安装和使用
在命令行:
npm i @vue/cli -g
使用脚手架创造项目,这种方法基于webpack
vue create project_name
会问你一堆问题,具体什么意思这里不赘述了
创建好之后可以运行项目
npm run serve
这里还有第二种方法创建vue项目,这个是基于vite工具
npm init vue@latest
其过程是:
- 安装一个本地工具:create-vue
- 使用create-vue创建项目
.browserslistrc
这里解释一下 .browserslistrc
> 1%
last 2 versions
not dead
not ie 11
- 只适配市场占有率大于百分之1的浏览器,通过 caniuse 网站
- 适配浏览器的最后两个版本
- 不适配 没有维护 的浏览器
- 不适配 ie 11
main.js
在src文件夹下有main.js文件
import { createApp } from 'vue/dist/vue.esm-bundler'
import App from './App.vue'
createApp(App).mount('#app')
这里说的是在 App.vue 中引入App组件并渲染
说一下下来两者的区别
import { createApp } from 'vue'
import { createApp } from 'vue/dist/vue.esm-bundler'
第一个是 runtime 模式
第二个是 runtime+compile 模式
具体是体现在<template>的编译模式,如果直接在 main.js文件中书写App,则在编译<template>时需要用到源码的 compile ,所以必须用第二种
如果在.vue文件中书写App,webpack在打包的时候就会直接编译,不会用到VUE源码,所有第一个就可以
那么在App.vue中,由三部分组成,即
<template>
<script>
<style>
这里使用一个最简单的例子
<template>
<h2>{{name}}</h2>
</template>
<script>
export default{
data(){
return{
name:'hhhh'
}
},
methods:{
}
}
</script>
<style>
</style>
代码书写逻辑跟之前书写的一样
这里需要注意的是style,当我们设置了一个样式,我们希望这个样式只在这个组件内生效,也就是说这个样式只在这个 .vue文件生效,即有自己的作用域,那么可以:
<style scoped>
</style>
jsconfig.json
jsconfig.json文件是给VScode的,为了让它有更加友好的提示
例如path:
"paths": {
"@/*": [
"src/*"
],
"utils/*":["src/utils/*"]
},
在打 utils时就会自动提示
jsconfig.json的作用_我叫火柴的博客-CSDN博客_jsconfig
组件通讯
一般的项目开发中会出现很多的 组件嵌套
即在一个组件定义中应用另外一个组件
导入方式:
<template>
<Infos></Infos>
</template>
<script>
import Infos from './components/infos.vue'
export default{
components: { Infos }
}
</script>
<style scoped>
</style>
如果存在嵌套,那么就需要进行数据通讯
父组件传递给子组件:采用 props属性
子组件传递给父组件: 采用$emit触发事件
父组件传递给子组件
props基础
子:
<template>
<div class="infos">
<h1>{{name}}</h1>
<h2>{{age}}</h2>
</div>
</template>
<script>
export default{
//定义props有两种方式,数组和对象方式
// props:['name','age']
props:{
name:{
type:String,
default:'nameeee'
},
age:{
type:Number,
default:18
}
}
}
</script>
<style scoped>
</style>
父组件:
<template>
<Infos name='kobe' :age='18'></Infos>
</template>
<script>
import Infos from './components/infos.vue'
export default{
components: { Infos }
}
</script>
<style scoped>
</style>
这里父组件引入子组件,并通过属性的方式给子组件传递数据
子组件在 api中通过 props属性添加变量名
props的设定有两种方法,数组法和对象法,对象法可以设定数据想要的数据类型和默认值,显然是更优秀的方法
这里有一个需要注意的,如果使用对象法,数据类型为对象或者数组,那么其默认值要通过函数来设置
frends:{
type:Object,
default:()=>({name:'111',age:19})
},
frends1:{
type:Array,
default:()=>(['aaa','bbb'])
}
非prop的attribute
也就是父组件传递给子组件一些数据,但是在子组件中的Props属性中没有进行设置
例如下面的这个class就是非prop的attribute
<Infos name='kobe' :age='18' class="abc"></Infos>
此属性会默认添加到子组件的根元素上
也就是说在子组件的 <div class="infos"> 中会添加 class='abc' 属性
当然如果你不希望这么做,可以直接禁止,在子组件中设置:
inheritAttrs:false,
props:{}
如果我希望拿到这个属性,赋予给其他的元素,而不是根元素,那么在子组件的此元素里应该:
<div class="infos">
<h1 :class='$attrs.class'>{{name}}</h1>
<h2>{{age}}</h2>
</div>
那么h1就可以拿到传进来的 class属性
子组件传递给父组件
这个通过一个小案例说明
在子组件中设置两个按钮,分布为 +1 +5按钮
<template>
<div class="infos">
<button @click='addnum(1)'>+1</button>
<button @click='addnum(5)'>+5</button>
</div>
</template>
<script>
export default{
methods:{
addnum(count){
this.$emit('addnumber',count)
}
}
}
</script>
<style scoped>
</style>
这里给按钮设置监听函数 addnum()
在这个函数里面用this.$emit向父组件发送监听名字addnumber(自己设置的)和参数 count
然后在父组件中:
<template>
<h1>{{counter}}</h1>
<Infos @addnumber='addcount'></Infos>
</template>
<script>
import Infos from './components/infos.vue'
export default{
data(){
return{
counter:0
}
},
components: { Infos },
methods:{
addcount(count){
this.counter = this.counter+count
}
}
}
</script>
<style scoped>
</style>
给用到的子组件添加 @addnumber='addcount' 这个监听名字是之前在子组件中自己设置的,然后使用addcount函数在父组件中执行想要的逻辑
一般来说子组件可能会要发送很多监听名字,这里可以用 emits方法进行说明,也方便他人查看
export default{
emits:['addnumber'],
methods:{
addnum(count){
this.$emit('addnumber',count)
}
}
}
插槽slot
对于这样一个导航栏,其实本质上都是三部分组成,既左边-中间-右边三部分
但是又各有不同,比如中间部分,有的是 搜索框 有的是 购物车几个字
那么在创建组件时,就可以使用插槽slot,将三个部分预留出来,后期再填入想要的内容
基础使用
子组件
这里使用slot作为插槽,当父组件没有传入元素时,会展示默认元素
<template>
<slot>
<p>我是默认内容</p>
</slot>
</template>
父组件
直接在调用的子组件下面写自己想要的元素
<template>
<Page :index="index">
<h1>hhhh</h1>
</Page>
<Page></Page>
</template>
具名插槽
如果存在多个slot,就需要设定名字
子组件
<template>
<div>
<slot name="one"></slot>
</div>
<div>
<slot name="two"></slot>
</div>
</template>
父组件
使用 v-slot:xxx 设定对应slot的名字
<template>
<Page>
<template v-slot:one>
<h1>hhhhh</h1>
</template>
<template v-slot:two>
<h1>wwwwww</h1>
</template>
</Page>
</template>
缩写:
<template #one>
<h1>hhhhh</h1>
</template>
动态插槽名
采用 v-slot:[xxx] 的方式将插槽绑定到变量xxx中
<template v-slot:[choose]>
<h1>hhhhwwh</h1>
</template>
作用域插槽
也就是通过插槽将子组件的变量传到父组件
子组件
<template>
<div>
<slot name="one" :item="item" :age="age">
<h1>我是默认值</h1>
</slot>
</div>
</template>
<script>
export default {
data(){
return{
item:'hhh',
age:18
}
},
}
</script>
<style scoped>
</style>
item 和 age 是定义在子组件中的两个变量,其作用域只在子组件
在slot中添加 :item="item" :age="age" 将两个变量传递出去
父组件
使用 #one="props" 拿到传递进来的变量,这个props是自己设置的名字,其本质是对象,包含了传递进的变量
<Page>
<template #one="props">
<h1>{{props.item}}</h1>
<h2>{{props.age}}</h2>
</template>
</Page>
这里通过一个案例说明作用域插槽的一般使用
子组件:
这里子组件使用 父组件传进来的 products 创建了n个div,每个div里面都有一个slot,其名字都是 one
但是每个slot具有不同的item,并将其传递给父组件
<template>
<div v-for="item in products">
<slot name="one" :item="item">
<span>{{item}}</span>
</slot>
</div>
</template>
<script>
export default {
props:['products']
}
</script>
<style scoped>
</style>
父组件
使用slot传递进来的item创建元素替代slot
<template>
<Page :products="products">
<template #one="props">
<button>{{props.item}}</button>
</template>
</Page>
<Page :products="products">
<template #one="props">
<a href="">{{props.item}}</a>
</template>
</Page>
</template>
<script>
import Page from './components/page.vue'
export default {
data(){
return{
products:['shoe','clothes','skirts']
}
},
components:{Page},
}
</script>
<style scoped>
</style>
可以实现改变元素种类,保持内容不变