此篇文章同时发布在知乎专栏 前端后端客户端,专栏专注于前端、后端、客户端开发的技术分享与探讨,欢迎关注。

虽然说一直不是很喜欢写前端的东西,但作为一个纯后端来说,想做一些直接提供给普通用户使用的 Demo 始终绕不开界面展示的问题。因为工作中也正好需要使用 Vue 进行项目的开发,所以趁此机会入门 Vue,作为前端新手正式上路。

安装

node

node 安装包

Vue

独立版本

官网下载 vue.min.js 并使用 <script> 标签引入。

CDN

国内的 CDN 较不稳定,还是建议下到本地进行引入。

CLI

1
$ sudo npm install --global vue-cli

项目初始化

定义一个基于 webpack 模板的新项目。

1
$ vue init webpack vue-project

进入项目,安装并运行。

1
2
$ npm install
$ npm run dev

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
├── README.md
├── build 项目构建(webpack)相关代码
├── config 配置目录,包括端口号等
├── index.html 首页入口文件
├── node_modules npm 加载的项目依赖模块
├── package.json 项目配置文件
├── src 开发目录
│   ├── App.vue 项目入口文件
│   ├── assets 放置一些图片
│   ├── components 放了一个组件文件
│   ├── main.js 项目的核心文件
│   └── router
├── static
└── test

Hello World

一起来看下 Vue 下载后自带的 Demo。

App.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
<template>
<div id="app">
<img src="./assets/logo.png">
<!-- 将路由指定的组件植入此处 -->
<router-view/>
</div>
</template>

<script>
export default {
name: 'App' // 其他文件 import 该文件时用到的名称
}
</script>

<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false // 关闭生成生产模式的提示

/* eslint-disable no-new */
new Vue({
el: '#app', // 对应 App.vue 中的 div
router,
components: { App }, // 页面组成的组件
template: '<App/>' // 使用的模板
})

router/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'

Vue.use(Router)

// 定义了一个新路由
export default new Router({
routes: [
{
path: '/', // 路由路径
name: 'HelloWorld', // 路由名称
component: HelloWorld // 路由组件
}
]
})

输出 Hello World!

看了上述代码结构后,大家应该很清楚了,如果需要输出 Hello World,修改 HelloWorld.vue 组件即可:

1
2
3
4
5
6
7
8
9
10
<script>
export default {
name: 'HelloWorld',
data () {
return {
msg: 'Hello World!'
}
}
}
</script>

相关语法

模板

插值

  • { {...} } 用于文本插值
  • v-html 用于输出 HTML 代码
  • v-bind 指令用于指定属性值
  • 支持所有 JavaScript 表达式

指令

指令是带有 v- 前缀的特殊属性,用于在表达式值改变时,将某些行为应用到 DOM 上

v-if 根据表达式 seen 的值来决定是否插入 p 元素:

1
2
3
4
5
6
7
8
9
10
11
12
<div id="app">
<p v-if="seen">现在你看到我了</p>
</div>

<script>
new Vue({
el: '#app',
data: {
seen: true
}
})
</script>
  • 参数:指令:参数
  • 修饰符:以 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定

用户输入

使用 v-model 双向绑定数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="app">
<p>{{ message }}</p>
<input v-model="message">
</div>

<script>
new Vue({
el: '#app',
data: {
message: 'Runoob!'
}
})
</script>

过滤器

过滤器用于文本格式化。

  • 用“管道符”指示
  • 接受表达式的值作为第一个参数
  • 过滤器是 JavaScript 函数
1
2
3
4
5
6
7
8
<!-- 在两个大括号中 -->
{{ message | capitalize }}

<!-- 在 v-bind 指令中 -->
<div v-bind:id="rawId | formatId"></div>

<!-- 允许串联 -->
{{ message | filterA | filterB }}

对输入的字符串第一个字母转为大写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="app">
{{ message | capitalize }}
</div>

<script>
new Vue({
el: '#app',
data: {
message: 'runoob'
},
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
})
</script>

缩写

v-bind 缩写:

1
2
3
4
<!-- 完整语法 -->
<a v-bind:href="url"></a>
<!-- 缩写 -->
<a :href="url"></a>

v-on 缩写:

1
2
3
4
<!-- 完整语法 -->
<a v-on:click="doSomething"></a>
<!-- 缩写 -->
<a @click="doSomething"></a>

条件

关键字:

  • v-if
  • v-else
  • v-else-if
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="app">
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
</div>

<script>
new Vue({
el: '#app',
data: {
type: 'C'
}
})
</script>

也可以指定键名和索引进行迭代:

1
2
3
4
5
6
7
<div id="app">
<ul>
<li v-for="(value, key, index) in object">
{{ index }}. {{ key }} : {{ value }}
</li>
</ul>
</div>

循环

  • 关键字:v-for
  • 迭代对象需要是 site in sites 形式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="app">
<ol>
<li v-for="site in sites">
{{ site.name }}
</li>
</ol>
</div>

<script>
new Vue({
el: '#app',
data: {
sites: [
{ name: 'Runoob' },
{ name: 'Google' },
{ name: 'Taobao' }
]
}
})
</script>

计算属性

当处理一些复杂逻辑时,可以把具体实现放到 computed 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div id="app">
<p>原始字符串: {{ message }}</p>
<p>计算后反转字符串: {{ reversedMessage }}</p>
</div>

<script>
var vm = new Vue({
el: '#app',
data: {
message: 'Runoob!'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
// vm.reversedMessage 依赖于 vm.message,在 vm.message 发生改变时,vm.reversedMessage 也会更新
return this.message.split('').reverse().join('')
}
}
})
</script>

computed 与 method 的区别

  • 效果上是一样的
  • computed 基于它的以来缓存,只有相关依赖发生改变时才会重新获取
  • methods 在重新渲染时总会重新调用执行

监听属性

关键字:watch,用于响应数据的变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id = "app">
<p style = "font-size:25px;">计数器: {{ counter }}</p>
<button @click = "counter++" style = "font-size:25px;">点我</button>
</div>
<script type = "text/javascript">
var vm = new Vue({
el: '#app',
data: {
counter: 1
}
});
// 当 counter 改变时 alert 弹窗
vm.$watch('counter', function(nval, oval) {
alert('计数器值的变化 :' + oval + ' 变为 ' + nval + '!');
});
</script>

事件处理器

v-on 用于监听事件。

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
<div id="app">
<!-- `greet` 是在下面定义的方法名 -->
<button v-on:click="greet">Greet</button>
</div>

<script>
var app = new Vue({
el: '#app',
data: {
name: 'Vue.js'
},
// 在 `methods` 对象中定义方法
methods: {
greet: function (event) {
// `this` 在方法里指当前 Vue 实例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName)
}
}
}
})
// 也可以用 JavaScript 直接调用方法
app.greet() // -> 'Hello Vue.js!'
</script>

表单

使用 v-model 进行双向绑定数据。

复选框

多个复选框绑定到同一个数组中。

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
<div id="app">
<p>单个复选框:</p>
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>

<p>多个复选框:</p>
<input type="checkbox" id="runoob" value="Runoob" v-model="checkedNames">
<label for="runoob">Runoob</label>
<input type="checkbox" id="google" value="Google" v-model="checkedNames">
<label for="google">Google</label>
<input type="checkbox" id="taobao" value="Taobao" v-model="checkedNames">
<label for="taobao">taobao</label>
<br>
<span>选择的值为: {{ checkedNames }}</span>
</div>

<script>
new Vue({
el: '#app',
data: {
checked : false,
checkedNames: []
}
})
</script>

单选按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="app">
<input type="radio" id="runoob" value="Runoob" v-model="picked">
<label for="runoob">Runoob</label>
<br>
<input type="radio" id="google" value="Google" v-model="picked">
<label for="google">Google</label>
<br>
<span>选中值为: {{ picked }}</span>
</div>

<script>
new Vue({
el: '#app',
data: {
picked : 'Runoob'
}
})
</script>

select 列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="app">
<select v-model="selected" name="fruit">
<option value="">选择一个网站</option>
<option value="www.runoob.com">Runoob</option>
<option value="www.google.com">Google</option>
</select>

<div id="output">
选择的网站是: {{selected}}
</div>
</div>

<script>
new Vue({
el: '#app',
data: {
selected: ''
}
})
</script>

修饰符

  • .lazy:在默认情况下, v-model 在 input 事件中同步输入框的值与数据,添加一个修饰符 lazy 后转变为在 change 事件中同步
  • .number:输入值转为 number
  • .trim:过滤首尾空格
1
2
<!-- 在 "change" 而不是 "input" 事件中更新 -->
<input v-model.lazy="msg" >

组件

  • 组件可以扩展 HTML 元素,对代码进行封装
  • 任何类型的应用界面都可以抽象为一个组件树

全局组件

注册一个全局组件:

1
Vue.component(tagName, options)

其中:

  • tagName:组件名
  • options:配置选项

组件调用方式:

1
<tagName></tagName>

注册一个组件并使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="app">
<runoob></runoob>
</div>

<script>
// 注册
Vue.component('runoob', {
template: '<h1>自定义组件!</h1>'
})
// 创建根实例
new Vue({
el: '#app'
})
</script>

局部组件

局部组件只能在某个实例中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="app">
<runoob></runoob>
</div>

<script>
var Child = {
template: '<h1>自定义组件!</h1>'
}

// 创建根实例
new Vue({
el: '#app',
components: {
// <runoob> 将只在父模板可用
'runoob': Child
}
})
</script>

Prop

  • prop 是父组件用来传递数据的自定义属性
  • 父组件的数据需要通过 props 把数据传递给子组件
  • 子组件需要显式地用 props 选项声明 prop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div id="app">
<child message="hello!"></child>
</div>

<script>
// 注册
Vue.component('child', {
// 声明 props
props: ['message'],
// 同样也可以在 vm 实例中像 "this.message" 这样使用
template: '<span>{{ message }}</span>'
})
// 创建根实例
new Vue({
el: '#app'
})
</script>

路由

Vue Router 中文文档

<router-link> 相关属性:

  • to:表示目标路由的链接。 当被点击后,内部会立刻把 to 的值传到 router.push()
  • replace:当点击时,会调用 router.replace() 而不是 router.push(),导航后不会留下 history 记录
  • append:在当前 (相对) 路径前添加基路径
  • tag:想要 <router-link> 渲染成某种标签可以使用 tag
  • active-class:设置链接激活时使用的 CSS 类名
  • exact-active-class:配置当链接被精确匹配的时候应该激活的 class
  • event:声明可以用来触发导航的事件

HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<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>

JavaScript

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
// 0. 如果使用模块化机制编程,导入 Vue 和 VueRouter,要调用 Vue.use(VueRouter)

// 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: routes
})

// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
router
}).$mount('#app')

// 现在,应用已经启动了!

Ajax 请求

Vue 使用异步加载需要使用到 vue-resource 库:

1
<script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>

Get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
window.onload = function(){
var vm = new Vue({
el:'#box',
data:{
msg:'Hello World!',
},
methods:{
get:function(){
//发送get请求
this.$http.get('get.php', {params : {a:1,b:2}}).then(function(res){
document.write(res.body);
},function(){
console.log('请求失败处理');
});
}
}
});
}

POST

emulateJSON 的作用: 如果Web服务器无法处理编码为 application/json 的请求,你可以启用 emulateJSON 选项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
window.onload = function(){
var vm = new Vue({
el:'#box',
data:{
msg:'Hello World!',
},
methods:{
post:function(){
//发送 post 请求
this.$http.post('/try/ajax/demo_test_post.php',{name:"test",url:"http://www.runoob.com"},{emulateJSON:true}).then(function(res){
document.write(res.body);
},function(res){
console.log(res.status);
});
}
}
});
}

其他请求方式

vue-resource 还提供了其他 7 种符合 RESTful 设计的请求方式:

1
2
3
4
5
6
7
get(url, [options])
head(url, [options])
delete(url, [options])
jsonp(url, [options])
post(url, [body], [options])
put(url, [body], [options])
patch(url, [body], [options])

参考资料