vue基础知识入门级总结
1.vue-cli3快速创建项目
- 1.全局安装 npm install -g @vue/cli 或 yarn global add @vue/cli
- 2.查看版本/是否安装成功 vue -V
- 3.创建项目
vue create <Project Name> //文件名 不支持驼峰(含大写字母)
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)
>( ) Babel //转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。
( ) TypeScript// TypeScript是一个JavaScript(后缀.js)的超集(后缀.ts)包含并扩展了 JavaScript 的语法,需要被编译输出为 JavaScript在浏览器运行,目前较少人再用
( ) Progressive Web App (PWA) Support// 渐进式Web应用程序
( ) Router // vue-router(vue路由)
( ) Vuex // vuex(vue的状态管理模式)
( ) CSS Pre-processors // CSS 预处理器(如:less、sass)
( ) Linter / Formatter // 代码风格检查和格式化(如:ESlint)
( ) Unit Testing // 单元测试(unit tests)
( ) E2E Testing // e2e(end to end) 测试
参考引用:vue-cli3.0项目快速搭建详解
2.项目结构
3.vue实例
每个 Vue 应用程序都是通过 Vue 函数创建出一个新的 Vue 实例开始的:
var vm = new Vue({
// 选项
})
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style scoped>
</style>
new Vue({
router,
store,
render: h => h(App),
}).$mount('#app');
4.模板语法
<span>Message: {{ msg }}</span>
<span v-once>这个将不会改变: {{ msg }}</span> // 通过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新
双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html 指令:
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
Mustache 语法不能作用在 HTML attribute 上,遇到这种情况应该使用 v-bind 指令:
<div v-bind:id="dynamicId"></div>
使用 JavaScript 表达式
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
指令:
v-if
v-for
v-on 缩写@click
v-bind 缩写:
动态参数
<a v-on:[eventName]="doSomething"> ... </a>
在这个示例中,当 eventName 的值为 "focus" 时,v-on:[eventName] 将等价于 v-on:focus。
修饰符
.stop - 调用 event.stopPropagation()。
.prevent - 调用 event.preventDefault()。
.capture - 添加事件侦听器时使用 capture 模式。
.self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
.{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
.native - 监听组件根元素的原生事件。
.once - 只触发一次回调。
.left - (2.2.0) 只当点击鼠标左键时触发。
.right - (2.2.0) 只当点击鼠标右键时触发。
.middle - (2.2.0) 只当点击鼠标中键时触发。
.passive - (2.3.0) 以 { passive: true } 模式添加侦听器
5.生命周期
每个 Vue 实例在被创建之前,都要经过一系列的初始化过程 - 例如,Vue 实例需要设置数据观察(set up data observation)、编译模板(compile the template)、在 DOM 挂载实例(mount the instance to the DOM),以及在数据变化时更新 DOM(update the DOM when data change)。在这个过程中,Vue 实例还会调用执行一些生命周期钩子函数,这样用户能够在特定阶段添加自己的代码。
6.vue-router
SPA(single page application):单一页面应用程序,只有一个完整的页面;它在加载页面时,不会加载整个页面,而是只更新某个指定的容器中内容。单页面应用(SPA)的核心之一是: 更新视图而不重新请求页面;vue-router在实现单页面前端路由时,提供了两种方式:Hash模式和History模式;根据mode参数来决定采用哪一种方式。
- Hash模式
- History模式
History模式url中的/#去掉,变好看一点,需要把其他不符合要求的路由值定位到主页上来,要不然就直接404,这个功能要后台来配置
6.1 vue-router使用方式
- 1:下载 npm i vue-router -S
- 2:在main.js中引入 import VueRouter from ‘vue-router’;
- 3:安装插件Vue.use(VueRouter);
- 4:创建路由对象并配置路由规则 let router = new VueRouter({routes:[{path:’/home’,component:Home}]});
- 5:将其路由对象传递给Vue的实例,options中加入 router:router
- 6:在app.vue中留坑
router.js
import Vue from 'vue';
import VueRouter from 'vue-router';
//安装插件
Vue.use(VueRouter); //挂载属性
//创建路由对象并配置路由规则
export default new VueRouter({
routes: [
//一个个对象
{
path: 'index',
component: () => import('@/views/first/index'),
name: 'index'
}
]
});
main.js
import router from './router';
new Vue({
router,
store,
render: h => h(App),
}).$mount('#app');
app.vue中“留坑”
<template>
<div id="app">
<!-- 留坑,非常重要 -->
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style scoped>
</style>
6.2 vue-router 子路由嵌套
router.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router);
export default new Router({
routes: [
{
path: '/first',
name: 'first',
component: () => import('@/views/index'),
children: [
{
path: 'login',
name: 'login',
component: () => import('@/views/login')
},
{
path: 'register',
name: 'register',
component: () => import('@/views/register')
}
]
}
]
})
index.vue
这时用来渲染匹配到的路由组件的
<template>
<article class="index__container">
<h1>首页</h1>
<router-link to="/first/login">登录</router-link>
<router-link to="/first/register">注册</router-link>
<router-view></router-view>
</article>
</template>
首页:
点击登录tab:
点击注册tab:
6.3 路由导航两种方式
- 标签导航
//跳转到名为user路由,并传递参数userId
<router-link :to="/user/123"></router-link>
或者
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
- 编程式导航:
router.push('home')
router.push({path: 'home'}) // 和上面等价
router.push({name: 'user',params: {userId: 123}}) // 这个是命名路由 // user/123
router.push({path: 'register',query: {plan: 'private'}}) // '/register?plan=private'
使用router.replace(location)和router.push相似,只不过这里不会产生浏览记录
使用router.go(n)
命名路由:
命名路由是用来给懒人用的,给router-link传递to属性,使用path很长,很难写,所以,起个名字,方便写
当某个路由配置是
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})
正常人是这样访问的 <router-link :to="/user/123"></router-link>
懒人是这样访问的 <router-link :to="{name: 'user',params: {userId: 123}}"></router-link>
6.4 命名视图
当匹配到一个路由值,同时渲染多个组件,很简单,看下面demo
<router-view></router-view> //这个没有起名的默认就是 default
<router-view name="b"></router-view>
<router-view name="c"></router-view>
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: RA,
b: RB,
c: RC
}
}
]
})
// 这样同时就渲染出 RA,RB,RC三个组件了
6.5 重定向和别名
重定向就是当访问一个路由值,定向到另外一个路由值,简单demo如下
import Vue from 'vue'
import Router from 'vue-router'
import Layout from '@/views/layout/Layout';
Vue.use(Router);
export default new Router({
routes: [
{
path: '/first',
name: 'first',
redirect: '/first/index',
component: Layout,
meta: {
title: '首页'
},
children: [
{
path: 'index',
component: () => import('@/views/first/index'),
name: 'index',
alias: 'demo'
}
]
}
]
})
6.6 导航钩子
有全局的,单个路由的,组件的
1.全局的钩子,注册一个全局的before钩子
const router = new VueRouter({...});
router.beforeEach((to,from,next) => {
// 可以注册多个before,多个before之间是异步的,但是导航必须等到这些钩子执行完成,才开始
// to 目标路由对象
// from 当前路由对象
// next 有如下几种用法
// next() 执行下一个钩子函数,没有钩子,则to到目标路由
// next(false) 不to了,直接留在from这个路由
// next('/') 不管这个to了,另找了一个to
// next方法必须调用,不调用就是死循环
})
注册一个after钩子,这个作用不大,毕竟路由都跳转了,唯一的作用就是来看看而已
router.afterEach(route => {
// ...
})
2.单个路由的
const router = new VueRouter({
routes: [{
path: '/first',
component: () => import('@/views/first/index'),
beforeEnter: (to,from,next) => {
//...
}
}]
})
3.组件内的
beforeRouteEnter (to,from,next) {
// 这里不能直接访问当前实例this,因为实例还没有渲染,不过可以通过next来访问
next(vm => {
// ...
})
},
beforeRouteUpdate (to,from,next) {
// 多个路由值访问同一个组件,来回切换,组件不会刷新,那么上面的这个钩子就没有了,此时这个钩子登场
},
beforeRouteLeave (to,from,next) {
}
4.组件内路由监听
watch: {
$route(){
...
}
}
参考引用:vue-router路由详细讲解
7. 父子组件通信
- 通过prop实现通信
- 通过$ref 实现通信
- provide 和 inject (高阶组件/组件库用的较多)
- Vuex
- .sync 修饰符
- $attrs 和 $listeners
- $parent
- $root
- broadcast / dispatch
- 声明一个全局Vue实例变量 EventBus , 把所有的通信数据,事件监听都存储到这个变量上
7.1 通过prop/$emit实现通信
局部引入子组件
import Child from '@/components/Child';
components: {
Child
}
通过:message="message" props传值 父传子传值
<child :message="message" @replay="replayBack"/>
子传父传值 --- @replay="replayBack" 父组件接收值
this.$emit('replay', '子组件发送的信息')
Demo如下:
parent.vue
<template>
<article class="index__container">
<child :message="message" @replay="replayBack"/>
<div>{{replyMsg}}</div>
</article>
</template>
<script>
import Child from '@/components/Child';
export default {
name: 'index',
components: {
Child
},
data() {
return {
message: '父组件发送的信息',
replyMsg: ''
};
},
methods: {
replayBack(e) {
this.replyMsg = e;
}
}
};
</script>
Child.vue子组件:
<template>
<div>
<div>Child页面</div>
<div>{{message}}</div>
<el-button type="primary" @click="reply">回复</el-button>
</div>
</template>
<script>
export default {
name: 'Child',
props: {
message: {
type: String,
default: ''
}
},
data() {
return {};
},
methods: {
reply() {
this.$emit('replay', '子组件发送的信息')
}
}
}
</script>
7.2 通过$ref 实现通信
如果ref用在子组件上,指向的是组件实例,可以理解为对子组件的索引,通过 r e f 可 能 获 取 到 在 子 组 件 里 定 义 的 属 性 和 方 法 ; 如 果 r e f 在 普 通 的 D O M 元 素 上 使 用 , 引 用 指 向 的 就 是 D O M 元 素 , 通 过 ref可能获取到在子组件里定义的属性和方法; 如果ref在普通的 DOM 元素上使用,引用指向的就是 DOM 元素,通过 ref可能获取到在子组件里定义的属性和方法;如果ref在普通的DOM元素上使用,引用指向的就是DOM元素,通过ref可能获取到该DOM 的属性集合,轻松访问到DOM元素;
父组件:
<el-button type="primary" @click="send">发送</el-button>
<child ref="child"/>
send() {
this.$refs.child.setMessage('send的值');
}
子组件的方法:
setMessage(val) {
this.msg = val;
}
7.3. provide 和 inject (高阶组件/组件库用的较多)
成对出现:provide和inject是成对出现的
作用:用于父组件向子孙组件传递数据
使用方法:provide在父组件中返回要传给下级的数据,inject在需要使用这个数据的子辈组件或者孙辈等下级组件中注入数据。
使用场景:由于vue有$parent属性可以让子组件访问父组件。但孙组件想要访问祖先组件就比较困难。通过provide/inject可以轻松实现跨级访问父组件的数据
parent页面:
<template>
<article>
<div>Parent页面</div>
<child/>
</article>
</template>
<script>
import Child from '@/components/Child';
export default {
name: 'index',
provide: {
text: 'Parent组件的值'
},
components: {
Child
},
data() {
return {};
},
methods: {}
};
</script>
child页面:
<template>
<div>
<div>Child页面</div>
<!--grandson组件-->
<grandson/>
</div>
</template>
<script>
import Grandson from '../Grandson';
export default {
name: 'Child',
components: {
Grandson
},
data() {
return {};
},
methods: {}
}
</script>
Grandson页面:
<template>
<div>
<div>Grandson页面</div>
<div>{{text}}</div>
</div>
</template>
<script>
export default {
name: 'Grandson',
inject: ['text'],
data() {
return {};
},
methods: {}
}
</script>
7.4 Vuex
VueX是适用于在Vue项目开发时使用的状态管理工具。
-
安装
npm i vuex -s -
使用
data.js
const data = {
state: {
text: ''
},
mutations: {
SET_TEXT: (state, text) => {
state.text = text;
}
},
actions: {
setText({commit}, text) {
commit("SET_TEXT", text);
}
}
};
export default data;
index.js
import Vue from 'vue';
import Vuex from 'vuex';
import getters from './getters';
import data from './modules/data';
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
data
},
getters
});
export default store;
main.js
Vuex实例挂载到这个vue实例中
import Vue from 'vue'
import router from './router'
import store from './store'
new Vue({
el: '#app',
router,
store, //store:store 和router一样,将我们创建的Vuex实例挂载到这个vue实例中
render: h => h(App)
})
组件中调用
this.$store.getters.text
或者
computed: {
...mapGetters(["text"])
},
组件中设值
this.$store.dispatch("setText", '11111');
-
VueX中的核心内容
-
state 存放状态
-
mutations state成员操作
-
getters 加工state成员给外界
-
actions 异步操作
-
modules 模块化状态管理
8.watch
watch的作用可以监控一个值的变换,并调用因为变化需要执行的方法。可以通过watch动态改变关联的状态。
watch的用法大致有三种:
- 直接写一个监听处理函数,当每次监听到 cityName 值发生改变时,执行函数。
watch: {
cityName(newName, oldName) {
// ...
}
}
- immediate和handler
这样使用watch时有一个特点,就是当值第一次绑定的时候,不会执行监听函数,只有值发生改变才会执行。如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate属性。
比如当父组件向子组件动态传值时,子组件props首次获取到父组件传来的默认值时,也需要执行函数,此时就需要将immediate设为true。
immediate表示在watch中首次绑定的时候,是否执行handler,值为true则表示在watch中声明的时候,就立即执行handler方法,值为false,则和一般使用watch一样,在数据发生变化的时候才执行handler。
watch: {
cityName: {
handler(newName, oldName) {
// ...
},
immediate: true
}
}
- deep
当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听。
new Vue({
el: '#root',
data: {
cityName: {id: 1, name: 'shanghai'}
},
watch: {
cityName: {
handler(newName, oldName) {
// ...
},
deep: true,
immediate: true
}
}
})
设置deep: true 则可以监听到cityName.name的变化,此时会给cityName的所有属性都加上这个监听器,当对象属性较多时,每个属性值的变化都会执行handler。如果只需要监听对象中的一个属性值,则可以做以下优化:使用字符串的形式监听对象属性:
watch: {
'cityName.name': {
handler(newName, oldName) {
// ...
},
deep: true,
immediate: true
}
}
这样只会给对象的某个特定的属性加监听器。
数组(一维、多维)的变化不需要通过深度监听,对象数组中对象的属性变化则需要deep深度监听。
9.directive
内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。有的情况下,对普通 DOM 元素进行底层操作,这时候就会用到自定义指令
钩子函数
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
- inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
- update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
- unbind:只调用一次,指令与元素解绑时调用。
指令钩子函数会被传入以下参数:
- 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 钩子中可用。
实例:拖拽的demo 全局定义
drag.js
export default {
bind(el, binding) {
const div = el;
let is_moving = false;
div.onmousedown = (e) => {
const oDiv = e.target; // 获取目标元素;
const disX = e.clientX - oDiv.offsetLeft;
const disY = e.clientY - oDiv.offsetTop;
is_moving = true;
document.onmousemove = (e) => {
let left = e.clientX - disX;
let top = e.clientY - disY;
// 移动当前元素
if (is_moving) {
if (left <= 0) {
left = 0;
} else if (left >= document.documentElement.clientWidth - oDiv.offsetWidth) {
// document.documentElement.clientWidth 屏幕的可视宽度
left = document.documentElement.clientWidth - oDiv.offsetWidth;
}
if (top <= 0) {
top = 0;
} else if (top >= document.documentElement.clientHeight - oDiv.offsetHeight) {
// document.documentElement.clientHeight 屏幕的可视高度
top = document.documentElement.clientHeight - oDiv.offsetHeight;
}
div.style.left = left + 'px';
div.style.top = top + 'px';
}
};
document.onmouseup = (e) => {
is_moving = false;
div.onmousemove = null;
div.onmouseup = null;
};
// return false不加的话可能导致黏连,就是拖到一个地方时div粘在鼠标上不下来,相当于onmouseup失效
return false;
};
},
unbind(el, bing) {
}
}
index.js
import drag from './drag';
const plugin = {};
plugin.install = function (Vue, options = {}) {
Vue.directive("drag", drag);
};
export default plugin;
// 通过调用全局方法 Vue.use() 使用插件:
全局定义
main.js
import drag from './directive/drag/index';
Vue.use(drag);
页面使用:
<div class="index__img" v-drag></div>
局部定义指令
<template>
<div class="hello">
<div v-test='name'></div>
</div>
</template>
<script>
export default {
data () {
return {
name:'我是名字',
}
},
directives:{
test:{
inserted: function (el,binding) {// 指令的定义
/ /el为绑定元素,可以对其进行dom操作
console.log(binding) //一个对象,包含很多属性属性
},
bind: function (el, binding, vnode) {
el.innerHTML =binding.value
}
}
},
created:function(){
},
mounted:function(){
},
methods:{
}
}
</script>
10.mixins
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
- 数据对象合并
数据对象在内部会进行浅合并 (一层属性深度),在和组件的数据发生冲突时以组件数据优先
var mixin = {
data() {
return {
msg_mixins: 'mixins',
msg: '123'
}
}
}
var app = new Vue({
mixins: [mixin],
el: '#app',
data: {
msg: 'app'
}
})
// app mixins
- 钩子函数合并
同名钩子函数将混合为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
var mixin = {
data() {
return {
msg_mixins: 'mixins',
msg: '123'
}
},
created: function () {
console.log('混入对象的钩子被调用')
}
}
var app = new Vue({
mixins: [mixin],
el: '#app',
data: {
msg: 'app'
},
created: function () {
console.log('组件钩子被调用')
}
})
- methods, components 和 directives合并
methods, components 和 directives,将被混合为同一个对象。两个对象键名冲突时,取组件对象的键值对。
var mixin = {
methods: {
foo: function () {
console.log('foo')
},
conflicting: function () {
console.log('from mixin')
}
}
}
var vm = new Vue({
mixins: [mixin],
methods: {
bar: function () {
console.log('bar')
},
conflicting: function () {
console.log('from self')
}
}
})
vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"
- 全局混入
混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。
// 直接注册
Vue.mixin({
data() {
return {
globalMsg2: 'hello global mixin 2222'
}
},
created() {
console.log('模块化注册全局混入的钩子函数 2222');
},
methods: {
globalShow2() {
alert('模块化注册全局混入的钩子函数 2222');
}
}
});
11.computed
计算属性将被混入到 Vue 实例中。所有 getter 和 setter 的 this 上下文自动地绑定为 Vue 实例。
计算属性是基于它们的响应式依赖进行缓存的。
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})
Original message: “Hello”
Computed reversed message: “olleH”
计算属性的 setter
计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:
// ...
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
// ...
现在再运行 vm.fullName = ‘John Doe’ 时,setter 会被调用,vm.firstName 和 vm.lastName 也会相应地被更新。
12.filter
Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示:
<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
filters: {
capitalize: function (value) {
if (!value) return '';
value = value.toString();
return value.charAt(0).toUpperCase() + value.slice(1);
}
}
创建 Vue 实例之前全局定义过滤器:
Vue.filter('capitalize', function (value) {
if (!value) return '';
value = value.toString();
return value.charAt(0).toUpperCase() + value.slice(1);
})
new Vue({
// ...
})
当全局过滤器和局部过滤器重名时,会采用局部过滤器。
过滤器可以串联:
{{ message | filterA | filterB }}
filterA 被定义为接收单个参数的过滤器函数,表达式 message 的值将作为参数传入到函数中。然后继续调用同样被定义为接收单个参数的过滤器函数 filterB,将 filterA 的结果传递到 filterB 中。
过滤器是 JavaScript 函数,因此可以接收参数:
{{ message | filterA('arg1', arg2) }}
filterA 被定义为接收三个参数的过滤器函数。其中 message 的值作为第一个参数,普通字符串 ‘arg1’ 作为第二个参数,表达式 arg2 的值作为第三个参数。