Vue.js 重点


Vue的生命周期

  • beforeCreate
    实例组件刚创建,元素DOM和数据都还没有初始化,暂时不知道能在这个周期里面进行生命操作。

  • created
    数据data已经初始化完成,方法也已经可以调用,但是DOM为渲染。在这个周期里面如果进行请求是可以改变数据并渲染,由于DOM未挂载,请求过多或者占用时间过长会导致页面线上空白。

  • beforeMount
    DOM未完成挂载,数据也初始化完成,但是数据的双向绑定还是显示{{}},这是因为Vue采用了Virtual DOM(虚拟Dom)技术。先占住了一个坑。

  • mounted
    数据和DOM都完成挂载,在上一个周期占位的数据把值给渲染进去。一般请求会放在这个地方,因为这边请求改变数据之后刚好能渲染。

  • beforeUpdate
    只要是页面数据改变了都会触发,数据更新之前,页面数据还是原来的数据,当你请求赋值一个数据的时候会执行这个周期,如果没有数据改变不执行。

  • updated
    只要是页面数据改变了都会触发,数据更新完毕,页面的数据是更新完成的。beforeUpdate和updated要谨慎使用,因为页面更新数据的时候都会触发,在这里操作数据很影响性能和容易死循环。

  • beforeDestroy
    这个周期是在组件销毁之前执行,在我项目开发中,觉得这个其实有点类似路由钩子beforeRouterLeave,都是在路由离开的时候执行,只不过beforeDestroy无法阻止路由跳转,但是可以做一些路由离开的时候操作,因为这个周期里面还可以使用data和method。比如一个倒计时组件,如果在路由跳转的时候没有清除,这个定时器还是在的,这时候就可以在这个里面清除计时器。

  • Destroyed
    说实在的,我还真的不知道这个周期跟beforeDestroy有什么区别,我在这个周期里面调用data的数据和methods的方法都能调用,所以我会觉得跟beforeDestroy是一样的。


数据双向绑定

Object.defineProperty是ES5新增的一个API,其作用是给对象的属性增加更多的控制
Object.defineProperty(obj, prop, descriptor)
参数 obj: 需要定义属性的对象(目标对象)
prop: 需被定义或修改的属性名(对象上的属性或者方法)
对于setter和getter,我的理解是它们是一对勾子(hook)函数,当你对一个对象的某个属性赋值时,则会自动调用相应的setert函数;而当获取属性时,则调用getter函数。这也是实现双向数据绑定的关键。
代码实现:

<body>
  <div id="app">
    <input type="text" id="txt">
    <p id="show-txt"></p>
  </div>
</body>
<script>
  var obj = {}
  Object.defineProperty(obj, 'txt', {
    set: function (val) {
      document.getElementById('txt').value = val
      document.getElementById('show-txt').innerHTML = val
    }
  })

  document.addEventListener('keyup', function (e) {
    obj.txt = e.target.value
  })
</script>

Vue父子组件传递参数

第一种就是普通的

// parent
<template>
  <child name="son" @changeValue="changeValue"></child>
</template>
<script>
module.exports = {
  methods: {
    changeValue(val) {
      console.log(val)
    }
  }
}
</script>

// child
<script>
module.exports = {
  props:{
    name: {
      required: true,
      type: String
    }
  },
  data: {},
  methods: {
    changeValue() {
      this.$emit('changeValue', '123')
    }
  }
 }
</script>

第二种是我用的比较多的.sync,因为Vue规定prop是不能直接修改的,默认是传值,类似形参

// parent
<template>
  <child name.sync="son"></child>
</template>
<script>
module.exports = {
  methods: {
    changeValue(val) {
      console.log(val)
    }
  }
}
</script>

// child
<script>
module.exports = {
  props:{
    name: {
      required: true,
      type: String
    }
  },
  data: {},
  methods: {
    changeValue() {
      this.$emit('update:name', '123')
    }
  }
 }
</script>

Vue路由传递参数方法

  • 通过name传值
routes: [
    {
      path: '/Message',
      name: 'Message',
      component: resolve => require(['../components/page/Message.vue'], resolve)
    }
]

// vue页面中
this.$router.push({name: 'Message',  params: {pa: aa})

// 接收页面
console.log(this.$route.params.pa)
  • 通过<router-link>
<router-link :to="{name:'hi1',params:{username:'jspang'}}">Hi页面1</router-link>

{path:'/hi1',name:'hi1',component:Hi1}

{{$route.params.username}}
  • 利用url传递参数
{
     path:'/params/:newsId/:newsTitle',
     component:Params
}
<router-link to="/params/198/jspang website is very good">params</router-link>

Vue自定义组件


Vue自定义指令

// 和自定义过滤器一样,我们这里定义的是全局指令
Vue.directive('focus',{
    inserted(el) {
      el.focus()
    }
})
<div id='app'>
    <input type="text">
    <input type="text" v-focus placeholder="我有v-focus,所以,我获取了焦点">
</div>

这里放了两个 input ,但是后面的 input 才使用了我们的自定义 v-focus 指令,所以看到了是后面那个文本框获取了焦点,而不是前面一个。

看到上面这个例子,可以总结几点

  • 使用 Vue.directive() 来新建一个全局指令,(指令使用在HTML元素属性上的)
  • Vue.directive('focus') 第一个参数focus是指令名,指令名在声明的时候,不需要加 v-
  • 在使用指令的HTML元素上,<input type="text" v-focus placeholder="我有v-focus,所以,我获取了焦点"/> 我们需要加上 v-.
  • Vue.directive('focus',{}) 第二个参数是一个对象,对象内部有个 inserted() 的函数,函数有 el 这个参数.
  • el 这个参数表示了绑定这个指令的 DOM元素,在这里就是后面那个有 placeholder 的 input
  • el 就等价于 document.getElementById('el.id')....可以利用 $(el) 无缝连接 jQuery

下面说下指令的生命周期

Vue.directive('gqs',{
    bind() {
      // 当指令绑定到 HTML 元素上时触发.**只调用一次**
      console.log('bind triggerd')
    },
    inserted() {
      // 当绑定了指令的这个HTML元素插入到父元素上时触发(在这里父元素是 `div#app`)**.但不保证,父元素已经插入了 DOM 文档.**
      console.log('inserted triggerd')
    },
    update() {
      // 所在组件的`VNode`更新时调用.
      console.log('updated triggerd')
    },
    componentUpdated() {
      // 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
      console.log('componentUpdated triggerd')
      
    },
    unbind() {
      // 只调用一次,指令与元素解绑时调用.
      console.log('unbind triggerd')
    }
  })

vuex工作原理和组成

首先要说的是,vuex作为一个插件,在vue中使用,就会调用他的install方

// src/store.js
export function install (_Vue) {
  if (Vue && _Vue === Vue) {
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}

其代码比较简单,调用了一下applyMixin方法,该方法主要作用就是在所有组件的beforeCreate生命周期注入了设置this.$store这样一个对象

下面来看一下他的构造函数

// src/store.js
constructor (options = {}) {
  const {
    plugins = [],
    strict = false
  } = options

  // store internal state
  this._committing = false
  this._actions = Object.create(null)
  this._actionSubscribers = []
  this._mutations = Object.create(null)
  this._wrappedGetters = Object.create(null)
  this._modules = new ModuleCollection(options)
  this._modulesNamespaceMap = Object.create(null)
  this._subscribers = []
  this._watcherVM = new Vue()

  const store = this
  const { dispatch, commit } = this
  this.dispatch = function boundDispatch (type, payload) {
    return dispatch.call(store, type, payload)
  }
  this.commit = function boundCommit (type, payload, options) {
    return commit.call(store, type, payload, options)
  }

  // strict mode
  this.strict = strict

  const state = this._modules.root.state

  // init root module.
  // this also recursively registers all sub-modules
  // and collects all module getters inside this._wrappedGetters
  installModule(this, state, [], this._modules.root)

  // 重点方法 ,重置VM
  resetStoreVM(this, state)

  // apply plugins
  plugins.forEach(plugin => plugin(this))

}

除了一堆初始化外,我们注意到了这样一行代码resetStoreVM(this, state)他就是整个vuex的关键

// src/store.js
function resetStoreVM (store, state, hot) {
  // 省略无关代码
  Vue.config.silent = true
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
}

去除了一些无关代码后我们发现,其本质就是将我们传入的state作为一个隐藏的vue组件的data,也就是说,我们的commit操作,本质上其实是修改这个组件的data值,结合上文的computed,修改被defineReactive代理的对象值后,会将其收集到的依赖的watcher中的dirty设置为true,等到下一次访问该watcher中的值后重新获取最新值。

这样就能解释了为什么vuex中的state的对象属性必须提前定义好,如果该state中途增加一个属性,因为该属性没有被defineReactive,所以其依赖系统没有检测到,自然不能更新。

由上所说,我们可以得知store._vm.$data.$$state === store.state, 我们可以在任何含有vuex框架的工程验证这一点。

后一句话结束vuex工作原理,vuex中的store本质就是没有template的隐藏着的vue组件


Vue-Router的原理

使用

const routes = [
  {
    path: '/',
    redirect: '/recommend'
  },
  {
    path: '/recommend',
    component: () => import('../components/recommend/view.vue')
  },
  {
    path: '/singer',
    component: () => import('../components/singer/view.vue')
  },
  {
    path: '/rank',
    component: () => import('../components/rank/view.vue')
  },
  {
    path: '/search',
    component: () => import('../components/search/view.vue')
  }
]
export default routes
import Vue from 'vue'
import Router from 'vue-router'
import routes from './routes'

Vue.use(Router)

export default new Router({
  // mode: 'history',
  routes
})

vue-router通过hash与History interface两种方式实现前端路由,更新视图但不重新请求页面”是前端路由原理的核心之一,目前在浏览器环境中这一功能的实现主要有两种方式

  1. hash ---- 利用URL中的hash(“#”)
  2. 利用History interface在 HTML5中新增的方法

那么,我们要选择用哪种方式呢?
在vue-router中,它提供mode参数来决定采用哪一种方式,选择流程如下:
mode 参数:

默认hash
history 注:如果浏览器不支持history新特性,则采用hash方式
如果不在浏览器环境则使用abstract(node环境下)

mode: 'hash'

http://localhost:8080/#/recommend

mode: 'history'

http://localhost:8080/recommend
 // 根据mode确定history实际的类并实例化    

// 根据mode确定history实际的类并实例化
switch (mode) {
  case 'history':
    this.history = new HTML5History(this, options.base)
    break
  case 'hash':
    this.history = new HashHistory(this, options.base, this.fallback)
    break
  case 'abstract':
    this.history = new AbstractHistory(this, options.base)
    break
  default:
    if (process.env.NODE_ENV !== 'production') {
      assert(false, `invalid mode: ${mode}`)
    }
}
HashHistory

HashHistory真是身怀绝技,会很多东西。特别是替换路由特别厉害。还可以通过不同的方式,一个是push,一个是replace.

1 $router.push() //调用方法

2 HashHistory.push() //根据hash模式调用,设置hash并添加到浏览器历史记录(添加到栈顶)(window.location.hash= XXX)

3 History.transitionTo() //监测更新,更新则调用History.updateRoute()

4 History.updateRoute() //更新路由

5 {app._route= route} //替换当前app路由

6 vm.render() //更新视图
HTML5History

History interface是浏览器历史记录栈提供的接口,通过back(), forward(), go()等方法,我们可以读取浏览器历史记录栈的信息,进行各种跳转操作。
从HTML5开始,History interface有进一步修炼:pushState(), replaceState() 这下不仅是读取了,还可以对浏览器历史记录栈进行修改


vue的seo问题

由于传统的搜索引擎只会从 HTML 中抓取数据,导致前端渲染的页面无法被抓取。前端渲染常使用的 SPA 会把所有 JS 整体打包,无法忽视的问题就是文件太大,导致渲染前等待很长时间。特别是网速差的时候,让用户等待白屏结束并非一个很好的体验。

解决方案:

  • vue ssr
  • nuxt.js
  • prerender-spa-plugin插件
    由于项目只是改善少数页面的seo,所以使用预渲染方式,预渲染插件使用vue官方推荐的prerender-spa-plugin

生命周期内create和mounted的区别

created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。

其实两者比较好理解,通常created使用的次数多,而mounted通常是在一些插件的使用或者组件的使用中进行操作,比如插件chart.js的使用: var ctx = document.getElementById(ID);通常会有这一步,而如果你写入组件中,你会发现在created中无法对chart进行一些初始化配置,一定要等这个html渲染完后才可以进行,那么mounted就是不二之选。


Vue实现登陆拦截

Step1: requireAuth属性
requireAuth属性作用是表明该路由是否需要登录验证,在进行全局拦截时,我们将通过该属性判断路由的跳转,该属性包含在meta属性中:

routes = [
    {
        name: 'detail',
        path: '/detail',
        meta: {
            requireAuth: true
        }
    },
    {
        name: 'login',
        path: '/login'
    }
]

Step2: router.beforeEach

router.beforeEach((from, to, next) => {
    if (to.meta.requireAuth) { // 判断跳转的路由是否需要登录
        if (store.state.token) { // vuex.state判断token是否存在
            next() // 已登录
        } else {
            next({
                path: '/login',
                query: {redirect: to.fullPath} // 将跳转的路由path作为参数,登录成功后跳转到该路由
            })
        }
    } else {
       next()
    }
})
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 一、了解Vue.js 1.1.1 Vue.js是什么? 简单小巧、渐进式、功能强大的技术栈 1.1.2 为什么学习...
    蔡华鹏阅读 3,315评论 0 3
  • 要招一个会vue的开发者: 作为面试官的你,你还会每次都只是问这些老土的问题吗?你对MVVM的理解是什么?你知道什...
    浪子神剑阅读 23,049评论 0 260
  • 你知道vue的模板语法用的是哪个web模板引擎的吗?说说你对这模板引擎的理解 你知道v-model的原理吗?说说看...
    w晚风阅读 3,629评论 4 20
  • Vue也已经升级到2.0版本了,到现在为止(2016/11/19)比较流行的MVVM框架有AngularJS(也有...
    彬_仔阅读 27,209评论 12 114
  • 基于Vue的一些资料 内容 UI组件 开发框架 实用库 服务端 辅助工具 应用实例 Demo示例 element★...
    尝了又尝阅读 1,142评论 0 1