$ npm i vue-cli -g
首先,在组件脚本定义中使用data定义用于内部访问的数据模型:
export default { ... data () { return { title: "vue-todos" } } }
export default { ... data : { title: "vue-todos" } }
在Vue实例内的其他地方可以直接用this引用data内定义的任何属性,比如this.title就是引用了data.title。
循环对象(value,key,index) in user
可以写事件名也可以写表达式
event:
不传入参数时,默认事件的第一个参数为事件对象
传入参数时,要获得事件对象需要传入$event
1. lazy
输入框失去焦点或者按下回车时,v-model绑定的值才会发生变化,即在“change”时而非“input”时更新
2. number
自动将用户的输入值转为数值类型
。
3. trim
自动过滤用户输入的首尾空白字符
。
使用$nextTick
能立即拿到结果
这里推荐一个简单的记忆方法来学习Vue的样式绑定,无论绑定的是样式类还是样式属性,:class和:style表达式内一定是一个JSON对象。凡是样式绑定必然是绑定到判断对象上的
● :class的JSON对象的值一定是布尔型的,true表示加上样式,false表示移除样式类。
● :style的JSON对象则像是一个样式配置项,key声明属性名,value则是样式属性的具体值。
<li v-for="(todo,index) in todos" v-bind:class="{'checked': todo.done}">
三元表达式方法: :class="{to.done?'checked':''}"
一个很出名的时间格式化专用的包——moment.js,先安装moment.js:
$ npm i moment -S import moment from 'moment' import 'moment/locale/zh-cn' moment.locale('zh-cn')
export default { // 省略... filters: { date(val) { return moment(val).calendar() } } }
最后在模板上应用这个过滤器:
<time>{{ todo.created | date }}</time>
在所有的过滤器中是没有this引用的,过滤器内的this是一个undefined的值,所以不要在过滤器内尝试引用组件实例内的变量或方法,否则会引发空值引用的异常。
咳咳
安装
$ npm i vue-router -D
vue-router实例是一个Vue的插件,我们需要在Vue的全局引用中通过Vue.use() 将它接入到Vue实例中。在我们的工程中,main.js是程序入口文件,所有的全局性配置都会
在这个文件中进行。
打开main.js文件并加入以下的引用:
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter)
Vue.js是没有页面这个概念的,Vue.js的容器就只有组件。但我们用vue-router配合组件又会重新形成各种的“页”,那么我们可以这样来约定和理解:
(1)页面是一个抽象的逻辑概念,用于划分功能场景。
(2)组件是页面在Vue的具体实现方式。
一定要谨记以上这两点,因为在后面的内容中还会围绕这个约定对我们的项目进行结构性的优化。
我们创建VueRouter实例时用了mode:history的参数,这个值的意思是使用history模式,这种模式充分利用了history.pushState API来完成URL跳转而无须重新加载页面。
如果不使用history模式,当访问home的时候地址就会变为:
http://localhost/#home
反之为:
http://localhost/home
这就是history模式与hash模式的区别
● Hash:使用URL hash值来作为路由。支持所有浏览器,包括不支持HTML5 History API的浏览器。
● History:依赖HTML5 History API和服务器配置。查看HTML5 History模式。
vue-router提供了两个指令标签(directive)组件来处理这个导航与自动渲染逻辑:
● <router-view>——渲染路径匹配到的视图组件,它还可以内嵌自己的<router-view>,根据嵌套路径渲染嵌套组件。
● <router-link>——支持用户在具有路由功能的应用中(点击)导航。
<router-link :to="{ name : 'Home' }">
有一个细节需要留意,使用命名路由引用时采用的是:to而不是to,因为这个时候向<router-link>传入的是一个对象{name:'Home'}而不是字符串。
routes: [{ name:'BookDetails', path:'/books/:id' component: BookDetails
在<router-link>中我们就可以加入一个params的属性来指定具体的参数值:
<router-link :to="{name:'BookDetails', params: { id: 1 }}"> <!-- ... --> </router-link>
可以通过$router.params这个属性获取指定的参数值,例如:
export default { created () { const bookID = this.$router.params.id } }
对路由参数的变化做出响应的话,就需要在watch对象内添加对$route对象变化的跟踪函数:
export default { template: '...', watch: { '$route' (to, from) { // 对路由变化作出响应 } } }
routes: [ { name:'Main', path:'/', component:Main, children:[ {name:'Home', path: 'home', component: Home}, {name:'Categories',path: 'categories', component: Category}, {name:'ShoppingCart',path: 'shopping-cart', component: ShoppingCart},
注意: 子路由中的path就不需要重新声明主路由上的path了,在生成路由时,主路由的path会被自动添加到子路由之前。另外,以“/”开头的嵌套路径会被当作根路径,所以不要在子路由上加上“/”。
想要链接使用“精确匹配模式”,则使用exact属性
<!-- 这个链接只会在地址为/的时候被激活 --> <router-link :to="{name:'Home'}" exact>
当我们在使用HTML5的History模式的时候,每次路由的改变都会被“推”(push)到导航历史中保留,
首先Vue实例内有一个$router对象,这个对象会提供三个方法,<router-link>则是用两种属性来对应这三个方法的调用:
router的方法 | 属 性 | 说 明 |
push() | — | 默认调用此方法 |
append() | append | 将目标URL追加到当前URL下 |
replace() | replace | 以目标URL替换现有的URL |
设置replace属性的话,当点击时,会调用router.replace()而不是router.push(),于是导航后不会留下History记录。
<router-link :to="{ name: 'Home' }" replace></router-link>
区块的划分
我们做前端开发都是从上而下地进行设计与布局,如果按功能或者内容分类来对整个页面进行划分的话,你会很自然地将一个页面的内容分为一个或多个功能区
用HTML的注释标记作为页面上的“区域占位”,先给页面搭一个最基本的结构,把组件放在主组件上,样式布局
避免低耦合应该使用Vue.js提供的更精确的指明方式在元素中添加ref属性,然后在代码内通过this.$refs.引用名来引用。
例子:
一个父组件包含一个子组件,这个子组件界面各个部位都需要相同的数据显示框,那这个显示框可以作为一个公共的组件componets,子组件各个部位数据定义在自己script里,通过props传值到公共组件内显示。
子组件代码:
<div class="section"> <book-list :books="latestUpdated" heading="最新更新"> </book-list> </div> <div class="section"> <book-list :books="recommended" heading="编辑推荐"> </book-list> </div> // 按照工程结构约定,组件放置在components目录 import BookList from "./components/BookList.vue" export default { data (){ announcement:'今日上架的图书全部8折', slides:[ { id:1, img_url:'./fixtures/sliders/t2.svg' }, { id:2, img_url:'./fixtures/sliders/t2.svg' } ], latestUpdated: [...],// 这两个数组内容太多,为了便于阅读此处略去具体定义 recommended : [...] }, components: { BookList }, ... }
BooKList作为显示数据的公共组件。代码如下:
<template> <div class="book-list"> <div class="header"> <div class="heading">{{ heading }}</div> <div class="more">更多...</div> </div> <div class="book-items"> <div class="book" v-for="book in books"> <div class="cover"> <img :src="book.img_url"/> </div> <div class="title">{{ book.title }}</div> <div class="authors">{{ book.authors | join }}</div> </div> </div> </div> </template> export default { props: [ 'heading', // 标题 'books' // 图书对象数组 ], 过滤器作用:将author作者数组转换成字符串类型 filters: { join(args){ return args.join(',') } } }
要向组件输入数据就不能使用data来作为数据的容器了,因为data是一个内部对象,此时就要换成props。公共组件最好不要有data,因为他的数据展示来源于要用它的组件,所以prop传值比较好!
其实组件间有关系就肯定涉及到组件通讯。父子传值(可以看我之前发的博客有介绍)
data里数据重构,网络请求,一般data里留空
公共组件已经布局好,需要新增一些自己这个界面需要的东西新功能,比如插入代码实现,就用到了插槽<slot>
<template> <div class="dialog"> <div> <!-- 头部及标题 --> 命名插槽 <slot name="header"></slot> </div> <div> <!-- 内容区域 --> 默认插槽,一个组件只能拥有一个默认插槽,其他的插槽则需要采用name属性进行命名,在使用的时候也需要对插槽进行声明。 <slot></slot> </div> </div> </template>
别的组件用这个组件时,只需要在这个组件标签内使用template标签包裹 v-slot=‘name’值,即可一一对应。插入部分可写代码,再运用父子传值。
<Slotname>
<template v-slot : header>
nihao
</template>
</Slotname>
(1)依葫芦画瓢 ——拿到界面设计图后无须思考太多,先用框架圈出功能区块,然后直接编写视图的HTML。
(2)代码去重 ——将视图模板中不断重复的逻辑封装为组件,减少页面的重复逻辑。
(3)抽取数据结构 ——将页面中的文字用数据对象与数组取代,并制定数据结构的说明文档。
(4)采集与制作样本数据 ——参照数据结构说明文档采集更多的真实样本,切忌胡乱地敲入一些字符,在数据不明确的情况下可能会遮盖一些本应很明显的使用需求。
(5)分析设计组件接口 ——简化组件的使用接口,让组件变得更好用。
(6)组件内部的细化与重构 ——优化组件的内部实现,使其变得更合理。
将不同组件拥有相同功能一样的代码弄出来成立一个js文件,再到不同的组件内导入这个js,再使用
mixins: [BaseListMixin],
例子:
这两个组件在交互处理的逻辑上有很大一部分是相同的,或者说它们的控制部分应该是从一个组件中继承下来的。这个时候我们就可以用Vue的mixins实现这种功能性的混合。首先将两个控件中完全相同的部分提取出来,做成一个BaseListMixin.js的组件:
然后将uk-list和uk-dropdown-list中相同的代码删除,用mixins引入BaseMixinList类,这样在BaseMixinList中定义的属性(props)、方法(methods)、计算属性等所有的Vue组件内允许定义的字段都会被混合到新的组件中,其效果就如类继承。
import BaseListMixin from './BaseListMixin' export default { mixins: [BaseListMixin],
混合比继承好的地方就是一个Vue组件类可以与多个不同的组件进行混合(mixins是一个数组,可以同时声明多个混合类),复合出新的组件类
1.安装axios npm install axios -S
2.全局注册,在main.js中 引入 import axios from 'axios' , 注册Vue.prototype.$http = axios
axios封装:
import axios from 'axios' import config from '@/config' const baseUrl = process.env.NODE_ENV === 'development' ? config.baseUrl.dev : config.baseUrl.pro //console.log(process.env.NODE_ENV) class HttpRequest{ constructor(baseUrl){ this.baseUrl = baseUrl this.queue = {} } getInsideConfig(){ const config = { baseURL:this.baseUrl, header:{ // } } return config } interceptors(instance,url){ instance.interceptors.request.use((config)=>{ //处理config console.log('拦截和处理请求') config.data = { msg:"helloworld" } console.log(config) return config }) instance.interceptors.response.use((res)=>{ //处理响应 console.log("处理响应") //console.log(res) return res.data },(error)=>{ //请求出问题,处理问题 console.log(error) return {error:"网络出错了"} }) } request(options){ const instance = axios.create()//创造实例对象 options = Object.assign(this.getInsideConfig(),options) this.interceptors(instance,options.url) return instance(options) } } const axiosObj = new HttpRequest(baseUrl) export default axiosObj
data.js里面的代码
import axios from '@/api/axios'
export const getTableData = () => {
return axios.request({
url: "tabledata",
method: 'get'
})
}
在main.js里面判断,请求去api文件夹下的mock.js
if (process.env.NODE_ENV == 'development') require('@/api/mock')
Mock.js代码
import Mock from 'mockjs';
//配置请求延时
Mock.setup({
timeout: 1000
})
Mock.mock(‘url’,{
"date":"@date",//随机日期
"float":"@float",//随机浮点数
"name":"xxxx",//固定值
"quoteStrin1": "@name",//引用其他属性
"user": {
"name": "demo"
},//固定值
"quoteString": "@user/name",//引用其他属性
})
Vue-DevTools对于初学者来说是一个很不错的选择,可以很好地辅助理解Vue的运行原理。
视图的常规操作有:
● 数据分页——对于数据量很大的数据表我们会将其分成很多个数据页显示,在移动端会表现为以滑动加载的方式渐入分页;
● 条件查询——包括快速查询或者多个条件组合性的查询,用于过滤和筛选目标数据;
● 排序——对各个列进行正向或逆向的数据排序;
● 多行选定——当我们需要对一多行数据进行同一个操作时就需要视图能支持多行选定功能,例如批量删除;
● 添加/编辑/显示单行数据的入口——这是数据视图的一个很重要的功能,即使是一个只读视图我们也应该提供一个能查看数据的详情表单。
其实可以在这章进行elementui框架的学习,有时间补充。
$ npm i vuex -S
在全局Vue实例中引入store后,在每个Vue组件实例内,可以用this.$store来引用这个store对象了。
● state——Vuex store实例的根状态对象,用于定义共享的状态变量,就像Vue实例中的data。
● getters——读取器,外部程序通过它获取变量的具体值,或者在取值前做一些计算(可以认为是store的计算属性)。
● actions——动作,向store发出调用通知,执行本地或者远端的某一个操作(可以理解为store的methods)。
● mutations——修改器,它只用于修改state中定义的状态变量。
● modules——模块,向store注入其他子模块,可以将其他模块以命名空间的方式引用。
● strict——用于设置Vuex的运行模式,true为调试模式,false为生产模式。
● plugin——用于向Vuex加入运行期的插件。
用Vuex提供的帮助方法mapGetters,
mapGetters本质上就是动态方法生成器,作用就是生成上面那些将store.getter方法映射为Vue实例的computed。
例子:store.js代码
export default { state: { announcements:[], promotions:[], recommended:[] }, getters: { announcements: state => state.announcements, promotions: state => state.promotions, recommended: state => state.recommended, totalPromotions: state => state.promotions.length, totalRecommended: state => state.recommended.length } }
vue界面展示vuex数据
import {mapGetters} from 'vuex' export default { computed: { ...mapGetters([ 'announcements', 'promotions', 'recommended', 'promotionCount', 'recommendedCount' ]) } }
● commit——等同于store.commit,用于提交一个mutation。
● dispatch——等同于store.dispatch,用于调用其他action。
在组件界面可以通过
this.$store.dispatch('getStarted')
访问到vuex中的action内的方法
actions: { getStarted (context) { Vue.http.get('/api/get-start', (res)=>{ context.commit('startedDataReceived',res.body) }) } }
同样地,Vuex也提供了将Actions映射为methods的帮助方法mapActions,这样我们就不用直接调用dispatch,而是可以对象化地进行操作了:
methods: { ...mapActions(['getStarted']) }, created () { this.getStarted() }
Vue官方推荐了以下的文件结构的组织方式及使用原则:
(1)应用级的状态集中在store中。
(2)修改状态的唯一方式就是通过提交mutation来实现的,它是同步的事务。
(3)异步逻辑应该封装在action中,并且可以组合action。
只要遵循这些规则,可以任意设计项目结构。如果store文件非常大,直接开始分割action、mutation和getter到多个文件。
对于复杂的应用,我们可能需要使用模块化。
个人总结:在一个小型模块项目中,所有的网络请求均需要进行封装成各个模块对应的js,在vuex中action中进行异步请求可以直接使用这些模块化的请求的函数,同时vuex也需要模块化。
本文总结根据《vue2实践揭秘》这本书所做的笔记,只作为相互学习的用途。