组件化的思想
- 模块化是一种思想,一种构建方式,把一种很复杂的事物拆分成一个一个的小模块,然后通过某种特定的方式把这些小模块组织到一起相互协作完成这个复杂的应用功能。
- 在 Vue 中,组件就是用来封装视图(HTML)的。组件思想就是把一个很大的复杂的 Web 页面视图拆分成一块一块的组件视图,然后利用某种特定的方式把他们组织到一起完成完整的 Web 应用构建。
- HTML 结构
- CSS 样式
- JavaScript 行为
- 为什么要把视图组件化,优势?
- 开发效率
- 可维护性
- 可重用性
- 便于分工
通过 Element 体会组件的威力
Element 是基于 Vue 开发的一个知名的第三方组件库,它能帮助我们更加快速的构建应用。
Element 官网
使用:
- 安装:`npm i element-ui -S``
- 引入 element-ui 组件库的 CSS 样式
- 导入 Vue
- 引入 element-ui 组件库的 JavaScript 脚本
- 调用 element-ui 组件
组件是什么
组件在代码中的直观体现就是封装了一个自定义的 HTML 标签
使用组件
组件的定义方式分为两种,全局定义和局部定义:
- 全局组件定义在全局,在任意组件中都能直接使用
- 局部组件定义在局部,只能在当前组件使用
- 建议把通用组件定义在全局,把不通用组件定义在局部
全局注册
注册:
基本形式:Vue.component('组件名字', 组件模板对象)
方式 一:
Vue.component('my-component',
Vue.extend({
template: '<div>A custom component!</div>'
})
)
new Vue({
el: '#app'
})
方式二:
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
new Vue({
el: '#app'
})
方式三:
<template id="tmpl">
<div>A custom component!</div>
</template>
<script>
Vue.component('my-component', {
template: '#tmpl'
})
new Vue({
el: '#app'
})
</script>
方式四:
<script type="text/x-template" id="tmpl">
<div>A custom component!</div>
</script>
<script>
Vue.component('my-component', {
template: '#tmpl'
})
var vm = new Vue({
el: '#app'
})
</script>
在模板中使用组件:
<div id="app">
<my-component></my-component>
</div>
渲染结果:
<div id="app">
<div>A custom component!</div>
</div>
局部注册
不必把每个组件都注册到全局。可以通过某个 Vue 实例/组件的实例选项 components
注册,仅作用在其作用域中可用的组件:
注册:
new Vue({
el: '#app'
components: {
// <my-component> 将只能在父组件模板中可用
// 键名就是组件名称,值是一个对象,对象中配置组件的选项
'my-component': {
template: '<div>A custom component!</div>'
}
}
})
使用:
<div id="app">
<my-component></my-component>
</div>
- 组件一般分为两种
- 通用的组件
- 不通用的业务组件
所以设计具体业务尽量定义成局部。不要污染全局
组件的模板
组件的模板 template 只能有有一个根元素,否则警告报错:
Component template should contain exactly one root element.
组件定义 template 可以是字面量字符串或是一个定义了字面量字符串的变量,缺点是没有高亮,内置在 JavaScript 中,写起来麻烦。
X-Templates:template 可以写在 script 标签中,解决了高亮的问题,但是当组件数量多的时候,也麻烦。
-
以上方式都不好,我们最终的解决方案是使用 Vue 的
.vue
单文件组件来写。但是要想使用这种方式必须结合 webpack 构建工具。
image.png
组件的复用
- 组件是可复用的 Vue 实例,可以有自己的 data、methods、computed、watch 等等选项
- 组件是独立的作用域,不能访问父组件的数据
- 组件的 data 必须是函数,函数中返回一个对象作为组件的 data。因此每个实例可以维护一份被返回对象的独立的拷贝。
data: function () {
return {
count: 0
}
}
组件的组织
-
采用组件化构建方式,一个应用被一个根组件管理起来,根组件中嵌套了子组件,子组件还可以嵌套自己的子子组件。
组件树
放到匿名自执行函数中,将某些代码包裹起来可以实现块级作用域的效果,减少全局变量的数量,减少命名冲突,在匿名自执行函数执行结束后变量就会被内存释放掉,从而也会节省了内存。
图解 Vue 组件化构建方式
从程序角度实现组件化应用构建的架构方式如下图,这种方式的缺点是:
- 组件的模板没有高亮
- 没有模块化之前,首页一堆 script 标签,比较麻烦
使用 vue 实例的 render 方法渲染组件
官网 render 选项:https://cn.vuejs.org/v2/api/#render
我们可以用 render 选项进行组件渲染。该渲染函数接收一个 createElement 方法作为第一个参数用来创建 VNode,与前面介绍的的组件渲染不同的是,使用 render 渲染会把 app 的 div 给覆盖。所以也就只能进行一次模板替代。
<!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>
<script src="node_modules/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
</div>
<script>
var login = {
template: '<h1>这是登录组件</h1>'
}
var vm=new Vue({
el:'#app',
render: function (createElement, context) {
return createElement(login)
}
});
</script>
</body>
</html>
组件通信
在 Vue 中,父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。
父子组件通信:Props Down
- 在父组件中通过子组件标签声明属性的方式传递数据。注意:只有
v-bind
才可以传递动态数据。
<child message="hello!"></child>
- 在子组件中声明 props 接收父组件传递的数据,组件接收到 props 就可以像访问 data 中的数据一样,来访问 props 中的数据并使用。
Vue.component('child', {
// 声明 props
props: ['message'],
// 就像 data 一样,prop 也可以再模板中使用
// 同样也可以再 vm 实例中通过 this.message 来使用
template: '<span>{{ message }}</span>'
})
- Vue 组件通信原则:单向数据流
父组件数据的改变可以影响到子组件,但是子组件不要去修改父组件的数据。因为当你的组件嵌套过深的时候,在子组件中修改某个父组件的数据可能会让你的应用数据流变得非常复杂而难以理解。
注意:在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。引用类型数据虽然可以修改,但是不建议使用,因为这样就违背了 Vue 组件的通信原则
思考:那子组件要改数据呢?子组件能不能把数据给父亲呢,让父组件自己去修改自己的数据呢?
父子组件通信:Event Up
父组件使用 prop 传递数据给子组件。但子组件怎么跟父组件通信呢?这个时候 Vue 的自定义事件系统就派的上用场了。
- 在子组件中调用
$emit()
方法发布一个事件
Vue.component('button-counter', {
template: `<button v-on:click="incrementCounter">{{ counter }}</button>`
data: function() {
return {
counter: 0
}
}
methods: {
incrementCounter: function () {
this.counter += 1
// 发布一个名字叫 increment 的事件
this.$emit('increment')
}
}
})
- 在父组件中提供一个子组件内部发布的事件处理函数
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal : function () {
this.total += 1
}
}
})
- 在子组件的模板的标签上订阅子组件内部发布的事件
<div id="counter-event-example">
<p>{{ total }}</p>
<!--订阅子组件内部发布的 increment 事件,当子组件内部 $emit('increment')
发布的时候,就会调用到父组件中的 incrementTotal 方法-->
<button-counter @increment="incrementTotal"></button-counter>
</div>
非父子组件通信:Event Bus
专业组件通信:Vuex
Vuex 是 Vue 配套的公共数据管理工具,它可以把一些共享的数据,保存到 Vuex 中,方便整个程序的任何组件直接获取或修改我们的公共数据。如果组件之间有共享的数据,可以直接挂载到 Vuex 中,而不必通过父子组件之间传值。
Vuex 是一个全局的共享数据存储区域,就相当于是一个数据的仓库
vuex 的使用
配置vuex的步骤
- 运行 cnpm i vuex -S
- 导入包:
import Vuex from 'vuex'
- 注册 vuex 到 vue 中:
Vue.use(Vuex)
- new Vuex.Store() 实例,得到一个 数据仓储对象。
var store = new Vuex.Store({
state: {
// 专门用来存储数据的
},
mutations: {
// 专门用来定义方法的
}
- 将 vuex 创建的 store 挂载到 VM 实例上, 只要挂载到了 vm 上,任何组件都能使用 store 来存取数据
})
import App from './App.vue'
const vm = new Vue({
el: '#app',
render: c => c(App),
store
- 操作共享的数据与方法。
- 如果在组件中想要访问store 中的数据,只能通过
this.$store.state
来访问。 - 如果组件想要调用 mutations 中的方法,只能使用
this.$store.commit('方法名')
。 - 如果要操作 store 中的 state 值,只能通过 调用 mutations 提供的方法,才能操作对应的数据,不推荐直接操作 state 中的数据,因为万一导致了数据的紊乱,不能快速定位到错误的原因,因为,每个组件都可能有操作数据的方法。
- mutations 的 函数参数列表中,最多支持两个参数:
- param1: 是 state 状态
+param2: 通过 commit 提交过来的参数
要想传递多个参数,可以通过对象或者数组去传递。
- param1: 是 state 状态
- Vuex 中除了 state 和 mutations 选项参数,还有 getters 选项参数,只负责对外提供数据,不修改数据(想要修改 state 中的数据用 mutations) 。
- getters 中的方法,和过滤器比较类似,因为过滤器和 getter 都没有修改原数据,只是把原数据做了一层包装,提供给调用者
- getters 正好也引用了这个数据,那么就会立即触发 getters 重新求值。
- 组件访问 getters 要用
this.$store.getters
来访问
路由
什么是路由?
- 后端路由:对于普通的网站,所有的超链接都是URL地址,所有的URL地址都对应服务器上对应的资源。
- 前端路由:对于单页面应用程序来说,主要通过URL中的hash(#号)来实现不同页面之间的切换,同时,hash有一个特点:HTTP请求中不会包含hash相关的内容;所以,单页面程序中的页面跳转主要用hash实现。
vue-router
vue-router官方文档:https://router.vuejs.org/zh/
安装:
npm install vue-router
引包:
<script src="node_modules/vue-router/dist/vue-router.js"></script>
箭头函数绑定父级上下的 this
路由、和服务端交互、webpack