基础部分
模版语法
1.computed和watch的区别
- 计算属性computed :
支持缓存,data数据不变则不会重新计算
不支持异步,当computed内有异步操作时无效,无法监听数据的变化
- 侦听属性watch
不支持缓存
watch支持异步
2.watch如何监听数组和对象呢
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../../vue.js"></script> </head> <body> <div id="app"> {{person.age}} </div> <script> var vm = new Vue({ el: "#app", data: { person: { username: "hanye", age: 18, }, arr:[1,2,3] }, mounted() { // Vue.js观察数组变化主要通过以下7个方法(push、pop、shift、unshift、splice、sort、reverse) // 或者这种写法 都能监听到 this.$set(this.arr, 0, 'aaaa') this.person.age =20 }, watch: { //对象的深度监听必须这样配置一下 person: { handler(newval, oldval) { console.log(newval, oldval) }, deep: true, //设置为true之后,回调函数会被立即触发 immediate: true }, arr(newval, oldval) { console.log(newval, oldval) } } }) </script> </body> </html>
声明周期
beforeCreate (使用的频率较低)
在实例创建以前
data 数据访问不到
created (使用频率高)
实例创建完
能拿到data下的数据,能修改data的数据
修改数据不会触发updated , beforeUpdate 钩子函数
取不到最终渲染完成的dom
beforeMount (在挂载之前)
编译模板已经结束
可以访问data数据
可以修改数据,修改数据不会触发updated , beforeUpdate 钩子函数
mounted (挂载)
真实的dom节点已经渲染到页面,可以操作渲染后的dom
可以访问和更改数据,会触发updated , beforeUpdate 钩子函数
beforeUpdate
修改之前会被调用
updated
修改数据之后会被调用
beforeDestroy
实例卸载之前会被调用,可以清理一些资源,防止内存泄漏
destroyed 销毁
<body> <div id="app"> {{username}} </div> <script> var vm = new Vue({ el: "#app", data: { username: 'hanye' }, beforeCreate() { console.log("beforeCreate:", this.username) }, created() { //最早可以发送ajax数据请求 console.log('created:', this.username) }, beforeMount() { console.log('before mount') console.log(document.getElementById("app")) this.username = "hansai"; }, mounted() { console.log('mounted:'); setTimeout(() => { vm.$destroy(); }, 3000) }, beforeUpdate() { console.log('beforeUPdate') }, updated() { console.log('updated') }, beforeDestroy() { console.log('beforeDestroy') }, destroyed() { console.log('destroyed') } }) </script> </body>
组件
全局组件
<body> <div id="app"> <component-test></component-test> </div> <script type="text/template" id="template-test"> <div> 全局组件 {{count}} </div> </script> <script> Vue.component('component-test',{ template:"#template-test", data(){ return { count:0 } }, }) var vm = new Vue({ el: "#app", }) </script> </body>
局部组件
<body> <div id="app"> <compa></compa> </div> <script> const compb = { template: '<div>compb</div>' } const compa = { template: ` <div> compa <compb></compb> </div> `, components: { compb } } var vm = new Vue({ el: "#app", components: { compa } }) </script> </body>
父传子
通过prop向子组件传递数据
<body> <div id="app"> <component-test :mytitle="title" :mylist="list"></ccomponent-test> //@key </div> <script type="text/template" id="template-test"> <div> <h2>{{mytitle}}</h2> <ul> <li v-for="user in mylist"> {{user.username}} </li> </ul> </div> </script> <script> Vue.component("component-test",{ props:['mytitle','mylist'],//@key template:"#template-test" }) var vm = new Vue({ el:"#app", data:{ title:"parent-title", list:[ {username:'hanye',age:20}, {username:'hansai',age:30} ] } }) </script> </body>
子传父
通过$emit 事件泡发
<body> <div id="app"> {{parent_count}} <component-test @countchange="handleChange"></component-test> //@key </div> <script type="text/template" id="template-test"> <div> <h2>{{count}}</h2> <button @click="increment">+</button> </div> </script> <script> Vue.component('component-test',{ template:"#template-test", data(){ return { count:0 } }, methods:{ increment(){ this.count++ this.$emit('countchange',this.count) //@key } } }) var vm = new Vue({ el:"#app", data:{ parent_count:0 }, methods:{ handleChange(num){ this. parent_count = num } } }) </script> </body>
非父子
非父子组件之间进行通信,用 中央事件总线 eventbus
中央总线的特点,只要订阅了,触发的时候就能收到通知
<body> <div id="app"> <component-a></component-a> <component-b></component-b> </div> <script> var eventbus = new Vue();//空vue实例 就是中央事件总线 Vue.component('component-a',{ template:`<div>组件a</div>`, mounted(){ eventbus.$on("message",function(msg){ //@key console.log(msg);//收到通知了吗 }) } }) Vue.component('component-b',{ template:` <button @click="handleclick">组件b 点击</button>`, methods:{ handleclick(){ eventbus.$emit("message","收到通知了吗") //@key } } }) var vm = new Vue({ el:"#app", data:{ username:"wanglei" }, }) </script> </body>
动态组件
通过 Vue 的 component 元素加一个特殊的 is
属性来实现
<div id="app"> <!-- 动态组件 --> <component v-bind:is="currentTap"></component> //@key <button v-for="tab in tabs" @click="handleChange(tab)"> {{tab}} </button> </div> <script> Vue.component('tap-position', { template: '<div>position</div>' }) Vue.component('tap-search', { template: '<div>search</div>' }) var vm = new Vue({ el: "#app", data: { currentTap: 'tap-position', tabs: ['tap-position', 'tap-search'] }, methods: { handleChange(tabs) { this.currentTap = tabs; } } }) </script> </body>
keep-alive
在动态组件上使用keep-alive
当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题
重新创建动态组件的行为通常是非常有用的,但是在这个案例中,我们更希望那些标签的组件实例能够被在它们第一次被创建的时候缓存下来。为了解决这个问题,我们可以用一个 `` 元素将其动态组件包裹起来
两个钩子函数:
activated
类型:func
触发时机:keep-alive组件激活时使用;
deactivated
类型:func
触发时机:keep-alive组件停用时调用;
<body> <div id="app"> <!-- 动态组件keep-alive --> <keep-alive> <component v-bind:is="currentTap"></component> </keep-alive> <button v-for="tab in tabs" @click="handleChange(tab)"> {{tab}} </button> </div> <script> Vue.component('tap-position', { template: '<div>position <input type="text"/></div>', activated(){ console.log("position激活"); }, deactivated(){ console.log("position无效"); } }) Vue.component('tap-search', { template: '<div>search</div>', activated(){ console.log("search激活"); }, deactivated(){ console.log("search无效"); } }) var vm = new Vue({ el: "#app", data: { currentTap: 'tap-position', tabs: ['tap-position', 'tap-search'] }, methods: { handleChange(tabs) { this.currentTap = tabs; } } }) </script> </body>
在组件上使用v-model
这个组件内的 input必须:
- 将其
value
attribute 绑定到一个名叫value
的 prop 上 - 在其
input
事件被触发时,将新的值通过自定义的input
事件抛出
- 自己实现的v-model
<body> <div id="app"> <h1>{{username}}</h1> <!-- <component-test v-model="username"></component-test> --> <!-- 这2句是等价的--> <component-test :value="username" @input="handleInput"></component-test> </div> <script> Vue.component('component-test',{ props:['value'], template:`<input type="text" :value="value" @input="handleclick">`, data(){ return { count:0 } }, methods:{ handleclick(event){ this.$emit('input',event.target.value) } } }) var vm = new Vue({ el:"#app", data:{ username:"wanglei" }, methods:{ handleInput(username){ this.username = username } } }) </script> </body>
- v-model
<body> <div id="app"> <h1>{{username}}</h1> <component-test v-model="username"></component-test> </div> <script> Vue.component('component-test',{ props:['value'], template:` <input type="text" :value="value" @input="handleclick">`, data(){ return { count:0 } }, methods:{ handleclick(event){ this.$emit('input',event.target.value) } } }) var vm = new Vue({ el:"#app", data:{ username:"wanglei" } }) </script> </body>
自定义组件
自定义组件的v-model
一个组件上的 v-model
默认会利用名为 value
的 prop 和名为 input
的事件,但是像单选框、复选框等类型的输入控件可能会将 value
attribute 用于不同的目的。model
选项可以用来避免这样的冲突,改变默认值
<body> <div id="app"> {{checkvalue}} <component-checkbox v-model="checkvalue"></component-checkbox> </div> <script> Vue.component('component-checkbox',{ //改变默认值 model:{ prop:'checked',//默认值是:value event:'change'//默认值:input }, props:['checked'], template:`<input type="checkbox" :checked="checked" @change="handleChange"/>`, methods:{ handleChange(event){ this.$emit("change",event.target.checked) } } }) var vm = new Vue({ el:"#app", data:{ checkvalue:true } }) </script> </body>
.sync修饰符
- sync 代码
<body> <div id="app"> {{name}} <component-input :myname.sync="name"></component-input> </div> <script> Vue.component('component-input',{ template:`<input type="text" @change="$emit('update:myname',$event.target.value)"/>` }) var vm = new Vue({ el:"#app", data:{ name:'' } }) </script> </body>
- 上面sync的代码实现的功能和下面这些代码一样
<body> <div id="app"> {{name}} <component-input @change="handleChange"></component-input> </div> <script> Vue.component('component-input',{ template:`<input type="text" @change="$emit('change',$event.target.value)"/>` }) var vm = new Vue({ el:"#app", data:{ name:'' }, methods:{ handleChange(val){ this.name = val } } }) </script> </body>
插槽
具名插槽
意思就是起了名字的插槽
<body> <div id="app"> <component-a> <template #header> <h1>header</h1> </template> <template #main> <h1>main</h1> </template> <h1>footer</h1> </component-a> </div> <script type="text/template" id="template-a"> <div> <header> <slot name="header"></slot> </header> <main> <slot name="main"></slot> </main> <footer> <slot></slot> </footer> </div> </script> <script> Vue.component('component-a',{ template:"#template-a" }) var vm = new Vue({ el:"#app", }) </script> </body>
作用域插槽
有时让插槽内容能够访问子组件中才有的数据是很有用的
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的
<body> <div id="app"> <component-user v-slot:default="obj"> //default是插槽的名字,default 可以省略掉 {{obj.myname}} </component-user> </div> <script> Vue.component('component-user',{ template:`<div> <slot :myname="name"></slot> //@key </div>`, data(){ return { name:'hanye' } } }) var vm = new Vue({ el:"#app", data:{ name:'' } }) </script> </body>
后备内容
意思其实就是默认值
<body> <div id="app"> <my-button>确定</my-button> </div> <script> Vue.component('my-button', { template: ` <button> <slot>submit</slot> //如果不传就是submit </button> ` }) var vm = new Vue({ el: "#app", data: { username: 'hanye' } }) </script> </body>
prop
<body> <div id="app"> <component-a a="123" :b="123" :c="[1,2]" :d="{name:'wanglei',age:'20'}" :f="handle" h="banana"> </component-a> </div> <script> Vue.component('component-a', { props:{ a:String, b:{ required:true, type:Number }, c:Array, f:Function, d:{ type:Object, default:function(){ return {message:'hello'} } }, i:{ required:false, type:Number, default:100 }, h:{ required:true, validator:function(value){ if(value.indexOf('banana') > -1) { return true; } else { return false; } } } }, template: '<div>test</div>' }) var vm = new Vue({ el: "#app", methods:{ handle(){ } } }) </script> </body>
处理边界情况
访问根 和父级组件实例
<body> <div id="app"> <compa></compa> </div> <script> const compb = { template:`<div> 访问跟组件的数据: {{$root.username}} <br/> 访问父元素的数据: {{$parent.title}} </div>`, mounted(){ console.log(this.$root.username);//wanglei console.log(this.$parent.title);//compa-title } } const compa = { template:`<div > <compb></compb> </div>`, components:{ compb }, data(){ return { title:"compa-title" } } } var vm = new Vue({ el:"#app", data:{ username:"wanglei" }, components:{ compa } }) </script> </body>
访问子组件实例
意思是父组件拿子组件的数据
尽管存在 prop 和事件,有的时候你仍可能需要在 JavaScript 里直接访问一个子组件。为了达到这个目的,你可以通过 ref
这个 attribute 为子组件赋予一个 ID 引用
<body> <div id="app"> <compa ref="refcompa"></compa> </div> <script> const compa = { template:`<div > compa </div>`, data(){ return { title:"compa-title" } } } var vm = new Vue({ el:"#app", data:{ username:"wanglei" }, components:{ compa }, mounted(){ console.log(this.$refs.refcompa.title);//compa-title } }) </script> </body>
依赖注入
provide
选项允许我们指定我们想要提供给后代组件的数据/方法,然后在任何后代组件里,我们都可以使用 inject
选项来接收指定的我们想要添加在这个实例上的属性
(注意不是响应式)
<body> <div id="app"> <compa ></compa> </div> <script> const compa = { inject:['username'],//@key template:`<div > {{username}} </div>`, } var vm = new Vue({ el:"#app", data:{ username:"wanglei2" }, components:{ compa }, provide(){ //@key return { username:this.username } } }) </script> </body>
访问元素
<body> <div id="app"> <input type="text" ref="myinput"> //@key </div> <script> var vm = new Vue({ el: "#app", mounted() { console.log(this.$refs.myinput); } }) </script> </body>
组件的生命周期
<body> <div id="app"> {{username}} <compa></compa> </div> <script> var compa = { template:`<div>组件a</div>`, beforeCreate(){ console.log("child beforeCreate"); }, created(){ console.log("child created"); }, beforeMount(){ console.log("child beforeMounted"); }, mounted(){ console.log("child mounted"); }, beforeUpdate(){ console.log("child beforeUpdate"); }, updated(){ console.log('child updated') }, beforeDestroy(){ console.log("child beforeDestory"); }, destroyed(){ console.log("child destoryed"); } } var vm = new Vue({ el:"#app", data:{ username:"wanglei" }, components:{ compa }, beforeCreate(){ console.log("root beforeCreate"); }, created(){ console.log("root created"); }, beforeMount(){ console.log("root beforeMounted"); }, mounted(){ console.log("root mounted"); this.username="guoguo" setTimeout(() => { vm.$destroy(); }, 1000) }, beforeUpdate(){ console.log("root beforeUpdate"); }, updated(){ console.log('root updated') }, beforeDestroy(){ console.log("root beforeDestory"); }, destroyed(){ console.log("root destoryed"); } }) </script> </body> 打印出来的结果是: root beforeCreate root created root beforeMounted child beforeCreate child created child beforeMounted child mounted root mounted root beforeUpdate root updated root beforeDestory child beforeDestory child destoryed root destoryed
组件之间传递数据的总结
- props emit
缺点:如果组件嵌套层次多的话,数据传递比较繁琐
- provide inject(依赖注入)
缺点:不支持响应式
root, parent refs
eventbus
缺点:数据不支持响应式
- vuex
数据的读取和修改需要按照流程来操作,不适合小型项目
可复用性&组合
混入
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项
意思就是<span style='color:red;'>多个组件有相同的逻辑,抽离出来</span>
- <span style='color:red;'>同名钩子函数将合并为一个数组,因此都将被调用。混入对象的钩子函数先调用</span>
- 其他数据在合并时,发生冲突时以组件数据优先
- 数据对象在内部进行递归合并
缺点:
- 变量来源不明确,不利于阅读
- 多mixin 可能造成命名冲突
- mixin和组件可能出现多对多的关系,复杂度较高
全局混入
<body> <div id="app"> </div> <script> Vue.mixin({ data(){ return { username:'xiaoming' } }, beforeCreate(){ console.log('mixin beforeCreate') }, created(){ console.log('mixin created'); }, mounted(){ console.log("mixin:",this.username);//root: wanglei } }) var vm = new Vue({ el:"#app", data:{ username:"wanglei" }, beforeCreate(){ console.log('beforeCreate') }, created(){ console.log('created'); }, mounted(){ console.log("root:",this.username);//root: wanglei } }) </script> </body> 执行结果: mixin beforeCreate beforeCreate mixin created created mixin: wanglei root: wanglei
局部混入
<body> <div id="app"> </div> <script> const mixin1 = { beforeCreate(){ console.log('mixin1 beforeCreate') }, data(){ return{ age:18 } } } const mixin2 = { data(){ return{ age:20 } }, methods: { getcity() { return 'beijing'; } } } var vm = new Vue({ mixins:[mixin1,mixin2], //有冲突,后面的覆盖前面的 el:"#app", methods: { getcity() { return 'beijing'; } }, created(){ console.log(this.getcity());//北京 console.log(this.age);//20 } }) </script> </body>
指令
除了核心功能默认内置的指令 (
v-model
和v-show
),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有钩子函数会被传入以下参数:
el
:指令所绑定的元素,可以用来直接操作 DOM。
binding
对象,包含以下属性:
name
:指令名,不包括v-
前缀。
value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。
oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变 都可用。
expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
:传给指令的参数,可选。例如
v-my-directive:foo中,参数为
"foo"`。
modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
vnode
:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用
全局指令
- 聚焦输入框的例子
<body> <div id="app"> <input type="text" v-focus> //@key </div> <script> Vue.directive('focus',{ //@key bind(){ // console.log('bind',arguments); }, inserted(el){ el.focus() } }) var vm = new Vue({ el:"#app", }) </script> </body>
- 颜色的例子
<body> <div id="app"> <h1 v-color="'blue'">gp18</h1> </div> <script> Vue.directive('color',{ inserted(el,binding){ el.style.color = binding.value } }) var vm = new Vue({ el:"#app", }) </script> </body>
局部指令
<body> <div id="app"> <input type="text" v-focus> </div> <script> var vm = new Vue({ el: "#app", directives: { // 注册一个局部的自定义指令 v-focus focus: { // 指令的定义 inserted: function (el) { // 聚焦元素 el.focus() } } } }) </script> </body>
插件
组件 (Component) 是用来构成你的 App 的业务模块,
插件 (Plugin) 是用来增强你的技术栈的功能模块,
插件应该暴露一个 install
方法。这个方法的第一个参数是 Vue
构造器,第二个参数是一个可选的选项对象
<body> <div id="app"> </div> <script> var cache = { add(key,val){ if(localStorage){ localStorage.setItem(key,val) }else{ document.cookie=`${key}=${val}` } } } var myplugin = { install(vue){ //暴露一个 install 方法 vue.prototype.$cache = cache } } Vue.use(myplugin)//注册插件 var vm = new Vue({ el:"#app", mounted(){ this.$cache.add('username','hanye') //使用插件 } }) </script> </body>
过滤器
Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和
v-bind
表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示:<!-- 在双花括号中 --> {{ message | capitalize }} <!-- 在 `v-bind` 中 --> <div v-bind:id="rawId | formatId"></div>
全局过滤器
<body> <div id="app"> {{time | formatdate("yyyy") | prefix }} //这块输出:今天是:2020 </div> <script> Vue.filter('formatdate',function(value,type){ if(type==="yyyy"){ return value.split('-')[0] } }) Vue.filter('prefix',function(value){ return "今天是:"+value }) var vm = new Vue({ el:"#app", data:{ time:"2020-04-03" } }) </script> </body>
局部过滤器
<body> <div id="app"> {{time | formatdate("yyyy")}} //这块输出:2020 </div> <script> var vm = new Vue({ el: "#app", data: { time: "2020-04-03" }, filters: { //定义局部过滤器 formatdate: function (value,type) { if (type === "yyyy") { return value.split('-')[0] } } } }) </script> </body>
路由
用 Vue.js + Vue Router 创建单页应用,是非常简单的。使用 Vue.js ,我们已经可以通过组合组件来组成应用程序,当你要把 Vue Router 添加进来,我们需要做的是,将组件 (components) 映射到路由 (routes),然后告诉 Vue Router 在哪里渲染它们
起步
<!DOCTYPE html>
<html lang="en">
<head>
<script src="../vue.js"></script>
<script src="../vue-router.js"></script>
<style>
.router-link-exact-active{
color: red;
}
</style>
</head>
<body>
<div id="app">
<ul>
<router-link to="/position">职位</router-link>
<router-link to="/search">搜索</router-link>
</ul>
<router-view></router-view>
</div>
<script>
const position = {
template:`<div>position</div>`
}
const search = {
template:`<div>search</div>`
}
var router = new VueRouter({
mode:'hash',
routes:[
{
path:"/",
redirect: '/position'
},
{
path:'/position',
component:position
},
{
path:'/search',
component:search
}
]
})
var vm = new Vue({
router,
el:"#app",
})
</script>
</body>
</html>
动态路由
<!DOCTYPE html> <html lang="en"> <head> <script src="../vue.js"></script> <script src="../vue-router.js"></script> <style> .router-link-exact-active { color: red; } </style> </head> <body> <div id="app"> <router-link tag="div" to="/position/35">职位</router-link> <router-link tag="div" to="/search">搜索</router-link> <router-view></router-view> </div> <script> const search = { template: `<div>search</div>` } var router = new VueRouter({ mode: 'hash', routes: [{ path: '/position/:id', //动态路由 component: { template: `<div>{{$route.params}} </div>` //$route.params等于{ "id": "35" } } }, { path: '/search', component: search }, ] }) var vm = new Vue({ router, el: "#app", }) </script> </body> </html>
嵌套路由
<!DOCTYPE html> <html lang="en"> <head> <script src="../vue.js"></script> <script src="../vue-router.js"></script> <style> .router-link-active{ color: red; } </style> </head> <body> <div id="app"> <ul> <router-link to="/position">职位</router-link> <router-link to="/search">搜索</router-link> </ul> <router-view></router-view> </div> <script> const position ={template:`<div> <router-link to="/position/fe">前端</router-link> <router-link to="/position/be">后端</router-link> <router-view></router-view> </div>` } const search = { template:`<div>search</div>` } var router = new VueRouter({ mode:'hash', routes:[ { path:'/position', component:position, children:[ //嵌套路由 { path:'fe', component:{ template:`<div>position fe</div>` } }, { path:'be', component:{ template:`<div>position be</div>` } } ] }, { path:'/search', component:search } ] }) var vm = new Vue({ router, el:"#app", }) </script> </body> </html>
编程式导航
<!DOCTYPE html> <html lang="en"> <head> <script src="../vue.js"></script> <script src="../vue-router.js"></script> <style> .router-link-active{ color: red; } </style> </head> <body> <div id="app"> <ul> <router-link to="/position">职位</router-link> <router-link to="/search">搜索</router-link> </ul> <router-view></router-view> <button @click="go">编程式导航</button> </div> <script> const position ={ template:`<div>position</div>` } const search = { template:`<div>search</div>` } var router = new VueRouter({ mode:'hash', routes:[ { path:'/position', component:position, }, { path:'/search', component:search } ] }) var vm = new Vue({ router, el:"#app", methods:{ go(){ // this.$router.push('/position') //这这两种写法都行, 编程式导航 this.$router.push({ path:'/position' }) } } }) </script> </body> </html>
命名路由
意思是用name标示路由
<!DOCTYPE html> <html lang="en"> <head> <script src="../vue.js"></script> <script src="../vue-router.js"></script> <style> .router-link-active{ color: red; } </style> </head> <body> <div id="app"> <ul> <router-link :to="{name:'ps'}">职位</router-link> //使用名字访问路由 <router-link to="/search">搜索</router-link> </ul> <router-view></router-view> </div> <script> const position ={ template:`<div>position</div>` } const search = { template:`<div>search</div>` } var router = new VueRouter({ mode:'hash', routes:[ { path:'/position', name:'ps', //命名路由 component:position, }, { path:'/search', component:search } ] }) var vm = new Vue({ router, el:"#app", }) </script> </body> </html>
命名视图
有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有
sidebar
(侧导航) 和main
(主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果router-view
没有设置名字,那么默认为default
<!DOCTYPE html> <html lang="en"> <head> <script src="../vue.js"></script> <script src="../vue-router.js"></script> <style> .router-link-active{ color: red; } </style> </head> <body> <div id="app"> <ul> <router-link to="/position">职位</router-link> <router-link to="/search">搜索</router-link> </ul> <router-view name="header"></router-view> //命名视图 @key <router-view ></router-view> <router-view name="footer"></router-view> </div> <script> const position ={ template:`<div>position</div>` } const search = { template:`<div>search</div>` } var router = new VueRouter({ mode:'hash', routes:[ { path:'/position', components:{ default:{template:'<div>main content</div>'}, //模版和命名视图名字对上 @key header:{template:'<div>header content</div>'}, footer:{template:'<div>footer content</div>'} } }, { path:'/search', component:search } ] }) var vm = new Vue({ router, el:"#app", }) </script> </body> </html>
路由组件传参
在组件中使用 $route
会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。
使用 props
将组件和路由解耦
布尔模式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../vue.js"></script> <script src="../vue-router.js"></script> <style> .router-link-active { color: red; } </style> </head> <body> <div id="app"> <router-link to="/position/35">职位</router-link> <router-view></router-view> </div> <script> const position = { props: ['id'], template: `<div>position {{id}}</div>` // 这块输出: position 35 } var router = new VueRouter({ mode: 'hash', routes: [{ path: '/position/:id', component: position, props: true, //@key } ] }) var vm = new Vue({ router, el: "#app", }) </script> </body> </html>
对象模式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../vue.js"></script> <script src="../vue-router.js"></script> <style> .router-link-active { color: red; } </style> </head> <body> <div id="app"> <router-link to="/position">职位</router-link> <router-view></router-view> </div> <script> const position = { props: ['uname','age'], //@Key template: `<div>{{uname}}-{{age}}</div>` //这块输出:hanye-20 } const search = { template: `<div>search</div>` } var router = new VueRouter({ mode: 'hash', routes: [{ path: '/position', component: position, props:{uname:'hanye',age:20}, //@key }, { path: '/search', component: search } ] }) var vm = new Vue({ router, el: "#app", }) </script> </body> </html>
函数模式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../vue.js"></script> <script src="../vue-router.js"></script> <style> .router-link-active { color: red; } </style> </head> <body> <div id="app"> <router-link to="/search?keyword=前端">搜索</router-link> <router-view></router-view> </div> <script> const search = { props: ['query'], //@Key template: `<div>参数:{{query}}</div>` //参数:前端 } var router = new VueRouter({ mode: 'hash', routes: [ { path: '/search', component: search, props:(route)=>{ return {query:route.query.keyword}} //@key } ] }) var vm = new Vue({ router, el: "#app", }) </script> </body> </html>
导航守卫
导航守卫其实也是路由守卫,也可以是路由拦截,我们可以通过路由拦截,来判断用户是否登录,该页面用户是否有权限浏览,就是拦截器,类似钩子之类的。。。
全局路由守卫有2个
- <span style="color:red">全局前置守卫</span> 应用场景:例如判断用户是否登录之类的
- <span style="color:red">全局后置守卫</span>
每个守卫方法接收三个参数:
to: Route
: 到哪个页面去from: Route
: 从哪个页面来next: Function
:next()
: 进行管道中的下一个钩子next(false)
: 中断当前的导航next('/')
或者next({ path: '/' })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向next
传递任意位置对象,且允许设置诸如replace: true
、name: 'home'
之类的选项以及任何用在router-link
的to
prop 或router.push
中的选项。next(error)
: (2.4.0+) 如果传入next
的参数是一个Error
实例,则导航会被终止且该错误会被传递给router.onError()
注册过的回调。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../vue.js"></script> <script src="../vue-router.js"></script> <style> .router-link-active { color: red; } </style> </head> <body> <div id="app"> <router-link to="/position?islogin=1">职位</router-link> <router-link to="/login">登录</router-link> <router-view></router-view> </div> <script> const position = { template: `<div>职位页面</div>` } const search = { template: `<div>登录页面</div>` } var router = new VueRouter({ mode: 'hash', routes: [{ path: '/position', component: position, }, { path: '/login', component: search } ] }) //@key 全局前置守卫 router.beforeEach((to, from, next) => { if(to.path == "/login") { //除了`/login`路由不需要判断,其余路由都需要判断 next() } else { if (to.query.islogin !=0) { //1 代表登录, 0 代表没有登录 next() } else { next('/login') } } }) //@key 全局后置守卫 router.afterEach((to,from)=>{ console.log("afterEach") alert(1) }) var vm = new Vue({ router, el: "#app", }) </script> </body> </html>
路由独享守卫
路由独享守卫是在路由配置页面单独给路由配置的一个守卫
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../vue.js"></script> <script src="../vue-router.js"></script> <style> .router-link-active { color: red; } </style> </head> <body> <div id="app"> <router-link to="/position?islogin=1">职位</router-link> <router-link to="/login">登录</router-link> <router-view></router-view> </div> <script> const position = { template: `<div>职位页面</div>` } const search = { template: `<div>登录页面</div>` } var router = new VueRouter({ mode: 'hash', routes: [{ path: '/position', component: position, beforeEnter:(to,from,next)=>{ //@key 路由守卫 console.log('路由守卫'); next() } }, { path: '/login', component: search } ] }) var vm = new Vue({ router, el: "#app", }) </script> </body> </html>
组件内守卫
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../vue.js"></script> <script src="../vue-router.js"></script> <style> .router-link-active { color: red; } </style> </head> <body> <div id="app"> <router-link to="/position?islogin=1">职位</router-link> <router-link to="/login">登录</router-link> <router-view></router-view> </div> <script> const position = { template: `<div>职位页面</div>`, beforeRouteEnter(to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 `this` // 因为当守卫执行前,组件实例还没被创建 next() }, beforeRouteUpdate(to, from, next) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 `this` }, beforeRouteLeave(to, from, next) { //这个离开守卫通常用来禁止用户在还未保存修改前突然离开, //该导航可以通过 next(false) 来取消。 // 导航离开该组件的对应路由时调用 // 可以访问组件实例 `this` let rs = confirm("确认要离开么?") if (rs) { next() } } } const search = { template: `<div>登录页面</div>` } var router = new VueRouter({ mode: 'hash', routes: [{ path: '/position', component: position, }, { path: '/login', component: search } ] }) var vm = new Vue({ router, el: "#app", }) </script> </body> </html>
路由元信息
就是可以给路由配置一些参数,例如,哪些路由判断是否登录,哪些路由不判断路由验证
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../vue.js"></script> <script src="../vue-router.js"></script> <style> .router-link-active { color: red; } </style> </head> <body> <div id="app"> <router-link to="/position?islogin=1">职位</router-link> <router-link to="/login">登录</router-link> <router-view></router-view> </div> <script> const position = { template: `<div>职位页面</div>` } const search = { template: `<div>登录页面</div>` } var router = new VueRouter({ mode: 'hash', routes: [{ path: '/position', component: position, meta:{ //@key 通过meta字段进行配置 auth:true } }, { path: '/login', component: search } ] }) router.beforeEach((to, from, next) => { if(to.meta.auth == true){ //@Key console.log('请您先登录'); } next() }) var vm = new Vue({ router, el: "#app", }) </script> </body> </html
vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能
核心概念
-
State
存储数据,相当于组件中的data属性
-
Getter
有时候我们state里面的数据不是直接我们想要的,做一些过滤,相当于组件中的计算属性
-
Mutation
更改 Vuex 的 state 中的状态的唯一方法是提交 mutation中
-
Action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作
-
Module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
例子
计算器小例子
<!DOCTYPE html> <html lang="en"> <head> <script src="../vue.js"></script> <script src="../vue-router.js"></script> <script src="../vuex.js"></script> <style> .router-link-exact-active{ color: red; } </style> </head> <body> <div id="app"> <count-btn type="inc"></count-btn> <count-span></count-span> <count-btn type="dec"></count-btn> <div> <!-- 访问getters里面的数据 --> 总和:{{$store.getters.sum}} </div> </div> <script> Vue.component("count-span",{ template:`<span>{{$store.state.count}}</span>` //访问state里面的数据 }) Vue.component("count-btn",{ props:['type'], template:`<button @click="handleClick">{{btntext}}</button>`, computed:{ btntext(){ return this.type=="inc" ? "+":"-" } }, methods:{ handleClick(){ if(this.type == 'inc'){ // this.$store.commit('increment',2) //调用mutations里面的方法用commit this.$store.dispatch('add',2) //调用actions里面的方法用dispatch }else{ this.$store.commit('decrement',2) } } } }) var store = new Vuex.Store({ //相当于组件中的data state:{ count:0 }, //相当于组件中的计算属性computed //有时候state 中的数据,并不是我们直接想要的,而是需要经过相应的处理后,才能满足我们的需求 getters:{ sum(state,getters){ let s=0; for(let i=0;i<=state.count;i++){ s+=i; } return s; } }, //所有state数据的修改必须zaimutations里修改 //同步数据的修改 mutations:{ increment(state,num){ state.count +=num }, decrement(state,num){ state.count -= num } }, //异步数据 actions:{ add(context,num){ setTimeout(()=>{ context.commit('increment',num) //调用mutations里面的方法 },2000) } } }) var vm = new Vue({ store, el:"#app" }) </script> </body> </html>
辅助函数
通常使用state,或者mutation都需要
this.$store.state或者this.$store.commit
来使用,但是有时候调用太多的状态,这样使用还是有些麻烦,所以可以借助vuex的辅助函数来解决这个问题。
通过辅助函数mapState、mapGetters,mapActions、mapMutations,把vuex.store中的属性映射到vue实例身上,这样在vue实例中就能访问vuex.store中的属性了。mapState、mapGetters这两个需要数据实时更新的书写在computed计算属性中,mapMutations,mapAction等方法写在methods方法中
//利用辅助函数改写的计算器 //store.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { count: 0 }, getters:{ sum(state,getters){ let s=0; for(let i=0;i<=state.count;i++){ s+=i; } return s; } }, mutations: { increment(state, num) { state.count += num }, decrement(state, num) { state.count -= num } }, actions: { add(context,num){ setTimeout(()=>{ context.commit('increment',num) //调用mutations里面的方法 },1000) } }, modules: {} })
//在组件上使用vuex <template> <div class="home"> <div> <button @click="increment(2)">+</button> {{count}} <button @click="decrement(3)">-</button> </div> {{sum}} </div> </template> <script> import {mapState,mapGetters,mapMutations,mapActions} from 'vuex' export default { computed:{ ...mapState(['count']), ...mapGetters(['sum']) }, methods:{ ...mapMutations(['increment','decrement']), ...mapActions(['add']) } } </script>
Module
//cart.js
export default { namespaced:true, //命名空间 state:{ name:"cart" }, getters:{ }, mutations:{ test(state){ console.log('cart'); } }, actions:{ } }
//product.js
export default { state:{ name:"product" }, getters:{ }, mutations:{ test(state){ console.log('product'); } }, actions:{ } }
//vuex.js
import Vue from 'vue' import Vuex from 'vuex' import Cart from './Cart' import Product from './Product' Vue.use(Vuex) export default new Vuex.Store({ modules:{ Cart, Product } })
//在模版中使用
<template> <div class="home"> <div> name:{{$store.state.Cart.name}} <hr> <button @click="$store.commit('test')">product</button>//没有用命名空间空间 <button @click="$store.commit('Cart/test')">cart</button> //用命名空间的方法这样访问 </div> </div> </template>
流程图
项目
移动端适配方案
之前做移动端适配时,基本上是采用rem方案,现在发现了一个新的方案,就是用viewport单位,现在
viewport
单位越来越受到众多浏览器的支持,<span style="color:red">在写代码的时候,设计稿标记的是多少px,就写多少px,配置好postcss会自动转换为vw</span>
postcss-px-to-viewport,将px单位自动转换成viewport单位,用起来超级简单
-
安装
yarn add postcss-px-to-viewport -D
-
在vue.config.js中配置
const pxTovw = require('postcss-px-to-viewport') const path = require('path') module.exports = { css: { loaderOptions: { css: { // 这里的选项会传递给 css-loader }, postcss: { //这是postcss-px-to-viewport 的配置 plugins: [ new pxTovw({ unitToConvert: 'px', // 默认值`px`,需要转换的单位 viewportWidth: 375, //视窗的宽度,对应的是我们设计稿的宽度,一般是750 unitPrecision: 5, // 指定`px`转换为视窗单位值的小数位数 propList: ['*'],// 转化为vw的属性列表 viewportUnit: 'vw',//指定需要转换成的视窗单位,建议使用vw fontViewportUnit: 'vw',// 字体使用的视窗单位 selectorBlackList: [],// 指定不需要转换为视窗单位的类 minPixelValue: 1, // 小于或等于`1px`时不转换为视窗单位 mediaQuery: false,// 允许在媒体查询中转换`px` replace: true,// 是否直接更换属性值而不添加备用属性 exclude: [/node_modules/]// 忽略某些文件夹下的文件或特定文件 }) ] } } }, //代理配置 devServer: { proxy: { '/api': { target: "http://localhost:3000", changeOrigin: true, pathRewrite: { // "^/api":"" } } } }, //别名配置 chainWebpack: config => { config.resolve.alias .set('assets', path.resolve(__dirname, './src/assets/')) .set('views', path.resolve(__dirname, './src/views/')) } }
mockjs
1.什么是mock.js
生成随机数据,拦截 Ajax 请求。
通过随机数据,模拟各种场景;不需要修改既有代码,就可以拦截 Ajax 请求,返回模拟的响应数据;支持生成随机的文本、数字、布尔值、日期、邮箱、链接、图片、颜色等;支持支持扩展更多数据类型,支持自定义函数和正则。
优点是非常简单方便, 无侵入性, 基本覆盖常用的接口数据类型
2.为什么使用mock.js
在做开发时,当后端的接口还未完成,前端为了不影响工作效率,手动模拟后端接口,
1.我们可以使用json文件来模拟后台数据,但比较局限,无法模拟数据的增删改查
2.使用json-server模拟,但不能随机生成所需数据
3.使用mockjs模拟后端接口,可随机生成所需数据,可模拟对数据的增删改查3.mock 优点
1.前后端分离,
2.可随机生成大量的数据
3.用法简单
4.数据类型丰富
5.可扩展数据类型
6.在已有接口文档的情况下,我们可以直接按照接口文档来开发,将相应的字段写好,在接口完成 之后,只需要改变url地址即可。4.mock使用
yarn add mockjs -D
//mock/index.js //随便建个文件,在main.js 文件引入执行了, 然后就可以访问一下这些地址了/api... import Mock from 'mockjs' //生成20条数据 var data = Mock.mock({ // 20条数据 "data|20": [{ // 商品种类 "goodsClass": "女装", // 商品Id //商品售价 "goodsSale|30-500": 30 }] }) console.log(data); Mock.mock('/api/list','get',{ "list|10":[ { "id|+1":1, name:"@word()", "star|1-5":"🌟", price:"@float(10,100)", img:"@image('200*100', '#ffcc33','#FFF','png','图片文字')" } ] }) Mock.mock('/api/one','get',()=>{ return Mock.mock({ code:1, data:{ id:"@integer()", name:"@cname()" } }) }) Mock.mock('/api/add','post',(options)=>{ console.log('post的参数',options.body) return Mock.mock({ code:1, message:"添加成功" }) })
//只要执行了以上代码,就可以ajax访问了 axios.get('/api/list').then((res)=>{ console.log(res.data); }) axios.post('/api/add',{name:'wanglei',age:20}).then((res)=>{ console.log('res:',res.data) }) axios.get('/api/one').then((res)=>{ console.log(res.data) })
mockjs提供了很多函数,请参考官网http://mockjs.com/