Featured image of post 从零开始个人博客设计(二)

从零开始个人博客设计(二)

vue 学习开发总结,归纳开发学习过程中遇到的问题

  上一篇文章主要介绍了博客设计的基本思路、数据库设计以及语言选取。随着开发的深入,从本篇文章开始大概花费5个篇幅讲述博客前端开发学习中遇到的问题以及相关的处理方式,而本篇文章将讲述vue基本语法,包含组件路由父子组件之间的数据交互等常用模块。

参考文章
vue官方教程
vue路由

一、vue基本语法

{{ text }}

在vue中使用两个大括号括起来的参数用来对应相关数据对象上的数据属性,使得html里面的值随着vue对象属性值的改变而改变,例如:

1
2
3
<div id="demo">
  <h1>{{ title }}</h1>
</div>
1
2
3
4
5
6
var demo = new Vue({
  el: '#demo',
  data: {
    title: 'todos',
  }
})

当程序运行时,html里面的{{ title }}就会被动态替换成todos

v-html

在默认条件下{{ text }}里面的text内容都会被解析成文本内容,所以如果将上面例子中的vue数据绑定的title设为<h2>todos</h2>,那么经过渲染显示出来的html内容将是被h1标签包围的**todos**;但是如果仅仅想渲染成以h2标签包围的todos,那么便可以借助v-html指令,将上面的例子改写成下面这样:

1
2
3
<div id="demo">
  <h1 v-html="title"></h1>
</div>
1
2
3
4
5
6
var demo = new Vue({
  el: '#demo',
  data: {
    title: '<h2>todos</h2>',
  }
})

v-if & v-show

这两个指令的意思都是根据条件展示元素,但是两者所使用的方式有一些不同。
v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。 v-show 是带有惰性的,使用v-show的元素,仅仅是在切换元素的display样式。

相比之下,v-show就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。 一般来说,v-if有更高的切换开销,而v-show有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show较好;如果在运行时条件很少改变,则使用v-if较好。

v-for

v-for指令常用列表循环展示,即遍历地展示vue中的数组数据。一般常用方式如下:

1
2
3
4
5
<ul id="example-1">
  <li v-for="item in items">
    {{ item.message }}
  </li>
</ul>
1
2
3
4
5
6
7
8
9
var example1 = new Vue({
  el: '#example-1',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

通常为了更高效地更新虚拟DOM,我们会在循环遍历的时候,增加一个唯一的key,使得在多次循环遍历的时候可以复用之前遍历所使用到的模块。因此,上面的html模块可以修改成下面这种方式:

1
2
3
4
5
<ul id="example-1">
  <li v-for="(item, index) in items" :key="index">
    {{ item.message }}
  </li>
</ul>

至于需要增加唯一key的具体原因,可以参开相关文章:为什么使用v-for时必须添加唯一的key?

v-bind

因为html本身的基本属性无法和vue进行结合使用,所以基于vuev-bind指令可以很好地结合html标签的属性,按照设计本身进行展示需要展示的内容,由于v-bind指令所使用到的地方很多,因此我们又常常将类似于<a v-bind:href="http://wongwongsu.com"></a>这类的指令,简单表示为<a :href="http://wongwongsu.com"></a>,使用:代替v-bind:

v-bind指令在本博客系统中的很多地方都有遇到,例如a标签的href属性中动态指定的url链接;路由router-link中指定的to属性;动态渲染的html样式中所使用的:class:style等。

v-model

你可以用 v-model 指令在表单 <input><textarea><select> 等元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

v-model 会忽略所有表单元素的 valuecheckedselected 特性的初始值而总是将 vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。

v-on

事件监听指令,一般使用方法如下:

1
2
3
4
<div id="example-1">
  <button v-on:click="counter += 1">Add 1</button>
  <p>The button above has been clicked {{ counter }} times.</p>
</div>
1
2
3
4
5
6
var example1 = new Vue({
  el: '#example-1',
  data: {
    counter: 0
  }
})

在本博客开发中常用到的便是监听鼠标的click事件,由于该指令经常会被使用,因此为了方便,和v-bind指令类似,使用@替代v-on:,所以v-on:click又可以写作@click

二、生命周期

lifecycle.png

beforecreated:el 和 data 并未初始化,都为 undefined created:完成了 data 数据的初始化,el 没有

渲染优先级:render函数 > template属性 > 外部html

beforeMount:完成了 el 和 data 初始化,但是通过 {{message}} (虚拟DOM)进行占位的 mounted:完成 Vue 实例挂载 updated:当 data 里的值被修改后,将会触发 update 操作 beforeDestroy:在实例销毁之前调用,此时实例仍可用 destroyed:data 里的数据没有变化,但是 Dom 结构还存在,也就是 Vue 实例不再受控制,完成了解耦(Vue 实例指定的所有东西都会解除绑定,所有的事件监听器都会被移除,所有的子实例也会被销毁)

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
let vm = new Vue({
  el: '#app',
  data: {
    message: 1
  },
  template: '<div id="app"><p>{{message}}</p></div>',
  beforeCreate() {
    console.log('调用了beforeCreate')
    console.log(this.message)
    console.log(this.$el)
  },
  created() {
    console.log('调用了created')
    console.log(this.message)
    console.log(this.$el)
  },
  beforeMount() {
    console.log('调用了beforeMount')
    console.log(this.message)
    console.log(this.$el)
  },
  mounted() {
    console.log('调用了mounted')
    console.log(this.message)
    console.log(this.$el)
  },
  beforeUpdate() {
    console.log('调用了beforeUpdate')
    console.log(this.message)
    console.log(this.$el)
  },
  updated() {
    console.log('调用了updated')
    console.log(this.message)
    console.log(this.$el)
  },
  beforeDestory() {
    console.log('调用了beforeDestory')
    console.log(this.message)
    console.log(this.$el)
  },
  destoryed() {
    console.log('调用了Destoryed')
    console.log(this.message)
    console.log(this.$el)
  }
})

vm.message = 2

输出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 调用了beforeCreate
// undefined
// undefined

// 调用了created
// 1
// undefined

// 调用了beforeMount
// 1
// <div></div>

// 调用了mounted
// 1
// <div id="app"><p>1</p></div>

// 调用了beforeUpdate
// 2
// <div id="app"><p>2</p></div>

// 调用了updated
// 2
// <div id="app"><p>2</p></div>

三、组件

Vue.component(tagName, options)

全局组件和局部组件

全局注册

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Vue.component('my-component', {
  template: '<div>A custom component!</div>'
})
// 创建根实例
new Vue({
  el: '#example'
})

<div id="example">
  <my-component></my-component>
</div>

局部注册

1
2
3
4
5
6
7
<div id="app">
    <input v-model="parentMsg">
    <br>
    <!-- kebab-case (短横线隔开式) in HTML:my-message -->
    <!-- 父组件向子组件传值 -->
    <child v-bind:my-message="parentMsg"></child>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
new Vue({
  el: '#app',
  data: {
    parentMsg: 'Message from parent'
  },
  components: {
    child: {
      // camelCase in JavaScript:'myMessage'
      props: ['myMessage'],
      template: '<span>{{myMessage}}</span>'
    }
  }
});

DOM模板解析 <tr is=""></tr>

四、路由

官方文档:Vue Router

前端路由是通过改变URL,在不重新请求页面的情况下,直接找到与地址匹配的一个组件或对象,将其渲染来更新页面视图

改变浏览器地址而不向服务器发出请求的两种方式:

  • hash 模式

在地址中加入 # 以欺骗浏览器,地址的改变是由于正在进行页内导航

  • history 模式

使用H5的window.history功能,使用URL的Hash来模拟一个完整的URL

基本使用

路由的命名

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<div id="app">
    <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>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<script>
    // 1. 定义 (路由) 组件。
    // 可以从其他文件 import 进来
    const Foo = { template: '<div>foo</div>' }
    const Bar = { template: '<div>bar</div>' }
    // 2. 定义路由
    // 每个路由应该映射一个组件。 其中"component" 可以是通过 Vue.extend() 创建的组件构造器,或者,只是一个组件配置对象
    const routes = [
      { path: '/foo', component: Foo },
      { path: '/bar', component: Bar }
    ]
    // 3. 创建 router 实例,然后传 `routes` 配置
    const router = new VueRouter({
      routes: routes
    })
    // 4. 创建和挂载根实例。
    // 记得要通过 router 配置参数注入路由,
    // 从而让整个应用都有路由功能
    const app = new Vue({
      router
    }).$mount('#app')
</script>

重定向和别名

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
const router = new VueRouter({
      routes: [
        {
          path: '/a',
          // 重定向的 URL
          redirect: '/b',

          // 重定向的目标也可以是一个命名的路由
          redirect: { name: 'foo' },

          // 方法接收 目标路由 作为参数
          // return 重定向的 字符串路径/路径对象
          redirect: to => {
          },

          // 别名(可以让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构)
          alias: '/b'
        }
      ]
    })

编程式导航

导航守卫

数据获取

动态路由匹配 & 嵌套路由

  • 当需要把某种模式匹配到的所有路由,全都映射到同个组件,可以在 vue-router 的路由路径中使用“动态路径参数”来达到这个效果
  • 一个“路径参数”使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用
1
2
3
4
5
<div id="app">
    <router-link to="/user/foo/profile">Go to Foo</router-link>
    <router-link to="/user/bar">Go to Bar</router-link>
    <router-view></router-view>
  </div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const User = {
      // 一个被渲染组件同样可以包含自己的嵌套 <router-view>
      template: '<div><h2>User {{ $route.params.id }}</h2>\
      <router-view></router-view></div>',
      // watch (监测变化) $route 对象
      watch: {
        '$route'(to, from) {
		...
        }
      }
    }
    const UserProfile = {
      template: '<h3>UserProfile {{ $route.params.id }}</h3>'
    }
    const UserPosts = {
      template: '<h3>UserPosts {{ $route.params.id }}</h3>'
    }
    const UserHome = {
      template: '<p>UserHome~~~</p>'
    }

    const router = new VueRouter({
      routes: [
        {
          // 使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用
          path: '/user/:id',
          component: User,
          // 要在嵌套的出口中渲染组件,需要在 VueRouter 的参数中使用 children 配置
          children: [
            {
              // 当 /user/:id/profile 匹配成功,
              // UserProfile 会被渲染在 User 的 <router-view> 中
              path: 'profile',
              component: UserProfile
            },
            {
              // 当 /user/:id/posts 匹配成功
              // UserPosts 会被渲染在 User 的 <router-view> 中
              path: 'posts',
              component: UserPosts
            },
            {
              // UserHome 会被渲染在 User 的 <router-view> 中
              path: '',
              component: UserHome
            }
          ]
        }
      ]
    })
    const app = new Vue({ router }).$mount('#app')

运行结果

1
2
3
4
5
6
7
8
9
1. 访问 /user/ 路径时,自动渲染出两条链接:
Go to Foo  
Go to Bar
2. 点击 Go to Foo 链接时,页面出现:
User foo
UserProfile foo
3. 点击 Go to Bar 链接时,页面出现:
User bar
UserHome~~~
  • 同一个路径可以匹配多个路由,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高;
  • / 开头的嵌套路径会被当作根路径

五、组件通信

子组件向父组件传值

子组件

  • templates 属性中添加 @click 点击事件
1
<input type="button" value="" @click="add">
  • methods 属性中定义点击事件对应的方法,使用 $emit('子组件名', 要传递的参数值),将数据传递给父组件
1
2
3
4
5
methods: {
            add() {
                this.$emit("adduser", this.newUser)
              }
        }

当一个组件被定义,data 必须声明为返回一个初始数据对象的函数:

1
2
3
4
5
data: function(){
   return {
         ...
        }
     }
  • 子组件标签中,添加 v-on 指令:@emit中自定义的子组件名="父组件中methods属性中定义的方法"
1
<side-bar @adduser="getMessage"></side-bar>

父组件

  • methods 属性中定义方法,获取子组件传来的值
1
2
3
4
5
methods: {
        getMessage(childMsg) {
          this.newUser = childMsg
        }
      }

父组件向子组件传值

父组件

  • data 属性中定义要传递的参数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
data: {
        infos: [
          {
              ...
          },
          {
              ...
          }
        ]
     }

子组件

  • 添加 props 属性值
1
props: ['users']
  • 子组件标签中,添加 v-bind 指令::子组件自定义的props属性名="父组件中定义的参数名"
1
<page-content :users="infos"></page-content>
  • 当需要在数据变化执行异步时,可以通过 watch 作为自定义的侦听器,来响应数据的变化
1
2
3
4
5
6
7
8
9
props: ['editinfo'],
watch: {
   // 如果 `editinfo` 发生改变,这个函数就会运行
   editinfo: function (newval, oldval) {
        // newval 是父组件传过来的值(即 editinfo)
        newval = JSON.parse(JSON.stringify(newval));
        ...
            }
        }

避免修改某个子组件导致另外一个子组件值也被修改

原因

因为 v-model 是双向绑定的,所以在 vue 中,如果多个组件引用了同一个对象作为数据,那么当其中一个组件改动对象数据时,其他对象的数据也会同步改动

解决

为了使各组件的对象数据之间相互独立,可以通过 JSON.parse(JSON.stringify(...)) 得到深拷贝的原始数据对象

1
2
3
4
5
6
7
computed: {  
     data: function () {  
         var obj={};  
         obj=JSON.parse(JSON.stringify(this.templateData)); //this.templateData是父组件传递的对象  
         return obj  
    }  
 }
Licensed under CC BY-NC-SA 4.0
comments powered by Disqus