【VUE3】保姆级基础讲解(二)计算属性,vue组件,vue-cli脚手架,组件通讯,插槽slot

目录

计算属性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>

可以实现改变元素种类,保持内容不变 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值