原先结构:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <header id="header">这里是头部</header>
    <div id="content">这里是内容区域</div>
    <footer id="footer">这里是底部区域</footer>
  </div>
</body>
<script src="vue.js"></script>
<script>
  new Vue({
    el: '#app'
  })
</script>
</html>

抽离头部组件(可读性差):

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <!-- <header id="header">这里是头部</header> -->
    <my-header></my-header>
    <div id="content">这里是内容区域</div>
    <!-- <footer id="footer">这里是底部区域</footer> -->
    <my-footer />
  </div>
</body>
<script src="vue.js"></script>
<script>
  // 定义一个组件
  var Header = { // 组件的首字母大写,template 表示该组件的 模板,类型是字符串
    template: `<header id="header">这里是头部----组件</header>`
  }
  var Footer = { // 组件的首字母大写,template 表示该组件的 模板,类型是字符串
    template: `<footer id="footer">这里是底部区域 ----组件</footer>`
  }
  // 注册组件  ----  全局注册组件  ---- 一定是在new Vue之前
  // Vue.component('自定义的组件名', 定义的组件) --- 以标签的形式调用 组件名 即可
  Vue.component('my-header', Header) // <my-header></my-header>  <my-header />
  Vue.component('my-footer', Footer)
  new Vue({
    el: '#app'
  })
</script>
</html>

抽离组件的模板:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <my-header></my-header>
  </div>
</body>
<!-- script 表示的模板的方式  只需要了解即可 -->
<!-- <script type="text/x-template" id="header">
  <header class="header">
    <button>返回</button>
    搜索
  </header>
</script> -->
<!-- 使用template标签定义模板 是推荐使用的 -->
<template id="header">
  <header class="header">
    <button>返回</button>
    搜索
  </header>
</template>
<script src="vue.js"></script>
<script>
  const Header = {
    template: '#header'
  }


  Vue.component('my-header', Header)

  new Vue({
    el: '#app'
  })
</script>
</html>

组建的选项:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <my-header></my-header>
  </div>
</body>
<template id="header">
  <header class="header">
    <button @click="back">返回</button>
    <input type="text" v-model="msg">
    {{ msg }} - {{ msgTip }}
  </header>
</template>
<script src="vue.js"></script>
<script>
  // 组件是 特殊 的Vue实例
  const Header = {
    template: '#header',
    data () { // 组件中的初始化数据, data必须是一个函数,返回一个对象 ---- 特殊点
      return {
        msg: '搜索中.......'
      }
    },
    methods: {
      back () {
        console.log('你点击了返回按钮')
      }
    },
    computed: {
      msgTip () {
        if (this.msg === '') {
          return '请输入搜索内容'
        } else {
          return this.msg
        }
      }
    }
  }


  Vue.component('my-header', Header)

  new Vue({
    el: '#app',
    
  })
</script>
</html>

局部注册组件:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <my-header></my-header>
  </div>
</body>
<template id="header">
  <header class="header">
    <my-back></my-back>
    <input type="text" v-model="msg">
    {{ msg }} - {{ msgTip }}
  </header>
</template>
<template id="back">
  <button @click="back">返回</button>
</template>
<script src="vue.js"></script>
<script>
  const Back = {
    template: '#back',
    methods: {
      back () {
        console.log('你点击了返回按钮')
      }
    }
  }


  // 组件是 特殊 的Vue实例
  const Header = {
    template: '#header',
    data () { // 组件中的初始化数据, data必须是一个函数,返回一个对象 ---- 特殊点
      return {
        msg: '搜索中.......'
      }
    },
    components: {
      'my-back': Back    //在谁的局部注册,就只能在谁的内部使用
    },
    computed: {
      msgTip () {
        if (this.msg === '') {
          return '请输入搜索内容'
        } else {
          return this.msg
        }
      }
    }
  }

  // Vue.component('my-back', Back)
  new Vue({
    el: '#app',
    data: {
      msg: '搜索...'
    },
    components: {
      'my-header': Header  
    }
  })
</script>
</html>

父子组件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <my-list></my-list>
  </div>
</body>
<template id="list">
  <ul>
    <li>
      <img src="" alt="" style="display:block;width: 150px;height: 150px;border: 1px solid #f66">
      名称
    </li>
  </ul>
</template>
<script src="vue.js"></script>
<script>
  const List = {
    template: '#list'
  }
  // 全局注册组件 
  // Vue.component('my-list', List)
  new Vue({
    el: '#app',
    components: {
      'my-list': List
    }
  })
</script>
</html>

父组件给子组件传值1:

父组件在调用子组件的地方,添加一个自定义的属性,属性的值就是需要传递给子组件的值,如果需要传递的值是一个变量,或者是boolean,或者是number类型,需要使用绑定属性,在子组件定义的地方,添加一个选项 props,方式一 props的值为数组,元素为自定义的属性名

子组件的注册必须在父组件 ,或者直接使用全局注册组件
 app 为 父,header 为子
 header 为 父,back 为子

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <my-list :list="list" :num="list.length"></my-list>
  </div>
</body>
<template id="list">
  <ul>
    <li v-for="(item, index) of list" :key = "index">
      <img :src="item.img" alt="" style="display:block;width: 150px;height: 150px;border: 1px solid #f66">
      {{ item.title }}
    </li>
  </ul>
</template>
<script src="vue.js"></script>
<script>
  const List = {
    props: ['list'],
    template: '#list'
  }
  // 全局注册组件 
  // Vue.component('my-list', List)
  new Vue({
    el: '#app',
    data: {
      list: [{
        img: 'https://m.360buyimg.com/mobilecms/s750x750_jfs/t23491/230/1714105950/260598/44036572/5b680745N534fe714.jpg!q80.dpg.webp',
        title: '七匹狼中长款夹克男春季新款时尚立领外套休闲男装茄克衫 001(黑色) 175/92A(XL)'
      }, {
        img:'https://m.360buyimg.com/mobilecms/s750x750_jfs/t23386/9/1066712099/277967/615ccafb/5b4f0e3aN262237fc.jpg!q80.dpg.webp',
        title: '【官方AppleCare+版】Apple MacBook Pro 15.4英寸笔记本电脑 深空灰色 配备Touch Bar 2018新款(八代i5/16G)'
      }]
    },
    components: {
      'my-list': List
    }
  })
</script>
</html>

父组件给子组件传值2:

在子组件定义的地方,添加一个选项 props, 方式二 props的值是一个对象,key值为自定义的属性名,value值为 数据类型  ----  团队合作 提升代码的严谨性,如果类型不对,会有警告信息出现,但是不会阻止你的代码的渲染

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <my-list :list="list" :num="list.length"></my-list>
  </div>
</body>
<template id="list">
  <ul>
    <li v-for="(item, index) of list" :key = "index">
      <img :src="item.img" alt="" style="display:block;width: 150px;height: 150px;border: 1px solid #f66">
      {{ item.title }}
    </li>
  </ul>
</template>
<script src="vue.js"></script>
<script>
  const List = {
    props: {
      list: Number
    },
    template: '#list'
  }
  // 全局注册组件 
  // Vue.component('my-list', List)
  new Vue({
    el: '#app',
    data: {
      list: [{
        img: 'https://m.360buyimg.com/mobilecms/s750x750_jfs/t23491/230/1714105950/260598/44036572/5b680745N534fe714.jpg!q80.dpg.webp',
        title: '七匹狼中长款夹克男春季新款时尚立领外套休闲男装茄克衫 001(黑色) 175/92A(XL)'
      }, {
        img:'https://m.360buyimg.com/mobilecms/s750x750_jfs/t23386/9/1066712099/277967/615ccafb/5b4f0e3aN262237fc.jpg!q80.dpg.webp',
        title: '【官方AppleCare+版】Apple MacBook Pro 15.4英寸笔记本电脑 深空灰色 配备Touch Bar 2018新款(八代i5/16G)'
      }]
    },
    components: {
      'my-list': List
    }
  })
</script>
</html>

父组件给子组件传值3:

在子组件定义的地方,添加一个选项 props, 方式三 props的值是一个对象, key值是自定义的属性名,value值为一个对象,这个对象的key值分别为 type 和 default,表示数据类型和默认值,如果数据类型是 对象和 数组,默认值必须写为函数,其余直接赋值

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <my-list :list="list" :num="list.length"></my-list>
    <hr />
    <my-list></my-list>
  </div>
</body>
<template id="list">
  <ul>
    <li v-for="(item, index) of list" :key = "index">
      <img :src="item.img" alt="" style="display:block;width: 150px;height: 150px;border: 1px solid #f66">
      {{ item.title }}
    </li>
  </ul>
</template>
<script src="vue.js"></script>
<script>
  const List = {
    props: {
      list: {
        type: Array,
        default: function () {
          return [
            {
              img: '1',
              title: '您还没有传值哦'
            }
          ]
        }
      }
    },
    template: '#list'
  }
  // 全局注册组件 
  // Vue.component('my-list', List)
  new Vue({
    el: '#app',
    data: {
      list: [{
        img: 'https://m.360buyimg.com/mobilecms/s750x750_jfs/t23491/230/1714105950/260598/44036572/5b680745N534fe714.jpg!q80.dpg.webp',
        title: '七匹狼中长款夹克男春季新款时尚立领外套休闲男装茄克衫 001(黑色) 175/92A(XL)'
      }, {
        img:'https://m.360buyimg.com/mobilecms/s750x750_jfs/t23386/9/1066712099/277967/615ccafb/5b4f0e3aN262237fc.jpg!q80.dpg.webp',
        title: '【官方AppleCare+版】Apple MacBook Pro 15.4英寸笔记本电脑 深空灰色 配备Touch Bar 2018新款(八代i5/16G)'
      }]
    },
    components: {
      'my-list': List
    }
  })
</script>
</html>

子组件给父组件传值:

在父组件调用子组件的地方,给它绑定一个自定义的事件,事件的执行是由父组件执行,记住不要加()

在子组件定义的地方,在需要传值的函数内部,执行 this.$emit('自定义的事件名', '传递的值')

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <h1>这里是父组件</h1>
    <h2>子组件的值是: {{ msg }}</h2>
    <my-child @to-parent="getdata"></my-child>
  </div>
</body>
<template id="child">
  <div>
    <button @click="sendData">给父组件传值</button>
  </div>
</template>
<script src="vue.js"></script>
<script>
  const Child = {
    template: '#child',
    methods: {
      sendData () {
        this.$emit('to-parent', '啦啦啦啦啊啊啦')
      }
    }
  }
  new Vue({
    el: '#app',
    data: {
      msg: ''
    },
    components: {
      'my-child': Child
    },
    methods: {
      getdata (val) {
        this.msg = val
      }
    }
  })
</script>
</html>

非父子组件:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <my-content></my-content>
    <my-footer></my-footer>
  </div>
</body>
<template id="content">
  <div>
    内容
  </div>
</template>
<template id="footer">
  <ul>
    <li>首页</li>
    <li>分类</li>
    <li>购物车</li>
    <li>我的</li>
  </ul>
</template>
<script src="vue.js"></script>
<script>
  const Content = {
    template: '#content'
  } 
  const Footer = {
    template: '#footer'
  }

  new Vue({
    el: '#app',
    components: {
      'my-content': Content,
      'my-footer': Footer
    }
  })
</script>
</html>

非父子组件传值:

利用 中央事件总线 进行传值,利用new Vue()实例作为中央事件总线
const bus = new Vue()

在需要接收数据的组件内部,先监听某一个自定义的事件,接收传递过来的数据

在需要传递数据的组件内部的某一个函数内,去触发 一个 自定义的事件,发送传递的数据

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <my-content></my-content>
    <my-footer></my-footer>
  </div>
</body>
<template id="content">
  <div>
    内容 - {{ type }}
  </div>
</template>
<template id="footer">
  <ul>
    <li @click="changeContent('首页')">首页</li>
    <li @click="changeContent('分类')">分类</li>
    <li @click="changeContent('购物车')">购物车</li>
    <li @click="changeContent('我的')">我的</li>
  </ul>
</template>
<script src="vue.js"></script>
<script>
  const bus = new Vue()
  const Content = {
    template: '#content',
    data () {
      return {
        type: ''
      }
    },
    mounted () { // 用来监听点击的是哪一个
      bus.$on('footer-content', (val) => {
        this.type = val
      })
    }
  } 
  const Footer = {
    template: '#footer',
    methods: {
      changeContent (val) {
        bus.$emit('footer-content', val)
      }
    },
    mounted () {
      bus.$emit('footer-content', '首页')
    }
  }

  new Vue({
    el: '#app',
    components: {
      'my-content': Content,
      'my-footer': Footer
    }
  })
</script>
</html>

注册表单

使用 component 标签结合is属性,指明使用的是哪一个组件

 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <button @click="type='my-phone'">手机号注册</button>
    <button @click="type='my-email'">邮箱注册</button>
    <button @click="type='my-user'">用户名注册</button>
    <component :is="type"></component>
    <!-- <component is="my-user"></component> -->
    <!-- <my-phone></my-phone>
    <my-email></my-email>
    <my-user></my-user> -->
  </div>
</body>
<template id="phone">
  <div>
    <input type="text" placeholder="手机号">
  </div>
</template>
<template id="email">
  <div>
    <input type="text" placeholder="邮箱">
  </div>
</template>
<template id="user">
  <div>
    <input type="text" placeholder="用户名">
  </div>
</template>
<script src="vue.js"></script>
<script>
  const Phone = {
    template: '#phone'
  }
  const Email = {
    template: '#email'
  }
  const User = {
    template: '#user'
  }

  new Vue({
    el: '#app',
    data: {
      type: 'my-phone'
    },
    components: {
      'my-phone': Phone,
      'my-email': Email,
      'my-user': User
    }
  })
</script>
</html>

保留组件状态

使用 keep-alive 包裹 component 标签即可完成 保留组件的状态

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <button @click="type='my-phone'">手机号注册</button>
    <button @click="type='my-email'">邮箱注册</button>
    <button @click="type='my-user'">用户名注册</button>
    <keep-alive>
      <component :is="type"></component>
    </keep-alive>
  </div>
</body>
<template id="phone">
  <div>
    <input type="text" placeholder="手机号">
  </div>
</template>
<template id="email">
  <div>
    <input type="text" placeholder="邮箱">
  </div>
</template>
<template id="user">
  <div>
    <input type="text" placeholder="用户名">
  </div>
</template>
<script src="vue.js"></script>
<script>
  const Phone = {
    template: '#phone',
    mounted () {
      console.log('phone')
    }
  }
  const Email = {
    template: '#email',
    mounted () {
      console.log('email')
    }
  }
  const User = {
    template: '#user',
    mounted () {
      console.log('user')
    }
  }

  new Vue({
    el: '#app',
    data: {
      type: 'my-phone'
    },
    components: {
      'my-phone': Phone,
      'my-email': Email,
      'my-user': User
    }
  })
</script>
</html>

新增钩子函数

keep-alive 保留组件的状态,避免组件的重新渲染 --- 不会再次触发mounted钩子函数,如果想要了解到执行到哪一个选项,可以使用 activated 和 deactivated 钩子函数

使用了 keep-alive 才有钩子

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <button @click="type='my-phone'">手机号注册</button>
    <button @click="type='my-email'">邮箱注册</button>
    <button @click="type='my-user'">用户名注册</button>
    <keep-alive>
      <component :is="type"></component>
    </keep-alive>
  </div>
</body>
<template id="phone">
  <div>
    <input type="text" placeholder="手机号">
  </div>
</template>
<template id="email">
  <div>
    <input type="text" placeholder="邮箱">
  </div>
</template>
<template id="user">
  <div>
    <input type="text" placeholder="用户名">
  </div>
</template>
<script src="vue.js"></script>
<script>
  const Phone = {
    template: '#phone',
    mounted () {
      console.log('phone')
    },
    activated () {
      console.log('phone 被激活')
    },
    deactivated () {
      console.log('phone 被隐藏')
    }
  }
  const Email = {
    template: '#email',
    mounted () {
      console.log('email')
    },
    activated () {
      console.log('email 被激活')
    },
    deactivated () {
      console.log('email 被隐藏')
    }
  }
  const User = {
    template: '#user',
    mounted () {
      console.log('user')
    },
    activated () {
      console.log('user 被激活')
    },
    deactivated () {
      console.log('user 被隐藏')
    }
  }

  new Vue({
    el: '#app',
    data: {
      type: 'my-phone'
    },
    components: {
      'my-phone': Phone,
      'my-email': Email,
      'my-user': User
    }
  })
</script>
</html>

部分保留组件状态:

* 定义组件添加 name属性,keep-alive 添加include 属性,记住不要加空格

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <button @click="type='my-phone'">手机号注册</button>
    <button @click="type='my-email'">邮箱注册</button>
    <button @click="type='my-user'">用户名注册</button>
    <keep-alive include="phone,email">
      <component :is="type"></component>
    </keep-alive>
  </div>
</body>
<template id="phone">
  <div>
    <input type="text" placeholder="手机号">
  </div>
</template>
<template id="email">
  <div>
    <input type="text" placeholder="邮箱">
  </div>
</template>
<template id="user">
  <div>
    <input type="text" placeholder="用户名">
  </div>
</template>
<script src="vue.js"></script>
<script>
  const Phone = {
    name: 'phone',
    template: '#phone'
  }
  const Email = {
    name: 'email',
    template: '#email'
  }
  const User = {
    name: 'user',
    template: '#user'
  }

  new Vue({
    el: '#app',
    data: {
      type: 'my-phone'
    },
    components: {
      'my-phone': Phone,
      'my-email': Email,
      'my-user': User
    }
  })
</script>
</html>

VUE脚手架安装操作:

cnpm i @vue/cli@3 -g
> vue create myapp

* 选择 Manually select features ----- 自选预设文件

* 选择 vue 项目的 东西 除了typesctipt 不选,其余都选 --- 选择用空格
* 选择应用的历史模式 Y
* 选择样式文件 sass/scss (with node-sass)
* 代码的校验规则 ESLint + Standard config
* Lint on save 保存检验代码格式
* 测试的模式 Mocha + Chai
* 运行模式 Cypress (Chrome only)
* 配置文件在哪里 In package.json
* 是否保存预设文件 y (以后直接一开始就选择预设文件,后续不需要自动选择)

 

长风破浪会有时,直挂云帆济沧海