在我看来,渐进式代表的含义是:主张最少。
每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主张,主张有强有弱,它的强势程度会影响在业务开发中的使用方式。
比如说,Angular,它两个版本都是强主张的,如果你用它,必须接受以下东西:必须使用它的模块机制- 必须使用它的依赖注入- 必须使用它的特殊形式定义组件(这一点每个视图框架都有,难以避免)
所以Angular是带有比较强的排它性的,如果你的应用不是从头开始,而是要不断考虑是否跟其他东西集成,这些主张会带来一些困扰。
比如React,它也有一定程度的主张,它的主张主要是函数式编程的理念,比如说,你需要知道什么是副作用,什么是纯函数,如何隔离副作用。它的侵入性看似没有Angular那么强,主要因为它是软性侵入。
Vue可能有些方面是不如React,不如Angular,但它是渐进的,没有强主张,你可以在原有大系统的上面,把一两个组件改用它实现,当jQuery用;也可以整个用它全家桶开发,当Angular用;还可以用它的视图,搭配你自己设计的整个下层用。你可以在底层数据逻辑的地方用OO和设计模式的那套理念,也可以函数式,都可以,它只是个轻量视图而已,只做了自己该做的事,没有做不该做的事,仅此而已。
渐进式的含义,我的理解是:没有多做职责之外的事。
.stop 阻止单机事件冒泡
.prevent 阻止默认行为(比如 @submit.prevent 会阻止提交后刷新页面)
.capture 添加事件侦听器时使用捕获模式
.self 只有事件在元素本身(而不是子元素)触发时触发回调
.once 只触发一次(组件也适用)
.key 触发事件的按键
.lazy 在输入框中,默认是在input事件中同步输入框的数据,使用lazy修饰符会转变为在change事件中同步(失去焦点或按回车才更新)。
. number将输入转换为Number类型,默认是String
.trim 自动过滤输入的首尾空格
.once 数据只响应一次(2.x版本移除)
.sync 对一个 prop 进行“双向绑定” 子组件通过update:propName
来触发更新如
this.$emit('update:title', newTitle)
(这个是2.x版本用法)
可以
<!-- v-on在vue2.x中测试,以下两种均可-->
<input type=“text” :value=“name” @input=“onInput” @focus=“onFocus” @blur=“onBlur” />
<button v-on="{mouseenter: onEnter,mouseleave: onLeave}">鼠标进来1</button>
<!-- 一个事件绑定多个函数,按顺序执行,这里分隔函数可以用逗号也可以用分号-->
<button @click="a(),b()">点我ab</button>
key值:用于 管理可复用的元素。因为Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做使 Vue 变得非常快,但是这样也不总是符合实际需求
1.事件绑定的函数参数为空时,函数中的第一个参数就是event对像
<button v-on:click="click()">click me</button>
methods: {
click(event) {
console.log(typeof event); // object
}
}
2.事件绑定的函数还有其他参数时需要显式的去传入event
<button v-on:click="click($event, 111)">click me</button>
methods: {
click(event, val) {
console.log(typeof event); // object
}
}
Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。
$nextTick 是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用 $nextTick,则可以在回调中获取更新后的 DOM。
vue响应式的改变一个值以后,此时的dom并不会立即更新,如果需要在数据改变以后立即通过dom做一些操作,可以使用$nextTick获得更新后的dom。
在 new Vue() 中,data 是可以作为一个对象进行操作的,然而在 component 中,data 只能以函数的形式存在,不能直接将对象赋值给它。这并非是 Vue 自身如此设计,而是跟 JavaScript 特性相关。组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。
v-for 比 v-if 的优先级高,所以永远不要把这两个放在同一个元素上
this.$parent.fatherMethod('hello');
this.$emit('fatherMethod', 'hello');
父组件
<template>
<div>
<child :fatherMethod="fatherMethodOther">child>
div>
template>
<script>
import child from './child';
export default {
components: {
child
},
methods: {
fatherMethodOther(str) {
console.log(str);
}
}
};
script>
子组件
<template>
<div>
<button @click="childMethod">点击button>
div>
template>
<script>
export default {
props: {
fatherMethod: {
type: Function,
default: null
}
},
methods: {
childMethod() {
if (this.fatherMethod) {
this.fatherMethod('hello');
}
}
}
};
script>
activated:在组件被激活时调用,在组件第一次渲染时也会被调用,之后每次keep-alive激活时被调用。
deactivated:在组件被停用时调用。
注意:
可复用组件具有高内聚、低耦合的特性。
在 Vue 组件中,状态称为 props,事件称为 events,片段称为 slots。组件的构成部分也可以理解为组件对外的接口。良好的可复用组件应当定义一个清晰的公开接口。
1.规范化命名:组件的命名应该跟业务无关,而是依据组件的功能命名。
2.数据扁平化:定义组件接口时,尽量不要将整个对象作为一个 prop 传进来。每个 prop应该是一个简单类型的数据。
这样做有下列几点好处:
(1) 组件接口清晰。
(2) props 校验方便。
(3) 当服务端返回的对象中的 key 名称与组件接口不一样时,不需要重新构造一个对象。扁平化的 props 能让我们更直观地理解组件的接口。
3.可复用组件只实现 UI 相关的功能,即展示、交互、动画,如何获取数据跟它无关,因此不要在组件内部去获取数据。
4.可复用组件应尽量减少对外部条件的依赖。
5.组件在功能独立的前提下应该尽量简单,越简单的组件可复用性越强。
6.组件应具有一定的容错性。
生命周期钩子 | 详细 |
---|---|
beforeCreate | 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。(此时data和el均未初始化,值为undefined) |
created | 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。(此时data的值可以拿到,el还未初始化) |
beforeMount | 在挂载开始之前被调用:相关的 render 函数首次被调用。(此时data和el均已经初始化,但是dom上绑定的数据{{}}没有渲染) |
mounted | el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。(此时el已经渲染完成并挂载到实例上) |
beforeUpdate | 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。 |
updated | 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。 |
activated | keep-alive 组件激活时调用。 |
deactivated | keep-alive 组件停用时调用。 |
beforeDestroy | 实例销毁之前调用。在这一步,实例仍然完全可用。 |
destroyed | Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 |
Vue父子组件生命周期钩子的执行顺序遵循:从外到内,然后再从内到外,不管嵌套几层深,也遵循这个规律。
vue为几个常用按键提供了别名
<input @keyup.enter="function">
别名 | 实际键值 |
---|---|
.delete | delete(删除)/BackSpace(退格) |
.tab | Tab |
.enter | Enter(回车) |
.esc | Esc(退出) |
.space | Space(空格键) |
.left | Left(左箭头) |
.up | Up(上箭头) |
.right | Right(右箭头) |
.down | Down(下箭头) |
.ctrl | Ctrl |
.alt | Alt |
.shift | Shift |
.meta | (window系统下是window键,mac下是command键) |
组合写法 | 按键组合 |
---|---|
@keyup.alt.67=”function” | Alt + C |
@click.ctrl=”function” | Ctrl + Click |
注意:
如果是在自己封装的组件或者是使用一些第三方的UI库时,会发现并不起效果,这时就需要用到.native修饰符了
<el-input v-model="name" @keyup.enter.native="search()" >el-input>
this.$set(array, indexOfItem, newValue)
this.array.$set(indexOfItem, newValue)
this.$delete(array, indexOfItem)
this.array.$delete(indexOfItem)
this.array[0].show = true;
this.array.splice(indexOfItem, 1, newElement)
let tempArray = this.array;
tempArray[0].show = true;
this.array = tempArray;
//Object.assign的单层的覆盖前面的属性,不会递归的合并属性
this.obj = Object.assign({},this.obj,{a:1, b:2})
//assign与Object.assign一样
this.obj = _.assign({},this.obj,{a:1, b:2})
//merge会递归的合并属性
this.obj = _.merge({},this.obj,{a:1, b:2})
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
受现代 JavaScript 的限制 (而且 Object.observe 也已经被废弃),Vue 无法检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。
对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性。
有时你可能需要为已有对象赋值多个新属性,比如使用 Object.assign() 或 _.extend()。但是,这样添加到对象上的新属性不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的属性一起创建一个新的对象。
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
vue页面在加载的时候闪烁花括号{{}},v-cloak
指令和css规则如[v-cloak]{display:none}
一起用时,这个指令可以隐藏未编译的Mustache标签直到实例准备完毕。
<ul class="nav-tab">
<li v-for="(nav,index) in navs" @click="currentIndex = index" :class="{active: index === currentIndex}">
<p>{{ nav.label }}p>
li>
ul>
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value 特性用于不同的目的。model 选项可以用来避免这样的冲突
自定义表单组件例子
<base-checkbox v-model="lovingVue">base-checkbox>
<template>
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
template>
<script>
export default {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
}
}
script>
当模板中双向绑定的表达式(这个表达式使用data里的属性计算的)很复杂,代码就会变得雕肿甚至难以阅读和维护
这时便可用计算属性代替这个表达式
所有的计算属性都以函数的形式写在 Vue 实例内的 computed 选项内,最终返回计算后的结果。
1、我们可以将同一函数定义为一个方法或是一个计算属性。两种方式的最终结果确实是完全相同的。不同的是计算属性是基于它们的依赖进行缓存的。只在相关依赖发生改变时它们才会重新求值。相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。
2、使用计算属性还是methods取决于是否需要缓存,当遍历大数组和做大量计算时,应当使用计算属性,除非你不希望得到缓存。
我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A 。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。
3、计算属性是根据依赖自动执行的,methods需要事件调用
v-bind
将数据传给子组件,子组件通过props接收一般情况下从/user/foo
导航到 /user/bar
,原来的组件实例会被复用,这也意味着组件的生命周期钩子不会再被调用。
如果要响应路由参数的变化可以采用以下方式:
router.beforeEach((to, from, next) => {
/* must call `next` */
})
router.beforeResolve((to, from, next) => {
/* must call `next` */
})
router.afterEach((to, from) => {})
router.push(location, onComplete?, onAbort?)
router.replace(location, onComplete?, onAbort?)
router.go(n)
router.back()
router.forward()
vue-router的动态路由主要是指路由params,query,hash动态改变
params: 一个“路径参数”使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params
模式 | 匹配路径 | $route.params |
---|---|---|
/user/:username | /user/evan | { username: 'evan' } |
/user/:username/post/:post_id | /user/evan/post/123 | { username: 'evan', post_id: '123' } |
<router-link :to="{ path: 'user', params: { userId: 123 }}">Userrouter-link>
router.push({ path: 'user', params: { userId: 123 }})
query:
<router-link :to="{ path: 'user', query: { userId: 123 }}">Userrouter-link>
router.push({ path: 'user', query: { userId: 123 }})
hash:
<router-link :to="{ path: 'user', hash: 'home'}">Userrouter-link>
router.push({ path: 'user', hash: 'home')
1.定义子路由
routes: [
{ path: '/user/:id', component: User,
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 中
path: 'posts',
component: UserPosts
}
]
}
]
父组件:
<div id="app">
<router-view>router-view>
div>
子组件:
<div class="user">
<h2>User {{ $route.params.id }}h2>
<router-view>router-view>
div>
组件及其属性
组件支持用户在具有路由功能的应用中(点击)导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的 标签,可以通过配置 tag 属性生成别的标签.。另外,当目标路由成功激活时,链接元素自动设置一个表示激活的 CSS 类名。
router-link
会拦截点击事件,让浏览器不在重新加载页面。属性:
<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>
replace:设置 replace 属性的话,当点击时,会调用 router.replace() 而不是 router.push(),于是导航后不会留下 history 记录。
append: 设置 append 属性后,则在当前(相对)路径前添加基路径。例如,我们从 /a 导航到一个相对路径 b,如果没有配置 append,则路径为 /b,如果配了,则为 /a/b
tag: 有时候想要 渲染成某种标签,例如 于是我们使用 tag prop类指定何种标签,同样它还是会监听点击,触发导航。
active-class :设置 链接激活时使用的 CSS 类名。默认值可以通过路由的构造选项 linkActiveClass 来全局配置
exact: 是否激活" 默认类名的依据是 inclusive match (全包含匹配)。 举个例子,如果当前的路径是 /a
开头的,那么
也会被设置 CSS 类名。按照这个规则,
将会点亮各个路由!想要链接使用 “exact 匹配模式”,则使用 exact 属性:
1 . vue异步组件技术 ==== 异步加载
vue-router配置路由 , 使用vue的异步组件技术 , 可以实现按需加载 .
但是,这种情况下一个组件生成一个js文件
/* vue异步组件技术 */
{
path: '/home',
name: 'home',
component: resolve => require(['@/components/home'],resolve)
},{
path: '/index',
name: 'Index',
component: resolve => require(['@/components/index'],resolve)
},{
path: '/about',
name: 'about',
component: resolve => require(['@/components/about'],resolve)
}
2.使用import
// 下面2行代码,没有指定webpackChunkName,每个组件打包成一个js文件。
/* const Home = () => import('@/components/home')
const Index = () => import('@/components/index')
const About = () => import('@/components/about') */
// 下面2行代码,指定了相同的webpackChunkName,会合并打包成一个js文件。 把组件按组分块
const Home = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/home')
const Index = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/index')
const About = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/about')
{
path: '/about',
component: About
}, {
path: '/index',
component: Index
}, {
path: '/home',
component: Home
}
3.使用webpack的require.ensure技术,也可以实现按需加载。
这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件
/* 组件懒加载方案三: webpack提供的require.ensure() */
{
path: '/home',
name: 'home',
component: r => require.ensure([], () => r(require('@/components/home')), 'demo')
}, {
path: '/index',
name: 'Index',
component: r => require.ensure([], () => r(require('@/components/index')), 'demo')
}, {
path: '/about',
name: 'about',
component: r => require.ensure([], () => r(require('@/components/about')), 'demo-01')
}
router 默认 hash 模式,还有一种是history模式
hash 模式:hash模式背后的原理是onhashchange事件,可以在window对象上监听这个事件
history模式: 这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面,如果不想要很丑的 hash,我们可以用路由的 history 模式,不过这种模式要玩好,还需要后台配置支持。
不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看。所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。
具体配置参见:https://router.vuejs.org/zh/guide/essentials/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90
State:Vuex 使用单一状态树,用一个对象State包含了整个应用层级的所有状态,你可以理解为这些状态就是一堆全局变量和数据。
Getters:当我们需要从 state 中派生出一些状态的时候,就会使用到getters
,你可以将 getters
理解 state 的计算属性
Mutations: Vuex给我们提供修改仓库 store 中的状态的唯一办法就是通过提交mutation。它和上面的 getters ,会接受 state 作为第一个参数
Vuex要求我们要想通过 mutations 更改内容,就必须提交mutation:
//提交一个名为increment的mutation
store.commit('increment');
action:Action 类似于 mutation,不同在于
vuex一般用于比较大型的项目中,比如保存用户的登录状态,导航的状态等