关于Vue的一些入门知识

  1. watchcomputedmethods 区别是什么?
    1. 翻译一遍,说出作用,再找不同
      computed:计算属性
      watch:监听属性,一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。
      methodsmethods 将被混入到 Vue 实例中。可以直接通过 VM 实例访问这些方法,或者在指令表达式中使用。
    2. 要点:
      1. 最大区别是 computed 有缓存:如果 computed 属性依赖的属性没有变化,那么 computed 属性就不会重新计算。watch在每次监听的值变化时候,都会执行回调。
      2. computed 是计算出一个属性,而 watch 则可能是做别的事情(如上报数据)
  2. Vue 有哪些生命周期钩子函数?分别有什么用?
    :不要在生命周期函数中使用箭头函数,因为箭头函数没有this。使用会导致this作为变量一直向上级词法作用域查找,直至找到为止,从而引发类如Uncaught TypeError: Cannot read property of undefinedUncaught TypeError: this.myMethod is not a function之类的错误。
    1. beforeCreate:在实例初始化之后,数据观测(data observer)和event/watcher事件配置之前被调用。
    2. created:在实例创建完成后被立即调用,在这一步实例已经完成以下配置:数据观测(data observer)、属性和方法的运算、event/watch事件回调,然而挂载还没开始,$el属性目前还不可见。
    3. beforeMount:在挂载开始之前被调用,相关的渲染函数首次被调用 。
    4. mountedel被新创建的vm.$el替换,挂载成功。一般在这个钩子中请求数据
    5. beforeUpdate:数据更新前调用。
    6. updated:组件 DOM已经更新,组件更新完毕。
    7. beforeDestroy:整个页面,应用被销毁前调用,此时实例仍然完全可用。
    8. destroyedVue实例销毁后调用,Vue实例指示的所有东西都会解绑定,所有时间监听器会被移除,所有子实例也会被销毁。
  3. Vue 如何实现组件间通信?
    1. 父子组件:$emit('xxx',data) $on('xxx',function(){})
    2. 任意组件,爷孙组件:使用 var eventBus = new Vue() 来通信,eventBus.$oneventBus.$emit 是主要API
    3. 任意组件:使用 Vuex 通信
  4. Vue 数据响应式怎么做到的?
    1. 这篇文章我觉得讲的比较明了《详解Vue响应式原理
    2. Vue 2.0版本 主要通过Object.defineProperty实现
      1. 使用 Object.defineProperty 把这些属性全部转为 getter/setter,每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
      2. Vue 不能检测到对象属性的添加或删除,解决方法是手动调用 Vue.set(object,key,value) 或者 this.$set(this.object,key,value)
    3. Vue 3.0主要通过 Proxy实现,区别就在于Proxy代理的是整个对象,而Object.defineProperty代理的是对象的某个属性,需要遍历整个对象的属性。
  5. Vue.set 是做什么用的?
    添加或者删除一个对象属性时候用
  6. Vuex 你怎么用的?
    1. Vuex 是一个专为 Vue.js 应用程序开发的状态管理工具
    2. 核心概念:
      2.1 State:声明定义数据,唯一的数据源。 从store 实例中读取状态最简单的方法就是在计算属性中返回某个状态。
      Vuex通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用Vue.use(Vuex)):
const app = new Vue({
  el: '#app',
  // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
  store,
  components: { Counter },
  template: `
    <div class="app">
      <counter></counter>
    </div>
  `
})

通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到。

const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}

2.2 Getters:可以认为是 store 的计算属性,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。Getter 接受 state 作为其第一个参数:

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

Getter 也可以接受其他 getter 作为第二个参数:

getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    },
   doneTodosCount: (state, getters) => {
      return getters.doneTodos.length
   }

注意:getter在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。
2.3 Mutation:更改Vuexstore 中的状态的唯一方法是提交 mutation

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment//事件类型type (state) {
      // 变更状态
      state.count++
    }//回调函数 (handler)
  }
})
store.commit('increment')  //提交一个`mutation`来触发状态的更改

可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload):

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}

store.commit('increment', {
  amount: 10
})
或者
store.commit({
  type: 'increment',
  amount: 10
})

注意:Mutation 必须是同步函数!!因为当 mutation 触发的时候,回调函数还没有被调用
2.4 ActionsAction 类似于 mutation,不同在于:1.Action 提交的是 mutation,而不是直接变更状态。2.Action 可以包含任意异步操作。
Action 函数接受一个与store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取stategetters

...
actions: {
    increment (context) {
      context.commit('increment')
    }
  }

实践中,我们常常用参数解构来简化代码,特别是需要commit很多次的时候:

actions: {
  increment ({ commit }) {
    commit('increment')
  }
}
//异步
actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

Action 通过 store.dispatch 方法触发:

store.dispatch('increment') 
//or
store.dispatch({
    type: 'increment',
    payload: payload
})

2.5 Modules:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

对于模块内部的mutationgetter,接收的第一个参数是模块的局部状态对象。
对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState(第三个参数):

const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

默认情况下,模块内部的 actionmutationgetter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutationaction 作出响应。

如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getteractionmutation 都会自动根据模块注册的路径调整命名:

const moduleA = {
    namespaced: true,
    state: { 
        count: 3
    },
    mutations: {
        increment (state) {
            state.count++
        }
    }
...
}

store.commit('a/increment')  //才能访问到对应的`mutation`
  1. VueRouter你怎么用的?
    1. Vue RouterVue.js 官方的路由管理器。
    2. 核心概念:
  • 动态路由匹配:我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如现在有个User组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果:
const User = {
  template: '<div>User</div>'
}

const router = new VueRouter({
  routes: [
    // 动态路径参数 以冒号开头
    { path: '/user/:id', component: User }
  ]
})

现在呢,像 /user/foo 和 /user/bar 都将映射到相同的路由。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用。于是,我们可以更新User的模板,输出当前用户的 ID:const User = { template: '<div>User {{ $route.params.id }}</div>' }
常规参数只会匹配被 / 分隔的 URL 片段中的字符。如果想匹配任意路径,我们可以使用通配符 (*):

{
  // 会匹配所有路径
  path: '*'
}
{
  // 会匹配以 `/user-` 开头的任意路径
  path: '/user-*'
}

当使用通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后。当使用一个通配符时,$route.params 内会自动添加一个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分:// 给出一个路由 { path: '/user-*' } this.$router.push('/user-admin') this.$route.params.pathMatch // 'admin'

  • History 模式:vue-router默认的是hash模式----使用 URLhash 来模拟一个完整的 URL,于是当URL 改变时,页面不会重新加载。如果不想要这种带hashURL的话,可以使用History模式,这种模式利用的是history.pushState API来完成URL的跳转,因此不会重新加载页面。不过History模式需要后台的支持,需要服务端增加一个覆盖所有情况的候选资源,当URL匹配不到任何静态资源,则应该返回app依赖的页面。
const router = new VueRouter({
  mode: 'history',
  routes: [...]
})
  • 路由懒加载:当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。结合webpack的代码分块功能,只需要import('./Foo.vue')//返回Promise就可以实现懒加载。
    结合vue的异步组件和webpack,就能定义一个能够被 Webpack 自动代码分割的异步组件:
const Foo = () => import('./Foo.vue')
  • 重定向和别名:
    重定向:重定向通过 routes 配置来完成,从/a重定向到/b:
const router = new VueRouter({
  routes: [
    { path: '/a', redirect: '/b' }
  ]
})

重定向的目标也可以是一个命名的路由:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: { name: 'foo' }}
  ]
})

甚至是一个方法,动态返回重定向目标:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: to => {
      // 方法接收 目标路由 作为参数
      // return 重定向的 字符串路径/路径对象
    }}
  ]
})

别名:/a 的别名是 /b,意味着,当用户访问/b 时,URL 会保持为/b,但是路由匹配则为/a,就像用户访问 /a 一样。

const router = new VueRouter({
  routes: [
    { path: '/a', component: A, alias: '/b' }
  ]
})

代码为上述的路由配置。 ''别名''的作用是让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构。
3. 常用 API:
router-link: 当被点击后,内部会立刻把 to 的值传到router.push()

<router-link to="home">Home</router-link> //字符串
<router-link :to="'home'">Home</router-link> //v-bind的简略写法
<router-link :to="{ path: 'home' }">Home</router-link> //对象
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link> //命名的路由
<router-link :to="{ path: 'register', query: { plan: 'private' }}"
  >Register</router-link
>//带查询参数 /register?plan=private

设置 replace 属性的话,当点击时,会调用 router.replace() 而不是 router.push(),于是导航后不会留下 history 记录。

<router-link :to="{ path: '/abc'}" replace></router-link>

router-view : 命名视图,有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。如果 router-view 没有设置名字,那么默认为 default。

<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>

路由配置:

const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    }
  ]
})

router.push/router.replace/router.go/router.back/router.forward :动态的导航到一个新 URL

router.push(location, onComplete?, onAbort?)
router.push(location).then(onComplete).catch(onAbort)
router.replace(location, onComplete?, onAbort?)
router.replace(location).then(onComplete).catch(onAbort)
router.go(n)
router.back()
router.forward()

router.onError:注册一个回调,该回调会在路由导航过程中出错时被调用。

router.onError(callback)

注意被调用的错误必须是下列情形中的一种:
1.错误在一个路由守卫函数中被同步抛出;
2.错误在一个路由守卫函数中通过调用 next(err)的方式异步捕获并处理;
3.渲染一个路由的过程中,需要尝试解析一个异步组件时发生错误

  1. 什么是导航守卫?
  • 概念: 导航守卫:vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。简单来说,导航守卫就是路由跳转期间的钩子函数,它分为全局的、单个路由独享的、组件内部的三种
  • 导航守卫分类:
    1.全局的
    全局前置守卫
const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。

全局解析守卫
在 2.5.0+ 你可以用router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

全局后置钩子

router.afterEach((to, from) => {
  // ...
})

router.afterEach不会接受 next 函数也不会改变导航本身。

2.路由独享的守卫
目前只有一个钩子函数beforeEnter

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

这些守卫与全局前置守卫router.beforeEach的方法参数是一样的。

3.组件内部的守卫
beforeRouteEnter beforeRouteUpdate (2.2 新增) beforeRouteLeave

const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
}

注意 beforeRouteEnter 是支持给 next传递回调的唯一守卫。对于 beforeRouteUpdatebeforeRouteLeave 来说,this已经可用了,所以不支持传递回调,因为没有必要了。

beforeRouteLeave通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。

beforeRouteLeave (to, from, next) {
  const answer = window.confirm('你确定要离开嘛? 你还未保存更改!')
  if (answer) {
    next()
  } else {
    next(false)
  }
}
  • 导航参数解析
    每个守卫方法接收三个参数(router.afterEach没有next):
    to: Route 即将要进入的目标 路由对象。
    from: Route 当前导航正要离开的路由。
    next: Function 一定要调用该方法来resolve 这个钩子,如果不写这个方法,路由将不会跳转。执行效果依赖 next 方法的调用参数:
    - next() 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
    - next(false) 中断当前的导航。如果浏览器的URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from 路由对应的地址。
    - next('/') 或者 next({ path: '/' }) 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。 可传递的参数和router-link :to='参数'或者router.push('参数')中的参数是一样的。
    - next(error) (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
    - 在beforeRouteEnternext(vm => {//通过 vm 访问组件实例})

  • 完整的导航解析流程
    当点击跳转路由时:
    1. 导航被触发。
    2. 在失活的组件内调用 beforeRouteLeave 守卫。
    3. 调用全局的 beforeEach 守卫。
    4. 在重用的组件内调用 beforeRouteUpdate 守卫 (2.2+)。
    5. 在路由配置里调用 beforeEnter
    6. 解析异步路由组件。
    7. 在被激活的组件内调用 beforeRouteEnter
    8. 调用全局的beforeResolve 守卫 (2.5+)。
    9. 导航被确认。
    10. 调用全局的 afterEach 钩子。
    11. 触发 DOM 更新。
    12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容

  • 前言 使用Vue在日常开发中会频繁接触和使用生命周期,在官方文档中是这么解释生命周期的: 每个 Vue 实例在被创...
    心_c2a2阅读 2,233评论 1 8
  • 前言 接上篇前端Js笔试题面试题,收集整理Vue相关的面试题,供自己现在和以后学习和面试,也希望能对点进来的小伙伴...
    蛙哇阅读 2,571评论 0 10
  • 1.css只在当前组件起作用答:在style标签中写入scoped即可 例如: 2.v-if 和 v-show 区...
    小棋子js阅读 510评论 0 0
  • Vue知识点的总结 Vue中的指令及其基本语法: 第一步:从官网上下载vue开发版本的js文件 引入js文件 ...
    往前走莫回头_2cd6阅读 1,457评论 0 1
  • 一. Vue核心小知识点 1、vue中 key 值的作用 key 的特殊属性主要用在 Vue的虚拟DOM算法,在新...
    素心_b7d9阅读 757评论 0 0