为了账号安全,请及时绑定邮箱和手机立即绑定

Vue.js的点点滴滴(四)

https://img1.sycdn.imooc.com//5ce7da4f0001678719201200.jpg

一、Vue组件

一个Vue项目是由很多个组件构成的。

Vue组件分为全局组件和局部组件。

组件是可以复用的Vue实例,且带有一个名字,它与new Vue({})接收相同的选项,比如data,computed,watch,methods以及生命周期钩子等,除了el。

组件可以复用,每个相同的组件相互独立,各自独立维护自己的数据,你每用一次组件,就会有一个它的新实例被创建。

一个组件的data选项必须是一个函数,每个实例可以维护一份被返回对象的独立的拷贝。


通常一个应用会以一棵嵌套的组件树的形式来组织。

为了能在模板中使用,这些组件必须先注册以便Vue能够识别。

在注册一个组件的时候,必须给组件一个名字;


在Vue里 ,每一个Vue组件,都是一个Vue实例  【实例=组件】;

Vue实例有的功能,Vue组件都拥有。

每个组件都是vue的实例;

每个实例的组成部分:

props、template、data、methods...

每一个组件都有一个template模板,如果没有写template,默认为挂载点下面的所有DOM标签作为模板。


组件的三个特点:独立性,可复用性,完整性


组件化问题:

1、组件状态管理(vuex)

2、多组件的混合使用、多页面复杂业务(vue-router)

3、组件之间的传参、消息、事件管理(props,emit/on,bus)


为什么要组件化:

1.     实现功能模块的复用

2.     高效执行

3.     开发单页面复杂应用

二、全局组件:

通过Vue.component定义的组件是全局组件, 可以在任何地方使用

Vue.component('todo-item', {
    props: ['content'], //这个组件接收从外部传进来的叫 content的属性
     template: '<li>{{content}}</li>'
});

全局组件的组件名,就是Vue.component的第一个参数。

定义全局组件名的方式有两种:

第一种:Vue.component('my-component-name', { /* ... */ })  组件名使用短横线-进行连接;

第二种:Vue.component('MyComponentName', { /* ... */ })   组件名首字母大写;


子组件通过props选项来接收父组件传递的参数,比如:

Vue.component('blog-post', {
  props: ['title'],
  template: '<h3>{{ title }}</h3>'
})


记住:全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生。

全局组件在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。

实例代码1:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue组件</title>
<script type="text/javascript" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="./vue.js"></script>
</head>
<body>
<div id="components-demo">
<!--组件多次复用-->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script type="text/javascript">
//自定义全局组件
Vue.component('button-counter', {
data: function(){
return{
count: 0
}
},
template: '<button v-on:click="count++">你点击了{{count}}次</button>'
})
new Vue({
el: "#components-demo"
})
</script>
</body>
</html>


实例代码2:


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue组件</title>
<script type="text/javascript" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="./vue.js"></script>
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="blog">
<div v-bind:style="{fontSize: postFontSize + 'em'}">
<!-- 使用v-bind来动态传递prop -->
<blog-post v-for="post in posts" v-bind:key="post.id" v-bind:post="post" v-on:enlarge-text="onEnlargeText">
 
</blog-post>
</div>
</div>
<script type="text/javascript">
Vue.component('blog-post', {
props: ['post'],
template: '<div class="blog-post">\
<h3>{{post.title}}</h3>\
<div v-html="post.content"></div>\
</div>'
})
new Vue({
el: "#blog",
data: {
posts: [
               {id: 1, title: "雨霖铃-柳永", content: "杨柳岸,晓风残月。此去经年,应是良辰好景虚设。便纵有千种风情,更与何人说?"},
               {id: 2, title: "将近酒-李白", content: "岑夫子,丹丘生,将进酒,杯莫停。"},
               {id: 3, title: "虞美人-李煜", content: "剪不断,理还乱,是离愁,别是一般滋味在心头。"}
],
postFontSize: 1
},
methods: {
onEnlargeText: function(enlargeAmount){
this.postFontSize += enlargeAmount
}
}
})
</script>
</body>
</html>


实例代码3:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>vue - 全局组件</title>
<script type="text/javascript" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="./vue.js"></script>
</head>
<body>
<div id="todo-list-example">
<form v-on:submit.prevent="addNewTodo">
<label for="new-todo">Add a todo</label>
<input v-model="newTodoText" id="new-todo" placeholder="todo list">
<button>Add Todo</button>
</form>
<ul>
<li is="todo-item" 
    v-for="(todo,index) in todos" 
    v-bind:key="todo.id" 
    v-bind:title="todo.title" 
    v-on:remove="todos.splice(index, 1)">
    
</li>
</ul>
</div>
<script type="text/javascript">
//定义全局组件
Vue.component('todo-item', {
       template: '\
    <li>\
      {{ title }}\
      <button v-on:click="$emit(\'remove\')">Remove</button>\
    </li>\
  ',
         props: ['title']
})
new Vue({
el: '#todo-list-example',
data: {
newTodoText: '',
todos: [
               {id: 1, title: '不思量'},
               {id: 2, title: '自难忘'},
               {id: 3, title: '千里孤坟'},
               {id: 4, title: '无处话凄凉'}
],
nextTodoId: 5
},
methods: {  //方法
addNewTodo: function(){   //添加新的Todo
this.todos.push({
id: this.nextTodoId++,
title: this.newTodoText
})
this.newTodoText = ''   //添加之后清空文本框
}
}
})
</script>
</body>
</html>


实例代码4:


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue-TodoList</title>
<script type="text/javascript" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="./vue.js"></script>
<style type="text/css">
</style>
</head>
<body>
<!--
父组件通过属性的形式向子组件传递数据;
子组件通过发布订阅模式向父组件传递数据;子组件发布一个事件,父组件通过监听(订阅)这个事件,就可以实现子组件向父组件传递数据。
-->
<div id="root">
<div>
<input type="text" name="name" v-model="inputValue" style="width: 250px;height: 30px;border: solid;">
<button v-on:click="handleSubmit">提交</button>
<ul>
<!-- <li v-for="(item, index) of list" :key="index" style="color: blue;font-size: 30px;">{{item}}</li> -->
<!-- 使用组件 -->
    <todo-item v-for="(item, index) of list" 
               :key="index" 
               :content="item" 
               :index="index"
               v-on:delete="handleDelete"
               style="color: blue;font-size: 30px;">
   
    </todo-item> 
</ul>
</div>
</div>
<script type="text/javascript">
//定义全局组件
Vue.component('todo-item', {
props: ['content','index'],
template: '<li @click="handleClick">{{content}}{{index}}</li>',
methods: {
handleClick: function(){
//删除内容
this.$emit('delete', this.index)
}
}
})
new Vue({
el: "#root",  //挂载点
data: {   //数据
inputValue: '',
list: []
},
methods: {  //事件
handleSubmit: function(){
this.list.push(this.inputValue)   //添加内容
this.inputValue = ''  //内容提交之后,恢复为空
},
handleDelete: function(index){
console.log(index)
                this.list.splice(index, 1)
}
}
})
</script>
</body>
</html>


三、局部组件

局部组件必须在new Vue()内通过components注册声明

var TodoItem = { template: '<li>item</li>' }
new Vue({
    el: '#root',
    data: {
        list: []
    },
    components: { 'todo-item': TodoItem } //注册局部组件
});


对于 components 对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。

注意:局部注册的组件不能在子组件中使用;

如果你希望局部组件ComponentA在子组件ComponentB中可以用,你可以这样写:

var ComponentA = { /* ... */ }
var ComponentB = {
  components: {
    'component-a': ComponentA
  },
  // ...
}

实例代码:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue-TodoList</title>
<script type="text/javascript" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="./vue.js"></script>
<style type="text/css">
</style>
</head>
<body>
<!--
v-model: 实现双向数据绑定
v-for: 实现循环列表
v-on: 实现事件绑定
this.list.push(): 向列表中添加内容数据
-->
<div id="root">
<div>
<input type="text" name="name" v-model="inputValue" style="width: 250px;height: 30px;border: solid;">
<button v-on:click="handleSubmit">提交</button>
<ul>
<!-- <li v-for="(item, index) of list" :key="index" style="color: blue;font-size: 30px;">{{item}}</li> -->
<!-- 使用组件 -->
    <todo-item v-for="(item, index) of list" :key="index" :content="item" style="color: blue;font-size: 30px;">
   
    </todo-item> 
</ul>
</div>
</div>
<script type="text/javascript">
//定义全局组件
//Vue.component('todo-item', {
//template: '<li>item</li>'
//})
//定义局部组件
var TodoItem = {
template: '<li>item</li>'
}

new Vue({
el: "#root",  //挂载点
data: {   //数据
inputValue: '',
list: []
},
methods: {  //事件
handleSubmit: function(){
this.list.push(this.inputValue)   //添加内容
this.inputValue = ''  //内容提交之后,恢复为空
}
},
components: {   //局部组件注册
'todo-item' : TodoItem
}
})
</script>
</body>
</html>


四、组件属性——props

在这里以下面的组件为例:

Vue.component('blog-post', {
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})

使用组件:

<blog-post post-title="hello!"></blog-post>


一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。

通常情况下,prop是以字符串数组的形式列出的,如下:

props: ['title', 'likes', 'isPublished', 'commentIds', 'author']

我们也可以以对象形式列出prop,如下:

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}

给prop赋值:

(1)给prop传入一个静态值:

<blog-post title="Vue.js的点点滴滴"></blog-post>


(2)给prop传入一个变量的值

<blog-post v-bind:title="post.title"></blog-post>


(3)给prop传入一个复杂的表达式

<blog-post v-bind:title="post.title + ' by ' + post.author.name"></blog-post>


(4)给prop传入一个数字

<!-- 即便 `42` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:likes="42"></blog-post>


<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:likes="post.likes"></blog-post>


(5)给prop传入一个布尔值

<!-- 包含该 prop 没有值的情况在内,都意味着 `true`。-->
<blog-post is-published></blog-post>


<!-- 即便 `false` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:is-published="false"></blog-post>


<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:is-published="post.isPublished"></blog-post>


(6)给prop传入一个数组

<!-- 即便数组是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>


<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:comment-ids="post.commentIds"></blog-post>


(7)给prop传入一个对象

<!-- 即便对象是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post
  v-bind:author="{
    name: '刘豆豆',
    company: '云天之上科技有限公司'
  }"
></blog-post>


<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:author="post.author"></blog-post>


(8)给prop传入一个对象的所有属性

我们可以通过不带参数的v-bind指令将一个对象的所有属性传给prop

比如:对象user包含如下信息:

user: {
id: 1,
name: "刘豆豆",
sex: "男",
age: 18,
content: "天国虽美,没有了你,万杯觥筹只不过是提醒寂寞罢了"
}


直接传入:<blog-post v-bind="user"></blog-post>


所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定,也就是父组件的 prop的更新会向下流动到子组件中,但是反过来则不行。

换句话说:父组件与子组件之间的数组传输是单向的,只能是父组件传递给子组件,当父组件的prop更新时,子组件中所有的prop都会被刷新为最新的值。


注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。


在实际的开发过程中,我们需要对props中的键值对进行验证,为了定制prop的验证方式,我们可以为props中的值提供一个带有验证需求的对象,而不是一个字符串数组,比如:

Vue.component('my-component', {
  props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})

如果验证失败,会在控制台给出警告信息。


prop中的type对应的类型有:String, Number, Boolean, Array, Object, Date, Function, Symbol, 也可以是一个自定义的构造函数,我们可以使用instanceof类进行检查确认。

比如:

//自定义构造函数
function User(id, name){
this.id = id;
this.name = name;
}
//在组件中使用
Vue.component('blog-post',{
props: {
   author: User
}
})


通常情况下,组件可以接受任意的特性,这些接受到的特性会被添加到这个组件的根元素上。为什么会这样呢?因为开发组件库的作者并不知道你会把这个组件用在什么场景下。一个非 prop 特性是指传向一个组件,但是该组件并没有相应 prop 定义的特性。

比如说:

在Bootstrap插件中使用<bootstrap-date-input>组件,而这个插件需要在其 <input> 上用到一个 data-date-picker 特性。我们可以将这个特性添加到你的组件实例上,如下:

<bootstrap-date-input data-date-picker="activated"></bootstrap-date-input>

这个 data-date-picker="activated" 特性就会自动添加到 <bootstrap-date-input> 的根元素上。


在这里,如果你不希望组件的根元素继承特性,你可以在组件中设置inheritAttrs: false,来禁用特性继承。

Vue.component('my-component', {
  inheritAttrs: false,
  // ...
})


注意 inheritAttrs: false 选项不会影响 style 和 class 的绑定。


五、动态组件:

在一个Vue项目中,会有很多个组件,我们可以使用v-bind:is特性来切换不同的组件,如下:

<component v-bind:is="currentTabComponent"></component>

每次切换组件的时候,Vue都会创建一个新的组件实例。

通常情况下,我们更希望在组件实例第一次创建的时候,被及时缓存下来。为了解决这个问题,我们可以使用<keep-alive>元素将一个动态组件包裹起来,已达到缓存的目的。如下:

<!-- 失活的组件将会被缓存!-->
<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>

全局组件和局部组件都可以使用<keep-alive>元素包裹,以达缓存目的。


六、异步组件:

在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。

比如:

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回调传递组件定义
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})

这个工厂函数会收到一个 resolve 回调,这个回调函数会在你从服务器得到组件定义的时候被调用。


一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用:

Vue.component('async-webpack-example', function (resolve) {
  // 这个特殊的 `require` 语法将会告诉 webpack
  // 自动将你的构建代码切割成多个包,这些包
  // 会通过 Ajax 请求加载
  require(['./my-async-component'], resolve)
})


你也可以在工厂函数中返回一个 Promise,所以把 webpack 2 和 ES2015 语法加在一起,我们可以写成这样:


Vue.component(
  'async-webpack-example',
  // 这个 `import` 函数会返回一个 `Promise` 对象。
  () => import('./my-async-component')
)

当使用局部注册的时候,你也可以直接提供一个返回 Promise 的函数:

new Vue({
  // ...
  components: {
    'my-component': () => import('./my-async-component')
  }
})


在Vue的2.3.0+ 的版本中,这里的异步组件工厂函数也可以返回一个如下格式的对象:

const AsyncComponent = () => ({
  // 需要加载的组件 (应该是一个 `Promise` 对象)
  component: import('./MyComponent.vue'),
  // 异步组件加载时使用的组件
  loading: LoadingComponent,
  // 加载失败时使用的组件
  error: ErrorComponent,
  // 展示加载时组件的延时时间。默认值是 200 (毫秒)
  delay: 200,
  // 如果提供了超时时间且组件加载也超时了,
  // 则使用加载失败时使用的组件。默认值是:`Infinity`
  timeout: 3000
})



七、自定义组件的v-model

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value 特性用于不同的目的。model 选项可以用来避免这样的冲突。如下:

Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})

<base-checkbox v-model="lovingVue"></base-checkbox>

这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的属性将会被更新。

以上就是Vue.js组件相关的知识点了,关于Vue.js的更多相关知识,本人还在继续学习中!

点击查看更多内容
5人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
1.5万
获赞与收藏
8507

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消