一 安装
1.cnpm install -g vue-cli
2.用vue-cli脚手架默认是生成vue 2.x的版本,但可以通过修改命令,来生成1.0的版本。
vue init webpack#1.0 my-project
npm install -g vue-cli //全局安装vue-cli
vue init webpack projectName //生成项目名为projectName的模板,这里的项目名projectName随你自己写
cd projectName
npm install //初始化安装依赖
这样子项目就安装完了。生成的项目下面的目录是这样的
然后执行
npm run dev
在浏览器打开http://localhost:8080,则可以看到欢迎页了。
二 v-for
数组变化检测(Array Change Detection)
变化数组方法(Mutation Methods)
Vue 将观察数组(observed array)的变化数组方法(mutation method)包裹起来,以便在调用这些方法时,也能够触发视图更新。这些包裹的方法如下:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
可以打开控制台,然后对前面示例中的 items 数组调用变化数组方法。例如:example1.items.push({ message: 'Baz' })。
替换一个数组(Replacing an Array)
变化数组方法(mutation method),顾名思义,在调用后会改变原始数组。相比之下,还有非变化数组方法(non-mutating method),例如 filter(), concat() 和 slice(),这些方法都不会直接修改操作原始数组,而是返回一个新数组。当使用非变化数组方法时,可以直接将旧数组替换为新数组:
|
你可能会认为这将导致 Vue 丢弃现有 DOM 并重新渲染(re-render)整个列表 - 幸运的是,情况并非如此。Vue 实现了一些智能启发式方法(smart heuristic)来最大化 DOM 元素重用(reuse),因此将一个数组替换为包含重叠对象的另一个数组,会是一种非常高效的操作。
注意事项(Caveats)
由于 JavaScript 的限制,Vue 无法检测到以下数组变动:
- 当你使用索引直接设置一项时,例如 vm.items[indexOfItem] = newValue
- 当你修改数组长度时,例如 vm.items.length = newLength
为了解决第 1 个问题,以下两种方式都可以实现与 vm.items[indexOfItem] = newValue 相同的效果,但是却可以通过响应式系统出发状态更新:
|
|
为了解决第 2 个问题,你可以使用 splice:
|
https://www.vuefe.cn/v2/guide/list.html#数组变化检测-Array-Change-Detection
三 简写(shorkhands)
v-bind
简写
|
v-on 简写
|
四 v-model
v-model
- 预期: 随表单控件类型不同而不同。
- 限制:<input><select><textarea>components
- 修饰符:.lazy - 取代 input 监听 change 事件.number - 输入字符串转为数字.trim - 输入首尾空格过滤
- 用法:在表单控件或者组件上创建双向绑定。细节请看下面的教程链接。
- 参考:表单控件绑定组件 - 在输入组件上使用自定义事件
- Source
五 本地模拟线上数据
①
1.dev-server.js
const jsonServer = require('json-server')
const apiServer = jsonServer.create()
const apiRouter = jsonServer.router('db.json')
const middlewares = jsonServer.defaults()
apiServer.use(middlewares)
apiServer.use('/api', apiRouter)
apiServer.listen(port + 1, () => {
console.log('JSON Server is running')
})
2.config下的index
设置代理
proxyTable: {
'/api/': 'http://localhost:8081/'
},
3.前台页面调用路由
created () {
this.$http.post('api/getList',{userId:123})
.then(function (data) {
console.log(data)
},function (err) {
console.log(err)
})
},
4.访问
http://localhost:8080/api/getNewsList
②
var apiServer = express()
var bodyParser = require('body-parser')
apiServer.use(bodyParser.urlencoded({ extended: true }))
apiServer.use(bodyParser.json())
var apiRouter = express.Router()
var fs = require('fs')
apiRouter.route('/:apiName')
.all(function (req, res) {
fs.readFile('./db.json', 'utf8', function (err, data) {
if (err) throw err
var data = JSON.parse(data)
if (data[req.params.apiName]) {
res.json(data[req.params.apiName])
}
else {
res.send('no such api name')
}
})
})
apiServer.use('/api', apiRouter);
apiServer.listen(port + 1, function (err) {
if (err) {
console.log(err)
return
}
console.log('Listening at http://localhost:' + (port + 1) + '\n')
})
六 props $emit
①props子组件向父组件传递属性
props: {
slides: {
type: Array,//属性类型
default: [ { label:'test', value:0 } ] //属性默认值
},
inv: {
type: Number,
default: 1000
}
},
②$emit向父组件传递事件
methods: {
goto (index) {
this.isShow = false
setTimeout(() => {
this.isShow=true
this.nowIndex=index
this.$emit('onchange', index) //向父组件传递事件
},10)
},
③父组件调用
<slide-show :slides="slides" :inv="slideSpeed" @onchange="doSomethingSlideChange"></slide-show>
methods: {
doSomethingSlideChange (index) {
console.log("doSomethingSlideChange run!"+index); //拿到子组件传递来的参数
}
}
七 slot 插槽 $refs
弹窗一样内容不一样
import Dialog from './base/dialog.vue'
import LogForm from './logForm.vue'
import RegForm from './regForm.vue'
export default {
components:{MyDialog: Dialog,LogForm,RegForm},
<my-dialog :isShow="isShowLogDialog" @on-close="closeDialog('isShowLogDialog')">
<log-form @has-log="onSuccessLog"></log-form>
</my-dialog>
<my-dialog :isShow="isShowRegDialog" @on-close="closeDialog('isShowRegDialog')">
<reg-form></reg-form>
</my-dialog>
<my-dialog :isShow="isShowAboutDialog" @on-close="closeDialog('isShowAboutDialog')">
<p>本报的发展态势和面临的问题。报企事业单位和社会各界提供决策依据。</p>
</my-dialog>
这里在父组件演示了三个弹窗,两个组件(LogForm,RegForm)为插槽,但内容不同,组件里面的为插槽,子组件为
<transition name="drop">
<div class="dialog-content" v-if="isShow" >
<p class="dialog-close" @click="closeMyself">x</p>
<slot>empty</slot>
</div>
</transition>
methods: {
closeMyself () {
this.$emit('on-close')
}
}
refs dom元素接口
<div class="slider-group" ref="sliderGroup">
<slot>
</slot>
</div>
this.$refs.sliderGroup 为当前dom元素
this.$refs.sliderGroup.children 他的子集
八 嵌套路由 children
main.js
{
path: '/detail',
component: DetailPage,
redirect:'/detail/analysis',
children: [
{
path: 'analysis',
component:DetailAnaPage
},
{
path: 'count',
component:DetailConPage
},
{
path: 'forecast',
component:DetailForPage
},
{
path: 'publish',
component:DetailPubPage
}
]
}
默认重定向'/detail/analysis',嵌套路由用children,不能有斜杠,要不然会回到主页面。
detail.vue
<img :src="productIcon">
<ul>
<router-link v-for="item in products"
:to="{path:item.path} " tag="li" active-class="active">
{{ item.name }}
</router-link>
</ul>
img不在遍历的范围内,要映射路由
export default {
data () {
return {
imgMap: {
'/detail/count': require("../assets/images/1.png"),
'/detail/forecast': require("../assets/images/2.png"),
'/detail/analysis': require("../assets/images/3.png"),
'/detail/publish': require("../assets/images/4.png")
}
}
},
computed: {
productIcon () {
return this.imgMap[this.$route.path]
}
}
}
九 watch mounted
子组件通过input框的动态数据监听,并且发送监听事件和参数
watch:{
number () {
this.$emit('on-change',this.number)
}
},
父组件通过绑定on-change事件处理数据和参数$event
$event就是val的值,第一个参数给默认值赋值
<v-mul-chooser :selections="versionList" @on-change="onParamChange('versions', $event)"></v-mul-chooser>
onParamChange (attr, val) {
this[attr] = val
this.getPrice()
},
mouted页面加载完成发送一次服务器请求
getPrice () {
let reqParams = {
buyNumber: this.buyNum,
buyType: this.buyType.value,
period: this.period.value,
version: this.versions.join(',')
}
this.$http.post('/api/getPrice', reqParams)
.then((res) => {
this.price = res.data.amount
console.log(reqParams )
})
}
十 内部通过点击的方法进入其他路由
toOrderList () {
this.$router.push({path:'/orderList'})
}
十一 非父子组件通信
新建eventBus.js
import Vue from 'vue'
const eventBus =new Vue()
export { eventBus }
全局layout.vue文件
<template>
<div @click="resetComponent">
import {eventBus } from '../eventBus'
resetComponent () {
eventBus.$emit('reset-component')
}
selection组件
import {eventBus} from '../../eventBus'
mounted(){
eventBus.$on('reset-component',()=>{
this.isDrop=false
})
},
https://www.vuefe.cn/v2/guide/components.html#非父子组件通信-Non-Parent-Child-Communication
十二 vuejs的.vue文件中的style标签中的css样式,背景图路径不对
如果你用了vue-cil,那么在build目录下找到utils.js中的ExtractTextPlugin.extract({}),里面添加下面这个属性就完美解决了publicPath: '../../',
输出空白页:conf文件夹下index build 添加一个点 assetsPublicPath: './',
style 也可以引入文件 添加scoped
<style src="../../static/css/flexslider.css" ></style>
<style src="../../static/css/index.css" scoped></style>
十三 代理跨域解决
proxyTable: { '/list': { target: 'http://api.xxxxxxxx.com', changeOrigin: true, pathRewrite: { '^/list': '/list' } } }
作者:almon123
链接:http://www.jianshu.com/p/95b2caf7e0da
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这样我们在写url的时候,只用写成/list/1
就可以代表api.xxxxxxxx.com/list/1
.
那么又是如何解决跨域问题的呢?其实在上面的'list'
的参数里有一个changeOrigin
参数,接收一个布尔值,如果设置为true
,那么本地会虚拟一个服务端接收你的请求并代你发送该请求,这样就不会有跨域问题了,当然这只适用于开发环境。增加的代码如下所示:
十四 axios配置
man.js
配置post请求处理数据
http://www.cnblogs.com/Upton/p/6180512.html
import Qs from 'qs'
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.prototype.$http=axios;
var axios_instance = axios.create({
// baseURL:'http://localhost',
transformRequest: [function (data) {
console.log(data);
data = Qs.stringify(data);
return data;
}],
headers:{'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'}
})
Vue.use(VueAxios, axios_instance)
axios请求数据接口,遇到困难的时候需要伪装
请求qq音乐全部歌单接口,增加headers已达到欺骗目的为自己所用
express后端配置
var app = express()
var apiRoutes = express.Router()
apiRoutes.get('/getDiscList', function (req, res) {
var url = 'https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg'
axios.get(url, {
headers: {
referer: 'https://c.y.qq.com/',
host: 'c.y.qq.com'
},
params: req.query
}).then((response) => {
res.json(response.data)
}).catch((e) => {
console.log(e)
})
})app.use('/api', apiRoutes)
前端请求
const url = '/api/getDiscList'
const data = {
g_tk: 1928093487,
inCharset: 'utf-8',
outCharset: 'utf-8',
notice: 0,
format: 'json',
platform: 'yqq',
hostUin: 0,
sin: 0,
ein: 29,
sortId: 5,
needNewCode: 0,
categoryId: 10000000,
rnd: Math.random(),
}
axios.get(url, {
params: data
}).then((res) => {
console.log(res);
})
十五 配置sass
最新版本 "vue": "^2.5.2" 配置sass
npm install node-sass --save-dev
npm install sass-loader --save-dev
<style lang="scss">
十六 查看详细文档
v-once
也可以通过使用 v-once 指令,执行一次性插值,也就是说,在数据改变时,插值内容不会随之更新。但是请牢记,这也将影响到同一节点上的所有绑定:
|
修饰符(Modifiers)
修饰符(modifier)是以 . 表示的特殊后缀,表明应当以某种特殊方式绑定指令。例如,.prevent 修饰符告诉 v-on 指令,在触发事件后调用 event.preventDefault():
|
computed 缓存 vs method 方法
不使用 computed 属性,而是在 methods 中定义一个相同的函数。对于最终结果,这两种方式确实恰好相同。然而,细微的差异之处在于,computed 属性会基于它所依赖的数据进行缓存。每个 computed 属性,只有在它所依赖的数据发生变化时,才会重新取值(re-evaluate)。这就意味着,只要 message 没有发生变化,多次访问 computed 属性 reversedMessage,将会立刻返回之前计算过的结果,而不必每次都重新执行函数。
这也同样意味着,如下的 computed 属性永远不会更新,因为 Date.now() 不是一个响应式的依赖数据
为什么我们需要将依赖数据缓存起来?假设一种场景,我们有一个高性能开销(expensive)的 computed 属性 A,在 computed 属性的 getter 函数内部,需要遍历循环一个巨大数组,并进行大量计算。然后还有其他 computed 属性直接或间接依赖于 A。如果没有缓存,我们将不可避免地多次执行 A 的 getter 函数,这远多余实际需要执行的次数!然而在某些场景下,你可能不希望有缓存,请使用 method 方法替代。
使用 v-for 遍历组件
现在,在 2.2.0+ 版本,当对组件使用 v-for 时,必须设置 key 属性。
然而,这里无法自动向组件中传入数据,这是因为组件有自己的独立作用域。为了将组件外部的迭代数据传入组件,我们还需要额外使用 props:
|
事件修饰符
|
2.1.4+ 新增
|
按键修饰符
在监听键盘事件时,我们经常需要监测常见的键值。 Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:
|
记住所有的 keyCode 比较困难,所以 Vue 为最常用的按键提供了别名:
|
全部的按键别名:
- .enter
- .tab
- .delete (捕获 “删除” 和 “退格” 键)
- .esc
- .space
- .up
- .down
- .left
- .right
可以通过全局 config.keyCodes 对象自定义按键修饰符别名:
|
修饰符
.lazy
在默认情况下, v-model 在 input 事件中同步输入框的值与数据 (除了 上述 IME 部分),但你可以添加一个修饰符 lazy ,从而转变为在 change 事件中同步:
|
.number
如果想自动将用户的输入值转为 Number 类型(如果原值的转换结果为 NaN 则返回原值),可以添加一个修饰符 number 给 v-model 来处理输入值:
|
这通常很有用,因为在 type="number" 时 HTML 中输入的值也总是会返回字符串类型。
.trim
如果要自动过滤用户输入的首尾空格,可以添加 trim 修饰符到 v-model 上过滤输入:
|
vue实例属性
vue实例属性
// console.log(app.$data)
// console.log(app.$props)
// console.log(app.$el)
// console.log(app.$options)
// app.$options.render = (h) => {
// return h('div', {}, 'new render function')
// }
// console.log(app.$root === app)
// console.log(app.$children)
// console.log(app.$slots)
// console.log(app.$scopedSlots)
// console.log(app.$refs)
// console.log(app.$isServer) 服务端渲染
// const unWatch = app.$watch('text', (newText, oldText) => {
// console.log(`${newText} : ${oldText}`)
// })
// setTimeout(() => {
// unWatch()
// }, 2000)
// app.$once('test', (a, b) => {
// console.log(`test emited ${1} ${b}`)
// })
// app.$emit('test',1,2)
// setInterval(() => {
// app.$emit('test', 1, 2)
// }, 1000)
// app.$forceUpdate() app.$set(app.obj,'a',i) app.$delete() 防止内存泄漏
module.exports = function (source, map) {
this.callback(null, 'module.exports = function(Component) {Component.options.__docs = ' +
JSON.stringify(source) +
'}', map)
}
// const docsLoader = require.resolve('./doc-loader');
module.exports = (isDev) => {
return {
preserveWhitepace: true,
extractCSS: !isDev,//上线环境不提取每个vue的css,开发环境进行热加载
cssModules: {
localIdentName: isDev ? '[path]-[name]-[hash:base64:5]' : '[hash:base64:5]', //生成另外一个自定义class
camelCase: true //将css中的-变成js中的与驼峰命名,在组件的style标签增加一个module属性,就会按照这个执行
},
// hotReload: false, // 组件热重载功能根据环境变量生成
// loaders:{
// 'docs':docsLoader
// }
}
}//调用 在app.vue中console.log(Header.__docs)
{
loader:'css-loader',
options:{
module:true,
localIdentName: isDev ? '[path]-[name]-[hash:base64:5]' : '[hash:base64:5]',
}
},
const merge = require('webpack-merge')//帮助合并config文件
const ExtractPlugin = require('extract-text-webpack-plugin') //抽离css文件
const devServer = {
port: 8000,
host: '0.0.0.0',
overlay: {
errors: true,
},
hot: true,//热加载 修改某个页面,就只重新渲染这个页面 增加 config.plugins.push
// open:true 默认打开浏览器
// historyFallback:{}
}
config = merge(baseConfig, {
devtool: '#cheap-module-eval-source-map', //帮助调试
plugins:defaultPluins.concat([ //顺序不能变
new ExtractPlugin('styles.[contentHash:8].css'),
new webpack.optimize.CommonsChunkPlugin({ //抽离稳定的内库文件
name: 'vendor'
}),
new webpack.optimize.CommonsChunkPlugin({ //webpack新的模块
name: 'runtime'
})
])
eslint自动修复
"lint": "eslint --ext .js --ext .jsx --ext .vue client/",
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue client/" //修复eslint的问题
"eslint": "^4.16.0",
"eslint-config-standard": "^11.0.0-beta.0",
"eslint-loader": "^1.9.0",
"eslint-plugin-html": "^4.0.1",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-node": "^5.2.1",
"eslint-plugin-promise": "^3.6.0",
"eslint-plugin-standard": "^3.0.1",
{
"extends": "standard",
"plugins": [
"html"
],
"parser": "babel-eslint",
"rules": {
"no-new": "off"
}
}
module: {
rules: [
{
test: /\.(vue|js|jsx)$/,
loader: 'eslint-loader',
exclude: /node_modules/,
enforce: 'pre' //预处理 post之后
},
"husky": "^0.14.3",
全局导航守卫
router.beforeResolve
注册一个全局守卫。这和 router.beforeEach
类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。路由独享的守卫
你可以在路由配置上直接定义 beforeEnter 守卫:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
组件内的守卫
最后,你可以在路由组件内直接定义以下路由导航守卫:
- beforeRouteEnter
- beforeRouteUpdate (2.2 新增)
- beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫 (2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
mapState 辅助函数
cnpm i babel-preset-stage-1 -D
"presets": [
"stage-1"
],
当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
严格模式
开启严格模式,仅需在创建 store 的时候传入 strict: true:
const store = new Vuex.Store({
// ...
strict: true
})
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
开发环境与发布环境
不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
类似于插件,我们可以让构建工具来处理这种情况:
const store = new Vuex.Store({
// ...
strict: process.env.NODE_ENV !== 'production'
})
命名空间
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为命名空间模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// 模块内容(module assets)
state: { ... }, // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// 嵌套模块
modules: {
// 继承父模块的命名空间
myPage: {
state: { ... },
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// 进一步嵌套命名空间
posts: {
namespaced: true,
state: { ... },
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})
启用了命名空间的 getter 和 action 会收到局部化的 getter,dispatch 和 commit。换言之,你在使用模块内容(module assets)时不需要在同一模块内额外添加空间名前缀。更改 namespaced 属性后不需要修改模块内的代码。
发表评论: