前端笔记之Vue(二)组件&案例&props&计算属性
一、vue组件(.vue文件)
组件 (component) 是 vue.js 最强大的功能之一。组件可以扩展 html 元素,封装可重用的代码。在较高层面上,组件是自定义元素,vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 html 元素。
所有的 vue 组件同时也都是 vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子。
说白了,就是html、css、js行为的一个封装。
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的配置)官网找:
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>
此时看见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(★)
l 使用 prop 传递数据
组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。父组件的数据需要通过 prop 才能下发到子组件中。
l 动态 prop:
与绑定到任何普通的 html属性相类似,可以用v-bind来动态地将 prop 绑定到父组件的数据。每当父组件的数据变化时,该变化也会传导给子组件
1) 组件的使用:子组件不能直接改变父组件传入的值,必须调用父组件的函数来改变(引用类型值能直接改)
2) 组件的思维:所有子组件不需要对其它兄弟组件负责,只对父组件负责即可。
3.1传基本类型值
l 如果想让父组件data中的数据传递给子组件,需要使用标签属性传递(如果是动态值用v-bind)
l 子组件需要使用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必须调用父亲传的函数
l 子组件不能直接改变父组件传入的值,必须调用父组件的函数来改变
l 传值就要传它的改变函数给子组件,本质上还是调用父亲的函数去改变。
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>
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 会在控制台给出警告。
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组件化思维-调色板案例
用组件重新做昨天的调色板和购物车案例。
l 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属性解释:
说白了,就是让它显示哪个组件。
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组件化思维-所有子组件不需要对其它兄弟组件负责
组件的思维:所有子组件不需要对其它兄弟组件负责,只对父组件负责即可。
教室:黑板、桌椅、侧面宣传栏、后黑板
只对父组件负责,不对其他组件负责!!!
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.vue,houheiban.vue,xuanchuanlan.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(){}、methods、components、props、watch
现在学习computed,表示计算后的值,做实验看看什么意思。
注意:{{}}中如果放computed的值,是没有圆括号的,虽然它是函数。
计算属性的结果会被缓存,除非依赖的属性(data、props中的值)变化才会重新计算。
当data/props中对应数据发生改变时,计算属性的值也会发生改变。
如果想触发computed的函数,必须在页面中使用,哪怕是display:none了
computed就是返回一些和data、props相关的值,data或props的值变化时能够自动触发。
computed就是计算最多的一个地方,这里算法很重。
4.2关于methods和computed的区别
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>