Todo App
# 安装Vue脚手架
$ npm i vue-cli -g
# 创建工程
$ vue init webpack-simple todos
$ cd todos
$ npm i
# 运行工程
$ npm run dev
# 浏览器访问
http://localhost:8080
目录结构
启动入口
# /todos/src/main.js
//引入组件文件
import Vue from 'vue'
import App from './App.vue'
//创建vue对象实例
new Vue({
el:'#app',
//绘制vue的组件App以完成初始化
//render()渲染机制采用Virtual DOM,一种比浏览器原生DOM更好性能的虚拟组件模型,具有更快的运行速度。
render:h=>h(App)
});
首页结构
# todos/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
</head>
<body>
<div id="app"></app>
<script src="/dist/build.js"></script>
</body>
</html>
组件系统
组件系统提供了一种抽象,使用独立可复用的小组件来构建大型应用,任意类型应用的界面都可以抽象为一个组件树。
单页组件
# /todos/src/App.vue
<template>
<div id="app"></div>
</template>
<script>
export default {
name: 'app',
}
</script>
<style lang="scss">
</style>
Vue实例App所对应的页面元素
<div id="app"></div>
- 一个Vue实例必须与一个页面元素绑定,Vue实例用作Vue的全局配置来使用。
例如:向实例安装路由、资源插件、配置应用于全局的自定义过滤器、自定义指令等。 -
*.vue
是Vue.js特有的文件格式,表示是一个Vue组件,被称为单页式组件。 -
*.vue
文件可同时承载视图模板、样式定义、组件代码,使得组件的文件组织更为清晰与统一。
单页组件由三部分组成
-
<template>
视图模板 -
<script>
组件定义 -
<style>
组件样式表
插值
Vue视图模板是基于DOM实现的,意味着它是有效且可解析的HTML,Vue对一些特殊的特性做了增强。
# /todos/src/App.vue
<template>
<div id="app">
<h1>{{title}}</h1>
</div>
</template>
<script>
export default {
name: 'app',
//data()返回Object对象,是一个对象的属性。使用函数返回是为了具有更高的灵活性。
data () {
return {
//对象属性在视图中以插值语法即Mustache语法,Mustache标签会被相应数据对象的属性值替换,每当属性变化时它也会更新。
title:'todos'
}
}
}
</script>
<style lang="scss">
</style>
- 变量输出会采用插值的方式
- 插值支持JS表达式运算和过滤器
- {{}}引用的内容会被编码,若输出未被编码的文本可使用{{{}}}。
data
有什么用呢?
将vue实例定义看成给一个类的定义,data
相当于类内部字段属性的定义区域,在vue实例内其他地方可直接使用this
引用data
内定义的任何属性。
注意事项
- Vue2组件模板必须有且仅有一个顶层元素,若组件模块内设置多个顶层元素间更会引起编译异常。
- 组件中
template
属性是视图View
,title
属性是模型Model
。
数据绑定
todos为了显示待办事项,这个数据模型应该是一个数组。
# /todos/src/App.vue
<template>
<div id="app">
<h1>{{title}}</h1>
<ul>
<li v-for="item,index in todos">
<strong>{{index+1}}.</strong>
<label>{{item.value}}</label></li>
</ul>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
//标题
title:'待办事项',
//待办事项数组
todos:[
{value:'今天继续阅读Vue', active:false},
{value:'项目注释补充', active:true},
{value:'每日心得日志', active:false}
]
}
}
}
</script>
<style lang="scss">
</style>
为什么没有操作DOM却将data内的变量设置到DOM上去了呢?
- vue代码中直接操作DOM是不被推荐的
- DOM是被vue直接托管的,绑定到DOM上的变量一旦变化,DOM对应的属性会被vue自动重绘而无需通过编码来显式地操作。
样式绑定
# 安装插件
& npm i less style-loader css-loader less-loader less -D
# webpack.config.js中设置modules
module: {
rules: [
{
test:/\.less$/,
loader:'style-loader!css-loader!less-loader'
},
}
# 创建样式表
$ vim /todos/assets/style.less
# 组件中引入样式 App.vue
//使用import将样式表直接导入到代码
import './assets/style.less'
export default {
//...
}
使用import
将样式表直接导入到代码:
webpack
的less-loader
会生成部分代码,在页面运行时将编译后的less代码生成到<style>
标签内并自动插入到页面的<head>
中。注意的是这种做法是全局的,样式会长期驻留页面直至vue的根(root)实例被销毁。
import './assets/style.less'
若希望样式表仅引用于当前组件,可使用<style scoped>
,然后用CSS的@import
导入样式表。
<style scoped>
@import './assets/app.less'
</style>
样式的绑定和属性的绑定方式是一样的
<li v-for="(item,index) in todos" v-bind:class="{'active':item.active}"></li>
# 简写
<li v-for="(item,index) in todos" :class="{'active':item.active}"></li>
完整代码
<template>
<div id="app">
<h1>{{title}}</h1>
<ul>
<li v-for="item,index in todos" :class="{'active':item.active}">
<strong>{{index+1}}.</strong>
<label>{{item.value}}</label></li>
</ul>
</div>
</template>
<script>
import './assets/style.less'
export default {
name: 'app',
data () {
return {
//标题
title:'待办事项',
//待办事项数组
todos:[
{value:'今天继续阅读Vue', active:false},
{value:'项目注释补充', active:true},
{value:'每日心得日志', active:false}
]
}
}
}
</script>
<style scoped>
@import './assets/app.less'
</style>
注意事项
vue的属性绑定语法是attribute="expression"
,attribute
是元素接收的属性值,既可以是原生的也可以是自定义的,expression
则是在vue组件内 由data
或props
内定义的对象属性,或者是一个合法的表达式。若在元素属性中不加入:
则认为是向这个属性赋上字符串值而非Vue组件上定义的属性引用。
样式绑定 VS 普通绑定
vue的样式绑定与普通属性绑定最大区别,凡是样式绑定必然是绑定到判断对象的,不能直接写CSS类名,即使要绑定一个固定的CSS类也都要写:class="{'classname':boolean}"
,除非不使用样式绑定。
样式类 VS 样式属性
无论绑定的是样式类还是样式属性,:class
和:style
表达式内一定是一个JSON对象。
- 样式类
:class
的JSON对象值是布尔型,true
表示添加样式false
表示移除样式。 - 样式属性
:style
的JSON对象是样式配置项,key
声明属性名,value
是样式属性值。
过滤器
需求:为待办事项添加时间字段并格式化后显示
# 安装时间格式化专用包
$ npm i moment -S
$ vim /src/App.vue
<template>
<div id="app">
<h1>{{title}}</h1>
<ul>
<li v-for="(item,index) in todos" :class="{'active':item.active}">
<strong>{{index+1}}.</strong>
<label>{{item.value}}</label>
<time>{{item.created_at | date}}</time>
</li>
</ul>
</div>
</template>
<script>
import './assets/style.less'
//时间日期专用插件
import moment from 'moment'
import 'moment/locale/zh-cn'
moment.locale('zh-cn')
export default {
name: 'app',
data () {
return {
//标题
title:'待办事项',
//待办事项数组
todos:[
{value:'今天继续阅读Vue', active:false, created_at:Date.now()},
{value:'项目注释补充', active:true, created_at:Date.now()+30000},
{value:'每日心得日志', active:false, created_at:Date.now()-30000}
]
}
},
//过滤器
filters:{
date(val){
return moment(val).calendar()
}
}
}
</script>
<style scoped>
@import './assets/app.less'
</style>
注意:过滤器中是没有this
引用的,过滤器内的this
是一个undefined
值。不要在过滤器中尝试引用组件实例的变量或方法,否则会引发空值引用的异常。
# package.json
{
"name": "todos",
"description": "A Vue.js project",
"version": "1.0.0",
"author": "junchow",
"license": "MIT",
"private": true,
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
},
"dependencies": {
"moment": "^2.22.1",
"normalize.less": "^1.0.0",
"vue": "^2.5.11"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.0",
"babel-preset-stage-3": "^6.24.1",
"cross-env": "^5.0.5",
"css-loader": "^0.28.11",
"file-loader": "^1.1.4",
"less": "^3.0.4",
"less-loader": "^4.1.0",
"node-sass": "^4.5.3",
"sass-loader": "^6.0.6",
"style-loader": "^0.21.0",
"vue-loader": "^13.0.5",
"vue-template-compiler": "^2.4.4",
"webpack": "^3.6.0",
"webpack-dev-server": "^2.9.1"
}
}