vue笔记
简介
Vue是JS延伸出的一种框架,能在一个组件中写HTML、CSS、JS,它不需要操作DOM,而是通过渲染方式挂载到页面上,效率是不是会高一些呢?
写代码也很爽,我想给一个功能添加点击事件,只需要给标签绑定@click,再到方法里写绑定事件的函数,这样就好了;对比原生js,你需要到html添加id属性,在回到js获取id,再给id绑定事件写函数,步骤是不是较为繁琐?
说说不好的点,你需要记住大量指令,经常查阅文档是家常便饭的事
安装过程与创建hello-world项目
安装Node,选择长期维护版,自带npm
1
2
3
4
5// 查询node版本
node -v
// 查询npm版本
npm -v通过npm指令安装nrm,nrm是管理npm的工具,由于npm服务器在国外,通过npm下载依赖会因为墙问题,导致项目运行不起来,所以需要nrm修改下载源,比如taobao镜像源在国内,下载速度会快一些
1
2
3
4
5
6
7
8// 安装nrm
npm install nrm -g
// 查看所有支持源
nrm ls
// 使用taobao镜像源
nrm use taobao安装vue脚手架,用游戏方式来理解,vue官方开局送你一套强力装备,让你能开局直接打Boss
1
2
3
4
5
6
7
8// 删除旧的vue脚手架,第一次安装vue脚手,请跳过它
npm uninstall vue-cli -g
// 删除旧的vue脚手架,第一次安装vue脚手,请跳过它
yarn global remove vue-cli
// 安装vue脚手架
npm install -g @vue/cli控制台指令,实在不懂上google
1
2
3
4
5
6
7
8
9
10
11// 返回上一级文件
cd ..
// 默认根目录,进入到桌面
cd Desktop
// 默认根目录,进入到文稿
cd Documents
// 清一下控制台
clear创建vue项目,我习惯在文稿中生成项目,当然你也可以在桌面或其他地方生成;如果你对vue-cli内容感兴趣,可到vue官网查看请点击我跳转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 默认根目录,进入到文稿,文稿指mac电脑,win电脑需要指定cdef盘
Mac: cd Documents
windows: 进入d盘,在d盘的地址栏输入cmd按下回车,会生成d盘路径的命令窗口;d盘鼠标右键也会生成d盘路径的命令窗口
// 创建新项目,项目名叫hello-world
vue create hello-world
// 学习vue基础,默认vue3版本够用了
// 手动选择功能则是添加额外插件,例如TypeScript、Router、Vuex...
Default([Vue 3]babel, eslint)
// 项目生成后,进入hello-world项目
cd hello-world
// 启动你的项目吧 XD
npm run serve
// 点击链接查看页面
Local: http://localhost:8080/项目文件说明
文件名 说明 public 项目入口文件,存放index.html文件,通常不做任何修改 src 写代码的地方 src/assets 存放静态资源,比如背景图、图标等图片资源,再或者公共样式文件 src/components 存放抽离出来的公共组件 src/router 路由管理 src/store 全局状态管理文件 src/views 存放比较大的视图级组件 src/App.vue 项目根组件,所有组件通过路由引入到App.vue才能在页面中显示出来 src/main.js 项目入口文件,webpack打包时会以main.js为起点 .gitignore 告诉git要忽略项目中的哪些文件或文件夹,主要用在上传项目到git仓库 babel.config.js 项目范围的配置,支持es6、es7等语法,最新语法能转换成浏览器识别的老式语言 jsconfig.json 对JS项目目录提供个性化支持:比如路径短写、引入文件自动完成等,提升开发体 package.json 项目描述文件,能了解到项目版本号、依赖等信息,记录版本范围 package-lock.json 项目所依赖的第三方包的版本号,记录精确版本号 README 项目说明文档 vue.config.js 不怎么了解,好像是用来修改webpack配置 github下载vue项目运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// cnpm是淘宝团队提供的一个npm国内替代工具,请先安装cnpm
npm install -g cnpm
// 进入项目
cd 项目文件名
// 安装依赖
npm install
// 如果npm安装依赖失败,换成cnpm再试试
cnpm install
// 运行项目,要么dev要么serve
// 不知道用哪个,看项目文档或package.js文件的"scripts"
npm run dev或serve
vue指令
- 常用指令
指令 说明 代码 v-bind 绑定属性 <a :href="url"> ... </a>
v-on 绑定事件 <a @click="doSomething"> ... </a>
v-if 判断 <p v-if="seen">现在你看到我了</p>
v-show 判断 <h1 v-show="ok">Hello!</h1>
v-for 循环 <li v-for="item in items"></li>
v-model 双向绑定 <input v-model="message" placeholder="edit me" />
v-once 只渲染一次 <span v-once>这个将不会改变: {{ msg }}</span>
v-html 按普通HTML插入 <div v-html="'<h1>Hello World</h1>'"></div>
v-if和v-show区别,如果是频繁切换显示,v-show性能影响较小
v-for第一个参数是值,第二个参数是索引;需配合:key使用否则报错
vue方法
- methods:方法,用来写方法的地方
- computed:计算属性,methods和computed都能用来计算,区别在computed有缓存
- watch:侦听器,用来感应数据变化,第一个参数新数据,第二个参数旧数据
class与style绑定
- 第一种class写法,通常用来增删、切换class
<div :class="msg">hello, 幽蓝</div>
- 第二种class写法,通常用来显示隐藏样式,active表示class名,isActive设置true表示显示
<div :class="{active: isActive}">hello,幽蓝</div>
- 第一种style绑定,通常绑定到对象,在styleObject上写样式更直观易懂
<div :style="styleObject"></div>
- 第二种style绑定,直接写上去,阅读体验可能不太好
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
补充说明:script写样式推荐用驼峰形式,短横杠形式需要添加引号怪麻烦的
1 | data() { |
事件传值与多个处理函数
- 事件传递参数,如果是传递event对象需要添加$符号
<h1 @click="addNumEventTwo(10, $event)">count: {{ num }}</h1>
- 事件绑定多个处理函数,用分号隔开,以及使用函数调用
1
2<h1 :style="{backgroundColor:color}">count: {{ num }}</h1>
<input type="submit" @click="addNumEvent(10);changeColor('yellow')">
修饰符
事件修饰符
事件修饰符 说明 代码 .stop 阻止事件冒泡 <a @click.stop="doThis"></a>
.prevent 阻止默认事件 <form @submit.prevent="onSubmit"></form>
.capture 添加事件捕获模式 <div @click.capture="doThis">...</div>
.self 自身触发处理函数 <div @click.self="doThat">...</div>
.once 只触发一次 <a @click.once="doThis"></a>
.passive 滚动时立即触发 <div @scroll.passive="onScroll">...</div>
按键修饰符
.enter:按下回车键触发处理函数<input type="text" @keydown="searchEvent">
.tab
.delete
.esc
.space
.up
.down
.left
.right系统修饰符
.ctrl
.alt
.shift
.meta鼠标修饰符
.left
.right
.middle表单修饰符
.lazy:失去焦点才会执行v-model.lazy="searchKey"
.number:转为数字
.trim:去掉前后空格
父子组件导入导出
template标签导入HelloWorld子组件,单、双标签使用上没区别;两个标签并不会覆盖,而是以复用形式存在
1
2
3
4
5<template>
<HelloWorld/>
<HelloWorld></HelloWorld>
...
</template>组件导入说明
1
2
3
4
5
6
7
8// @等同于/src
import HelloWorld from "@/components/HelloWorld.vue";
// .等同于当前路径
import App from "./App.vue";
// ..等同于上一级路径
import HomeView from "../views/HomeView.vue";组件导出说明,建议首字母大写,且组件名与文件名是相同的;components是注册组件,比如导入HelloWorld组件
1
2
3
4
5
6
7// 导出名为HomeView的组件,包含注册HelloWorld
export default {
name: "HomeView",
components: {
HelloWorld
}
}
props
- 父组件将数据传递给子组件,由props来接收,同时还具有校验数据的功能
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
39props: {
// 基础的类型检查 (`null` 和 `undefined` 值会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组的默认值必须从一个工厂函数返回
default() {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator(value) {
// 这个值必须与下列字符串中的其中一个相匹配
return ['success', 'warning', 'danger'].includes(value)
}
},
// 具有默认值的函数
propG: {
type: Function,
// 与对象或数组的默认值不同,这不是一个工厂函数——这是一个用作默认值的函数
default() {
return 'Default function'
}
}
}
生命周期函数
- beforeCreate():初始化数据之前
- created():初始化数据之后
- beforeMount():挂载渲染之前
- mounted():挂载渲染之后
- beforeUpdate():更新之前
- updated():更新之后
- beforeUnmount():销毁之前
- unmounted():销毁之后
组合式API
- 通常我们在data写数据,methods写方法,这种分开式写法,在后期修改某块功能,你需要上下反复查找,最怕不小心修改到别人的代码或其他功能;setup则是集中式写法,它使数据、方法等集中写在一块,像传统JS一样
- ref是定义单个数据,相当于data写数据;而定义函数,相当于methods内定义函数,另外说一下,setup不能用this找数据,而是用.value来找
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<template>
<div @click="changeNum">点我加十{{num}}</div>
</template>
<script>
import {ref} from 'vue';
export default {
name: "HelloWorld",
setup(){
const num = ref(0);
function changeNum(){
num.value += 10;
}
return {num, changeNum};
}
};
</script> - 如果说ref()是定义单个数据,那么reactive()就是用来定义多个数据;某一天,我想把名字反转后显示,可以用computed属性来帮助我计算结果,然后以插值形式来展示结果,假如不用computed属性,那么它就不会计算结果,而是获取reverseUsername的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<template>
<h1>用户名:{{ user.username }}</h1>
<h1>年龄:{{ user.age }}</h1>
<h1>反转:{{ user.reverseUsername }}</h1>
</template>
<script>
import { ref, reactive, computed } from "vue";
export default {
name: "HelloWorld",
setup() {
const user = reactive({
username: "幽蓝",
age: 9,
interest: "swim",
reverseUsername: computed(() => {
return user.username.split("").reverse().join("");
})
});
return { user };
}
};
</script> - toRefs用来解构对象,可以使插值不必打点调用,缺点是同名覆盖;如果想点击后修改对象的值,按JS方式来写
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>
<h1>用户名:{{ username }}</h1>
<h1 @click="changeAge">年龄:{{ age }}</h1>
<h1>反转:{{ reverseUsername }}</h1>
</template>
<script>
import { reactive, computed, toRefs } from "vue";
export default {
name: "HelloWorld",
setup() {
const user = reactive({
username: "幽蓝",
age: 9,
interest: "swim",
reverseUsername: computed(() => {
return user.username.split("").reverse().join("");
})
});
function changeAge() {
user.age = 20;
}
return { ...toRefs(user), changeAge };
}
};
</script> - watch与watchEffect看场景灵活使用吧
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>
<h1 @click="changeAge">年龄:{{ age }}</h1>
</template>
<script>
import { ref, watch, watchEffect } from "vue";
export default {
name: "HelloWorld",
setup() {
const age = ref(9);
// 监听指定属性,当属性变化时执行函数,可以获取新值和旧值
watch(age, (newAge, prevAge) => {
console.log("watch:" + age.value);
console.log(newAge , prevAge);
});
// 不需要监听指定属性,组件初始化前会自动执行一次,当属性变化时执行函数
watchEffect(() => {
console.log("watchEffect:" + age.value);
});
function changeAge() {
age.value++;
}
return { age, changeAge };
}
};
</script> - setup参数:第一个参数props,包含父组件传递给子组件所有数据,第二个参数context暴露一些属性方法,包含attrs、slots、emit
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// 父组件
<HelloWorld :username="username" :age="age"/>
data(){
return{
username: '幽蓝',
age: 9
}
}
// 子组件
<h1>username: {{username}}</h1>
<h1>age: {{age}}</h1>
<h1>description: {{description}}</h1>
import { ref } from "vue";
// props接收父组件传递给子组件所有数据
props: ["username", "age"],
// setup的props是响应式数据,不要解构;context是js对象,可以解构
setup(props, context) {
const description = ref(props.username + "年龄是" + props.age);
console.log(context);
return { description };
} - provide提供数据,inject接收数据,主要解决props深层嵌套问题
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 { reactive, provide } from "vue";
setup() {
const student = reactive({
name: "张三",
classname: "三年1班"
});
// 第一个参数是key,可以取任意名字,提供给inject接收
// 第二个参数是你想传的值,提供给第一个参数
provide("studentA", student);
}
// 子组件
<h1>学生</h1>
<h1>name: {{ name }}</h1>
<h1>classname: {{ classname }}</h1>
import { inject } from "vue";
props: ["username", "age"],
setup() {
const student = inject('studentA')
return { ...student };
}
路由
vue2脚手架自带路由安装,vue3新推出vite构建工具没有路由,开始安装
1
2
3
4
5
6
7
8
9
10
11// 用vite构建vue项目,名字取vue3router
npm init vite@latest vue3router --template vue
// 进入vue3router项目
cd vue3router
// 安装依赖
npm install
// 安装router
npm install vue-router@4你的路径是什么,显示相对应组件
<router-view></router-view>
路径参数,根据id的不同,显示不同样式,另外:id是一个变量
1
2
3
4
5// 路径:id写法
{ path: '/users/:id', component: User }
// 获取id
$route.params.id404页面
1
2// 设置404 notFound页面
{ path:"/:path(.*)", component:NotFound }正则匹配与重复参数
1
2
3
4
5
6
7// :id仅匹配数字
{ path:"/article/:id(\\d+)", component:Article }
// +是至少会有1个参数
// *是可有可没有,也可以任意多个
// ?是有或者没有,不可以重复
{ path:"/films/:id?", component:Films }嵌套路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 不同用户显示不同内容
// 这里:id表示 /user/123/xiaoming
// 去掉:id表示 /user/xiaoming
{
path:'/user/:id',
component:User,
children:[
{
path:'xiaoming',
component:XiaoMing
},
{
path:"xiaowang",
component:XiaoWang
}
]
}编程式导航
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// 声明式跳转页面,相当于调用router.push(...)
<router-link to="/about">点我去about页面</router-link>
// 编程式跳转页面
<button @click="goPage"></button>
methods: {
goPage(){
this.$router.push({path: "/about"})
}
}
// 携带参数跳转,首先用name起个名,这样编程式函数以name来查找,跳转后显示news/123
{
name: 'news',
path: '/news/:id',
components: News
}
this.$router.push({name: "news", params: {id:123}})
// 携带查询参数,比如百度搜索张三,路径会携带?wd=张三
this.$router.push({path: "/", query: {wd: "张三"}})
// 替换页面
this.$router.replace(...)
// 前进页面
router.go(1)
// 后退页面
router.go(-1)命名路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// shop路径下组册3个组件,key与value相同,可以使用了ES6简写
// key可以任意命名,只要router-view的name相同,就会加载名为ShopFooter的组件
// alias是取别名,不论输入/shop还是/helloshop,但会进入相同页面,支持数组形式多个别名
{
path: "/shop",
alias: ["/helloshop", "/hishop"],
components: {
default: ShopMain,
ShopTop,
ShopFooter01: ShopFooter
}
}
<router-view name="ShopTop"></router-view>
<router-view></router-view>
<router-view name="ShopFooter01"></router-view>重定向
1
2
3
4
5
6
7
8// 用户输入/mall,会帮它修改成/shop进行跳转
{
path: "/mall",
// 下面两个结果是相同的,第一个写死,第二个动态修改
redirect: "/shop"
redirect: (to) => { return {path: "/shop"} }
}历史模式
- Hash模式:内部传递的实际URL之前使用了一个哈希字符(#),由于这部分URL从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理.它在SEO中确实有不好的影响
history:createWebHashHistory()
- HTML5模式:当使用这种历史模式时,URL会看起来很”正常”,例如https://example.com/user/id
history:createWebHistory()
- Hash模式:内部传递的实际URL之前使用了一个哈希字符(#),由于这部分URL从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理.它在SEO中确实有不好的影响
导航守卫
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// 全局前置守卫,就像你去某个页面会验证权限,通过执行next(),不通过执行return false
// 第三个参数next作为可选
router.beforeEach((to, from, next) => {
// ...
// 返回 false 以取消导航
return false
})
// 组件内的守卫
// beforeRouteEnter进入页面触发
// beforeRouteUpdate更新页面触发
// beforeRouteLeave离开页面触发
beforeRouteEnter(){
console.log('路由进入组件')
},
beforeRouteUpdate(){
console.log('路由更新组件')
},
beforeRouteLeave(){
console.log('路由离开组件')
}
axios
- 安装axios
npm install axios --save
vuex
用vite构建工具生成vue项目
1
2
3
4
5
6// 这是2021年用vite构建工具生成vue项目,项目名vue3vuex02
npm init vite-app vue3vuex01
// 注入依赖与运行项目
npm install
npm run dev- src目录下创建store文件夹,并在里面创建index.js
1
2
3
4
5
6
7
8
9
10
11
12
13import { reactive } from "vue";
const store = {
state: reactive({
name: '张三',
age: 9
}),
setMessage(value){
this.state.message = value;
}
}
export default store; - App.vue删除必要内容,导入store
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<template>
<HelloWorld/>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import store from './store/index.js'
export default {
name: 'App',
components: {
HelloWorld
},
provide: {
store
}
}
</script> - HelloWorld.vue文件,用inject注入store,然后调用store显示name、age
1
2
3
4
5
6
7
8
9
10
11
12<template>
<h1>{{store.state.name}}</h1>
<h1>{{store.state.age}}</h1>
<button @click="store.setName('幽蓝')">点击修改名字</button>
</template>
<script>
export default {
name: 'HelloWorld',
inject: ['store']
}
</script> - 运行项目后,不报错能显示张三、9基本OK了
- src目录下创建store文件夹,并在里面创建index.js
安装axios尝试玩一下免费API
- 安装axios:
npm install axios --save
- store/index.js
1
2
3
4
5
6
7
8
9
10
11
12import { reactive } from "vue";
const store = {
state: reactive({
sayingList: []
}),
setSaying(list){
this.state.sayingList = list
}
}
export default store; - App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<template>
<p>名言名句:{{store.state.sayingList}}</p>
</template>
<script>
import store from './store/index.js'
import axios from 'axios'
export default {
name: 'App',
provide: {
store
},
setup(){
let api = 'https://api.apiopen.top/api/sentences';
// 请求api获取数据,调用setSaying方法,将名言名句存到sayingList
axios.get(api).then((result)=>{
console.log(result)
store.setSaying(result.data.result.name)
})
return {store}
}
}
</script>
- 安装axios:
使用vue-cli生成vuex,获取值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 给state添加name、age
export default createStore({
state: {
name: '幽蓝',
age: 9
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
// 组件读取name
<h1>{{$store.state.name}}</h1>
// 数据打印age
mounted(){
console.log(this.$store.state.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
28
29
30
31// 添加修改方法
state: {
name: '幽蓝',
age: 9
},
mutations: {
setName(state){
state.name = '幽蓝之家'
},
setAge(state, num){
state.age += num;
}
}
// 添加点击事件
<h1>{{$store.state.name}}</h1>
<h1>{{$store.state.age}}</h1>
<button @click="changeNameEvent">点击修改名字</button>
<button @click="changeAgeEvent">点击修改年龄</button>
// 获取修改方法要用commit,可接收多个参数
// 第一个参数是调用mutations下的函数
// 第二个参数相当于调用函数的第二个参数,比如传数值3对应num
methods: {
changeNameEvent(){
this.$store.commit('setName');
},
changeAgeEvent(){
this.$store.commit('setAge', 3);
}
}异步写法
store/index.js1
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
28import { createStore } from 'vuex'
// 安装axios然后导入axios
import axios from 'axios'
export default createStore({
state: {
sayingList: []
},
getters: {
},
mutations: {
setSayingList(state, arr){
state.sayingList = arr;
}
},
actions: {
getSaying(context){
let api = 'https://api.apiopen.top/api/sentences';
// 使用commit调用mutations下的setSayingList方法
// 第二个参数是名言名句
axios.get(api).then((result)=>{
context.commit('setSayingList', result.data.result.name)
})
}
},
modules: {
}
})HelloWorld.vue
1
2
3
4
5
6
7
8
9
10
11
12
13<template>
<h1>{{$store.state.sayingList}}</h1>
</template>
<script>
export default {
name: 'HelloWorld',
mounted(){
// 使用dispatch调用actions下的getSaying方法
this.$store.dispatch('getSaying')
}
}
</script>vuex的5大核心属性
- state:写数据,类似data
- getters:写计算属性,类似computed
- mutations:修改状态方法,同步操作;调用commit
- actions:写异步,如果修改状态要用到异步,就不要写在上面了;调用dispatch
- modules:模块可以拥有自己的state、getters…类似setup
映射状态数据和方法,它是用来简化写法,不必每次写$store.state那么长一串
- mapState:写在computed里面
- mapGetters:computed
- mapMutations:methods
- mapActions:methods
store/index.js文件
1 | import { createStore } from 'vuex' |
先说mapState与mapGetters
1 | <h1>{{name}}</h1> |
mapMutations与mapActions
1 | <h1>{{age}}</h1> |
- 模块化modules
未完待续…