美文网首页前端
Vue 学习笔记

Vue 学习笔记

作者: 地平线0530 | 来源:发表于2019-02-16 01:40 被阅读238次

    1. 使用vue-cli3构建项目

    全局安装 vue-cli3

    npm i -g @vue/cli
    

    查看 vue-cli 版本

    vue -V
    

    快速构建项目

    使用 vue ui 创建项目

    vue ui
    

    使用图形化界面管理和创建项目,一目了然,很方便

    使用命令行创建项目

    vue create xxx
    

    参考:创建一个项目

    项目配置

    编辑器打开项目,然后添加一些文件夹和文件如下:

    项目目录

    将 router.js 和 store.js 分别放到相应目录里面,改名为 index.js
    vue.config.js 配置参考

    运行项目

    方式一:命令行

    npm run serve
    

    方式二:图形界面

    运行项目g

    2. vue 路由

    router-link 和 router-view 组件

    <div id="app">
      <h1>Hello App!</h1>
      <p>
        <!-- 使用 router-link 组件来导航. -->
        <!-- 通过传入 `to` 属性指定链接. -->
        <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
        <router-link to="/foo">Go to Foo</router-link>
        <router-link to="/bar">Go to Bar</router-link>
      </p>
      <!-- 路由出口 -->
      <!-- 路由匹配到的组件将渲染在这里 -->
      <router-view></router-view>
    </div>
    

    路由文件做如下配置:

    // 0. 导入 Vue 和 VueRouter
    import Vue from 'vue'
    import Router from 'vue-router'
    
    // 1. 导入自定义的页面组件
    import Foo from './views/Foo.vue'
    import Bar from './views/Bar.vue'
    
    // 2. 定义路由
    const routes = [
      {
        path: '/foo',
        name: 'foo',
        component: Foo
      },
      {
        path: '/bar',
        name: 'bar',
        component: Bar
      }
    ]
    
    // 3. 加载 Router
    Vue.use(Router)
    
    // 4. 创建 router 实例
    const router = new Router({
      routes
    })
    
    // 5. 暴露 router 实例
    export default router
    

    路由配置

    起步

    上面的例子比较简单,在实际工程中,页面较多,需要将路由配置信息和加载分开来写:
    新建 router,js

    路由目录

    将 index.js 中的路由配置信息全部剪切到 router.js 中:
    index.js 文件将如下:

    import Vue from 'vue'
    import Router from 'vue-router'
    import routes from './router'
    
    Vue.use(Router)
    
    export default new Router({
      routes
    })
    

    router.js 如下:

    import Foo from './views/Foo.vue'
    import Bar from './views/Bar.vue'
    
    const routes = [
      {
        path: '/foo',
        name: 'foo',
        component: Foo
      },
      {
        path: '/bar',
        name: 'bar',
        component: Bar
      }
    ]
    
    export default routes
    

    动态路由

    import User from './view/User.vue'
    
    {
      path: '/user/:name',
      name: 'user',
      component: User
    }
    

    User.vue 中:

    <template>
      <div>
        {{ $route.params.name }}
      </div>
    </template>
    

    当我们在地址栏输入: http://localhost:8080/#/user/dog 时,页面显示了 dog

    嵌套路由

    /parent/child

    {
      path: '/parent',
      name: 'parent',
      component: () => import('@/views/Parent.vue'),  // 路由懒加载
      children: [
        {
          path: 'child',
          name: 'child',
          component: () => import('@/views/Child.vue')
        }
      ]
    }
    

    编程式导航

    例子:

    <div id="app">
      <router-link to="/foo">Go to Foo</router-link>
      <router-link to="/bar">Go to Bar</router-link>
      <router-link @click="handleClick">User</router-link>
        
      <router-view></router-view> 
    </div>
    
    <script>
    export default {
      name: '#app',
      methods: {
        handleClick() {
          this.$router.push({
            name: 'user',
            params: { name: 'dog' }
          })
        }
      }
    }
    </script>
    

    方式一览:

    // 字符串
    this.$router.push('bar')
    
    // 对象
    this.$router.push({ path: 'bar' })
    
    // 命名路由
    this.$router.push({ name: 'user', params: { name: 'dog' }})
    
    // 带查询参数: /xxx?name=dog
    this.$router.push({ path: 'xxx', query: { name: 'dog' }})
    
    // 含有变量的写法,实际中常见
    const userName = 'dog'
    this.$router.push({ name: 'user', params: { userName }})
    this.$router.push({ path: `/user${userName}` })
    
    // 错误写法:
    this.$router.push({ path: '/user', params: { userName }})  // 结果为:/user
    
    router.go(n)

    在 history 记录中向前或向后跳转 n 步

    // 在浏览器记录中前进一步,等同于 history.forward()
    this.$router.go(1)
    this.$router.forward()
    
    // 后退一步记录,等同于 history.back()
    this.$router.go(-1)
    this.$router.back()
    
    // 前进 3 步记录
    this.$router.go(3)
    
    // 如果 history 记录不够用,那就默默地失败呗
    this.$router.go(-100)
    this.$router.go(100)
    
    // 跳转页面,并替换记录
    this.$router.replace({name: 'foo'})
    

    vue中操纵 history 效仿的原生API,详细参考:MDN 操作浏览器历史记录

    命名路由

    可以发现,我们在配置路由信息时,给每个路由指定了一个 name,这种就是命名路由,要访问命名路由,只需访问其 name 就可以了:

    <router-link to="/foo">Go to Foo</router-link>
    <router-link :to="{ name: 'foo' }">Go to Foo</router-link>
    

    以上两个效果相同。
    还可以传递一些参数:

    <router-link :to="{ name: 'user', params: {name: 'cat'} }">User</router-link>
    

    命名视图

    在同级展示多个页面

    <router-view></router-view>
    <router-view name="a"></router-view>
    <router-view name="b"></router-view>
    

    路由配置

    {
      path: '/',
      components: {
        default: () => import('@/views/Foo.vue'),
        a: () => import('@/views/A.vue'),
        b: () => import('@/views/B.vue'),
      }
    }
    

    重定向和别名

    重定向

    当用户访问 '/a' 页面时,URL 被自动替换为 '/b'
    方式一: 从 /a 重定向到 /b

    {
      path: '/a',
      redirect: '/b'
    }
    

    方式二:重定向到命名路由

    {
      path: '/a',
      redirect: {
        name: 'foo'
      }
    }
    

    方法三:动态返回重定向目标

    {
      path: '/a',
      redirect: to => {
        return {
          name: 'foo'
        }
      }
    }
    
    别名

    当用户访问 '/''/home' 时显示的是想用的页面

    { 
      path: '/',
      alias: '/home',
      component: Home,
    }
    

    组件传参

    在前面,我们这样进行路由传参:

    const User = {
      template: '<div>User {{ $route.params.id }}</div>'
    }
    const router = new VueRouter({
      routes: [
        { path: '/user/:id', component: User }
      ]
    })
    

    直接使用 $route 会使之与对应路由形成高度的耦合,限制了其灵活性。
    我们可以通过 props 解耦:

    const User = {
      props: ['id'],
      template: '<div>User {{ id }}</div>'
    }
    const router = new VueRouter({
      routes: [
        { path: '/user/:id', component: User, props: true }
      ]
    })
    

    也可以设置接收参数的数据类型和默认值:

    const User = {
      props: {
        type: [Number, String],  // 或者只有一种数据类型,可以直接写 String
        default: 123
      },
      template: '<div>User {{ id }}</div>'
    }
    

    还可以创建一个函数返回 props,这样就可以对参数做更多的事情:

    const router = new VueRouter({
      routes: [
        {
          path: '/search',
          component: SearchUser,
          props: (route) => ({ query: route.query.q }) }
      ]
    })
    

    此时:/search?q=vue 将会以 {query: 'vue'} 为属性传递给 SearchUser 组件。

    HTML5 History 模式

    使用 history 模式,链接:http://localhost:8080/#/blog/123 就会像正常链接一样:http://localhost:8080/blog/123
    但是这种模式需要后台配置支持,以防访问页面出现 404 错误。

    nginx
    location / {
      try_files $uri $uri/ /index.html;
    }
    
    原生 Node.js
    const http = require('http')
    const fs = require('fs')
    const httpPort = 80
    
    http.createServer((req, res) => {
      fs.readFile('index.htm', 'utf-8', (err, content) => {
        if (err) {
          console.log('We cannot open "index.htm" file.')
        }
    
        res.writeHead(200, {
          'Content-Type': 'text/html; charset=utf-8'
        })
    
        res.end(content)
      })
    }).listen(httpPort, () => {
      console.log('Server listening on: http://localhost:%s', httpPort)
    })
    
    警告

    这样做,服务器就不再返回 404 错误页面了,这时我们需要在 vue 中返回一个 404 页面:

    const router = new VueRouter({
      mode: 'history',
      routes: [
        { path: '*', component: NotFoundComponent }
      ]
    })
    

    路由匹配优先级按照配置文件,从上向下,所以最好将上面的配置写在所有路由配置的最后。

    导航守卫

    有时候,我们需要判断用户的登录状态,来决定其是否有权限访问一些页面,这时我们就需要导航守卫了。

    配置全局前置守卫

    import Vue from 'vue'
    import Router from 'vue-router'
    import routes from './router'
    
    Vue.use(Router)
    
    const router = new Router({
      routes
    })
    
    const HAS_LOGINED = true  // 判断用户是否登录
    
    router.beforeEach((to, from ,next) => {
      if (to.name !== 'login') {
        if (HAS_LOGINED) next()
        else next({ name: 'login' })
      }
      else {
        if (HAS_LOGINED) next({ name: 'home' })
        else next()
      }
    })
    
    export default router
    

    参数说明:

    • to:即将进入的目标路由
    • from:正要离开的路由
    • next:必须调用,才能执行接下来的程序
      • next():进行管道中下一个钩子
      • next(false):中断当前的导航
      • next('/')next({path: '/'}):跳转到指定地址
      • next(error):终止错误并传递给 router.onError() 注册过的回调

    路由元信息

    自定义路由的 meta 字段
    比如,根据不同路由,改变 title
    先给路由添加 meta 信息

    {
      path: '/',
      alias: '/home',  // 别名
      name: 'home',
      component: Home,
      meta: {
        title: '首页'
      }
    }
    

    我们这里在 lib/util.js 文件中定义了一个改变 title 的函数:

    export const setTitle = (title) => {
      window.document.title = title || '欢迎来到我的博客'
    }
    

    最后在全局导航守卫处进行判断:

    router.beforeEach((to, from ,next) => {
      to.meta && setTitle(to.meta.title)
    })
    

    这里利用了短路逻辑原理,如果设置了 meta 字段,则运行 setTitle 函数,否则不做任何修改。

    3. 组件间通信

    父子组件通信

    • 所有 prop 都使父子 prop之间形成一个单向下行绑定:父级 prop 更新会向下流动到子组件中,但反过来不行。
    • 子组件通过自定义事件 this.$emit('myEvent') 的方式向父组件传参。

    父组件传参到子组件

    新建子组件 child.vue

    <template>
      <div>
        <h2>{{ title }}</h2>
      </div>
    </template>
    
    <script>
    export default {
      name: 'child',
      props: {
        title: {
          type: String,
          default: 'aaa'
        }
      }
    }
    </script>
    

    新建父组件 parent.vue

    <template>
      <div>
        <Child :title="title"/>
      </div>
    </template>
    
    <script>
    import Child from "@/components/Child.vue"
    
    export default {
      name: 'parent',
      data() {
        return {
          title: 'bbb'
        }
      },
      components: {
        Child
      }
    }
    </script>
    

    这时,当我们在父组件内改变 title 值时,子组件内的 title 也发生了变化,如果什么值都不传,则子组件 title 会显示默认值 aaa

    子组件传参到父组件

    继续改变上面的例子,我们给子组件添加一个 input 标签,父组件添加一个 p 标签,当我们在子组件内输入内容时,父组件可以显示出来。
    修改子组件:

    <template>
      <div>
        <h2>{{ title }}</h2>
        <input @input="handleInput" :value="value">
      </div>
    </template>
    
    <script>
    export default {
      name: 'child',
      props: {
        title: {
          type: String,
          default: 'aaa'
        },
        value: {
          type: [String, Number],
          default: ''
        }
      },
      methods: {
        handleInput(event) {
          const value = event.target.value
          this.$emit('input', value)
        }
      }
    }
    </script>
    

    修改父组件:

    <template>
      <div>
        <Child :title="title" v-model="inputValue"/>
        <p>{{ inputValue }}</p>
      </div>
    </template>
    
    <script>
    import Child from "@/components/Child.vue"
    
    export default {
      name: 'parent',
      data() {
        return {
          title: 'bbb',
          inputValue: ''
        }
      },
      components: {
        Child
      }
    }
    </script>
    

    参考:组件基础

    bus

    如何实现同级组件或是跨级组件之间的通信呢?我们可以通过全局注册一个空的 vue 实例暂存数据来实现。
    我们在 /lib 文件夹中新建 bus.js 文件:

    import Vue from 'vue'
    
    const Bus = new Vue()
    
    export default Bus
    

    main.js 中引入:

    import Bus from './lib/bus'
    
    // 将 bus 注册到 Vue 原型上
    Vue.prototype.$bus = Bus
    

    我们新建一个 All.vue 用来展示:

    <template>
      <div>
        <Aview/>
        <Bview/>
      </div>
    </template>
    
    <script>
    import Aview from '@/components/Aview.vue'
    import Bview from '@/components/Bview.vue'
    
    export default {
      components: {
        Aview,
        Bview
      }
    }
    </script>
    

    components 目录新建两个文件:
    Aview.vue

    <template>
      <div class="a_box">
        <h2>A页面</h2>
        <button @click="handleClick">点我</button>
      </div>
    </template>
    
    <script>
    export default {
      name: 'Aview',
      data() {
        return {
          msg: '我来自A'
        }
      },
      methods: {
        handleClick() {
          // 将事件绑定到 $bus 上
          this.$bus.$emit('on-click', this.msg)
        }
      },
    }
    </script>
    
    <style scoped>
    .a_box {
      border: 1px solid red;
    }
    </style>
    

    Bview.vue

    <template>
      <div class="b_box">
        <h2>B页面</h2>
        <p>{{ content }}</p>
      </div>
    </template>
    
    <script>
    export default {
      name: 'Bview',
      data() {
        return {
          content: ''
        }
      },
      mounted() {
        // 监听 $bus 上的事件
        this.$bus.$on('on-click', msg => this.content = msg)
      },
    }
    </script>
    
    <style scoped>
    .b_box {
      border: 1px solid blue;
    }
    </style>
    

    页面效果如下:

    截图

    我们可以点击 A页面的按钮,此时会在 B页面显示相应的信息,这就是利用 bus 来实现同级组件或跨级组件间通信。

    4. Vuex

    Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,其包含以下几个部分:

    • state:驱动应用的数据源
    • view:以声明方式将 state 映射到视图
    • actions:响应在 view 上的用户输入导致的状态变化
    vuex

    项目中配置

    在实际项目中,我们将 store.js 文件进行解耦,放在单独文件中,方便管理:

    截图
    • index.js:负责导入导出所有模块
    • state.js:写入一些全局使用的数据
    • actions.js:根级别的 action
    • mutations.js:根级别的 mutation
    • getters.js:根级别的 getter
    • modules:存放各模块

    index.js 配置如下;

    import Vue from 'vue'
    import Vuex from 'vuex'
    import state from './state'
    import mutations from "./mutations"
    import actions from "./actions"
    import getters from "./getters"
    import user from './module/user'
    import player from './module/player'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state,
      mutations,
      actions,
      getters,
      modules: {
        user,
        player
      }
    })
    

    我们新建三个文件,来做演示:
    新建 views/All.vue 用来装载其他组件,配置如下:(记得配置下路由,这里就不写了)

    <template>
      <div>
        <GroupOne/>
        <GroupTwo/>
      </div>
    </template>
    
    <script>
    import GroupOne from '@/components/GroupOne.vue'
    import GroupTwo from '@/components/GroupTwo.vue'
    
    export default {
      components: {
        GroupOne,
        GroupTwo
      }
    }
    </script>
    

    components 目录下新建两个文件:GroupOne.vueGroupTwo.vue

    文件直接复制就可以了,样式随便改改,用来做对比:

    <template>
      <div class="group-one">
        <h2>Group One</h2>
        <ul>
          <li>
            <span class="name">这里显示名字</span>
            <span class="health">这里显示血量</span>
          </li>
        </ul>
      </div>
    </template>
    

    State

    我们在 store/module/player.js 中配置一些数据:

    const state = {
      group: [
        { name: '东方未明', health: 100 },
        { name: '谷月轩', health: 90 },
        { name: '荆棘', health: 80 },
        { name: '王蓉', health: 50 }
      ]
    }
    
    const getters = {
      //
    }
    
    const mutations = {
      //
    }
    
    const actions = {
      //
    }
    
    export default {
      state,
      getters,
      actions,
      mutations
    }
    

    在 vue 文件中,可以使用 computed 计算属性来获取 store 数据。
    我们分别在 GroupOne.vueGroupTwo.vue 中的 script 标签中添加如下语句:

    export default {
      name: "groupOne",
      computed: {
        group() {
          return this.$store.state.player.group
        }
      }
    }
    

    注意这里的 player 是指模块 player,如果这里获取的是根 state 数据,就不加模块名。
    改写模板文件,使其显示数据:

    <template>
      <div class="group-two">
        <h2>Group Two</h2>
        <ul>
          <li v-for="(player, index) of group" :key="index">
            <span class="name">{{ player.name }}</span>
            <span class="health">{{ player.health }}</span>
          </li>
        </ul>
      </div>
    </template>
    

    效果如下:

    演示效果

    不过这些数据是不可变的,有时我们需要派生一些状态,这时就需要 getter 了。

    Getter

    我们在 player 模块中定义一个方法,使 store 输出的数据都翻一倍:

    const getters = {
      addHealth: state => {
        return state.player.group.map(item => {
          let { name, health } = item
          name = `${name}-加强版`
          health *=  2
          const obj = {
            name,
            health
          }
          return obj
        })
      }
    }
    

    我们在 GroupOne.vue 中调用:

    <li v-for="(player, index) of addHealth" :key="index">
    ...
    
    addHealth() {
      return this.$store.getters.addHealth
    }
    

    通过对比可以看出变化:

    截图

    我们可以认为 getter 就是 store 的计算属性,但是如果我们需要在组件中控制状态呢?比如我们添加一个按钮,点击按钮会对队伍群体伤害,血量 -1,这时我们就需要 mutation 了。

    Mutation

    player.js 中添加以下方法:

    const mutations = {
      reduceAllHealth: (state, payload) => {
        state.group.forEach(item => {
          item.health -= payload
        })
      }
    }
    

    这里的 payload 是提交载荷,是由 store.commit 传入的额外参数。
    GroupOne.vue 中做如下修改:

    <button @click="reduceAllHealth(reduceNum)">群体攻击</button>
    ...
    
    data() {
      return {
        reduceNum: 1
      }
    },
    methods: {
      reduceAllHealth(num) {
        this.$store.commit('reduceAllHealth', num)
      }
    }
    

    这时我们的页面如下:

    效果图

    点击一下按钮可见 Group One 的数值都减去了 2, Group Two 的数值都减去了 1。这是因为我们之前给 Group One 的模板添加了 getter 方法的原因。

    Action

    Action 类似于 mutation,不同在于:

    • Action 提交的是 mutation,而不是直接变更状态。
    • Action 可以包含任意异步操作。
      我们用控制台打印 action
    截图

    可以看到, action 不止可以提交 mutation,也可以直接改变 state
    组件中使用 this.$store.dispatch() 方法来调用 action
    比如我们定义一个 action 使其在触发时,提交一个 mutation

    const actions = {
      reduceAllHealth: (state, payload) => {
        state.commit('reduceAllHealth', payload)
      }
    }
    

    GroupOne.vue 中调用:

    this.$store.dispatch('reduceAllHealth', num)
    

    其达到的效果和直接调用 mutation 一样。

    辅助函数

    Vuex还提供了一些辅助函数,帮助我们更为方便的调用 store
    GroupOne.vue 中先引入辅助函数:

    import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
    

    然后修改上面所有的调用方法:

    computed: {
      ...mapState({
        group: state => state.player.group
      }),
      ...mapGetters([
        'addHealth'
      ])
    },
    methods: {
      ...mapMutations([
        'reduceAllHealth'
      ]),
      ...mapActions({
        reduce: 'reduceAllHealth'
      })
    }
    

    使用数组形式,可以直接调用方法,使用对象形式,可以重命名。
    Vuex 更多资料,参考官方文档

    相关文章

      网友评论

        本文标题:Vue 学习笔记

        本文链接:https://www.haomeiwen.com/subject/bwgveqtx.html