Vue实战必备知识

710 阅读5分钟

Vue开发必备入门知识

对应ppt(需要翻墙)

渐进式Javascript框架 - Progressive javascript framework

渐进式: vue可以一步步的拓展能力,vue core提供声明式渲染,组件系统等功能。 客户端路由由vue-router提供,大规模状态管理由vuex提供,构建工具由vue-cli负责。

  1. 易用: vue语法简单,有明显的html,css,js三层结构。
  2. 灵活: vue支持vue.install注册插件,并提供多种接口。第三方插件快速接入。
  3. 高效: vue文件小,以及快速的diff算法,让virtual dom渲染更少,更快。

对比 - Compare

jQuery Vue
便捷dom操作 数据驱动
浏览器兼容性更强 单文件组件
更多的插件 无桥梁,dom无关
virtual dom 异步渲染
状态管理

操作 - Operation

为一个标签添加点击事件,并修改文本

jQuery实现

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="./jquery.1.12.4.js"></script>
</head>
<body>
    <p id="text">Hello World</p>
    <script>
        (function () {
          // 获取dom、注册原生事件
          $('body').on('click', '#text', function () {
            // 获取dom,直接修改文本节点html
            $('#text').text('你好世界')
          });
        })();
    </script>
</body>
</html>

vue实现

<template>
    <!--模板注册事件,绑定数据对象-->
    <p @click="handleClick">{{text}}</p>
</template>

<script>
  export default {
    data () {
      return {
        // 设定数据初始值
        text: 'Hello World'
      }
    },
    methods: {
      handleClick () {
        // 修改数据,由Vue监控数据更新后异步渲染更新Dom
        this.text = '你好世界';
      }
    }
  }
</script>

使用jQuery操作时,有额外的dom操作。而且没有保存该对象的时候需要多次进行获取dom的操作。 vue则是通过模板的方式展示数据,并以直观的方式在标签上绑定事件和要执行的方法。 dom绑定和视图更新的操作在vue管理。在vue中一般只需关注数据的更新即可。

生命周期 - LifeCycle

js是单线程的语言。正常情况下,js代码会按顺序执行,除开回调函数和事件机制的情况,在js中代码的执行顺序基本就是我们代码的书写顺序。 vue提供了一些生命周期钩子(类似事件机制),这里我截取了vue源码中初始化的代码,并配合实际调用代码进行注释说明不同阶段的区别。

只截取经常用也比较容易用错的部分

initLifecycle(vm) // 重置配置信息,设置父节点
initEvents(vm) // 注册vue事件管理器
initRender(vm) // 处理插槽和父节点vnode
callHook(vm, 'beforeCreate')
initInjections(vm) // 处理inject数据, 并增加监听
initState(vm) // 依次处理props,methods,data,computed,watch
initProvide(vm) //provide暴露数据
callHook(vm, 'created')

// 省略..

vm.$el = el
// 检查template标签或者render函数
callHook(vm, 'beforeMount')

let updateComponent
updateComponent = () => {
  // 重新渲染模板
  vm._update(vm._render(), hydrating)
}

// 注册update监听器
new Watcher(vm, updateComponent, noop, {
  before () {
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')
    }
  }
}, true /* isRenderWatcher */)
hydrating = false

if (vm.$vnode == null) {
  vm._isMounted = true
  callHook(vm, 'mounted')
}
<script>
  export default {
    beforeCreated () {
      // 可以访问原型方法
      console.log('实例创建之前');
    },
    created () {
      // 可以访问data, props的数据, methods方法
      console.log('实例创建之后,但还未被挂载至虚拟dom');
    },
    beforeMount () {
      console.log('实例挂载dom之前');
    },
    mounted () {
      // 可以访问this.$refs, this.$el
      console.log('实例挂载dom之后');
    }
  }
</script>
注意:不同阶段能访问的内容不同

状态管理 - State

Vue Vuex
props:外部入参 mapState:全局唯一数据
data:组件自身数据 mapGetters:派生state
computed:派生数据
provide/inject:祖孙

vue可通过以上几种方式获取保存数据。当然遵循js语法还是访问作用域内能访问到的变量。 但是在template里的数据必须是以上几种或是vue提供的api

provide/inject: vue2.20新增,可以对后代传递数据

组件 - Component

vue明显的三段式代码,让人快速联想到了html,js,css。同时三者结合是一个无依赖组件基本要求。 通过vue提供的统一的交互方式v-model或.sync可以制作一个通用的组件。 以下我简易实现了一个下拉选择器

<template>
    <div class="dropdown">
        <a class="dropdown-link" @click="isShowDropList = true">{{innerValue}}</a>
        <ul v-show="isShowDropList" class="dropdown-menu">
            <!--template写的方法不会立刻执行,而是控制传入的参数, 通过$event获取原本的事件参数-->
            <li v-for="{value, label} in enums" @click="handleClick(value, $event)">{{label}}</li>
        </ul>
    </div>
</template>

<script>
  const DEFAULT_LABEL = '请选择';

  export default {
    name: 'selector',
    props: {
      enums: {
        type: Array,
        required: true
      },
      // 使用value获取外界值
      value: {
        // 传入类型检测
        type: [Number, String],
        required: true
      }
    },
    // data数据为该组件内部使用状态
    data () {
      return {
        isShowDropList: false
      }
    },
    // 计算属性用于获得需要基础数据二次计算的数据,懒加载
    computed: {
      innerValue () {
        const {label = DEFAULT_LABEL} = this.enums.find(({value: _value}) => _value === this.value) || {};
        return label;
      }
    },
    methods: {
      // 使用事件触发数据更新
      handleClick (value) {
        // 使用v-model语法糖时,触发input事件
        this.$emit('input', value);
        // 使用.sync语法糖时,触发update:xx事件
        this.$emit('update:value', value);
      },
      handleClose () {
        this.isShowDropList = false
      }
    },
    mounted () {
      // mounted保证dom已挂载真实dom, 此时才可使用dom相关方法
      document.body.addEventListener('click', this.handleClose, false)
    },
    beforeDestroy () {
      // destroyed会将dom移除,需在beforeDestroy阶段前进行dom操作
      document.body.removeEventListener('click', this.handleClose, false)
    }
  }
</script>

<style lang="less" scoped>
    // 省略css代码
    .dropdown {
        .dropdown-link {
        }
        .dropdown-menu {
        }
    }
</style>

组件化提高了代码的内聚性

vue 2.3对于.sync使用描述

插槽 - slot

使用插槽可以让组件插入到已定义的模板之中,以进行布局的控制

<template>
    <div class="page-panel page-content" flex="dir:top" flex-box="1">
        <div class="page-panel-toolbar" v-if="$slots.toolbar">
            <slot name="toolbar"></slot>
        </div>
        <div class="page-panel-main" flex-box="1">
            <slot></slot>
        </div>
        <div class="page-panel-footer" v-if="$slots.footer">
            <slot name="footer"></slot>
        </div>
    </div>
</template>

<script>
  export default {
  }
</script>

以下代码实现了一个后端水印组件,只需通过这个组件进行包装即可对内层内容添加水印效果

<template>
    <div class="watermark-wrapper">
        <div class="watermark" :style="{backgroundImage: watermarkStyle, opacity}"></div>
        <slot></slot>
    </div>
</template>

<script>
  export default {
    props: {
      img: {
        type: String,
        default: '/watermark'
      },
      opacity: {
        type: Number,
        default: 0.1
      }
    },
    computed: {
      watermarkStyle: function () {
        return `url('${this.img}')`
      }
    }
  }
</script>

<style lang="less" scoped>
    .watermark-wrapper {
        position: relative;
        .watermark {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-repeat: repeat;
            z-index: 2;
            pointer-events: none;
        }
    }
</style>

组件内的特殊实现可以抽象出来通过slot插槽在外部实现

组合 - Composition

mixins extends
根据数组顺序混合mixin 单继承
生命周期钩子均执行 优先执行生命周期钩子

mixins: 可以提供通用的方法,复用代码

以下代码可以通过方法跟踪长时(ajax)操作

export default {
  data () {
    return {
      isLoading: false
    }
  },
  methods: {
    // 加载前钩子
    beforeLoading () {
    },
    // 加载后钩子
    afterLoading () {
    },
    loading () {
      this.beforeLoading();
      this.isLoading = true
    },
    loaded () {
      this.afterLoading();
      this.isLoading = false
    }
  }
}

extends: 适合显示覆盖原组件的原有实现

以下代码为dialog组件增加了一个将dom插入至body层的功能

<script>
  import { Dialog } from 'my-vue-component';
  export default {
    name: 'unique-dialog',
    // 继承已有dialog组件
    extends: Dialog,
    props: {
      // 将组件插入至body层
      appendToBody: {
        type: Boolean,
        default: true
      }
    },
    watch: {
      show (show) {
        if (show && this.appendToBody) {
          document.body.appendChild(this.$el);
        }
      }
    },
    mounted () {
      if (this.appendToBody) {
        document.body.appendChild(this.$el);
      }
    },
    destroyed () {
      if (this.appendToBody && this.$el && this.$el.parentNode) {
        this.$el.parentNode.removeChild(this.$el);
      }
    }
  }
</script>

抽象 - Abstract

页面一般由多个部分组成,仔细观察其实有些部分只有数据信息上的差异。

demo.png

如果是一个后台系统的增删改查,可以抽象成以上组件。只需对不同的字段部分进行修改和管理即可 。

字段 - Field

一个后台系统的增删改查,不考虑特殊情况的话,只有对应的字段信息不同。把字段从代码中抽离,让组件更加通用化

  1. 抽离业务相关部分
  2. 抽象组件渲染方式
  3. 定义统一格式的字段管理方案
  4. 提高可维护性
import {typesEnum, statusEnums} from './enums';
import ShowContentComponent from '../component/content/show';
import UserChooserComponent from '../component/user-chooser';

export const id = {
  prop: 'id',
  label: '公告id'
};

export const title = {
  prop: 'title',
  label: '公告标题',
  required: true
};

export const contents = {
  prop: 'contents',
  label: '公告内容',
  required: true,
  listComponent: ShowContentComponent
};

export const type = {
  prop: 'type',
  label: '公告重要程度',
  type: 'select',
  enums: typesEnum,
  required: true
};

export const beginTime = {
  prop: 'begin_time',
  label: '公告生效时间'
};

export const endTime = {
  prop: 'end_time',
  label: '公告失效时间'
};

export const periodTime = {
  prop: 'period_time',
  props: [beginTime.prop, endTime.prop],
  label: '发布生效时间',
  type: 'datetimerange',
  required: true
};

export const creator = {
  prop: 'create_user',
  label: '创建人',
  editComponent: UserChooserComponent
};

export const status = {
  prop: 'status',
  label: '状态',
  type: 'select',
  multiple: false,
  enums: statusEnums
};

由于查询条件的组件比较复杂,这里仅提供列表组件渲染模板。

<template>
    <el-table :data="datas">
        <el-table-column
                v-for="{prop, label, listComponent, enums, displayProp} in configs" :key="prop"
                :label="label"
                :prop="prop">
            <template slot-scope="{row}">
                <!--优先使用动态组件处理-->
                <component v-if="listComponent" :is="listComponent" v-model="row[prop]" :data.sync="row[prop]"/>
                <!--专用显示字段-->
                <span v-else-if="displayProp">{{row[displayProp] | mapArray | mapDefault }}</span>
                <!--使用枚举显示字段信息-->
                <span v-else-if="enums">{{getEnumsValue(enums, row[prop]) | mapDefault }}</span>
                <!--默认显示-->
                <span v-else>{{row[prop] | mapArray | mapDefault }}</span>
            </template>
        </el-table-column>
        <!--可以增加额外的列-->
        <slot></slot>
    </el-table>
</template>

<script>
  import GetEnumsValueMixin from 'src/modules/mixins/get-enums-value';
  export default {
    mixins: [GetEnumsValueMixin],
    // 过滤器,控制文本显示
    filters: {
      mapArray (val) {
        return Array.isArray(val) ? val.join(',') : val;
      },
      mapDefault (value) {
        return value || '--';
      }
    },
    // 动态传入配置和数据
    props: {
      configs: {
        type: Array,
        default () {
          return [];
        }
      },
      datas: {
        type: Array,
        default () {
          return [];
        }
      }
    }
  }
</script>

开发者工具 - Devtools

vue对浏览器提供了vue.js devtools。vue运行在development模式时,即可激活。

  1. 左侧面板可以查看到编写组件之间的嵌套关系,并快速定位要查看的组件
  2. 右侧面板可以查看当前组件所挂载的数据,包括props,data,computed,inject,只有data内的数据可以修改,修改会直接显式的在页面上查看到效果

devtools.png

不推荐dom操作 - Deprecated

  1. this.$el:访问当前组件的最顶层原属
  2. this.$refs:访问指定ref属性的元素