Vue2
|字数总计:8.5k|阅读时长:40分钟|阅读量:
创建实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <!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="../js/vue.js"></script> </head> <body> <div id="root"> <h1>年龄:{{age}} 姓名:{{name}} 时间戳 {{Date.now()}}</h1> </div> </body> <script> const app = new Vue({ el: "#root", data: { age: 18, name: "scarf", }, }); </script> </html>
|
vue 实例与容器为一对一关系,一个容器只能被一个 vue 实例管理,一个 vue 实例只能管理一个容器。
可使用以下方式动态指定服务的容器:
1 2 3 4 5 6 7 8
| const vm = new Vue({ data: { age: 18, name: "scarf", }, });
vm.$mount('#root');
|
data 也可函数式定义 (vue 组件中必须使用这种写法):
1 2 3 4 5 6 7 8 9 10 11
| data: function () { return { name: "scarf" }; },
data() { return { name: "scarf" }; },
|
基础语法
插值语法
指令语法
1 2 3
| <a v-bind:href="url">点击跳转</a>
<a :href="url">点击跳转</a>
|
数据绑定
1 2 3 4 5 6
| <input type="text" :value="name" />
<input type="text" v-model:value="name" />
<input type="text" v-model="name" />
|
不止 data 中的数据,Vue 实例本身具有的属性也可以进行绑定:
1 2
| <p>{{$el}}</p> <input type="text" :value="$el" />
|
数据代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| let person = { name: "围巾", };
Object.defineProperty(person, "age", { value: 18, }); console.log(person);
console.log(Object.keys(person));
for (let key in person) { console.log(key); }
|
通常情况下一段 js 代码执行后对象中的属性值就已经确定下来,不会随着另一个数据的改变而改变,如下:
1 2 3
| let number = 10; let age = number;
|
而 Object.defineProperty
提供了一种方式来让 age
和 number
能够互相感知对方的变化,Vue 就使用了这一方式实现了数据的实时渲染:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let number = 10; let person = { name: "围巾", };
Object.defineProperty(person, "age", { get() { return number; }, set(value) { number = value; }, });
|
writable
纵使没有设置为 true
也可以进行修改,因为修改的实际对象是 number
而不是 age
。
简要实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <script> function Vue(data) { this._data = data;
const keys = Object.keys(this._data); keys.forEach((key) => { Object.defineProperty(this, key, { get() { return this._data[key]; }, set(val) { this._data[key] = val; }, }); }); }
let data = { name: "scarf", age: 12, };
let vm = new Vue(data); </script>
|
问题:
1)对 data
的代理是在创建 vm
实例时就完成的,如果 vm
实例化后再对 data
中的属性(包括嵌套的属性)进行变更,这些属性将不会被 vue
所管理。
使用 Vue.set(target,key,val)
方法解决:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <body> <div id="root"> <button @click="addProperty">添加属性</button> </div> </body> <script> let vm = new Vue({ el: "#root", data: { person: { name: "scarf", }, }, methods: { addProperty() { this.$set(this.$data.person, "age", 12); }, }, }); </script>
|
综上,data
中的各属性最好在 vue
实例创建前就确定好,特别是根数据对象中的属性。
2)对于值是数组的属性,当通过索引
对数组中元素值进行修改时,vue
并不会监测到数组的改变,也就是页面不会立刻重新渲染,虽然值已经改变。如果想让 vue
监视到数组的改变,需要使用对数组操作相关的函数,如 push
、pop
、shift
等(vue 对这些方法进行了重写)。
如果数组元素是对象,通过索引操作可以被 vue
监视到。
1 2 3 4 5
| hobby: [{ age: 1 }, { age: 2 }, { age: 3 }]
this.hobby[0].age = 6;
this.hobby[0] = { age: 7 };
|
当使用 obj = {a:1, b:2}
方式替换对象或者通过 shift
等方法向数组添加对象,并且对象中包含了原本不存在的属性,后续这些属性的变化是可以被监视到的。
事件处理
点击事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <body> <div id="root"> <button v-on:click="showInfo1">展示信息</button> <button @click="showInfo2(66, $event)">展示信息</button> <a href="http://www.baidu.com" @click.prevent="showInfo1">点击跳转</a> </div> </body> <script> const vm = new Vue({ el: "#root", methods: { showInfo1(event) { alert("你好"); }, showInfo2(number, event) { alert(number); }, }, }); </script>
|
事件修饰符
prevent
:组织默认事件
stop
:阻止事件冒泡
once
:事件只触发一次
capture
:使用事件的捕获模式
self
:只有 event.target
是当前操作的元素才触发事件
passive
:事件的默认行为立即执行,无需等待事件回调执行完毕,并非所有都生效
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <a href="http://www.baidu.com" @click.prevent="showInfo1">点击跳转</a>
<div @click="showInfo1"> <button @click.stop="showInfo1">提示信息</button> </div> <button @click.once="showInfo1">提示信息</button>
<div @click.capture="showInfo2(1)"> <button @click="showInfo2(2)">提示信息</button> </div>
<div @click.self="showInfo1"> <button @click="showInfo1">提示信息</button> </div>
|
修饰符之间允许连写:
1 2 3
| <div @click="showInfo"> <a href="http://www.baidu.com" @click.prevent.stop="showInfo1">点击跳转</a> </div>
|
键盘事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <body> <div id="root"> <input type="text" placeholder="按下回车提示" @keyup.enter="showInfo" /> </div> </body> <script> const vm = new Vue({ el: "#root", methods: { showInfo(event) { console.log(event.target.value); }, }, }); </script>
|
vue 默认提供的按键别名:enter
、delete
、esc
、space
、tab
、up
、down
、left
、right
。对于 vue 未提供的别名可使用按键原始 key
或 keyCode
(不推荐)进行绑定,具体可以通过 event.key/event.keyCode
进行查看:
1 2 3 4 5 6 7 8
| <input type="text" placeholder="按下中英文切换键提示" @keyup.caps-lock="showInfo" />
<input type="text" placeholder="按下 tab 键提示" @keydown.tab="showInfo" />
<input type="text" placeholder="按下 command 键提示" @keydown.Meta="showInfo" />
<input type="text" placeholder="按下 Command+e 提示" @keydown.Meta.e="showInfo" />
|
可以直接将 @XXX="method"
中的方法替换成具体的语句,点击时会自动执行。但是需注意,语句中使用的属性或方法必须是被 vm
所管理的。
计算属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <body> <div id="root"> name: <input type="text" v-model="name" /> age: <input type="text" v-model="age" /><br /> info: {{getInfo()}} <br /> info: {{info}} info2: {{info2}} <input type="text" v-model="info" /><br /> </div> </body> <script> const vm = new Vue({ el: "#root", data: { name: "scarf", age: 18, }, methods: { getInfo() { return this.name + "-" + this.age; }, }, computed: { info: { get() { return this.name + "-" + this.age; }, set(value) { const arr = value.split("-"); this.name = arr[0]; this.age = arr[1]; }, }, info2() { return this.name + "-" + this.age; }, }, }); </script>
|
监视属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <script> const vm = new Vue({ el: "#root", data: { isHot: true, numbers: { a: 1, b: 2, }, }, watch: { isHot: { handler(newVal, oldVal) { console.log(newVal, oldVal); }, }, "numbers.a": { handler(newVal, oldVal) { console.log(newVal, oldVal); }, }, numbers: { deep: true, handler(newVal, oldVal) { console.log(newVal, oldVal); }, }, }, });
vm.$watch("isHot", { handler(newVal, oldVal) { console.log(newVal, oldVal); }, }); </script>
|
样式绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <style>...</style> <body> <div class="basic bg1"></div> <div id="root"> <div class="basic" :class="bg"></div> <div class="basic" :class="bgArr"></div> <div class="basic" :class="bgObj"></div> <div class="basic" :style="{fontSize: size + 'px'}">hello</div> <div class="basic" :style="styleObj">hello</div> <div class="basic" :style="[styleObj, styleObj2]">hello</div> </div> </body> <script> const vm = new Vue({ el: "#root", data: { bg: "bg1", bgArr: ["bg1", "bg2"], bgObj: { bg1: false, bg2: true, }, size: 40, styleObj: { fontSize: "20px", color: "red", }, styleObj2: { backgroundColor: "orange", }, }, }); </script>
|
条件渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <body> <div id="root"> <p v-show="isShow">hello</p> <p v-if="isShow">hello</p>
<p v-if="1 === 3">3</p> <p v-else-if="1 === 3">2</p> <p v-else>1</p>
<template v-if="true"> <p>1</p> <p>2</p> <p>3</p> </template> </div> </body> <script> new Vue({ el: "#root", data: { isShow: false, }, }); </script>
|
列表渲染
1 2 3 4 5 6 7 8 9 10
| <li v-for="person in persons" :key="person.id">{{person.name}}</li>
<li v-for="(person, index) in persons" :key="index">{{person.name}}</li>
<li v-for="(val,key) in persons[0]" :key="key">{{val}}</li>
<li v-for="(char,index) in '123'" :key="index">{{char}}</li>
<li v-for="(num,index) in 5" :key="index">{{num}}</li>
|
最好不要使用 index
作为 key
,如果后续向遍历对象非尾部新增一个数据,页面重新渲染后那些后移的元素 index
值将改变。而且由于对比算法
,使用 index
效率低下。
表单收集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <form> 账号:<input type="text" v-model.trim="account" /><br /> 密码:<input type="password" v-model="password" /><br /> 年龄:<input type="number" v-model.number="age" /><br /> 性别: 男<input type="radio" name="sex" value="male" v-model="sex" /> 女<input type="radio" name="sex" value="female" v-model="sex" /><br /> 爱好: 吃饭<input type="checkbox" value="eat" v-model="hobby" /> 睡觉<input type="checkbox" value="sleep" v-model="hobby" /><br /> 城市: <select v-model="city"> <option value="" selected disabled>选择地区</option> <option value="beijing">北京</option> <option value="shanghai">上海</option> </select> <br /> 其他信息: <textarea v-model.lazy="other"></textarea><br /> </form>
|
过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <body> <div id="root"> <p>{{ date | addMonth | addDay(1) }}</p> <input :value="date | addMonth | addDay(1)" /> </div> </body> <script> new Vue({ el: "#root", data: { date: 2024, }, filters: { addMonth(date) { return date + "-07"; }, addDay(date, day) { return date + "-0" + day; }, }, }); </script>
|
全局:
1 2 3 4 5 6
| Vue.filter("addMonth", function (date) { return date + "-07"; }); Vue.filter("addDay", function (date, day) { return date + "-0" + day; });
|
其他指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <head> <style> [v-cloak] { display: none; } </style> </head> <body> <div id="root"> <p v-text="name"></p> <p v-html="name"></p> <p v-cloak>{{name}}</p> <p v-once>{{name}}</p> <p v-pre>111</p> <p v-pre>{{name}}</p> </div> <script type="text/javascript" src="../js/vue.js"></script> </body>
|
自定义指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <body> <div id="root"> <span v-hello="name"></span> <input type="text" v-fbind="name" /> </div> </body> <script> new Vue({ el: "#root", data: { name: "scarf", }, directives: { hello(element, binding) { element.innerText = "hello " + binding.value; }, fbind: { bind(element, binding) { element.value = binding.value; }, inserted(element, binding) { element.focus(); }, update(element, binding) { element.value = binding.value; }, }, }, }); </script>
|
指令相关的回调函数中的 this
是 window
而不是 vm
实例。
生命周期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <script> new Vue({ el: "#root", beforeCreate() { console.log("beforeCreate"); }, created() { console.log("created"); }, beforeMount() { console.log("beforeMount"); }, mounted() { console.log("mounted"); }, beforeUpdate() { console.log("beforeUpdate"); }, updated() { console.log("updated"); }, beforeDestroy() { console.log("beforeDestroy"); }, destroyed() { console.log("destroyed"); }, }); </script>
|
组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| <body> <div id="root"> <nation></nation> <student></student> <student /> </div> </body> <script> const nation = Vue.extend({ template: ` <div>{{name}}</div> `, data() { return { name: "China", }; }, }); Vue.component("nation", nation); const student = Vue.extend({ name: "Scarf", template: ` <div>{{name}}</div> `, data() { return { name: "scarf", }; }, });
new Vue({ el: "#root", components: { student, }, }); </script>
|
ref
获取 dom
元素:
1
| <p ref="age">{{ age }}</p>
|
1 2
| const el = this.$refs.age; console.log(el.innerText);
|
获取 vc
实例:
1 2
| <!-- id 属性实际上加在子组件的根标签上 --> <Index ref="index" id="index" />
|
1 2 3 4 5
| const vc = this.$refs.index; console.log(vc);
console.log(document.getElementById("index"));
|
props
1 2
| <Index :age="age" sex="男" />
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
props: { name: { type: String, required: false, default: "scarf", }, age: { type: Number, required: true, }, sex: { type: String, required: true, }, obj: { type: Object, required: true, } } data() { return { myName: this.name, myObj: obj }; },
|
子组件不能直接修改 props
中的数据,可在 data
中定义不同名的属性解决。如果接收的是一个对象,myObj
中的属性修改会连带父组件中的对象一起修改,除非以 myObj = {}
形式进行修改。
mixin
代码复用
1 2 3 4 5 6 7
| export const mixin = { methods: { showName() { alert(this.name); }, }, };
|
不仅能混入 methods
,只要与 data
同级的都可以混入,包括生命周期钩子等,并且会自动进行合并。
如果混入冲突,以实际定义的为主。特别的,生命周期钩子混入冲突时,混入以及自定义的代码都会执行。
插件
1 2 3 4 5 6 7 8
| export default { install(Vue) { Vue.filter("sayHello", function (name) { return "hello " + name; }); }, };
|
1 2 3
| import plugin from "./plugins/plugin";
Vue.use(plugin);
|
1
| <p>{{ name | sayHello }}</p>
|
scoped
所有组件的 style
样式最终都会汇总到一起,使用 scoped
将作用域限制在自身。
不会影响到父组件,但子组件会被影响。
1 2 3 4 5
| <style scoped> .bg { background-color: pink; } </style>
|
组件通信
函数传递
父组件定义函数
1 2 3
| methods: { receive(data) {} }
|
将函数传给子组件
1 2
| <Child :receive="receive" />
|
子组件接收函数
子组件传递数据
自定义事件
父组件为子组件绑定自定义事件
1
| <Student ref="student" @demo="printName" />
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| mounted() { this.$refs.student.$on("demo2", this.printName); this.$refs.student.$on("demo3", function(name){ }); this.$refs.student.$on("demo4", (name)=>{ }); }, methods: { printName(name) { }, },
|
绑定后,自定义事件会存在于子组件实例 vc
上。
对于自定义事件也可以使用事件修饰符。eg:@demo.once
子组件触发自定义事件
1 2
| this.$emit("demo", this.name); this.$emit("demo2", this.name);
|
子组件解绑自定义事件
1 2 3 4 5
| this.$off("demo");
this.$off(["demo", "demo2"]);
this.$off();
|
eg:
1
| <Student @click="printName" />
|
默认为子组件绑定 click
自定义事件,而不是原生 dom
事件。如下方式解决:
1
| <Student @click.native="printName" />
|
全局事件总线
实现原理就是在 Vue
的原型对象上添加一个 vm
实例。
1 2 3 4 5 6
| new Vue({ render: (h) => h(App), beforeCreate() { + Vue.prototype.$bus = this; // $bus 名称随意 }, }).$mount("#app");
|
之后所有组件都能都过 this.$bus
逐层向上找到所添加的 vm
。
由于 vm
本身具有 $on/$emit
通信基础,即各组件都能通过 this.$bus.$on/this.$bus.$emit
在 vm
实例中绑定和触发自定义事件。
特别的,绑定的全局事件必须进行解绑:
1 2 3
| beforeDestroy() { this.$bus.$off("event"); },
|
消息订阅与发布
第三方库,任何前端框架都能使用。
安装
引入
1
| import pubsub from "pubsub-js";
|
订阅消息
1 2 3
| this.subId = pubsub.subscribe("demo", (msgName, data) => { console.log(data); });
|
发布消息
1
| pubsub.publish("demo", "围巾");
|
取消订阅
1
| pubsub.unsubscribe(this.subId);
|
浏览器存储
LocalStorage、SessionStorage
四个 API:getItem
、setItem
、removeItem
、clear
。
$nextTick
1
| <input v-if="isShow" type="text" ref="input" />
|
1 2 3 4
| handle(){ this.isShow = true; this.$refs.input.focus(); }
|
方法执行后输入框并没有自动获取焦点,原因是 isShow
改变后,vue
并没有立刻重新解析模板。出于效率问题,当方法执行完后才会对模板重新进行解析。
解决:
1 2 3 4 5 6 7
| handle(){ this.isShow = true; this.$nextTick(function () { this.$refs.input.focus(); }); }
|
动画效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <button @click="isShow = !isShow">显示/隐藏</button>
<transition name="hello" appear> <h2 v-show="isShow">hello</h2> </transition>
<style scoped>
@keyframes demo { from { transform: translateX(-100%); } to { transform: translateX(0px); } }
.hello-enter-active { animation: demo 1s; } .hello-leave-active { animation: demo 1s reverse; } </style>
|
transition
标签会自动判断元素状态并将对应的样式绑定到元素上,v-show
非必须。
样式可用如下写法替换:
1 2 3 4 5 6 7 8 9 10 11 12
| .hello-enter-active, .hello-leave-active { transition: 1s; } .hello-enter, .hello-leave-to { transform: translateX(-100%); } .hello-enter-to, .hello-leave { transform: translateX(0px); }
|
hello-enter/leave-active
:整个进入/离开过程一直生效
hello-enter
:起点样式
hello-enter-to
:终点样式
同时绑定多个元素:
1 2 3 4 5
| <transition-group name="hello2" appear> <h2 v-show="isShow" key="1">hello2</h2> <h2 v-show="isShow" key="2">hello2</h2> </transition-group>
|
第三方库:
1 2 3 4 5 6 7 8
| <transition name="animate__animated animate__bounce" enter-active-class="animate__zoomInDown" leave-active-class="animate__zoomOutDown" appear > <h2 v-show="isShow">hello3</h2> </transition>
|
配置代理
由于浏览器同源策略,需要解决跨域问题。
原理:请求先转发给代理服务器(地址跟前端一致),代理服务器再去请求后端资源并把结果转发给前端。代理服务器与后端的通信不经过浏览器,所以不受同源策略影响。
注意:代理服务器只适用于开发环境,打包后就不存在了。
1 2 3 4 5 6
| module.exports = defineConfig({ devServer: { proxy: "http://localhost:8888", }, });
|
1
| import axios from "axios";
|
1 2 3 4 5 6 7 8 9
| axios.get("http://localhost:8080/api/user/1").then( (response) => { console.log(response.data); }, (error) => { console.log(error.response.data.message); } );
|
改进:
1 2 3 4 5 6 7 8 9 10 11
| devServer: { proxy: { "/api": { target: "http://localhost:8888", }, }, },
|
vue-resource
1 2
| import vueResource from "vue-resource" Vue.use(vueResource);
|
之后 vm
/vc
实例上会多一个 $http
,用法与 axios
类似。
插槽
默认插槽
子组件定义插槽
父组件传递结构
1 2 3 4 5 6
| <Category> <img src="https://xxx.jpg" alt="" /> </Category> <Category> <p>hello</p> </Category>
|
具名插槽
1 2 3
| <slot name="slot1">111</slot> <slot name="slot2">222</slot> <slot name="slot3">333</slot>
|
1 2 3 4 5 6 7 8 9 10 11
| <Category> <img slot="slot1" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" /> <template slot="slot2"> <a href="#">跳转</a><a href="#">跳转</a> </template> <template v-slot:slot3> <p>你好</p> </template> </Category>
|
作用域插槽
允许子组件通过插槽传递数据给父组件。
1
| <slot name="slot4" :foods="foods">444</slot>
|
1 2 3 4 5 6 7 8
| <Category> <template slot="slot4" slot-scope="data"> <ul> <li v-for="(food, index) in data.foods" :key="index">{{ food }}</li> </ul> </template> </Category>
|
Vuex
基本使用
统一管理多个组件共享的数据。适用于任意组件间通信。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import Vue from "vue"; import Vuex from "vuex";
const state = { num: 0, };
const mutations = { INCR(state, value) { state.num += value; }, };
const actions = { incrment(context, value) { context.commit("INCR", value); }, };
const getters = { doubleNum(state) { return state.num * 2; }, };
Vue.use(Vuex); export default new Vuex.Store({ state, mutations, actions, getters, });
|
actions
中的方法参数 context
内容如下,可对 store
中定义的各层级进行操作。
1 2 3 4 5 6
| +import store from "./store/index.js";
new Vue({ render: (h) => h(App), + store, }).$mount("#app");
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <template> <div> <!-- 当 state 中的值改变会自动重新渲染 --> <h2>求和结果: {{ $store.state.num }}</h2> <h2>结果翻倍: {{ $store.getters.doubleNum }}</h2> <button @click="incr">自增</button> </div> </template>
<script> export default { name: "Vuex", methods: { incr() { this.$store.dispatch("incrment", 1); }, }, }; </script>
|
优化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <template> <div> <h2>求和结果: {{ num }}</h2> <h2>结果翻倍: {{ doubleNum }}</h2> <button @click="incrment(1)">自增</button> </div> </template>
<script> import { mapState, mapGetters, mapActions, mapMutations } from "vuex"; export default { name: "Vuex", computed: { // 相当于 num(){ return this.$store.state.num; } // ...mapState({ num: "num" }), // 写法一, 方法名可自定义 ...mapState(["num"]), // 写法二, 名称一致
...mapGetters(["doubleNum"]), }, methods: { // 相当于 incrment(value){ return this.$store.dispatch("incrment", value); } ...mapActions(["incrment"]), // mapMutations 略 }, }; </script>
|
模块化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import Vue from "vue"; import Vuex from "vuex";
const a = { namespaced: true, state: {}, mutations: {}, actions: {}, getters: {}, };
const b = { namespaced: true, state: {}, mutations: {}, actions: {}, getters: {}, };
Vue.use(Vuex); export default new Vuex.Store({ modules: { a, b, }, });
|
1 2 3 4 5 6 7
| this.$store.state.a.num this.$store.getters["a/doubleNum"] this.$store.dispatch("a/incrment", value) ...mapState(["a", "b"]) ==> {{ a.num }} ...mapState("a", ["num"]) ==> {{ num }}
|
路由
基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import VueRouter from "vue-router"; import About from "@/pages/About"; import Home from "@/pages/Home"; import Message from "@/pages/Message";
export default new VueRouter({ routes: [ { name: "about", path: "/about", component: About, }, { name: "home", path: "/home/:age?/:title?", component: Home, children: [ { path: "message", component: Message, }, ], }, ], });
|
1 2 3 4 5 6 7 8 9 10
| import Vue from "vue"; +import VueRouter from "vue-router"; +import router from "./router/index.js";
Vue.use(VueRouter);
new Vue({ render: (h) => h(App), + router, }).$mount("#app");
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <div class="route"> <ul> <li> <router-link to="/about">About</router-link> </li> <li> <router-link to="/home">Home</router-link> </li> </ul> <div> <router-view></router-view> </div> </div>
|
- 当展示区中的组件被替换,默认会销毁原来的组件。
- 组件实例身上会多出
$router
、$route
属性。
路由传参
query:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <router-link to="/about?title=关于">About</router-link> <router-link :to="`/about?title=${title}`">About</router-link>
<router-link :to="{ path: '/about', query: { title, }, }" > About </router-link>
<router-link :to="{ name: 'about', query: { title, }, }" > About </router-link>
|
params:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <router-link to="/home/66/主页">Home</router-link> <router-link :to="`/home/${age}/${title}`">Home</router-link>
<router-link :to="{ name: 'home', // 只能使用 name,不能使用 path params: { age, title, }, }" > Home </router-link>
|
props:
1 2 3 4 5 6 7
| { name: "about", path: "/about", component: About, props: { age: 66, name: "scarf" }, },
|
1 2 3 4 5 6 7 8 9 10 11
| { name: "about", path: "/about", component: About, props($route) { return { title: $route.query.title, }; }, },
|
1 2 3 4 5 6
| { name: "home", path: "/home/:age?/:title?", component: Home, props: true, },
|
meta:
1 2 3 4 5 6
| { name: "about", path: "/about", component: About, meta: { sex: "male" }, },
|
历史记录
模式:
push
(默认):使用栈存储地址的变化记录
replace
:新地址会覆盖栈中上一条地址记录
1
| <router-link replace to="/about"></router-link>
|
编程式路由
示例:
1 2 3 4 5 6
| this.$router.push({ path: "/about", });
this.$router.replace("/home");
|
$router
其他方法略。
缓存组件
组件被替换时,不进行销毁,而是缓存,从而保留原先数据。
1 2 3 4 5 6 7 8 9
| <keep-alive include="Message"> <router-view></router-view> </keep-alive>
<keep-alive :include="['News', 'Message']"> <router-view></router-view> </keep-alive>
|
生命周期钩子
1 2 3 4 5 6 7 8
| activated() { console.log("激活"); },
deactivated() { console.log("被替换"); },
|
路由守卫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import VueRouter from "vue-router"; import About from "@/pages/About";
const router = new VueRouter({ routes: [ { name: "about", path: "/about", component: About, beforeEnter: (to, from, next) => { next(); }, }, ], });
router.beforeEach((to, from, next) => { next(); });
router.afterEach((to, from) => {});
export default router;
|
1 2 3 4 5 6 7 8 9
| beforeRouteEnter(to, from, next) { next(); },
beforeRouteLeave(to, from, next) { next(); },
|
执行顺序:
- 全局前置守卫
- 路由独享守卫
- 组件前置守卫
- 全局后置守卫
- 组件失活守卫
工作模式
hash
(默认):地址中 #
即之后的路径不会作为网络请求的一部分。
history
:地址中不会出现 #
,但是可能因为路径冲突导致直接访问后端而不是路由跳转。
1 2 3 4
| const router = new VueRouter({ mode: "history", routes: [], });
|
测试:
新建文件夹并将之作为后端项目
编写 server.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const express = require("express");
const app = express(); app.use(express.static(__dirname + "/static"));
app.listen(5001, (err) => { if (!err) { console.log("服务器启动成功"); } });
app.get("/about", (req, res) => { res.send({ name: "scarf", age: 66, }); });
|
打包前端代码
将 dist
文件夹下的文件拷贝到后端服务的 static
文件夹下
启动后端服务
访问 http://localhost:5001/about
将直接返回后端服务响应数据,而不是具体的页面。
ElementUI
1 2 3 4
| import ElementUI from "element-ui"; import "element-ui/lib/theme-chalk/index.css";
Vue.use(ElementUI)
|
按需引入:
1
| npm i babel-plugin-component -D
|
babel.config.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| module.exports = { presets: [ "@vue/cli-plugin-babel/preset", + ["@babel/preset-env", { modules: false }], ], + plugins: [ + [ + "component", + { + libraryName: "element-ui", + styleLibraryName: "theme-chalk", + }, + ], + ], };
|
1 2 3 4
| import { Button, Row } from "element-ui";
Vue.component("el-button", Button); Vue.component("el-row", Row);
|