VUE之组件

1. 定义Vue组件

组件: 组件的出现,就是为了拆分Vue实例的代码量的,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件即可;
组件化和模块化的不同:

  • 模块化: 是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一;
  • 组件化: 是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用;

1.1 全局组件定义的四种方式

- extend + component
// 使用Vue.extend创建全局的Vue组件
var con1 = Vue.extend({
    template:'<h3>这是组件</h3>'  //指定组件要展示的html结构
})
// Vue.component('组件名称',创建出来的组件模板对象)
Vue.component('myCon1',con1)


//简写
Vue.component('myCon1',Vue.extend({
    template:'<h3>这是组件</h3>' 
}))

​ html结构:

​ 注意:驼峰命名规则

<!--  若: myCon1    -->
<my-con1></my-con1> 
<!-- 若: mycon-->
<mycon></mycon>
- Vue.component 方法
Vue.component('myCon2',{
    template:'<div><h3>这是组件</h3><span>123</span></div>' 
})
- 外部template标签
<div id="app"> 
    <my-con3></my-con3>
</div>
<!-- 在被控制的app外边,使用template元素定义组件的html模板结构 -->
<template id='tmp'> 
    <div>
        <h3>在外部定义组件结构</h3> 
    </div>
</template>

同时,需要使用 Vue.component 来定义组件:

Vue.component('myCon3',{
    template:'#tmp'
})

注意: 组件中的DOM结构,有且只能有唯一的根元素(Root Element)来进行包裹!

 template:'<h3>这是组件</h3><span>123</span>' 
// 报错:Component template should contain exactly one root element.
- 对象字面量
  • 页面结构
<div id="app">
    <!-- <my-con></my-con> -->
    <login></login>
</div>
  • vue实例定义
// 通过对象字面量的形式定义一个组件模板对象
var login = {
    template:'<h3>组件</h3>'
}
// Vue.component('myCon',login) //全局
 var vm = new Vue({         
    el:'#app',
    data:{},
    methods:{},
    components:{
        // 'myCon':login
        // 简写
        login  //等同于'login':login
    }
 })

1.2 定义局部子组件

  1. 组件实例定义方式:
<template id='tmp2'> 
    <div>
        <h3>这是tmp2</h3>
    </div>
</template>
var vm = new Vue({
   el:'#app2',
   data:{
   },
   methods:{
   },
   components:{
       myCon2:{
           template:'#tmp2'
       }
   }
   
})
  1. 引用组件:
<div id="app2"> 
    <my-con2></my-con2>
</div>

1.3 组件的数据和事件

  • 组件中的 data 除了必须为一个方法之外,这个方法内部,还必须返回一个对象才行;
  • 组件中 的data 数据,使用方式,和实例中的 data 使用方式完全一样
//<my-con4></my-con4>
Vue.component('myCon4',{
    template:'<h3 @click="login">这是组件---{{msg}}</h3>',
    data:function() {

        return {
            msg:'这是组件中的data定义的数据'
        }
    },
    methods:{
        login() {
            alert(this.msg)
        }
    }
    
})
  • 【重点】为什么组件中的data属性必须定义为一个方法并返回一个对象

方法 !== 方法 对象 === 对象 防止多个组件使用混乱

2. 组件切换

2.1 使用flag标识符

  • 页面结构:
<div id="app">
    <a href="javascript:;" @click.prevent="flag='login'">登录</a>
    <a href="javascript:;" @click.prevent="flag='register'">注册</a>
    <a href="javascript:;" @click.prevent="flag='about'">关于</a>
    <login v-if="flag == 'login'"></login>
    <register v-else-if="flag=='register'"></register>
    <about v-else="flag"></about>
</div>
  • Vue实例定义:
    <script>
        Vue.component('login',{
            template:'<h3>登录组件</h3>'
        })
        Vue.component('register',{
            template:'<h3>注册组件</h3>'
        })
		Vue.component('about',{
   			 template:'<h3>关于组件</h3>'
		})

         var vm = new Vue({
            el:'#app',
            data:{
               flag:'login'
            },
            methods:{},
         })

2.2 使用:is属性

Vue提供了 component ,来展示对应名称的组件

总结:当前学习了几个 Vue 提供的标签了???
​ component, template, transition, transitionGroup

  • 页面结构
<div id="app">
    <a href="" @click.prevent="comName='login'">登录</a>
    <a href="" @click.prevent="comName='register'">注册</a>
    <component :is="comName"></component>
  		<!-- component 是一个占位符, :is 属性,可以用来指定要展示的组件的名称 -->
</div>
  • Vue实例定义
Vue.component('login',{
    template:'<h3>登录</h3>'
})
Vue.component('register',{
    template:'<h3>注册</h3>'
})
var vm = new Vue({
    el:'#app',
    data:{
        comName:'login'
    }
})

3. 父子组件相传

3.1 父向子传值

  • 使用v-bind或简化指令,将数据传递到子组件中:
<div id="app">
    <mycon v-bind:pmsg="msg"></mycon>
</div>
  • Vue实例定义

    注意:

    • 一定要使用props属性来定义父组件传递过来的数据
    • 父组件的数据只读不可更改
var vm = new Vue({
    el:'#app',
    data:{
        msg:'这是父组件的数据'
    },
    components:{
        
        mycon:{
            data(){  //子组件的data属性必须定义为一个方法,并返回一个对象
                return {
                    child:'子组件数据'
                }
            },  
            template:'<h3 @click="change">这是子组件--{{pmsg}}--{{child}}</h3>',
            props:['pmsg'],
            methods:{
                change(){
                    this.child = '子组件数据被修改了',  //子组件可读可写
                    this.pmsg = '父的数据不能被修改'    //父数据只读不可写
           //报错:[Vue warn]: Avoid mutating a prop directly since the value will be overwritten 
                }
            }
        }
       
    }
})

3.2 父向子传方法

  • 自定义一个属性将父方法传递进去<con @pclick="show"></con>
  • 在子方法中拿到父方法this.$emit('pclick')
<body>
    <div id="app">
        <con @pclick="show"></con>
      	<!--自定义一个属性将父方法传递进去-->
    </div>

    <template id="tmp">
        <div>
            <h3>子组件</h3>
            <input type="button" value="子组件按钮,触发父组件方法" @click="myclick">
        </div>
    </template>
    <script>
        var con = {
            template:'#tmp',
            methods:{
                myclick(){
                    console.log('调用了子组件的方法')
                    this.$emit('pclick')  //拿到父组件传过来的pclick方法
                        // emit:发射 调用 触发
                }
            }
        }
         var vm = new Vue({
            el:'#app',
            data:{},
            methods:{
                show(){
                    console.log("调用了父组件的方法")
                }
            },
            components:{
                con
            }
         })
    </script>
</body>

3.3 子向父传值

  • 父组件将方法的引用,传递到子组件内部,子组件在内部调用父组件传递过来的方法,同时把要发送给父组件的数据,在调用方法的时候当作参数传递进去;
  • 在父组件方法中接收子数据show(data){this.parent=data}
        var con = {
            template:'#tmp',
            data(){
                return {
                    child:{name:'cc',age:16}
                }
            },
            methods:{
                myclick(){
                    this.$emit('pclick',this.child)  //拿到父组件传过来的pclick方法
                  //把要发送给父组件的数据,在调用方法的时候当作参数传递进去;
                }
            }
        }
         var vm = new Vue({
            el:'#app',
            data:{
                parent:null
            },
            methods:{
                show(data){
                  //data为子组件传递过来的数据
                    this.parent=data
                }
            },
            components:{
                con
            }
         })
  • 子组件通过父组件的方法修改父组件的属性
	function A() {
		this.name = "name1";
		this.changeName = (name) => {
			this.name = name;
		}
	}

	function B(func) {
		this.func = func;
		this.click = () => {
			this.func("name2");
		};
	}

	var a = new A();
	console.log(a);
		// {
		// 	changeName: (name) => { this.name = name; }
		// 	name: "name1"
		// }

	var b = new B(a.changeName);
	console.log(b);
		// {
			// func: (name) => { this.name = name; }
			// click: () => { this.func("name2"); }
		// }

	b.click();
	console.log(a);
		// {
		// 	changeName: (name) => { this.name = name; }
		// 	name: "name2"
		// }

评论列表案例

目标:主要练习父子组件之间传值

  • html结构
  <div id="app">
    <cms-box @func="loadContent"></cms-box>

    <ul class="list-group">
      <li v-for="(item,i) in list" :key="i" class="list-group-item" @click="del(i)">
          <span class="badge">评论人: {{ item.user }}</span>
          {{ item.comment }}
      </li>
    </ul>
  </div>

  <template id='cmsTmp'>
      <!-- 组件中的DOM结构有且仅有一个根目录 -->
    <div>
        <div class="form-group">
          <label>评论人:</label>
          <input type="text" class="form-control" v-model="user">
        </div>
  
        <div class="form-group">
          <label>评论内容:</label>
          <textarea class="form-control" v-model="comment"></textarea>
        </div>
  
        <div class="form-group">
          <input type="button" value="发表评论" class="btn btn-primary" @click="postCmts">
        </div>
    </div>
  </template>
- 父向子传方法
    var cmsBox = {
      template:'#cmsTmp',
      data(){
        return {
          user:'',
          comment:'',
        }
      },
      methods:{
        postCmts(){

          var content = {id:Date.now(),user:this.user,comment:this.comment}
          // 获取之前的数据转成数组形式
          var list = JSON.parse(localStorage.getItem('content') || '[]')
              // JSON.parse('[]')  //[]
          //将新数据插入到数组中
          list.unshift(content)
          //将数组转成字符串添加到localStorage中
          localStorage.setItem('content',JSON.stringify(list))
          
          // 清空输入框
          this.user = this.comment = ''
          this.$emit('func')  //拿到父元素的方法
            // <cms-box @func="loadContent"></cms-box>
        }
      }
    }
    var vm = new Vue({
      el:"#app",
      data:{
        list:[
          {id:Date.now(),user:'李白',comment:'不及汪伦送我情'},
          {id:Date.now(),user:'杜甫',comment:'商女不知亡国恨'},
          {id:Date.now(),user:'骆宾王',comment:'曲项向天歌'}
        ]
      },
      methods:{
        loadContent(){
          // 从localStorage中加载评论列表
          var list = JSON.parse(localStorage.getItem('content') || '[]')
          this.list = list
        }
      },
      created(){
        // 调用加载评论列表方法
        this.loadContent()
      },
      components:{
        cmsBox
      }
    })
- 使用钩子函数
    var cmsBox = {
      template:'#cmsTmp',
      data(){
        return {
          user:'',
          comment:'',
        }
      },
      methods:{
        postCmts(){
          this.$emit('func',this.user,this.comment)  
          //<cms-box @func="loadContent"></cms-box>
          this.user = this.comment = ''
        }
      }
    }
    var vm = new Vue({
      el:"#app",
      data:{
        list:[
          {id:Date.now(),user:'李白',comment:'不及汪伦送我情'},
          {id:Date.now(),user:'杜甫',comment:'商女不知亡国恨'},
          {id:Date.now(),user:'骆宾王',comment:'曲项向天歌'}
        ]
      },
      created(){
        // 获取localStroage中的content
        this.list = JSON.parse(localStorage.getItem('content') || '[]')
                    
        console.log(localStorage.getItem('content'))
      },
      beforeUpdate(){
        // 将list保存到localStorage中
        localStorage.setItem('content',JSON.stringify(this.list))
      },
      
      methods:{
        loadContent(user,comment){
          // 将数据添加到list中
          this.list.unshift({id:Date.now(),user:user,comment:comment})
        },
        del(i) {
          this.list.splice(i, 1);
        }
      },
      components:{
        cmsBox
      }
    })

4. this.$refs获取元素和组件

ref 是 英文单词 【reference】 值类型 和 引用类型 referenceError

<body>
    <div id="app">
        <input type="button" value="获取元素" @click="getElement">
        <h3 ref="myh3">哈啊哈哈哈哈</h3>

        <login ref="mylogin"></login>
    </div>
    <script>
        var login = {
            template:'<h3>登录组件</h3>',
            data(){
                return {
                    son:'子组件数据'
                }
            },
            methods:{
                show(){
                    console.log('调用了子组件方法')
                }
            }
        }

         var vm = new Vue({
            el:'#app',
            data:{ },
            methods:{
                getElement(){   
                    console.log(this.$refs.myh3.innerText) //哈啊哈哈哈哈
                    console.log(this.$refs.mylogin.son)  //子组件数据
                    this.$refs.mylogin.show()  //子组件数据
                }
            },
            components:{
                login
            }
         })

    </script>
</body>

5. 路由

  1. 对于普通的网站,所有的超链接都是URL地址,所有的URL地址都对应服务器上对应的资源;
  2. 对于单页面应用程序来说,主要通过URL中的hash(#号)来实现不同页面之间的切换,同时,hash有一个特点:HTTP请求中不会包含hash相关的内容;所以,单页面程序中的页面跳转主要用hash实现;
  3. 在单页面应用程序中,这种通过hash改变来切换页面的方式,称作前端路由(区别于后端路由);

5.1 在 vue 中使用 vue-router

  1. 导入 vue-router 组件类库:vue-router-3.0.1.js

  2. 使用 router-link 组件来导航

  3. 使用 router-view 组件来显示匹配到的组件

  4. 创建组件

  5. 创建一个路由 router 实例,通过 routers 属性来定义路由匹配规则

  6. 使用 router 属性来使用路由规则

<body>
    <div id="app">
        <!-- 2. 使用router-link组件来导航 -->
        <router-link to="/login">登录</router-link>
        <router-link to="/register">注册</router-link>

        <!-- 3. 使用router-view组件来显示匹配到的组件 -->
        <router-view></router-view>
    </div>

    <script>
        // 4. 创建组件
        var login = {
            template:'<h3>登录组件</h3>'
        };
        var register = {
            template:'<h3>注册组件</h3>'
        }
        // 5. 创建一个路由router实例
        var router = new VueRouter({
            // 通过routes属性来定义路由匹配规则
            routes:[
                {path:'/',redirect:'/login'},
                    // redirect重定向,与node中的重定向不一回事
                {path:'/login',component:login},   
                    // path:监听哪个路由连接地址
                    // component:必须是一个组件模板对象,不能是组件的引用名称
                {path:'/register',component:register},
            ],
            linkActiveClass:'myclass'  //修改默认激活类 
        })
        var vm = new Vue({
            el:'#app',
            data:{},
            //  6 使用router属性来使用路由规则
            router,  //将路由规则对象,注册到VM实例上,用来监听url地址的变化,然后展示对应的组件
            
        })
    </script>
</body>

5.2 在路由规则中定义参数

- query

to="/login?name=cc&age=16"则 路由规则不需修改

{{this.$route.query.name}}

- params

to="/register/cc/16"path:'/register/:name/:age'

{{this.$route.params.name}}

<body>
    <div id="app">
        <router-link to="/login?name=cc&age=16">登录</router-link>
        <router-link to="/register/cc/16">注册</router-link>
        <router-view></router-view>
    </div>
    
    <script>
        var login = {
            template:'<h3>登录组件 --{{this.$route.query.name}}:{{$route.query.age}}</h3>',
                // this可省略
            created(){
                // console.log(this.$route)
                console.log(this.$route.query)  //{name: "cc", age: "16"}
            }
        }  
        var register = {
            template:'<h3>注册组件--{{this.$route.params.name}}:{{$route.params.age}}</h3>',
            created(){
                console.log(this.$route.params)  //{name: "cc", age: "16"}
            }
        }  
        var router = new VueRouter({
            routes:[
                {path:'/login',component:login},  
                    //若 to="/login?name=cc&age=16" 则 路由规则不需修改
                {path:'/register/:name/:age',component:register},
                    // 若 to="/register/cc/16"  则 path:'/register/:name/:age'
            ]
        })
        var vm = new Vue({
            el:'#app',
            router
        })
    </script>
</body>

5.2 路由嵌套child

<body>
    <div id="app">
        <router-link to="/account">Account</router-link>
        <router-view></router-view>  <!--展示account组件-->
    </div>

    <template id="tmp">
        <div>
            <h3>这是account组件</h3>
            <router-link to="/account/login">登录</router-link>
            <router-link to="/account/register">注册</router-link>
            <router-view></router-view>  <!--展示login或register组件-->
        </div>
    </template>
    <script>

        var account = {
            template:'#tmp',
        }
        var login = {
            template:'<h3>登录</h3>'
        }
        var register = {
            template:'<h3>注册</h3>'
        }
    
        var router = new VueRouter({
            routes:[
//1 
                // {path:'/account',component:account},
                // {path:'/account/login',component:login},
                // {path:'/account/register',component:register}
                    // 页面跳转了,不符合嵌套关系
//2     
                {
                    path:'/account',
                    component:account,
                    children:[
                        {path:'login',component:login},
                        {path:'register',component:register}
                      		//子路由的 path 前面不要带 / ,否则永远以根路径开始请求
                    ]
                }
            ]
        })
         var vm = new Vue({
            el:'#app',
            data:{},
            methods:{},
            router
         })

    </script>
</body>

5.3 命名视图实现经典布局

  • name属性指定要展示的组件 :<router-view name="left"></router-view>
<body>
  <div id="app">
    <router-view></router-view>
    <router-view name="left"></router-view> 
    <router-view name="main"></router-view>
        <!-- name属性指定要展示的组件 -->
  </div>

  <script>
    var header = {
      template: '<h1 class="header">Header头部区域</h1>'
    }
    var leftBox = {
      template: '<h1 class="left">Left侧边栏区域</h1>'
    }
    var mainBox = {
      template: '<h1 class="main">mainBox主体区域</h1>'
    }
    // 创建路由对象
    var router = new VueRouter({
      routes: [
        {path:'/',components:{
            'default':header,
            'left':leftBox,
            'main':mainBox
            // 属性可以不加引号
        }}
      ]
    })
    var vm = new Vue({
      el: '#app',
      data: {},
      methods: {},
      router
    });
  </script>
</body>
  • CSS 样式:
<style>
    html,
    body,
    h1 {
      margin: 0;
      padding: 0;
    }
    .header {
        background-color: skyblue;
        width: 100%;
        height: 100px;
        line-height: 100px;
    }
    .container {
        display:flex;
        height: 800px;
    }
    .left {
        flex: 1;
        background-color: greenyellow;
    }
    .main {
        flex:3;
        background-color: yellow;
    }
  </style>

6. 名称案例

需求: 两个文本框的内容改变,则全名的文本框中的值也跟着改变

6.1 method方法

<body>
    <div id="app">
        <input type="text" v-model="firstname" @keyup="getfullname">
        <input type="text" v-model="lastname" @keyup="getfullname">
        <input type="text" v-model="fullname">
    </div>

    <script>
         var vm = new Vue({
            el:'#app',
            data:{
                firstname:'',
                lastname:'',
                fullname:'',
            },
            methods:{
                getfullname(){
                    this.fullname = this.firstname + '-' + this.lastname
                }
            }
         })

    </script>
</body>

6.2 watch属性

- 监听data中属性的改变
<body>
    <div id="app">
        <input type="text" v-model="firstname">+
        <input type="text" v-model="lastname">=
        <input type="text" v-model="fullname">
    </div>

    <script>
         var vm = new Vue({
            el:'#app',
            data:{
                firstname:'',
                lastname:'',
                fullname:'',
            },
            methods:{},
            watch:{   
      //watch:可以监视 data 中指定数据的变化,然后触发这个 watch 中对应function 处理函数
                // 作为属性名: firt-name 必须加引号 first可不加引号
                firstname:function(nVal,oVal){
                  	// 第一个参数是新数据,第二个参数是旧数据
                    // console.log(oVal,nVal)
                    this.fullname = nVal + '-' + this.lastname
                },
                lastname:function(nVal) {
                    this.fullname = this.firstname + '-' + nVal
                }
            }
         })

    </script>
</body>
- 监听路由对象的改变
<body>
    <div id="app">
        <router-link to="/login">登录</router-link>
        <router-link to="/register">注册</router-link>
        <!-- <login></login>
        <register></register> -->
        <router-view></router-view>
    </div>

    <script>
        var login ={
            template:'<h3>登录组件</h3>'
        }
        var register ={
            template:'<h3>注册组件</h3>'
        }

//  创建一个路由对象
        var router = new VueRouter({
        routes: [ // 路由规则数组
            { path: '/', redirect: '/login' },
            { path: '/login', component: login },
            { path: '/register', component: register }
        ],
        linkActiveClass: 'myactive' // 和激活相关的类
        })



         var vm = new Vue({
            el:'#app',
            data:{
                
            },
            methods:{

            },
            router,
            watch:{
                '$route.path':function(nVal,oVal){
                    if(nVal === '/login') {
                        console.log('登录页面')
                    } else if(nVal === '/register') {
                        console.log('注册页面')
                    }
                }
            }
         })

    </script>
</body>

6.3 computed计算属性

  • 注意: 计算属性,在引用的时候不要加()去掉用,直接把他当做普通属性去使用computed:{}

  • 只要计算属性function内部中所用的任何data中的数据发生了变化,都会重新计算这个计算属性的值

  • 计算属性的求值结果会被缓存,方便下次直接使用,若数据未发生变化,则不会重新计算只打印一次ok

    <body>
      <div id="app">
          <input type="text" v-model="firstname">+
          <input type="text" v-model="lastname">=
          <input type="text" v-model="fullname">
    
          <p> {{fullname}}  </p>
          <p> {{fullname}}  </p>
          <p> {{fullname}}  </p>
          <p> {{fullname}}  </p>  
        <!-- 只打印一次 ok -->
      </div>
    
      <script>
           var vm = new Vue({
              el:'#app',
              data:{
                  firstname:'',
                  lastname:'',
              },
              methods:{},
              computed:{
                  fullname:function(){
                      console.log('ok')
                      return this.firstname + '-' + this.lastname
                  }
              }
           })
    
      </script>
    </body>
    

- 默认只有`getter`的计算属性:

​```html
<div id="app">
    <input type="text" v-model="firstName"> +
    <input type="text" v-model="lastName"> =
    <span>{{fullName}}</span>
  </div>

  <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
      el: '#app',
      data: {
        firstName: 'jack',
        lastName: 'chen'
      },
      methods: {},
      computed: { // 计算属性; 特点:当计算属性中所以来的任何一个 data 属性改变之后,都会重新触发 本计算属性 的重新计算,从而更新 fullName 的值
        fullName() {
          return this.firstName + ' - ' + this.lastName;
        }
      }
    });
  </script>
  • 定义有gettersetter的计算属性:
<div id="app">
    <input type="text" v-model="firstName">
    <input type="text" v-model="lastName">
    <!-- 点击按钮重新为 计算属性 fullName 赋值 -->
    <input type="button" value="修改fullName" @click="changeName">

    <span>{{fullName}}</span>
  </div>

  <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
      el: '#app',
      data: {
        firstName: 'jack',
        lastName: 'chen'
      },
      methods: {
        changeName() {
          this.fullName = 'TOM - chen2';
        }
      },
      computed: {
        fullName: {
          get: function () {
            return this.firstName + ' - ' + this.lastName;
          },
          set: function (newVal) {
            var parts = newVal.split(' - ');
            this.firstName = parts[0];
            this.lastName = parts[1];
          }
        }
      }
    });
  </script>

6.4 watch,computed,methods对比

  1. computed属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。主要当作属性来使用;需要return
  2. methods方法表示一个具体的操作,主要书写业务逻辑;
  3. watch一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;可以看作是computedmethods的结合体;

相关文件

  1. URL中的hash(井号)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值