欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

前端笔记之Vue(二)组件&案例&props&计算属性

程序员文章站 2022-04-15 16:57:26
一、Vue组件(.vue文件) 组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。 所 ......

一、vue组件(.vue文件)

组件 (component) vue.js 最强大的功能之一。组件可以扩展 html 元素,封装可重用的代码。在较高层面上,组件是自定义元素,vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 html 元素。

所有的 vue 组件同时也都是 vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子。

说白了,就是htmlcssjs行为的一个封装。

 

vue中组件的实例化是对程序员不透明的,组件必须在components中进行“注册”才能使用。

vue中用k:v对的形式定义类,让你*的注册名字,vue官方推荐使用含有短横的名字来表示自定义组件。

 

翻译.vue文件需要安装vue-loader依赖:

 

安装其他4个开发依赖:

npm install --save-dev css-loader
npm install --save-dev vue-loader
npm install --save-dev vue-style-loader
npm install --save-dev vue-template-compiler

 

修改webpack的配置(关于vue-loader的配置)官网找:

前端笔记之Vue(二)组件&案例&props&计算属性


1.1组件的写法1

.vue文件的结构:

<template></template>     html结构
<script></script>            js程序
<style></style>            样式表

 app.vue父组件:

<template>
    <h1>我是app父组件{{a}}</h1>
</template>
<script>
    export default {
        data(){
            return {
                a:100
            }
        }
    }
</script>
<style></style>

 

有了app.vue组件就可以在main.js中通过import引入,并注册:

import vue from 'vue';
import app from './app.vue';

new vue({
    el : "#app",
    data : {

    },
    components : {
        app
    }
})

 

index.html页面中使用即可:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>document</title>
</head>
<body>
    <div id="app">
        <app></app>
    </div>
</body>
<script type="text/javascript" src="dist/all.js"></script>
</html>

前端笔记之Vue(二)组件&案例&props&计算属性

此时看见app父组件的内容了


1.2组件的写法2

使用vue2新增的render()函数

index.html页面中就不用放在<app></app>自定义标签了,也不需要注册了。

main.js

import vue from 'vue';
import app from './app.vue';

new vue({
    el : "#app",
    render : (h)=> h(app)
})

 

vue中不允许出现片段标签,必须用一个标签包裹所有

<template>
    <div>
        <h1>我是app组件{{a}}</h1>
        <h1>我是app组件{{a}}</h1>
    </div>
</template>
错误写法:
<template>
    <h1>我是app组件{{a}}</h1>
    <h1>我是app组件{{a}}</h1>
</template>

 

如果有两个组件就需要有一个components的文件夹 里面可以放.vue组件

mian.vue

<style>
</style>
<template>
    <div>
        <h1>我是mian组件{{a}}</h1>
    </div>
</template>
<script>
    export default{
        data(){
            return{
                a : 100
            }
        }
    }
</script>

 

组件在app.vue里面使用

<style>
</style>
<template>
    <div>
        <vue-main></vue-main>
        <vuemain></vuemain>
    </div>
</template>
<script>
import vuemain from "./components/main.vue"
export default {
    data(){
        return {
            a: 100
        }
    },
    components:{
        vuemain
    }
}
</script>

 

使用的时候以下两种方法:

<vuemain></vuemain> 

等价于:

<vue-main></vue-main>

1.3关于data

在前面看到,在new vue()的时候,创建或注册模板时,传入一个data属性作为用来绑定的数据。是可以给data直接赋值为一个对象的。但是在组件中,data必须是一个函数,而不能直接把一个对象赋值给它。

 

1种,在main.js主入口中的写法:

new vue({
    el:'#app',
    data:{
            
    }
})

 

2种,在组件中data选项必须是一个函数:

new vue({
    el:'#app',
    data(){
        return { //返回一个唯一的对象,不要和其他组件共用一个对象进行返回
                
        }
    }
})

 

【区别】:

1)在简单的vue实例中,没什么区别,因为你new出的对象不会被复用。

new vue({...})

2)但在组件中,因为可能在多处调用同一组件,所以为了不让多处的组件共享同一data对象,只能返回函数。


 

二、案例

2.1调色板

<template>
    <div>
        <div class="box" :style="{background:`rgb(${r},${g},${b})`}"></div>
        <p>
            <input type="range" min="0" max="255" v-model="r">
            <input type="number" min="0" max="255" v-model="r">
        </p>
        <p>
            <input type="range" min="0" max="255" v-model="g">
            <input type="number" min="0" max="255" v-model="g">
        </p>
        <p>
            <input type="range" min="0" max="255" v-model="b">
            <input type="number" min="0" max="255" v-model="b">
        </p>
    </div>
    
</template>
<script>
    export default {
        data(){
            return {
                r : 100,
                g : 100,
                b : 100
            }
        }
    }
</script>
<style>
    .box{
        width: 200px;
        height: 200px;
    }
</style>

2.2购物车

<template>
    <div>
        <table>
            <tr>
                <th>号码</th>
                <th>东西</th>
                <th>价格</th>
                <th>数量</th>
                <th>小计</th>
            </tr>
            <tr v-for="item in carts">
                <td>{{item.id}}</td>
                <td>{{item.title}}</td>
                <td>{{item.price}}</td>
                <td>
                    <button @click="minus(item.id)">-</button>
                    <input  type="number" min="0" v-model="item.number">
                    <button @click="add(item.id)">+</button>
                </td>
                <td>{{item.number * item.price}}</td>
            </tr>
        </table>
        <h1>总价格:{{this.carts.reduce((a,b)=>a + b.price * b.number , 0)}}</h1>
    </div>
</template>
<script>
    export default {
        data(){
            return {
                carts : [
                    {"id" : 1 , "title" : "空调" , "price" : 5000, "number" : 1},
                    {"id" : 2 , "title" : "手机" , "price" : 3000, "number" : 1},
                    {"id" : 3 , "title" : "鼠标" , "price" : 200 , "number" : 1}
                ]
            }
        },
        methods : {
            add(id){
                //this.carts.filter(item=>item.id == id)[0].number++
                //vue不能识别数组某个项单独修改!!要改必须直接改变数组!!
                this.carts = this.carts.map(item=>item.id == id ? {...item , "number" : item.number +}: item);
            },
            minus(id){
                //this.carts.filter(item=>item.id == id)[0].number--
                this.carts = this.carts.map(item=>item.id == id ? {...item , "number" : item.number -}: item);
            }
        }
    }
</script>
<style>
     table,tr,td,th{border:1px solid red;}
     td{width:200px;height:60px;}
</style>

2.3选项卡

<style>
.box {
    width: 600px;height: 400px;
    margin: 10px auto;
    border: 1px solid #333;
        header {
        ul {
            overflow: hidden;
            li {
                float: left;
                width: 33.333%;
                height: 40px;
                line-height: 40px;
                text-align: center;
            }
            li.cur {
                background: red; color: #fff;
            }
        }
    }
}
</style>
<template>
    <div class="box">
        <header>
            <ul>
                <li v-for="(item,index) in tabnav" :class="{cur:item.click}" @click="changetab(dex)">{{item.title}}</li>
            </ul>
        </header>
        <div class="content">
            <div v-show="tabindex == 0">
                新闻新闻新闻新闻新闻
            </div>
            <div v-show="tabindex == 1">
                军事军事军事军事军事
            </div>
            <div v-show="tabindex == 2">
                图片图片图片图片图片
            </div>
        </div>
    </div>
</template>
<script>
export default {
    data() {
        return {
            tabnav: [
                {title: "新闻", click: true},
                {title: "军事", click: false},
                {title: "图片", click: false}
            ],
            tabindex: 0
        }
    },
    methods:{
      changetab(index){
          // 遍历tabnav数组 进行循环 去掉所有类
          this.tabnav.foreach(function(item){
              item.click = false
          });
          // 点击的那个tab 加类
          this.tabnav[index].click = true
          // 改变索引
          this.tabindex = index
      }
    }
}
</script>

2.4三级联动

<template>
    <div>
        <select v-model="sheng">
            <option v-for="item in info" :value="item.name">
                {{item.name}}
            </option>
        </select>

        <select v-model="shi">
            <option 
                v-for="item in info.filter(i=>i.name == sheng)[0].city" 
                :value="item.name"
            >
                {{item.name}}
            </option>
        </select>

         <select v-model="xian">
            <option 
v-for="item in info.filter(i=>i.name==sheng)[0].city.filter(i=>i.name==shi)[0].area" 
                :value="item"
            >
                {{item}}
            </option>
        </select>
        <h1>你的地址{{sheng}}{{shi}}{{xian}}</h1>
    </div>
</template>
<script>
    import info from "./info.js"; //引入全国省市数据
    export default {
        data(){
            return {
                info ,
                sheng:"广东省",
                shi : "广州市",
                xian: "花都区"
            }
        },
        watch : {
            sheng(){
                //当data中的sheng变化的时候,触发
              this.shi = info.filter(i=>i.name == this.sheng)[0].city[0].name;
              this.xian=info.filter(i=>i.name==this.sheng)[0].city.filter(i=>i.name==this.shi)[0].area[0]
            }
        }
    }
</script>

简化:

<template>
    <div>
         <!-- <select v-model="sheng" @change="changesheng($event)"> -->
         <select v-model="sheng">
             <option v-for="item in info" :value="item.name">
                 {{item.name}}
             </option>
         </select>

         <select v-model="shi">
             <option v-for="item in allshi()" :value="item.name">
                 {{item.name}}
             </option>
         </select>

         <select v-model="xian">
             <option v-for="item in allxian()" :value="item">
                 {{item}}
             </option>
         </select>
    </div>
</template>
<script>
    import info from "../lib/info.js";
    export default {
        data(){
            return {
                info,
                sheng:"广东省",
                shi:"广州市",
                xian:"天河区"
            }
        },
        methods:{
            // changesheng(e){
            //     this.shi = info.filter(i=>i.name == this.sheng)[0].city[0].name;
            //     this.xian = info.filter(i=>i.name == this.sheng)[0].city.filter(i=>i.name == this.shi)[0].area[0];
            // }
            allshi(){
                return info.filter(i=>i.name == this.sheng)[0].city;
            },
            allxian(){
                return info.filter(i=>i.name == this.sheng)[0].city.filter(i=>i.name == this.shi)[0].area
            }
        },
        watch:{
            //当data中的sheng变化的时候,触发这个函数
            sheng(){
                this.shi = info.filter(i=>i.name == this.sheng)[0].city[0].name;
                this.xian = info.filter(i=>i.name == this.sheng)[0].city.filter(i=>i.name == this.shi)[0].area[0];
            }
        }
    }
</script>

三、props(★)

使用 prop 传递数据

组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。父组件的数据需要通过 prop 才能下发到子组件中。

动态 prop

与绑定到任何普通的 html属性相类似,可以用v-bind来动态地将 prop 绑定到父组件的数据。每当父组件的数据变化时,该变化也会传导给子组件

 

1) 组件的使用:子组件不能直接改变父组件传入的值,必须调用父组件的函数来改变(引用类型值能直接改)

2) 组件的思维:所有子组件不需要对其它兄弟组件负责,只对父组件负责即可。


 

3.1传基本类型值

如果想让父组件data中的数据传递给子组件,需要使用标签属性传递(如果是动态值用v-bind

子组件需要使用props接收父组件的值,如果父组件中修改a值,同时会影响子组件。

 

app.vue父组件

<template>
    <div>
        <haha a="8"></haha>
        <haha :b="a"></haha>
    </div>
</template>
<script>
    import haha from "./components/haha.vue";
    export default {
        data(){
            return {
                a : 100
            }
        },
        components : {
            haha
        }
    }
</script>

 

haha.vue子组件

<template>
    <div>
        <h1>我是子组件{{a}} {{b}}</h1>
    </div>
</template>
<script>
    export default {
        props : ["a","b"],  //接收父组件传过来的属性的值
        data(){
            return {
           a : 100   //从父组件接收有a,这里就不能有同名的a了
            }
        }
    }
</script>

3.2子组件要改props必须调用父亲传的函数

子组件不能直接改变父组件传入的值,必须调用父组件的函数来改变

传值就要传它的改变函数给子组件,本质上还是调用父亲的函数去改变。

 

app.vue父组件传值:

<template>
    <div>
        <h1>父组件{{a}}</h1>
        <button @click="add">父组件按钮+</button>
        <button @click="minus">父组件按钮-</button>

        <haha :a="a" :add="add" :minus="minus"></haha>
    </div>
</template>
<script>
    import haha from "./components/haha.vue";
    export default {
        data(){
            return {
                a : 100
            }
        },
        components : {
            haha
        },
        methods : {
            add(){
                this.a++;
            },
            minus(){
                this.a--;
            }
        }
    }
</script>

 

haha.vue子组件接收

<template>
    <div>
        <h1>我是子组件{{a}}</h1>
        <button @click="add">子组件按钮+</button>
        <button @click="minus">子组件按钮-</button>
    </div>
</template>
<script>
    export default {
        //子组件不能直接改父组件的数据,要改就必须传父组件的函数来改变
        props : ["a","add","minus"],  //接收父组件传过来的属性的值
        data(){
            return {

            }
        }
    }
</script>

前端笔记之Vue(二)组件&案例&props&计算属性


3.3传引用类型值可以直接改

如果传入引用类型值,子组件是可以直接改父组件的值,而不报错的。

 

app.vue父组件

<template>
    <div>
        <h1>父组件{{obj.a}}</h1>
        <haha :obj="obj"></haha>
        <button @click="add">父组件按钮+</button>
    </div>
</template>
<script>
    import haha from "./components/haha.vue";
    export default {
        data(){
            return {
                obj : {
                    a : 100
                }
            }
        },
        components : {
            haha
        },
        methods : {
            add(){
                this.obj.a++;
            }
        }
    }
</script>

 

haha.vue子组件

<template>
    <div>
        <h1>我是子组件{{obj.a}}</h1>
        <button @click="add">子组件按钮+</button>
    </div>
</template>
<script>
    export default {
        // 子组件不能直接改父组件的数据,要改就必须传父组件的函数来改变
        props : ["obj"],  //接收父组件传过来的属性的值
        methods:{
            add(){
                this.obj.a++
            }
        }
    }
</script>

l 单向数据流

prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是反过来不会。这是为了防止子组件无意间修改了父组件的状态,来避免应用的数据流变得难以理解。

另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop。如果你这么做了,vue 会在控制台给出警告。

 前端笔记之Vue(二)组件&案例&props&计算属性


3.4字面量语法 vs 动态语法

使用v-bind是动态语法,不使用就是字符串

app.vue父组件:

<div>
    <haha a="a"></haha>
</div>
<script>
import haha from "./components/haha.vue"
export default {
    data() {
        return {
            a: 100
        }
    }
    components:{
        haha
    }
}
</script>

 

haha.vue子组件:

<template>
    <div> 
        <h1>{{typeof a}}</h1> 
    </div>
</template>
<script>
    export default{
        props:["a"]
    }
</script>

这样传递得到的是字符串的 "a"

 

初学者常犯的一个错误是使用字面量语法传递数值:

因为它是一个字面量 prop,它的值是字符串 "a" 而不是一个数值。如果想传递一个真正的 javascript 数值,则需要使用 v-bind,从而让它的值被当作 javascript 表达式计算。


3.5组件化思维-调色板案例

用组件重新做昨天的调色板和购物车案例。

v-model可以绑定一个引用类型值。

如果props是一个引用类型值,而不是基本类型值,此时v-model可以直接绑定修改,由vue内部实现对父组件的更改。

app.vue父组件

<template>
    <div>
        <div class="box" :style="{background:`rgb(${color.r},${color.g},${color.b})`}"></div>
        <!-- <bar :v="r" name="r"></bar>
        <bar :v="g" name="g"></bar>
        <bar :v="b" name="b"></bar> -->
        <bar :color="color" name="r"></bar>
        <bar :color="color" name="g"></bar>
        <bar :color="color" name="b"></bar>
    </div>
</template>
<script>
    import bar from "./components/bar.vue";
    export default{
        data(){
            return {
                // 这里为什么要封装一个对象
                // 因为vue有一个机制,如果子组件的v-model与父组件传入的引用类型值绑定
                // 会自动帮你改父组件的值,而不会报错。
                color : {
                    r : 100,
                    g : 100,
                    b : 100
                }
            }
        }
        components : {
            bar
        }
    }
</script>
<style>
    .box{
        width: 200px;
        height: 200px;
    }
</style>

 

bar.vue子组件

<template>
    <div>
        <input type="range" max="255" v-model="color[name]">
        <input type="number" max="255" v-model="color[name]">
    </div>
</template>
<script>
    export default{
        props:["color","name"],
        data(){
            return {

            }
        }
    }
</script>

3.6组件化思维-购物车案例

is属性解释:

前端笔记之Vue(二)组件&案例&props&计算属性

前端笔记之Vue(二)组件&案例&props&计算属性

说白了,就是让它显示哪个组件。

bar.vue子组件:

<template>
    <tr>
        <td>{{item.id}}</td>
        <td>{{item.title}}</td>
        <td>{{item.price}}</td>
        <td>
            <button @click="minus">-</button>
            <input type="number" min="0" v-model.number="item.number">
            <button @click="add">+</button>
        </td>
        <td>
            {{item.number * item.price}}
        </td>
    </tr>
</template>
<script>
    export default {
        props : ["item"],
        data(){
            return {

            }
        },
        methods : {
            add(){
                // 可以直接改,因为父亲传入引用类型值
                this.item.number++
            },
            minus(){
                if(this.item.number <= 0 ) return;
                this.item.number--
            }
        }
    }
</script>

 

app.vue父组件

<template>
    <div>
        <table>
            <tr>
                 <th>编号</th>
                 <th>商品</th>
                 <th>价格</th>
                 <th>数量</th>
                 <th>小计</th>
            </tr>
            <!-- <bar v-for="item in carts" :item="item"></bar> -->
            <tr is="bar" v-for="item in carts" :item="item"></tr>
        </table>
        <h1>总价格:{{this.carts.reduce((a,b)=>a + b.price * b.number , 0)}}</h1>
    </div>
</template>
<script>
    import bar from "./components/bar.vue";
    export default {
        data(){
            return {
                carts : [
                    {"id":1,"title":"空调", "price":5000, "number":1},
                    {"id":2,"title":"手机", "price":4999, "number":1},
                    {"id":3,"title":"电脑", "price":6000, "number":1},
                    {"id":4,"title":"冰箱", "price":8000, "number":1}
                ]
            }
        },
        components : {
            bar
        }
    }
</script>

3.7组件化思维-所有子组件不需要对其它兄弟组件负责

组件的思维:所有子组件不需要对其它兄弟组件负责,只对父组件负责即可。

教室:黑板、桌椅、侧面宣传栏、后黑板

只对父组件负责,不对其他组件负责!!!

 前端笔记之Vue(二)组件&案例&props&计算属性

app.vue父组件:

<template>
    <div>
        <heiban class="heiban" :banzhang="banzhang"></heiban>
        <zhuoyi :students="students" :changebanzhang="changebanzhang"></zhuoyi>
        <xuanchuanlan class="xuanchuanlan" :banzhang="banzhang"></xuanchuanlan>
        <houheiban class="houheiban" :banzhang="banzhang"></houheiban>
    </div>
</template>
<script>
    import zhuoyi from "./components/zhuoyi.vue";
    import houheiban from "./components/houheiban.vue";
    import xuanchuanlan from "./components/xuanchuanlan.vue";
    import heiban from "./components/heiban.vue";
    export default {
        data(){
            return {
                students : ["小明","小红","小强","小黑","小刚"],
                banzhang : "小明"
            }
        },
        components : { //注册组件
            heiban,zhuoyi,xuanchuanlan,houheiban
        },
        methods : {
            changebanzhang(banzhang){
                this.banzhang = banzhang; //改变班长
            }
        }
    }
</script>
<style>
    .heiban, .houheiban{ width:650px; height:100px; border:1px solid #000; }
    .zhuoyi{ float:left; width:400px;height:300px; border:1px solid #000; }
    .xuanchuanlan{float:left; width:250px;height:300px; border:1px solid #000; }
</style>

 

heiban.vuehouheiban.vuexuanchuanlan.vue子组件,都一样:

<template>
    <div>
        我是黑板。我班班长:{{banzhang}}
    </div>
</template>
<script>
    export default {
        props : ["banzhang"]
    }
</script>

 

zhuoyi.vue子组件:

<template>
    <div>
        <ul>
            <li v-for="item in students">
                {{item}}
                <button @click="changebanzhang(item)">成为班长</button>
            </li>
        </ul>
    </div>
</template>
<script>
    export default {
        props : ["students" , "changebanzhang"]
    }
</script>
<style>
    li{float:left;width:100px;height:100px;margin:10px;background:orange;}
</style>

四、计算属性(computed )★

4.1 computed属性的使用

我们现在export default(){ }中只写了:data(){}methodscomponentspropswatch

现在学习computed,表示计算后的值,做实验看看什么意思。

注意:{{}}中如果放computed的值,是没有圆括号的,虽然它是函数。

 前端笔记之Vue(二)组件&案例&props&计算属性

计算属性的结果会被缓存,除非依赖的属性(dataprops中的值)变化才会重新计算。

data/props中对应数据发生改变时,计算属性的值也会发生改变。

 

如果想触发computed的函数,必须在页面中使用,哪怕是display:none

computed就是返回一些和dataprops相关的值,dataprops的值变化时能够自动触发。

computed就是计算最多的一个地方,这里算法很重。


4.2关于methodscomputed的区别

https://www.cnblogs.com/coderl/p/7506957.html

l computed就是要监听哪些数据变化时要用到的。当监听的数据发生变化时,立刻会执行计算,并返回结果。

l methods只是定义函数的。如要执行,还得自己手动执行,来动态当作方法来用的。

 

l 相同:两者达到的效果是同样的。

l 不同:

1.最明显的不同,就是调用的时候,methods要加上()

2.可以使用methods替代computed,效果上两个都是一样的,但是 computed 是基于它的依赖缓存,只有相关依赖发生改变时才会重新取值。只要相关依赖未改变,只会返回之前的结果,不再执行函数。

而使用 methods,在重新渲染时,函数总会重新调用执行。

可以说使用 computed 性能会更好,但是如果你不希望缓存,你可以使用 methods 属性。

computed 属性默认只有 getter ,不过在需要时你也可以提供一个 setter :所以其实computed也是可以传参的。

 

<template>
    <div>
        <h1>{{a}}</h1>
        <h2>{{pingfang}}</h2>
        <button @click="add">+</button>
    </div>
</template>
<script>
    export default {
        data(){
            return {
                a:100
            }
        },
        computed:{
            pingfang(){
                return this.a * this.a
            }
        },
        methods:{
            add(){
                this.a++
            }
        }
    }
</script>
<style></style>