Vue 学习
Vue扩展
Vue实例参数补充
const vm = new Vue({
el:'#app',
data:{
// 实例数据
},
computed:{
// 计算属性
},
methods:{
// 实例的方法
},
watch:{
// 监听器
},
// ...一堆生命周期钩子函数
router: xxx // 通过new VueRouter() 创建的实例与Vue实例进行绑定
render: (createElement) => {
// 渲染函数,自带一个参数,这个参数是一个函数
// 返回的结果会直接替换整个 #app 的区域(类似于v-text)
// 而一般的组件是只替换组件使用处的代码 相当于 插值表达式(具体可以往后看)
return createElement(组件对象) // 返回值是html代码
}
})
修饰符补充
.native修饰符
一般注册的事件 @click @keyup 都是vue的合成事件,不是原生事件,如果想直接越过vue,不进行合成事件的处理,直接原生事件,那么就在注册事件的时候,加上 .native修饰符就可以了 @click.native=""
在使用elementUI中的el-input 组件的时候,因为这个组件没有提供回车的事件,因此我们想要使用自己定义的回车事件就需要越过Vue的事件处理,执行原生事件就需要用到.native修饰符了
组件
组件是可复用的 Vue 实例,且带有一个名字。因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。并且在组件中,data 必须是一个函数
一个页面可以是有多个组件组成
组件的优势:
1. 便于复用
2. 便于维护
3. 便于分工
组件化和模块化的区别
区别:
组件是具体的:按照一些小功能的通用性和可复用性来抽象组件
组件化更多关注的 UI 部分,页面的每个部件,比如头部,内容区,弹出框甚至确认按钮都可以成为一个组件,每个组件有独立的 HTML、css、js 代码。
模块是抽象的:按照项目业务划分的大模块
模块化侧重的功能的封装,主要是针对 Javascript 代码,隔离、组织复制的 javascript 代码,将它封装成一个个具有特定功能的的模块。
模块可以通过传递参数的不同修改这个功能的的相关配置,每个模块都是一个单独的作用域,根据需要调用。一个模块的实现可以依赖其它模块。
组件注册
- 当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,比如:my-header
- 当使用 PascalCase (首字母大写命名) 定义一个组件时,在引用这个自定义元素时使用 kebab-case 方式
- template 在如下这样使用的时候必须有一个 root Element(根元素)进行包裹,不然会报错
- 全局注册组件
注册
// Vue.component('组件名', { 组件对象 })
// 方式一
Vue.component('myheader', {
template: '<h1>hello world!</h1>'
})
// 方式二
Vue.component('my-header', {
template: '<h1>hello world!</h1>'
})
// 方式三
Vue.component('myHeader', {
template: '<h1>hello world!</h1>'
})
使用
<!-- 对应上面的方式一 -->
<div id="app">
<myheader></myheader>
</div>
<!-- 对应方式二和三 -->
<div id="app">
<my-header></my-header>
</div>
- 局部注册组件
const vm = new Vue({
el: '#app',
components: {
// 也可以写成 myheader 或者 myHeader 方式与上面一样
'my-header': {
template: '<h1>hello world</h1>'
}
}
})
组件对象参数
data 必须是一个函数 vue 组件中的 data 必须是个函数的目的:就是为了保证组件实例之间不会相互影响!
- 如果 data 是个对象,就是引用类型的,任意一个地方发生改变,其他人都会受到影响
- 用了函数之后,每次创建 vue 组件实例,都会调用函数,重新创建一个新的对象,那么每个实例都有自己的 data 对象,就不会互相影响了
组件嵌套
父子组件——在使用组件的时候,如果一个组件的模板中,用到了另一个组件标签,则这两个组件就形成了父子关系
Vue.component('father', {
// 在父组件的模板中引用了子组件,可以使用双标签<son></son> 也可以使用单标签 <son />
template: '<h1> {{msg}} <son></son></h1>',
data() {
return {
msg: 'hello father'
}
}
})
Vue.component('son', {
template: '<h1>{{msg}}</h1>',
data() {
return {
msg: 'hello son'
}
}
})
// 这种并不是父子组件,这样定义只是在组件中又定义了一个组件,而这个son组件的使用范围只是在father组件内部,无法在外部使用,是一个局部组件
Vue.component('father', {
template: '<h1>hello father</h1>',
components: {
son: {
template: '<h1>hello son</h1>'
}
}
})
以上这些只是组件的最最最最……基础的使用,这样的使用很费劲,但是这部分是基础,必须会了才能更好的学习之后的
组件通讯 ★
组件之间是相互独立的,数据是无法直接互相访问的!!在实际开发当中,我们经常会遇到,在一个组件中,要使用另外一个组件中的数据的情况。这个时候就需要使用组件通讯技术!!即在组件之间传递数据
-
父组件 → 子组件(属性传值)
Prop 是你可以在组件上注册的一些自定义特性。当一个值传递给一个 prop 特性的时候,它就变成了那个组件实例的一个属性。为了给博文组件传递一个标题,我们可以用一个 props 选项将其包含在该组件可接受的 prop 列表中。一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop
// 父组件传值给子组件是在编译的时候自动的
Vue.component('father', {
// step 1: 给子组件添加通过 v-bind 绑定的属性(prop),并且把father的数据绑定给属性(prop)
template: '<h1> {{msg}} <son :msgFromFather="msg"></son></h1>',
data() {
return {
msg: '这是father的msg'
}
}
})
Vue.component('son', {
// step 2: 在子组件中增加属性 props 这是一个数组,用来接收step1中绑定的属性(prop)
props: ['msgFromFather'],
// step 3: 在子组件模板中可以直接props中的属性名
template: '<h1>{{msgFromFather}}</h1>'
})
-
子组件 → 父组件(事件传值)
单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
Vue.component('father', {
// 通过事件绑定的方式,用 @ 给子组件添加事件(事件名自定义),并且与step1的方法绑定,传递给子组件
template: '<div>{{sonData}} <son @getData="getSonData"></son></div>',
data() {
return {
// 用于存放子组件的数据
sonData: ''
}
},
methods: {
// step 1: 声明一个函数,并且有一个形参用于接收子组件的数据
getSonData(value) {
console.log(value)
this.sonData = value
}
}
})
Vue.component('son', {
template: `<div>
<button @click="clickHandler">sendToFather</button>
</div>`,
data() {
return {
msg: '这是son的msg'
}
},
methods: {
// 子组件传值给父组件是需要自己触发的
clickHandler() {
// step 3: 通过this.$emit('事件名',子组件数据),调用父组件传递的函数把子组件的数据传递给父组件
this.$emit('getData', this.msg)
}
}
})
-
父子组件双向绑定
尽管因为存在单向数据流,导致我们不能直接在子组件中去修改父组件传递过来的数据,但是我们可以通过双向绑定的方式,在实现子组件中修改父组件中传递的数据
方法一:组件间的数据传递
这个方法的实现其实就是父传子,子传父的结合,父组件把数据传递给子组件(属性传值),然后子组件通过事件传值又把修改的数据传递给父组件,这样实现了双向绑定
Vue.component('father', {
template: `<div>父组件:<input type="text" v-model="msg"><son :val="msg" @xxx="msg = $event"></son></div>`,
data() {
return {
msg: 'felix'
}
},
methods: {
getval(val) {
this.msg = val
}
}
})
Vue.component('son', {
props: ['val'],
template: `<div>子组件:<input type="text" :value=sonMsg @input="changeval"></div>`,
data() {
return {
sonMsg: this.val
}
},
methods: {
changeval(e) {
this.$emit('xxx', e.target.value)
}
},
watch: {
val() {
this.sonMsg = this.val
}
}
})
const vm = new Vue({
el: '#app'
})
方法二:.sync修饰符
使用.sync修饰符其实就是方法一的简写
// 方法一中的
<son :val="msg" @xxx="msg = $event"></son>
// 修改成如下这样
<son :val.sync="msg"></son>
// 方法一种的
this.$emit('xxx', e.target.value)
// 修改成如下这样,这里的必须要这么写update:xxx,冒号后面的是与上面的绑定,但是update是必不可少的,我们在网上看到的一些双向绑定使用方法一的时候也是用update这样的方式写的,但是实际的逻辑就是通过上面说的来实现的,没必要使用update,但是这里必须使用update,调用Vue内部的update事件
this.$emit('update:val', e.target.value)
方法三:v-model绑定
Vue.component('father', {
// step 1: 通过v-model给子组件绑定数据,其实v-model相当于是给value值绑定数据的
template: `<div><son v-model="msg"></son>父组件:<input type="text" v-model="msg">
</div>`,
// 上面的son中的v-model其实是这个的简写 <son :value="msg" @input="msg = $event"></son>
data() {
return {
msg: 'felix'
}
}
})
Vue.component('son', {
// step 3:给子组件添加input事件
template: `<div>子组件: <input type="text" v-model="sonMsg" @input="set"></div>`,
// step 2:接收父组件中绑定的数据,因为v-model相当于是给value绑定数据,因此这里用value接收
props: ['value'],
data() {
return {
sonMsg: this.value
}
},
methods: {
set(e) {
// step 4:通过文本框的input事件去触发父组件中用v-model绑定的input事件,并把数据传递给父组件,实现双向数据绑定
this.$emit('input', e.target.value)
}
},
watch: {
value() {
// 添加监视,对传递过来的value值,这样只要父组件中的数据修改了,子组件中的数据sonMsg也可立即修改并响应式的在界面上显示
this.sonMsg = this.value
}
}
})
const vm = new Vue({
el: '#app'
})
- 非父子组件传值
同级组件之间的数据传递
// bear2 发送数据给 bear1 两者不是父子组件,是兄弟组件
// step 1 : 定义global-event-bus (全局事件总线) 一个空的Vue实例
const bus = new Vue()
Vue.component('bear1', {
template: `<div>{{msg}}</div>`,
data() {
return {
msg: ''
}
},
methods: {
// step 2 : 定义一个函数用于接收另一个组件传递过来的数据通过形参mes
getBear2Msg(mes) {
this.msg = '收到熊二来信:' + mes
}
},
created() {
// step 3 : 在组件刚创建之后就把声明的函数通过$on 注册到总线(bus)上,方便另一个组件可以通过总线触发
bus.$on('getMsg', this.getBear2Msg)
}
})
Vue.component('bear2', {
template: `<div>
<button @click="sendMsg">告诉熊大</button>
</div>`,
data() {
return {
msg: '熊大,光头强又来砍树啦!'
}
},
methods: {
sendMsg() {
// step 4 : 通过$emit 触发接收组件(即bear1组件)的函数,并且把数据传递过去,这样上面的getBear2Msg的mes参数就有了值
bus.$emit('getMsg', this.msg)
}
}
})
const vm = new Vue({
el: '#app'
})
还有一种方法就是使用 Vuex 来解决
组件补充(了解)
下面两个属性在实际开发中用的不多,作为了解知道就行
-
$refs 属性
- 这个属性可以用来在组件中访问子组件或者子元素
- 如果要在当前组件中访问子组件或子元素,那么就需要给子组件或者子元素标签添加 ref 属性,随便给个名字 ref=名字
- 在当前组件中就可以通过 this.$refs.名字访问到子组件或子元素了
- 如果是子组件,则获取到的是 vue 实例
- 如果是子元素,则获取到的是 dom 对象
Vue.component('father', { template: `<div><son ref="child"></son><button @click="handler">按钮</button></div>`, methods: { handler() { console.log(this.$refs.child.msg) } } }) Vue.component('son', { template: `<div>这是子组件数据: {{msg}}</div>`, data() { return { msg: '这是son的msg' } } }) const vm = new Vue({ el: '#app' })
-
$parent 属性
获取当前组件的父组件或者父元素
Vue.component('father', { template: `<div>{{msg}}<son ref="child"></son></div>`, data() { return { msg: '这是father的msg' } } }) Vue.component('son', { template: `<div><button @click="handler">按钮</button></div>`, methods: { handler() { console.log(this.$parent.msg) } } }) const vm = new Vue({ el: '#app' })
插槽
插槽,当组件中需要一些内容,这些内容是通过在组件标签内书写,传递进去的,那么我们需要使用插槽进行接收
匿名插槽
定义组件
Vue.component("mycomp", {
template:"<div><slot></slot></div>",
data(){
return {
btnText: "按钮文字"
}
}
});
new Vue({
el: "#app"
});
页面代码
<div id="app">
<mycomp>
<div>这是放在组件最前面的内容</div>
<div>这是放在组件最后面的内容</div>
</mycomp>
</div>
最后就会把<mycomp>标签里的两个div替换template模板中的slot标签
具名插槽
具名插槽: 插槽可以起名字, 将内容和插槽进行对应
我要实现让一个div放前面,一个放后面,这时候就需要让给插槽命名,这样才可以让对应的东西放到对应的插槽中去
Vue.component("mycomp", {
template:
"<div>1: <slot name='first'></slot> 2:<slot name='second'></slot></div>",
data(){
return {
btnText: "按钮文字"
}
}
});
<mycomp>
<div slot="front">这是放在组件最前面的内容</div>
<div slot="back">这是放在组件最后面的内容</div>
</mycomp>
当有很多内容需要放到某个具名插槽中去,如果每个都给添加一个slot属性来指定名字,这样是很麻烦的
<mycomp>
<div slot="front">这个要放到前面</div>
<div slot="front">这个也要放到前面</div>
<div slot="back">这个放在后面</div>
<div slot="back">这个也要放在后面</div>
</mycomp>
因此可以如下修改
<mycomp>
<!-- 如果有多个内容要放到同一个插槽里,那么就可以使用template标签把他们包起来 -->
<!-- template标签不会生成额外的标签,不会影响标签结构 ,用div包裹的话,会增加一个div标签,结构就发生了改变-->
<template slot="front">
<div>这个要放到前面</div>
<div>这个也要放到前面</div>
</template>
<template slot="back">
<div>这个放在后面</div>
<div>这个也要放在后面</div>
</template>
</mycomp>
作用域插槽
作用域插槽: 当我们在给组件中插槽填写内容的时候,如果想要用到组件中的数据,直接使用拿不到数据 这个时候就可以使用作用域插槽,把组件中的数据给传递出来,就可以直接使用了
Vue.component("mycomp", {
// 通过v-bind(或 : )来绑定组件中的数据,绑定的名字自定义
template:
"<div>1: <slot name='first' :btnText='btnText'></slot> 2:<slot name='second' :btnText='btnText'></slot></div>",
data(){
return {
btnText: "按钮文字"
}
}
});
<!-- v-slot:插槽的名字="组件中传递出来的数据对象" -->
<mycomp>
<!-- 因为传递的值是一个对象,这里可以通过{上面定义的名字}来进行解构,然后就可以用插值表达式-->
<template v-slot:second="{btnText}">
<button>{{btnText}}</button>
</template>
</mycomp>
插槽这东西我们在正常的开发中基本上很难用到,因为我们一般可以直接用人家封装的组件,不用自己去写组件提供给别人用,如果需要提供给别人使用的,那么就需要用到插槽方便用户在组件中输入值来进行传递,但是当我们用到别人的组件的时候就会用到插槽,比如ElementUI的el-table组件中,我们要获取其中某一行的数据,这时候就需要通过v-slot=“scope”来接收。
单页面应用
单页面应用就是根据hash值来改变页面内容的
单页面应用(single-page application 简写SPA)是指一个项目只有一个页面,页面的切换效果,是通过组件的切换来完成的。
单页面应用因为只有一个页面,所以页面不能发生跳转,但是,我们又需要根据url地址来展示不同的组件
这个时候,只能用哈希值来表示究竟要展示哪个组件了
模拟实现一个单页面应用
<div id="app">
<!-- 这就相当于一个占位符,告诉Vue我要在这里放一个组件,具体放哪个组件通过is属性来控制 -->
<component :is="currentPage"></component>
</div>
Vue.component('login', {
template: `<div><h1>这是登录页</h1><input type="text"><input type="text"><button>登录</button></div>`
})
Vue.component('register', {
template: `<div><h1>这是注册页</h1><input type="text"><input type="text"><input type="text"><button>注册</button></div>`
})
Vue.component('list', {
template: `<div><h1>这是列表页</h1><input type="text"><input type="text"><button>登录</button></div>`
})
const vm = new Vue({
el: '#app',
data: {
currentPage: 'login'
},
created() {
this.goPage()
// 通过这个属性来监听地址栏中的hash值是否改变了,hash值就是咱们之前用到的" #/xxx "的
window.onhashchange = () => {
this.goPage()
}
},
methods: {
goPage() {
switch (location.hash) {
case '#/login':
this.currentPage = 'login'
break
case '#/register':
this.currentPage = 'register'
break
case '#/list':
this.currentPage = 'list'
break
}
}
}
})
vue-router
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌
用 vue-router 实现上面的单页面案例
<div id="app">
<!-- vue-router中提供了一个声明式导航 -->
<ul>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 可以通过tag 属性来修改渲染的标签 -->
<li>
<router-link to="/login" tag="button" active-class="active">登录页</router-link>
</li>
<li><router-link to="/register">注册页</router-link></li>
<li><router-link to="/list">列表页</router-link></li>
</ul>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
// 1. 定义 (路由) 组件,这个就是之前创建组件时候的组件参数对象
const login = {
template: `<div><h3>这是登录页</h3><input type="text"><input type="text"><button>登录</button></div>`
}
const register = {
template: `<div><h3>这是注册页</h3><input type="text"><input type="text"><input type="text"><button>注册</button></div>`
};
const list = {
template: `<div><h3>这是列表页</h3><input type="text"><input type="text"><button>登录</button></div>`
};
// 2. 定义路由 每个路由应该映射一个组件。 其中"component" 可以是 通过 Vue.extend() 创建的组件构造器, 或者,只是一个组件配置对象
const router = new VueRouter({
// routes 里面存放的就是路由规则(hash值和组件的对应关系)
routes: [
// 每一条路由规则就是一个对象
{path: "/",component: login},
{path: "/login",component: login},
{path: "/register",component: register},
{path: "/list",component: list}
]
});
const vm = new Vue({
el: "#app",
// 3. 将Vue根组件实例和路由对象关联起来
// 完整写法是 router : router ES6可以简写成下面的
router
});
编程式导航
const vm = new Vue({
el: "#app",
router,
methods: {
clickHandler(){
// 在js代码中如果想要跳转页面,有个东西
// router.push("/register");
this.$router.push("/register")
// this.$router.push({path: "/register"})
// this.$router.push({name: "rg"})
}
}
});
<router-link>
常用属性
1. to
表示目标路由的链接。当被点击后,内部会立刻把
to
的值传到router.push()
,所以这个值可以是一个字符串或者是描述目标位置的对象。
<!-- 字符串 -->
<router-link to="home">Home</router-link>
<!-- 渲染结果 -->
<a href="home">Home</a>
<!-- 使用 v-bind 的 JS 表达式 -->
<router-link v-bind:to="'home'">Home</router-link>
<!-- 不写 v-bind 也可以,就像绑定别的属性一样 -->
<router-link :to="'home'">Home</router-link>
<!-- 同上 -->
<router-link :to="{ path: 'home' }">Home</router-link>
<!-- 命名的路由 -->
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
<!-- 带查询参数,下面的结果为 /register?plan=private -->
<router-link :to="{ path: 'register', query: { plan: 'private' }}">Register</router-link>
2. active-class
设置 链接激活时使用的 CSS 类名。默认值可以通过路由的构造选项
linkActiveClass
来全局配置
路由参数
- 通过 ? 设置的参数
我们常见的设置参数是 #/detail?id=1&name=zs 这种形式
// 页面代码,设置连接
<router-link to="/detail?id=1">详情页</router-link>
const detail = {
// 可以通过$route.query获取对应的值,这里是可以省略this的
template: '<h1>详情界面 ----- {{$route.query.id}}</h1>',
created() {
console.log(this.$route)
}
}
const home = {
template: '<h1> home </h1>'
}
const router = new VueRouter({
routes: [
{ path: '/', component: home },
{ path: '/detail', component: detail }
]
})
const vm = new Vue({
el: '#app',
router
})
- 通过动态路由设置参数
还有一种形式是 #/detail/1/zs,直接写传递的值
const detail = {
// 这种形式的就通过params来获取参数,我们可以直接带地址栏输入 #/detail/1
template: '<h1>详情界面 ----- {{$route.params.id}}</h1>',
created() {
console.log(this.$route)
}
}
const home = {
template: '<h1> home </h1>'
}
const router = new VueRouter({
routes: [
{ path: '/', component: home },
// 我们需要修改路由的匹配规则,这个意思是在/后面用id来占位,表示这个位置放一个id值
{ path: '/detail/:id', component: detail }
]
})
const vm = new Vue({
el: '#app',
router
})
导航守卫
vue-router
提供的导航守卫主要用来通过跳转或取消的方式守卫导航。
举个例子:我们进行用户没有登录,那么我们就需要这个守卫帮我们进行拦截,然后重定向到登录界面,登录后如果用户的token或者cookie什么的保存的信息是合法的,就允许跳转,这就是守卫的一个作用之一。
全局前置守卫
你可以使用 router.beforeEach
注册一个全局前置守卫:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
每个守卫方法接收三个参数:
- to: Route: 即将要进入的目标路由对象
- from: Route: 当前导航正要离开的路由
- next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖
next
方法的调用参数。- next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
- next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到
from
路由对应的地址。 - next(’/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向
next
传递任意位置对象,且允许设置诸如replace: true
、name: 'home'
之类的选项以及任何用在 router-link的
toprop或
router.push`中的选项。 - next(error): (2.4.0+) 如果传入
next
的参数是一个Error
实例,则导航会被终止且该错误会被传递给router.onError()
注册过的回调。
确保要调用 next 方法,否则钩子就不会被 resolved。
next方法简单的说就是通过next()来让守卫进行放行
守卫还有些别的,但是基本上类似,参数也类似,当用到的时候去查看就行
路由嵌套
有时我们会遇到这样一种情形,比如我们的界面如下图
整个页面我们用一个router-view进行了展示,但是当我们点击侧边菜单的时候一些东西是展示在Main中的,这时候如果我们还是通过配置如下的路由,那么整个界面都是会重新渲染的
{
path:'/xxx',
component: xxxx
}
这显然不是我们想要的结果,这我们要怎么实现呢?
这时候就用到了子路由(嵌套路由),假设当前这个页面的路由是 /index
首先我们需要在Main中再放一个<router-view>,作为嵌套路由的出口,在这块区域中进行渲染
{
path:"/index",
component:index,
children:[
{
path:'xxx', // 还有一种形式是 path:'/xxx'
component:xxx
}
]
}
以 / 开头的嵌套路径会被当作根路径。 这让你充分的使用嵌套组件而无须设置嵌套的路径。
带 / 和不带 /
// 路由配置中设置了 带 /,则跳转的连接这样设置
// 显示 http://localhost:8080/#/xxx
<router-link to="/xxx" />
// 路由配置中设置了不带 / ,则如下设置
// 显示 http://localhost:8080/#/index/xxx
<router-link to="/index/xxx" />
至于你到底要配置带不带 / 纯看你自己
路由懒加载
当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了
const Foo = () => import('./Foo.vue')
在路由配置中什么都不需要改变,只需要像往常一样使用 Foo
:
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
把组件按组分块
有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用 命名 chunk,一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4)。
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
Webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中。
Vue CLI
这个脚手架工具主要是用于开发单页面应用的,因此我们可以看到在生成的项目中只有一个HTML页面,在public文件夹中
单文件组件
在很多 Vue 项目中,我们使用 Vue.component 来定义全局组件,紧接着用 new Vue({ el: '#container '}) 在每个页面内指定一个容器元素
这种方式在很多中小规模的项目中运作的很好,在这些项目里 JavaScript 只被用来加强特定的视图。但当在更复杂的项目中,或者你的前端完全由 JavaScript 驱动的时候,
下面这些缺点将变得非常明显:
全局定义 (Global definitions) 强制要求每个 component 中的命名不得重复
字符串模板 (String templates) 缺乏语法高亮,在 HTML 有多行的时候,需要用到丑陋的 \
不支持 CSS (No CSS support) 意味着当 HTML 和 JavaScript 组件化时,CSS 明显被遗漏
没有构建步骤 (No build step) 限制只能使用 HTML 和 ES5 JavaScript, 而不能使用预处理器,如 Pug (formerly Jade) 和 Babel
文件扩展名为 .vue 的 single-file components(单文件组件) 为以上所有问题提供了解决方法,并且还可以使用 webpack 或 Browserify 等构建工具
单文件组件默认有下面三部分,分别是HTML、CSS 、JS
<template>
<!-- 通过lang来设置模板语言,默认是HTML 也可以设置为pug jade ejs等 -->
<div>
<!-- 组件HTML代码 -->
</div>
</template>
<script>
// 可以通过增加lang属性来设置使用什么语言,默认是js 也可以设置为 ts(typescript)
export default {
// 写之前组件中的东西,比如data(){return {}} 、 props:[] ……
};
</script>
<style scoped>
/*
写css样式,可以通过lang属性来设置,是css、less、sass 等,
默认情况下样式是全局作用的,加了scoped表示只在这个组件中起作用
*/
</style>
Vue-CLI使用
通常我们把它叫做Vue脚手架
在使用Vue开发的时候,我们会用到好多工具,webpack,babel,eslint…
在使用这些工具的时候,我们需要自己手动配置好多内容,这些内容配置太过繁琐。
vue官方就出了一个脚手架工具,这个工具里面会把所有我们需要的配置,全部帮我们自动处理完毕。
Vue-CLI的使用
- 安装
npm i -g @vue/cli
- 创建项目
# 终端创建项目
vue create 项目名称
# 图形化界面创建
vue ui
- 生成项目目录结构说明
1. 最外层的,基本上全都是配置文件,不需要过多的关注
2. node_modules 所有的npm下载的包
3. public 公共的静态资源
4. src目录(工作中需要关注的重点!)
1. main.js 这是整个项目的入口文件,项目就是从他开始执行的,render函数之前有介绍
2. router.js 这是用来配置路由规则文件
3. App.vue 这是整个项目的根组件
4. assets目录,静态资源,所有的组件中用到的静态资源,全部放到这个目录中 (图片 css 字体文件)
5. components目录,页面内使用的组件,就都放到这个文件夹中
6. views目录,所有的页面组件都放到这个文件夹里 (页面组件就是指会有路由规则进行对应的组件)
- 运行项目
# server是在package.json文件中生成项目的时候scripts配置的,根据实际配置的来修改
# "serve": "vue-cli-service serve", package.json中是这样的
npm run server
- 打包项目
# bulid 同样是在package.json 中配置的
# "build": "vue-cli-service build",
npm run bulid
通过打包后会在根目录下生成一个dist文件夹,这个文件就是经过打包压缩后的,一般都是直接把这个文件夹放到服务器上
配置反向代理
devServer.proxy
如果你的前端应用和后端 API 服务器没有运行在同一个主机上,你需要在开发环境下将 API 请求代理到 API 服务器。这个问题可以通过 vue.config.js 中的 devServer.proxy 选项来配置。
注意:devServe这个名字不要自己改动,之前自己改动了导致配置的不起作用
devServer.proxy 可以是一个指向开发环境 API 服务器的字符串:
module.exports = {
devServer: {
proxy: 'http://localhost:4000'
}
}
这会告诉开发服务器将任何未知请求 (没有匹配到静态文件的请求) 代理到http://localhost:4000。
如果你想要更多的代理控制行为,也可以使用一个 path: options 成对的对象
module.exports = {
devServer: {
proxy: {
'/api': {
target: '<url>',
ws: true,
changeOrigin: true,
// '/api/home' 路径重写为:'/home'
pathRewrite: { "^/api": "/" }
},
'/foo': {
target: '<other_url>'
}
}
}
}
这里的vue.config.js这个文件不需要自己去引入哪里,在运行项目的时候脚手架会自己去读取这个配置文件,并编译里面的代码,这样我们就实现了反向代理
至于怎么使用?我们在axios中写路径的时候可以省略上面的target部分,直接以你写的/api 开始 接后面的路径就行。
// 假设url http://localhost:8080/home?id=1&name=zs
// 并且上面的targe配置了 http://localhost:8080/
// 那么我们就可以直接 以 /api/home?id=1&name=zs,这样的方式去写
axios补充
在vue开发中存在的问题
-
每个需要用到axios的组件,都需要单独引入axios
-
axios的基地址每次都要写
-
headers每次都要写
解决办法
- 我们可以把axios加到Vue构造函数的原型中 这样每个组件中都可以通过 this.$http
// 把axios加到Vue的原型上
Vue.prototype.$http = axios;
- axios.defaults.baseURL = “”
// 通过defaults给axios设置一个默认的baseURL,可以在所有请求中都能用到这个地址
axios.defaults.baseURL = "http://localhost:8888/api/private/v1/";
// 其实请求头中的一些数据也可以在这里设置
axios.defaults.headers.common['Authorization'] = localStorage.getItem("token");
- 通过axios请求拦截器来实现(请求拦截器和响应拦截器)
axios.interceptoers.request.use(function(config){
// config 就是拦截到的请求相关的所有的信息
// 这个信息是可以进行修改的
config.headers.Authorization = localStorage.getItem("token")
// return config不能动,这个函数中必须有这个内容
return config;
})
举个形象的例子
这个拦截器就好比是海关,你如果要出去就要经过海关的检查,这里就相当于拦截器进行检查,而最后的return 是必不可少的,意思就相当于给予放行。
- 响应拦截器
axios.interceptors.response.use(function (response) {
// Do something with response data
// respone就是响应对象
return response;
});
在服务端返回响应的时候,我们也会对token进行验证,验证token是否有效,然后决定页面的跳转
Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,所谓的状态管理可以理解为对数据的管理。
之前我们接触到的数据传递分为父传子(属性传值)、子传父(事件传值)、兄弟组件传值,这几种传值方式如果嵌套结构过于复杂的时候,会导致数据的传递变得很复杂,因此Vuex在这里就体现出了重要的作用, 一个项目只需要一个Vuex来管理我们的数据,集中管理项目中的状态的,便于组件之间的数据共享。
Vuex是Vue的一个插件,因此要在Vue之后使用,或者Vue.use(vuex)
Vuex使用
在Vuex的实例对象中可以包括以下四个部分
// 通过Vuex.Store构造函数可以创建一个store对象
// store对象中的数据是响应式的
const store = new Vuex.Store({
// 存放状态(数据)
state: {
},
// mutations里面放的就是用来修改数据的方法,一般都是写同步操作的
mutations: {
},
// 一些异步修改数据的方法,最终调用的还是mutation的方法
actions:{
},
// 与vue实例中的计算属性类似
getters:{
}
});
与vue实例绑定
// 当把store实例和根组件关联之后
// store就可以在任意组件中通过 this.$store 就可以访问到这个store实例了
const vm = new Vue({
el:"#app",
store
})
在Vue中使用
<!-- 可以通过$store 来获取Vuex的对象-->
<div id="app">
{{$store.state.msg}}
</div>
mutation
更改 Vuex 的 store 中的状态(数据)的唯一方法是提交 mutation
const store = new Vuex.store({
state:{
msg: "hello world"
},
mutations:{
updateMsg(state, value){
// 这个函数就是用来修改state中的数据的
// 可以接收两个参数
// state 指的就是vuex的存储数据的对象
// value 当用户通过 store.commit 方法来触发这个函数的时候
// commit方法的第二个参数就会被赋值给value
state.msg = value
}
}
})
// vuex不允许直接通过给store.state中的数据赋值 来修改数据
store.state.msg = "123"
// 在vuex中如果需要修改数据
// 那么我们就需要在vuex中提供修改数据的方法
// 这些修改数据的方法,全都放在一个叫mutations的对象中
// mutations中的方法,如何触发?
// 只允许传递一个参数,如果需要传递多个数据,可以传一个对象
store.commit("updateMsg", 123)
getter
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
const store = new Vuex.Store({
state: {
arr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
// getters就像是vue中的计算属性
// 如果要获取一个数据,而这个数据是通过对state中的数据进行计算之后的结果
// 我们就可以通过getters来实现了
getters: {
evenNum(state){
return state.arr.filter(v => v % 2 == 0);
},
// 在vue中计算属性是无法传参的
// 但是在vuex中,getters是可以传递参数的
// $store.getters.zhengchuArr(3)
zhengchuArr(state){
return function(value){
return state.arr.filter(v => v % value == 0);
}
},
// zhengchuArr: state => value => state.arr.filter(v => v % value == 0)
}
})
<div id="app">
<ul>
<!-- 无参数 -->
<li v-for="item in $store.getters.evenNum">{{item}}</li>
<!-- 需要传参 -->
<li v-for="item in $store.getters.zhengchuArr(4)">{{item}}</li>
</ul>
</div>
action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit
提交一个 mutation,或者通过 context.state
和 context.getters
来获取 state 和 getters
const store = new Vuex.Store({
state: {
msg: "hello world"
},
// mutations里面放的就是用来修改数据的方法
mutations: {
updateMsg(state, value){
state.msg = value;
}
},
actions: {
updateMsg(context, value){
setTimeout(() => {
// 在修改数据的时候,其实还是提交一个mutation完成数据修改
context.commit("updateMsg", value);
}, 1000)
}
}
});
// store.commit("updateMsg", 123)
store.dispatch("updateMsg", 456);
辅助函数
我们在使用的时候常常会遇到如下的使用
// 在组件的methods中是如下使用的
toggleTodo(id) {
this.$store.commit("toggleTodo", id);
}
// 在vuex的store中的mutation的方法
toggleTodo(state, id) {
state.todoList.forEach(v => {
if (v.id === id) {
v.isCompleted = !v.isCompleted;
}
});
}
可以发现基本上每次我们在组件中创建的方法中只有一条调用mutation中的方法的代码,因此可以用到Vuex提供的辅助函数mapMutation
我们怎么在组件中引入呢??
- mapMutation
// 导入mapMutation方法
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}
}
这样做了之后,我们就可以直接在组件中使用这些方法了
- mapGetters
getter中的一些计算属性也可以这么映射出来,这样我们就不用写很长的一串 $store.getters.xxx,而是可以直接使用xxx
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
- mapAction
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
- 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
}
})
}
注意mutation和action是映射到methods中的,而state和getters是映射到computed计算属性中去的
module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
4. mapState
```js
// 在单独构建的版本中辅助函数为 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
}
})
}
注意mutation和action是映射到methods中的,而state和getters是映射到computed计算属性中去的
module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态