04 Vue组件 - 图1


组件

1 fetch和axios

axios与fetch实现数据请求
(1)fetch(不是所有浏览器都支持,谷歌浏览器支持)
XMLHttpRequest 是一个设计粗糙的 API,配置和调用方式非常混乱,而且基于事件的异步模型写起来不友好。 兼容性不好
polyfill: https://github.com/camsong/fetch-ie8

1.1 fetche使用


  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>fetch</title>
  6. <script src="js/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="box">
  10. <button @click="handleClick()">获取影片信息</button>
  11. <ul>
  12. <li v-for="data in datalist">
  13. <h3>{{data.name}}</h3>
  14. <img :src="data.poster"/>
  15. </li>
  16. </ul>
  17. </div>
  18. <script type="text/javascript">
  19. new Vue({
  20. el: "#box",
  21. data: {
  22. datalist: []
  23. },
  24. methods: {
  25. handleClick() {
  26. //https://m.maizuo.com/v5/?co=mzmovie#/films/nowPlaying
  27. fetch("./json/test.json").then(res => res.json()).then(res => {
  28. console.log(res.data.films)
  29. this.datalist = res.data.films
  30. })
  31. }
  32. }
  33. })
  34. /*
  35. // post-1
  36. fetch("**",{
  37. method:'post',
  38. headers: {
  39. "Content‐Type": "application/x‐www‐form‐urlencoded"
  40. },
  41. body: "name=kerwin&age=100",
  42. credentials:"include"
  43. }).then(res=>res.json()).then(res=>{console.log(res)});
  44. // post-2
  45. fetch("**",{
  46. method:'post',
  47. headers: {
  48. "Content‐Type": "application/json"
  49. },
  50. body: JSON.stringify({
  51. myname:"kerwin",
  52. myage:100
  53. })
  54. }).then(res=>res.json()).then(res=>{console.log(res)});
  55. */
  56. </script>
  57. </body>
  58. </html>

1.2 axios的使用


  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>axios</title>
  6. <script type="text/javascript" src="js/vue.js"></script>
  7. <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  8. </head>
  9. <body>
  10. <div id="box">
  11. <button @click="handleClick()">正在热映</button>
  12. <ul>
  13. <li v-for="data in datalist">
  14. <h3>{{data.name}}</h3>
  15. <img :src="data.poster"/>
  16. </li>
  17. </ul>
  18. </div>
  19. <script type="text/javascript">
  20. new Vue({
  21. el:"#box",
  22. data:{
  23. datalist:[]
  24. },
  25. methods:{
  26. handleClick(){
  27. axios.get("./json/test.json").then(res=>{
  28. console.log(res.data.data.films) // axios 自动包装data属性 res.data
  29. this.datalist = res.data.data.films
  30. }).catch(err=>{
  31. console.log(err);
  32. })
  33. }
  34. }
  35. })
  36. </script>
  37. </body>
  38. </html>

2 计算属性

  1. 复杂逻辑,模板难以维护
  2. (1) 基础例子
  3. (2) 计算缓存 VS methods-计算属性是基于它们的依赖进行缓存的。-计算属性只有在它的相关依赖发生改变时才会重新求值
  4. (3) 计算属性 VS watch
  5. - v-model3

2.1 通过计算属性实现名字首字母大写


  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <script src="js/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="box">
  10. <!--大段的代码写在这里不好,使用计算属性-->
  11. {{mytext.substring(0,1).toUpperCase()+mytext.substring(1)}}
  12. <p>计算属性:{{getname}}</p>
  13. <!--普通方法要加括号-->
  14. <p>普通方法:{{getNameMethod()}}</p>
  15. <!--区别是在同一个页面中使用多次计算属性,不会多次执行-->
  16. </div>
  17. </body>
  18. <script>
  19. var vm = new Vue({
  20. el: '#box',
  21. data: {
  22. mytext:'lqz',
  23. },
  24. computed:{
  25. getname(){//依赖的状态改变了,会重新计算
  26. console.log('计算属性')
  27. return this.mytext.substring(0,1).toUpperCase()+this.mytext.substring(1)
  28. }
  29. },
  30. methods:{
  31. getNameMethod(){
  32. console.log('普通方法')
  33. return this.mytext.substring(0,1).toUpperCase()+this.mytext.substring(1)
  34. }
  35. }
  36. })
  37. </script>
  38. </html>

2.2 通过计算属性重写过滤案例


  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <script src="js/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="box">
  10. <p><input type="text" v-model="mytext" @input="handleChange"></p>
  11. <ul>
  12. <li v-for="data in newlist">{{data}}</li>
  13. </ul>
  14. </div>
  15. </body>
  16. <script>
  17. var vm = new Vue({
  18. el: '#box',
  19. data: {
  20. mytext: '',
  21. datalist: ['aaa', 'abc', 'abcde', 'abcdef', 'bbb', 'bac'],
  22. },
  23. computed: {
  24. newlist() {
  25. var newlist = this.datalist.filter(item => {
  26. return item.indexOf(this.mytext) > -1
  27. })
  28. return newlist
  29. },
  30. },
  31. })
  32. </script>
  33. </html>

4 虚拟dom与diff算法 key的作用

4.1 Vue2.0 v-for 中 :key 有什么用呢?


  1. 其实呢不只是vue,react中在执行列表渲染时也会要求给每个组件添加key这个属性。
  2. key简单点来说就是唯一标识,就像ID一样唯一性
  3. 要知道,vue和react都实现了一套虚拟DOM,使我们可以不直接操作DOM元素,只操作数据便可以重新渲染页面。而隐藏在背后的原理便是其高效的Diff算法。
  4. 只做同层级的对比
  5. 按照key值比较,出现新的key就插入
  6. 通组件对比

4.2 虚拟DOM的diff算法


04 Vue组件 - 图2
04 Vue组件 - 图3

4.3 具体实现


4.3.1 把树按照层级分解

04 Vue组件 - 图4

4.3.2 同key值比较

04 Vue组件 - 图5

4.3.3 通组件对比

04 Vue组件 - 图6

  1. <div id="box">
  2. <div v-if="isShow">111</div>
  3. <p v-else>222</p>
  4. <!--
  5. {tag:div,value:111}
  6. {tag:p,value:222}
  7. 直接不比较,直接删除div,新增p
  8. -->
  9. <div v-if="isShow">111</div>
  10. <div v-else>222</div>
  11. <!--
  12. {tag:div,value:111}
  13. {tag:div,value:222}
  14. 比较都是div,只替换文本内容
  15. -->
  16. </div>

https://segmentfault.com/a/1190000020170310

5 组件化开发基础

5.1 组件是什么?有什么用


  1. 扩展 HTML 元素,封装可重用的代码,目的是复用
  2. -例如:有一个轮播,可以在很多页面中使用,一个轮播有js,css,html
  3. -组件把js,css,html放到一起,有逻辑,有样式,有html

6 组件注册方式

  1. 1 全局组件
  2. Vue.component
  3. 2 局部组件

6.1 定义全局组件,绑定事件,编写样式


  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <script src="js/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="box">
  10. <navbar></navbar>
  11. </div>
  12. </body>
  13. <script>
  14. //没有代码提示,语法检查,目前这么用
  15. //后面会使用webpack打包,直接定义成 xx.vue文件,通过webpack打包
  16. Vue.component('navbar',{
  17. template:`
  18. <div>
  19. <button @click="handleClick">返回</button>
  20. 我是NavBar
  21. <button style="background: red">主页</button>
  22. </div>
  23. `,
  24. methods:{
  25. handleClick(){
  26. console.log('nav nav')
  27. }
  28. }
  29. })
  30. var vm = new Vue({
  31. el: '#box',
  32. data: {
  33. },
  34. })
  35. </script>
  36. </html>

6.2 定义局部组件


  1. Vue.component('navbar', {
  2. template: `
  3. <div>
  4. <button @click="handleClick">返回</button>
  5. 我是NavBar
  6. <button style="background: red">主页</button>
  7. <br>
  8. <child></child>
  9. </div>
  10. `,
  11. methods: {
  12. handleClick() {
  13. console.log('nav nav')
  14. },
  15. },
  16. components: {
  17. child: {
  18. template: `<button>儿子</button>`,
  19. }
  20. }
  21. })

7 组件编写方式与Vue实例的区别

  1. 1 自定义组件需要有一个root element,一般包裹在一个div中
  2. 2 父子组件的data是无法共享
  3. 3 组件可以有data,methods,computed....,但是data 必须是一个函数
  1. Vue.component('navbar', {
  2. template: `
  3. <div>
  4. <button @click="handleClick">返回</button>
  5. 我是NavBar{{aa}}
  6. <button style="background: red">主页</button>
  7. <br>
  8. <child></child>
  9. </div>
  10. `,
  11. methods: {
  12. handleClick() {
  13. console.log('nav nav')
  14. },
  15. },
  16. components: {
  17. child: {
  18. template: `<button>儿子</button>`,
  19. }
  20. },
  21. data(){
  22. return {
  23. aa:'lqz'
  24. }
  25. },
  26. })

8 组件通信

  1. 1 父子组件传值 (props down, events up)
  2. 2 父传子之属性验证props:{name:Number}Number,String,Boolean,Array,Object,Function,null(不限制类型)
  3. 3 事件机制a.使用 $on(eventName) 监听事件b.使用 $emit(eventName) 触发事件
  4. 4 Ref<input ref="mytext"/> this.$refs.mytext
  5. 5 事件总线var bus = new Vue();* mounted生命周期中进行监听

8.1 父子通信之父传子


  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <script src="js/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="box">
  10. <!--保证属性名和props中的属性名和变量名一致即可-->
  11. <navbar myname="lqz"></navbar>
  12. <navbar myname="egon"></navbar>
  13. <!--注意数据绑定-->
  14. <navbar :myname="egon"></navbar>
  15. <!--可以传多个,但是注意,传入的isshow是字符串,可以使用数据绑定变成布尔-->
  16. <navbar :myname="egon" isshow="false"></navbar>
  17. <navbar :myname="egon" :isshow="false"></navbar>
  18. </div>
  19. </body>
  20. <script>
  21. //没有代码提示,语法检查,目前这么用
  22. //后面会使用webpack打包,直接定义成 xx.vue文件,通过webpack打包
  23. Vue.component('navbar', {
  24. template: `
  25. <div>
  26. <button>返回</button>
  27. 父组件传递的内容是:{{myname}}
  28. <button>主页</button>
  29. <br>
  30. </div>
  31. `,
  32. props:['myname']
  33. })
  34. var vm = new Vue({
  35. el: '#box',
  36. data: {},
  37. })
  38. </script>
  39. </html>

属性验证

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <script src="js/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="box">
  10. <navbar myname="egon" :isshow="false"></navbar>
  11. <!--报错-->
  12. <navbar myname="egon" isshow="false"></navbar>
  13. </div>
  14. </body>
  15. <script>
  16. Vue.component('navbar', {
  17. template: `
  18. <div>
  19. <button>返回</button>
  20. 父组件传递的内容是:{{myname}}
  21. 传入的布尔是{{isshow}}
  22. <button>主页</button>
  23. <br>
  24. </div>
  25. `,
  26. // props:['myname'],
  27. props:{
  28. myname:String,
  29. isshow:Boolean,
  30. },
  31. })
  32. var vm = new Vue({
  33. el: '#box',
  34. data: {},
  35. })
  36. </script>
  37. </html>

8.2 父子通信之子传父(通过事件)


  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <script src="js/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="box">
  10. 子组件中监听自定义事件,随便起名
  11. <!-- <navbar @myevent="handleClick"></navbar>-->
  12. <navbar @myevent="handleClick($event)"></navbar>
  13. </div>
  14. </body>
  15. <script>
  16. Vue.component('navbar', {
  17. template: `
  18. <div>
  19. <button>返回</button>
  20. 组件
  21. <button @click="handleEvent">点击按钮把子组件数据传递到父组件</button>
  22. <br>
  23. </div>
  24. `,
  25. data(){
  26. return {
  27. name:'lqz'
  28. }
  29. },
  30. methods:{
  31. handleEvent(){
  32. // this.$emit('myevent') //myevent:子组件中监听自定义事件
  33. this.$emit('myevent',100) //100表示传递的参数
  34. }
  35. }
  36. })
  37. var vm = new Vue({
  38. el: '#box',
  39. data: {},
  40. methods:{
  41. handleClick(ev){
  42. console.log('点击子组件,我会执行')
  43. console.log(ev)
  44. }
  45. }
  46. })
  47. </script>
  48. </html>

8.3 通过子传父控制字组件显示隐藏


  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <script src="js/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="box">
  10. 普通方式
  11. <button @click="isShow=!isShow">点击隐藏显示</button>
  12. <navbar v-show="isShow"></navbar>
  13. <hr>
  14. 字传父方式
  15. <mybutton @myevent="handleShow"></mybutton>
  16. <navbar v-show="isShow"></navbar>
  17. </div>
  18. </body>
  19. <script>
  20. Vue.component('mybutton', {
  21. template: `
  22. <div>
  23. <button @click="handleClick">点我隐藏显示</button>
  24. </div>
  25. `,
  26. methods: {
  27. handleClick() {
  28. this.$emit('myevent')
  29. }
  30. }
  31. })
  32. Vue.component('navbar', {
  33. template: `
  34. <div>
  35. <ul>
  36. <li>111</li>
  37. <li>222</li>
  38. <li>333</li>
  39. </ul>
  40. </div>
  41. `,
  42. data() {
  43. return {
  44. name: 'lqz'
  45. }
  46. },
  47. methods: {
  48. handleEvent() {
  49. // this.$emit('myevent') //myevent:子组件中监听自定义事件
  50. this.$emit('myevent', 100) //100表示传递的参数
  51. }
  52. }
  53. })
  54. var vm = new Vue({
  55. el: '#box',
  56. data: {
  57. isShow: true
  58. },
  59. methods: {
  60. handleShow() {
  61. this.isShow=!this.isShow
  62. }
  63. }
  64. })
  65. </script>
  66. </html>

8.4 ref属性


  1. ref放在标签上,拿到的是原生节点
  2. ref放在组件上,拿到的是组件对象,
  3. 通过这种方式实现子传父(this.$refs.mychild.text)
  4. 通过这种方式实现父传子(调用子组件方法传参数)
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <script src="js/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="box">
  10. <!-- 通过ref,获取input的值-->
  11. <input type="text" ref="mytext">
  12. <button @click="handleClick">点我</button>
  13. <child ref="mychild"></child>
  14. </div>
  15. </body>
  16. <script>
  17. Vue.component('child',{
  18. template:`<div>child</div>`,
  19. data(){
  20. return {
  21. text:'子组件数据'
  22. }
  23. },
  24. methods:{
  25. add(){
  26. console.log('子组件的add方法')
  27. }
  28. }
  29. })
  30. var vm = new Vue({
  31. el: '#box',
  32. data: {
  33. },
  34. methods: {
  35. handleClick() {
  36. console.log(this)
  37. //this.$refs.mytext 获取到input控件,取出value值
  38. console.log(this.$refs.mytext.value)
  39. console.log(this.$refs.mychild.text)
  40. // this.$refs.mychild.add()
  41. this.$refs.mychild.add('传递参数')
  42. }
  43. }
  44. })
  45. </script>
  46. </html>

8.5 事件总线(不同层级的不通组件通信)


  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <script src="js/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="box">
  10. <child1></child1>
  11. <child2></child2>
  12. </div>
  13. </body>
  14. <script>
  15. var bus=new Vue() //new一个vue的实例,就是中央事件总线
  16. Vue.component('child1', {
  17. template: `<div>
  18. <input type="text" ref="mytext">
  19. <button @click="handleClick">点我</button>
  20. </div>`,
  21. methods:{
  22. handleClick(){
  23. bus.$emit('suibian',this.$refs.mytext.value) //发布消息,名字跟订阅消息名一致
  24. }
  25. }
  26. })
  27. Vue.component('child2', {
  28. template: `<div>
  29. <div>收到的消息 {{msg}}</div>
  30. </div>`,
  31. data(){
  32. return {msg:''}
  33. },
  34. mounted(){
  35. //生命周期,当前组件dom创建完后悔执行
  36. console.log('当前组件dom创建完后悔执行')
  37. //订阅消息
  38. bus.$on('suibian',(item)=>{
  39. console.log('收到了',item)
  40. this.msg=item
  41. })
  42. }
  43. })
  44. var vm = new Vue({
  45. el: '#box',
  46. data: {},
  47. methods: {
  48. handleClick() {
  49. console.log(this)
  50. //this.$refs.mytext 获取到input控件,取出value值
  51. console.log(this.$refs.mytext.value)
  52. console.log(this.$refs.mychild.text)
  53. // this.$refs.mychild.add()
  54. this.$refs.mychild.add('传递参数')
  55. }
  56. }
  57. })
  58. </script>
  59. </html>

9 动态组件

  1. 1 <component> 元素,动态地绑定多个组件到它的 is 属性
  2. 2 <keep-alive> 保留状态,避免重新渲染

9.1 基本使用


  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <script src="js/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="box">
  10. <ul>
  11. <li><a @click="who='child1'">首页</a></li>
  12. <li><a @click="who='child2'">商品</a></li>
  13. <li><a @click="who='child3'">购物车</a></li>
  14. </ul>
  15. <component :is="who"></component>
  16. </div>
  17. </body>
  18. <script>
  19. var bus = new Vue() //new一个vue的实例,就是中央事件总线
  20. Vue.component('child1', {
  21. template: `<div>
  22. 首页
  23. </div>`,
  24. })
  25. Vue.component('child2', {
  26. template: `<div>
  27. 商品
  28. </div>`,
  29. })
  30. Vue.component('child3', {
  31. template: `<div>
  32. 购物车
  33. </div>`,
  34. })
  35. var vm = new Vue({
  36. el: '#box',
  37. data: {
  38. who:'child1'
  39. },
  40. })
  41. </script>
  42. </html>

9.2 keep-alive使用


  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <script src="js/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="box">
  10. <ul>
  11. <li><a @click="who='child1'">首页</a></li>
  12. <li><a @click="who='child2'">商品</a></li>
  13. <li><a @click="who='child3'">购物车</a></li>
  14. </ul>
  15. <keep-alive>
  16. <component :is="who"></component>
  17. </keep-alive>
  18. </div>
  19. </body>
  20. <script>
  21. var bus = new Vue() //new一个vue的实例,就是中央事件总线
  22. Vue.component('child1', {
  23. template: `<div>
  24. 首页
  25. </div>`,
  26. })
  27. Vue.component('child2', {
  28. template: `<div>
  29. 商品
  30. <input type="text">
  31. </div>`,
  32. })
  33. Vue.component('child3', {
  34. template: `<div>
  35. 购物车
  36. </div>`,
  37. })
  38. var vm = new Vue({
  39. el: '#box',
  40. data: {
  41. who:'child1'
  42. },
  43. })
  44. </script>
  45. </html>