Skip to content

Vue编写风格

  • HTML元素的属性值始终带引号<input type='text'>; <AppSidebar :style={width:sidebarWidth+'px'}>也是可以的(HTML中没有空格的属性值可以不用引号包裹)

Vue概念解释

单文件组件中 <style> 是可选的,<script><template> 至少要有一个。

一个理想的 Vue 应用是 prop 向下传递,事件向上传递的

html 使用双引号,js 使用单引号 DOM 模板使用短横线,其他的使用大驼峰

  • vscode + vue

  • sourcemap

  • webpack/vue-cli 打包构建的资源到哪里去了 在浏览器dev-tools 中的 source 中查看

  • this.$nextTick 一旦你使用了 keep-alive,那么你就可以访问另外两个生命周期钩子:activateddeactivated

  • 单文件组件

  • DOM模板 能被 html 识别的,以 html 形式编写

  • 字符串模板 以字符串的形式写在选项对象的 template 属性上(全局注册和局部注册组件时)

  • JSX

    html
    <!-- 在单文件组件、字符串模板和 JSX 中 -->
    <MyComponent/>
    
    <!-- 在 DOM 模板中, 没有自闭合, 不是匈牙利命名 -->
    <my-component></my-component>

响应式原理

Vue2的响应式是通过 Object.defineProperty 拦截数据,将数据(准确的说是对象属性)转换为 getter/setter 的形式,在访问属性时调用 getter函数,在修改属性时调用setter函数。然后利用发布-订阅(观察者模式)模式在数据变化时通知 Watcher 更新。

视图不会更新

  • 通过索引修改数组的值

    • this.idArr[0] = {}this.idMap.list[0] = xxx idMap 是一个对象。
    • 使用 arr.splice 设置或 this.$set()
  • 修改数组的 length 属性

    • 使用 arr.splice 设置或 this.$set()

数组有哪些方法支持响应式更新,如果不支持怎么办,底层原理如何实现?

  • 支持:push、pop、shift、unshift、splice、sort、reverse,这些方法会改变原数组。
  • 不支持:filter、concat、slice、forEach、map,这些方法不会改变原数组。可以修改整个数组实现响应式更新(将新的数组赋值给原来的数组)。
  • 原理同样是使用 Object.defineProperty 对数组方法(get、set)进行改写

父组件通过 props 传递一个引用类型的值(propsObj)给子组件,子组件将 propsObj 赋值给自身 data 属性。当子组件修改 initVal 时,父组件中的值也会被修改,因为父组件和子组件引用的用一个对象值。

js
props: ['propsObj'],
  data() {
  return {
    initVal: this.propsObj
  };
},

Vue 生命周期 watch 执行顺序 | 指令生命周期

初始渲染

  • beforeCreate
  • created
  • beforeMount
  • mounted

props 或 data 更新

  • watch
  • beforUpdate
  • updated

watch immediate 情况下初始渲染

  • beforeCreate this 不是 undefined,不能访问当前实例上的属性,可以访问 Vue.prototype 上的属性

  • data 函数 可以访问 this 及 props 上的属性,props 属性值是父组件执行 beforeMount 钩子时传入子组件的值

  • watch 监听器 可以访问 this

  • created

  • beforeMount

  • mounted

父组件和子组件的生命周期执行顺序

  • 父组件的beforeCreate

  • 父组件的created

  • 父组件的beforeMount

    • 子组件的beforeCreate
    • 子组件的created
    • 子组件的beforeMount
    • 子组件的mounted
  • 父组件的mounted

指令生命周期

bind inserted update componentUpdated unbind

input 验证

Q: 非数据驱动,给 input 传入固定的值,input 框依然可以输入。

js
  <input :value="text"/>

A: 使用 element.setAttirbute 不能设置 input 元素的值。使用 input.value = 'xx' 可以实现验证和长度限制。

异步更新DOM

vue观察到数据变化时并不是立即更新DOM,而是开启一个事件队列,缓冲在同一个事件循环中发生的所有数据改变,在缓冲时会去除重复数据。然后在下一个事件循环 tick 中刷新新队列并执行更新。

vue会根据当前浏览器环境优先使用原生 PromiseMutatinObserver

动态创建组件

动态创建组件有两种方式 1): new Vue; 2): new Vue.extend(cmp)

单文件组件 export 就是组件选项对象

vue
import Vue from 'vue';
import router from '@/router';
import CusDialog from './userDialog.vue';

export default function createDialog(title, fields) {
  const instance = new Vue({
    ...CusDialog,
    router,
    propsData: {
      visible: true,
      title,
      fieldOpts: fields,
    },
  }).$mount();
  document.body.appendChild(instance.$el);
  return instance;
}

// 用Vue.extend 继承 router、store 等,不用再次传递router
const extendCmp = Vue.extend(Cmp);
const renderCmp = (propsData) => {
  const instance = new extendCmp({ propsData }).$mount();
  document.body.appendChild(instance.$el);
  return instance;
}

// 销毁
instance.$destroy();
instance.$el.remove();

$mount('#app') 组件会替换 #app 元素本身(outerHtml

获取插槽实例

this.$slots.default[0].componentInstance

内容分发

作用域插槽在需要同时封装逻辑、组合视图界面时很有用

动态组件 <component> + <slot> 内容分发

表单组件包含了很多字段(input、radio、checkbox)等,在不同场景下使用表单组件,表单的逻辑是一致的,字段绑定、校验、提交,但是UI 布局会不同。

  • cus-form.vue
vue
<component :is="$slot.default">
  <input v-modal="name" slot="name">
  <checkbox v-modal="age"  slot="age" />
</component>
  • 使用
vue
<cus-form>
  <div>
    <h1>年龄</h1>
    <slot name="age"></slot>
  </div>
</cus-form>

插槽透传

vue
<template v-for="item in slots" #[item]="props">
  <slot :name="item" v-bind="props"></slot>
</template>
<script>
const slots = Object.keys({
  ...this.$slots,
  ...this.$scopedSlots
});
</script>

程序化事件监听器

js
this.$on('event-name', handler);
this.$once('eventName', handler);
// 监听生命周期
this.$on('hook:beforeDestroy', handler);

问答

this.$emit的返回值是什么?

this.$emit的返回值就是this,如果需要在子组件中向父组件返回其他值,可以通过回调参数实现,即在子组件中this.$emit的实参中传递一个函数。

js
// 子组件内部
methods: {
    handleChange(e) {
    	let callfn = val => {
    		console.log(val);
    	}
    	const res = this.$emit("change", e.target.value, callfn);
    	console.log(res, res === this);
    }
}

// 父组件
const res = this.$emit("change", e.target.value, callfn);触发change事件,父组件中调用handleEventChange事件处理函数
<Event :name="name" @change="handleEventChange" />

handleEventChange(val, callback) {
// 形参val和callback分别由this.$emit()第二和第三个参数传递
	this.name = val;
	callback("hello");
	return "hello";
}

相同名称的插槽(具名插槽)是合并还是替换?

  • Vue2.5版本:普通插槽合并、作用域插槽替换。
  • Vue2.6版本:都是替换。

数组有哪些方法支持响应式更新,如果不支持怎么办,底层原理如何实现?

  • 支持:push、pop、shift、unshift、splice、sort、reverse,这些方法会改变原数组。
  • 不支持:fiter、concat、slice,这些方法不会改变原数组;可以修改整个数组实现响应式更新(将新的数组赋值给原来的数组)。
  • 原理同样是使用Object.defineProperty对数组方法进行改写

ajax请求

  • ajax请求可以放在created和mounted生命周期中,但如果是做同构应用mounted不会在服务端调用,而created是会在服务端调用。

Vue-router

pushreplace 的 onComplete 和 onAbort 回调参数,这些回调将会在导航成功完成 (在所有的异步钩子被解析之后) 或终止 (导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由) 的时候进行相应的调用。在 3.1.0+,可以省略第二个和第三个参数,此时如果支持 Promise,router.push 或 router.replace 将返回一个 Promise。

  • 组件跳转到其他组件,触发 onComplete 回调

  • 组件跳到自己,不带参数

    js
    this.$router.push({ name: 'number'}, () => {
      console.log('组件2:onComplete回调');
    }, () => {
      console.log('组件2,自我跳转:onAbort回调'); // 会执行
    });
  • 组件跳转到自己,带参数

    js
    this.$router.push({ name: 'number', params: { foo: this.number}}, () => {
        console.log('组件2:onComplete回调');  // 不会执行
    }, () => {
        console.log('组件2,自我跳转:onAbort回调'); // 不会执行
    });

    onComplete 和 onAbort 都不会执行,但是 beforeRouteUpdate 会执行。


Vue组件库

Vue.use的作用

Vue.use(mintui)做的就是注册所有的全局组件(webpack require.context),并在Vue.prototype添加一些属性,这样在组件内就可以使用this.xx

  • 自定义插件,使用Vue.use()安装,如安装axios

    // installer.js

    js
    function Installer(){}
    // 必须要有一个install属性
    Installer.install = function(Vue){
        // 1. 注册全局组件
        Vue.component('xx',{
            ...
        });
        // 2. 添加属性
        // Vue.protype.$log = function() {
        //     console.log('hahaahhahaah')
        // }
        // this.$log = 'abxadksadas' 子类对象可以修改父类的属性
    
        let log = function () {
            console.log('我们自己插件的log函数')
        }
    
        // 给原型定义属性的获取和设置,设置:见鬼去吧,获取就给你
        Object.defineProperty(Vue.prototype,'$log',{
            // 设置 $log属性时的行为 || 不给,不能设置
            set:function (newV) {
                console.log('你做梦');
                // log = newV;
    
            },     
            get:function () {
                // 获取方式
                return log;
            }
        })
    }

Vue单页面SEO优化

  • 服务端渲染 SSR :Nuxt.js
  • 页面预渲染
    • 在页面中先预渲染部分静态内容,不用JS注入
    • 使用插件:vue-cli-plugin-prerender-spa 或 prerender-spa-plugin

预渲染

单页面(SPA) 应用的 SEO 优化有服务端渲染(SSR) & 页面预渲染两种方法。

  • 预渲染的使用场景更多是简单的静态页面,加快页面的加载速度,并且侵入性更小,在已上线的项目稍加改动也可以轻松引入预渲染机制。
  • 服务端渲染适用于复杂、较大型、与服务端交互频繁的功能型网站,比如电商网站。SSR方案则需要将整个项目结构推翻。
  • 两则的区别在于渲染的时机不同:prerender-spa-plugin是在打包过程中渲染,注定了其只能渲染静态路由,而prerender 是在请求时渲染,所以可以渲染动态的路由。

vue 预渲染实现

插件配置

prerender-spa-plugin & vue-meta-info

js
// vue.config.js
// 这三项一定要有,因为下面configureWebpack中用到了
const PrerenderSPAPlugin = require('prerender-spa-plugin');
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;
const path = require('path');

module.exports = {
  // 预渲染关键配置
  configureWebpack: () => {
    if (process.env.NODE_ENV !== 'production') return;
    return {
      plugins: [
        new PrerenderSPAPlugin({
          // 生成文件的路径,也可以与webpakc打包的一致。
          // 下面这句话非常重要!!!
          // 这个目录只能有一级,如果目录层次大于一级,在生成的时候不会有任何错误提示,在预渲染的时候只会卡着不动。
          staticDir: path.join(__dirname, 'dist'),
  
          // 对应自己的路由文件,如果有参数需要写具体参数,比如/a/:id需要写/a/123456
          routes: ['/', '/about'],
  
          // 这个很重要,如果没有配置这段,也不会进行预编译
          renderer: new Renderer({
            inject: {
              foo: 'bar'
            },
            headless: false,
            // 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。
            renderAfterDocumentEvent: 'render-event'
          })
        })
      ]
    };
  }
};

// main.js
  new Vue({
    router,
    store,
    render: (h) => h(App),
    // 添加mounted,不然不会执行预编译
    mounted() {
      document.dispatchEvent(new Event('render-event'));
    }
  }).$mount('#app');

服务端配置

History 模式需要后台配置支持,最简单的是通过 nginx 配置 try_files 指令。

vue-meta-info

组件内使用

js
<script>
  export default {
    metaInfo: {
      title: 'My Example App', // set a title
      meta: [{                 // set meta
        name: 'keyWords',
        content: 'My Example App'
      }]
      link: [{                 // set link
        rel: 'asstes',
        href: 'https://assets-cdn.github.com/'
      }]
    }
  }
</script>

用作鉴权的函数式组件

Authorized.vue 鉴权组件

js
<script>
import { check } from "../utils/auth";
export default {
  functional: true,
  props: {
    authority: {
      type: Array,
      required: true
    }
  },
  render(h, context) {
    const { props, scopedSlots } = context;
    // check 用来判断是否授权
    return check(props.authority) ? scopedSlots.default() : null;
  }
};
</script>
  • main.js 注册
js
import Authorized from "Authorized.vue";
Vue.component("Authorized", Authorized);
  • 使用
js
<Authorized :authority="['admin']">
  <SettingDrawer />
</Authorized>
// 如果传递的 authority 通过 check, 则 <SettingDdrawer> 组件会显示, 否则不会显示;

权限指令

指令 direction.js

js
import { check } from "../utils/auth";
function install(Vue, options = {}) {
  Vue.directive(options.name || "auth", {
    inserted(el, binding) {
      if (!check(binding.value)) {
        el.parentNode && el.parentNode.removeChild(el);
      }
    }
  });
}
export default { install };
  • 注册指令
js
import Auth from "direction.js";
Vue.use(Auth);
  • 使用指令
js
<comp v-auth="['admin']" />

JSX 使用

@vue/cli-plugin-babel/preset

js
module.exports = {
  presets: ['@vue/cli-plugin-babel/preset'],
};

Babel Preset JSX

Vue源码

其他

vue 函数式组件

$nextTick 是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用 $nextTick,则可以在回调中获取更新后的 DOM;