blackLearning.github.io
blackLearning.github.io copied to clipboard
浅谈基于vue的web组件开发
浅谈基于vue的web组件开发
(date: 2016-04-03 22:29:29 +0800)
一、什么是组件开发
1. html的组件化:
- HTML的组件化非常容易理解,那就是界面的片段化和模板化。
2. JS的组件化:
- 早期的共享文件,把公共功能的代码提出出来,多个页面共用
- 动态引用,消灭全局变量
- JavaScript组件化的目标是什么呢,是清晰的职责,松耦合,便于单元测试和重复利用。
3. CSS的组件化
- 传统的CSS是一种扁平的文本结构,变更成本较高,比如说想要把结构从松散改紧凑,需要改动很多。如果把实际使用的CSS只当作输出结果,而另外有一种适合变更的方式当作中间过程,这就好多了。比如说,我们把一些东西定义成变量,每个细节元素使用这些变量,当需要整体变更的时候,只需修改这些变量然后重新生成一下就可以,比如LESS,SASS,Stylus等.
二、基于官方脚手架vue-cli快速搭建项目
用法
1. npm install -g vue-cli
2. vue init <template-name> <project-name>
3. npm install
4. npm run dev
项目目录结构:
.
├── README.md
├── dist // 项目经过打包之后的生成的最终文件
├── node_modules // 依赖包
├── build // 项目的配置文件目录
│ ├── dev-server.js // 开发的服务配置
│ ├── webpack-dev-conf.js // 开发的Webpack 配置文件
│ ├── webpack-prod-conf.js // 生产的Webpack 配置文件
│ ├── webpack-base-conf.js // 基础的Webpack 配置文件
├── index.js // 项目发布后的启动文件
├── package.json // 项目配置文件
├── static // 用来存放不经编译的静态资源文件
├── test // 单元测试
├── src // 开发目录
│ ├── assets // css js 和图片资源
│ ├── data // 数据文件
│ ├── components // 各种组件
│ ├── views // 各种页面
│ ├── directives // 各种指令
│ ├── filters // 各种过滤器
│ ├── router.js // 路由配置
│ └── main.vue // 根组件
│ └── main.js // Webpack 预编译入口
│ └── index.html // 项目入口文件
.
可以看到它的目录结构和thinkjs还是一些类似,因为他也是基于express框架搭建的一个node服务器。
项目依赖包说明: 打开node_modules可以看到主要引入了下面的依赖:
- babel: 用于兼容es6
- css-loader: 用于打包css
- eslint: 用于语法检查(用起来很蛋疼,不过配置好了应该是好东西)
- karma: 用于单元测试(暂时没用过)
- vue-loader: 用于打包组件
- webpack: 模块管理工具
三、简单介绍vue-loader和webpack
1. vue-loader是vue格式文件的loader工具,让Webpack可以把以Vue格式编写的组件转换成纯JavaScript模块。
- 可以使用es6编写代码
- 允许webpack通过其他的打包模块将单组件下的template和style打包成html和css
- 支持css仅作用于单个组件
- 支持开发环境的热加载
2. Webpack可以将很多文件,把每个模块,找出它们之间的依赖关系,包成静态资源为部署做好准备.
例如我们有一堆CommonJS规范的模块。他们不能直接在浏览器中运行,所以我们需要“包”到一个文件,可以包括通过script标记。Webpack可以按照需要的依赖关系()调用,并自动将其动态的插入到页面中。他不仅能打包js模块, 也能打包css和其他格式的模块,在webpack中每个文件都是一个模块。
基本配置:
module.exports = {
entry: {
app: './src/main.js' //指定打包的入口文件,每有一个键值对,就是一个入口文件
},
output: { // 配置打包结果,path定义了输出的文件夹,filename则定义了打包结果文件的名称,filename里面的[name]会由entry中的键(这里是entry1和entry2)替换
path: path.resolve(__dirname, '../dist/static'),
publicPath: '/static/',
filename: '[name].js'
},
// 其他方案解决配置
resolve: {
//自动扩展文件后缀名,意味着我们require模块可以省略不写后缀名
extensions: ['', '.js', '.vue'],
fallback: [path.join(__dirname, '../node_modules')],
//模块别名定义,方便后续直接引用别名,无须多写长长的地址
alias: {
'src': path.resolve(__dirname, '../src')
}
},
// 这里可以引入我们常用的库作为扩展
externals: {
'zepto': 'Zepto'
},
resolveLoader: {
fallback: [path.join(__dirname, '../node_modules')]
},
// 为不同的文件定义不同的打包工具loader
module: {
loaders: [
{
test: /\.vue$/,
loader: 'vue'
}
四、vue-router
// 引入文件
import Vue from 'vue'
import VueRouter from 'vue-router'
// Router
Vue.use(VueRouter)
// 定义路由对象,可传入参数进行配置
const router = new VueRouter({
history: true, // 启用 HTML5 history 模式。
saveScrollPosition: true, // 保存滚动位置
suppressTransitionError: true
})
// 定义路由规则
// 每条路由规则应该映射到一个组件。
// 加上resolve可实现惰性加载路由。
router.map({
'*': {
component (resolve) {
require(['./views/welcome'], resolve)
}
},
'/': {
component (resolve) {
require(['./views/welcome'], resolve)
}
});
let app = Vue.extend({});
router.start(app, "#app");
<router-view> 用于渲染匹配的组件,它基于 Vue 的动态组件系统,所以它继承了一个正常动态组件的很多特性。
v-link 是一个用来让用户在 vue-router 应用的不同路径间跳转的指令。
<a v-link="{ name: 'user', params: { userId: 123 }}">User</a>
五、组件编写
- 组件格式 一个典型的组件的格式:
<!-- 组件模板 -->
<template>
<div id="app">
<img class="logo" src="./assets/logo.png">
<hello></hello>
<p>
Welcome to your Vue.js app!
</p>
<p style="color:red">
It seems you are using an outdated version of vue-cli.<br>
Upgrade to [email protected] to get access to newer versions of this template.
</p>
</div>
</template>
<!-- 定义组件内部的状态js -->
<script>
<!-- es6语法引入其他组件模块 -->
import Hello from './components/Hello'
<!-- 和普通写在页面中的组件类似,可以定义子组件,父组件数据,方法,路由,钩子函数 -->
export default {
components: {
Hello
}
}
</script>
<!-- 定义组件内部的样式(默认全局通用),加上scope后可只用于当前组件 -->
<style>
html {
height: 100%;
}
</style>
- 组件间的通信
- 父子通信props 子组件可以用 this.$parent 访问它的父组件。根实例的后代可以用 this.$root 访问它。父组件有一个数组 this.$children,包含它所有的子元素。尽管可以访问父链上任意的实例,不过子组件应当避免直接依赖父组件的数据,显式声明props,显式声明组件所依赖的数据,不仅可以让组件的数据流更清晰,也更利于组件的可复用性。
- 事件广播
methods: {
activate () {
// 将button-pressed事件冒泡给父组件
this.$dispatch('button-pressed')
}
}
// 父组件接收到该事件后将increment事件广播给所有子组件
events: {
'button-pressed': function () {
// Send a message to all children
this.$broadcast('increment')
}
}
// 在其他子组件中接收该事件
events: {
increment () {
this.count ++
}
}
缺点:每个行为都要派发一次事件,当组件间的通信过于密切,会让状态管理很麻烦。
- 共享状态
// 定义一个公用的对象进行状态转换
// store.js
export default {
state: {
counter: 0
}
}
由于js按引用传递,所以任何对sharedState状态的修改都会反映到store.js中
import store from 'store.js'
export default {
data () {
return {
sharedState: store.state
}
}
}
- vuex
import Vuex from 'vuex'
// 状态
const state = {
count: 0
}
// 变化方法
const mutations = {
INCREMENT (state) {
state.count++
}
}
// 实例化
const store = new Vuex.Store({
state,
mutations
})
export default store
Everything that does require('../store.js') or import store from '../store.js' will use the same store instance
// 触发改变
store.dispatch('INCREMENT')
// 读取状态
console.log(store.state.count) // -> 1
每当我们引入(“. . / store.js”)时我们将使用相同的store实例。每当我们要使用它的时候我们不在去订阅它,而是直接从store获取。 每一个基于 Vuex 的应用的核心就是 store. "store" 基本上就是一个装有你的应用的大部分 状态(即 state)的容器. Vuex 和前面讲到的普通的全局对象有以下两点不同。
- Vuex 的状态存储是动态的. 当 Vue 组件从 store 中的 state 读取状态的时候, 如果 store 中的 state 改变,那么响应的组件也会动态并高效的改变。
- 你不能直接改变 store 中的 state. 改变 store 中的 state 的唯一途径就是明确的 dispatch mutations 事件. 这样使得每一个状态的变化都很容易追踪, 并且能够让我们通过工具更了解应用内部的状态