blackLearning.github.io icon indicating copy to clipboard operation
blackLearning.github.io copied to clipboard

浅谈基于vue的web组件开发

Open Fadingvision opened this issue 7 years ago • 0 comments

浅谈基于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>

五、组件编写

  1. 组件格式 一个典型的组件的格式:
<!-- 组件模板 -->
<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>

  1. 组件间的通信
  • 父子通信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 和前面讲到的普通的全局对象有以下两点不同。

  1. Vuex 的状态存储是动态的. 当 Vue 组件从 store 中的 state 读取状态的时候, 如果 store 中的 state 改变,那么响应的组件也会动态并高效的改变。
  2. 你不能直接改变 store 中的 state. 改变 store 中的 state 的唯一途径就是明确的 dispatch mutations 事件. 这样使得每一个状态的变化都很容易追踪, 并且能够让我们通过工具更了解应用内部的状态

Fadingvision avatar Aug 14 '17 15:08 Fadingvision