vue的核心是 用简洁的模板语法 声明式的把数据 渲染进DOM 的一个系统
响应式 数据改变 dom上的内容自动改变
1、创建一个Vue实例
每个vue应用都是通过用 Vue函数创建一个新的Vue实例开始的
var vm = new Vue({
// 选项
})
vm viewModel的缩写
一个Vue应用由一个通过new Vue创建的跟Vue实例,以及可选的嵌套的 可复用的组件树组成。
举个例子,一个todo应用的组件树可以是这样的
根实例vm
└─ TodoList
├─ TodoItem
│ ├─ TodoButtonDelete
│ └─ TodoButtonEdit
└─ TodoListFooter
├─ TodosButtonClear
└─ TodoListStatistics
所有的Vue组件都是Vue实例,并且接受相同的选项对象(一些根实例特有的选项除外)
数据与方法
Vue2.0 在模版里用的变量必须在data里声明 有些不在模版中使用也不需要响应的变量尽量不要写到data中
data 要写在一个函数中,作为返回值返回 因为多个组件共享data数据,会造成data数据被其他组件改动
原来我们的每一个vue文件经过babel编译,将导出的对象直接替换成了一个对象变量 相当于一个es6模块 ,然后将这个变量传入到对应的组件构造函数中。因此,也就产生了引用共享的问题(所有js对象皆引用)。
一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中。 当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。
只有当实例被创建时就已经存在于data中的property才是响应式的,也就是说如果你添加一个新的property,比如
vm.b = 'hi'
那么对b的改动将不会触发任何视图的更新。 如果你知道你会在晚些时候需要一个property, 但一开始它为空或不存在,那么你仅需要设置一些初始值
data: {
newTodoText: '',
visitCount: 0,
hideCompletedTodos: false,
todos: [],
error: null
}
当使用object.freeze(), 这会阻止修改现有的property,也意味着响应系统无法再追踪变化
var obj = {
foo: 'bar'
}
Object.freeze(obj)
new Vue({
el: '#app',
data: obj
})
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property
Vue.set(vm.someObject, 'b', 2)
或
this.$set(this.someObject,'b',2)
有时你可能需要为已有对象赋值多个新 property,比如使用 Object.assign() 或 _.extend()。但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
// 代替 Object.assign(this.someObject, { a: 1, b: 2 })
属性不更新 $set
对于数组的处理参照下面 列表渲染
实例生命周期钩子
每Vue实例在被创建时都要经历一系列的初始化过程--例如, 需要设置数据监听、编译模板、将实例挂在到DOM并在数据变化时更新DOM等
同时在这个过程中也会运行一些叫做生命周期的钩子的函数,这给了用户在不同阶段添加自己的代码的机会
比如created钩子可以用来在一个实例被创建之后执行代码
new Vue({
data: {
a: 1
},
created: function () {
// `this` 指向 vm 实例
console.log('a is: ' + this.a)
}
})
// => "a is: 1"
生命周期钩子的this上下文指向调用他的Vue实例
不要在选项property或回调上使用箭头函数
比如
created: () => console.log(this.a)
或vm.$watch('a', newValue => this.myMethod())
因为箭头函数并没有 this, this会作为变量一直向上级词法作用域查找,直到找到为止, 经常导致ncaught TypeError: Cannot read property of undefined 或 Uncaught TypeError: this.myMethod is not a function 之类的错误。
父子组件生命周期顺序
规律就是:父组件先开始执行,然后等到子组件执行完,父组件收尾。
挂载阶段 执行顺序为: 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
更新阶段 执行顺序为: 父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
销毁阶段 执行顺序为: 父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
字符串模板与DOM模板
1.字符串模板
字符串模板就是写在vue中的template中定义的模板,如.vue的单文件组件模板和定义组件时template属性值的模板。字符串模板不会在页面初始化参与页面的渲染,会被vue进行解析编译之后再被浏览器渲染,所以不受限于html结构和标签的命名。
<div id="app">
<!-- 以下方式都可以 -->
<MyComponent postTitle="Hello Vue"></MyComponent>
<MyComponent post-title="Hello Vue"></MyComponent>
<my-component postTitle="Hello Vue"></my-component>
<my-component post-title="Hello Vue"></my-component>
</div>
Vue.component('MyComponent', {
props: ['postTitle']
template: '<div>{{ postTitle }}</div>',
methods: {
titleClick(){
this.$emit("componentClick", 123)
}
}
})
new Vue ({
el: '#app',
methods: {
myClick(val){
console.log(val)
}
}
});
2.dom模板(或者称为Html模板)
Dom 模板就是写在 html 文件中,一打开就会被浏览器进行解析渲染的,所以要遵循 html 结构和标签的命名,否则浏览器不解析也就不能获取内容了
HTML 中的 标签(比如 DIV)名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符,
下面的例子不会被正确渲染, 一打开会被浏览器解析成 mycomponent,但是注册的 vue 的组件是 MyComponent,因此无法渲染
<!DOCTYPE <html>
<head>
<meta charset="utf-8">
<title>Vue Component</title>
</head>
<body>
<div id="app">
<!-- 在 HTML 中是 PascalCase (首字母大写命名) 的不会被渲染 -->
<MyComponent></MyComponent>
<!-- 在 HTML 中是 camelCase (驼峰命名) 的不会被渲染 -->
<myComponent></myComponent>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script>
// 注册时:PascalCase (首字母大写命名)、camelCase (驼峰命名)、kebab-case (短横线命名) 都可以
Vue.component('MyComponent', {
template: '<div>Hello Vue</div>'
});
new Vue ({
el: '#app'
});
</script>
</html>
下面的例子就可以正常渲染了,这意味着当你使用 DOM 中的模板时,标签名需要使用其等价的 kebab-case (短横线分隔命名) 的方式书写
<!DOCTYPE <html>
<head>
<meta charset="utf-8">
<title>Vue Component</title>
</head>
<body>
<div id="app">
<!-- 在 HTML 中是 kebab-case (短横线命名) 的会被渲染 -->
<my-component></my-component>
<my-Component></my-Component>
<My-component></My-component>
<My-Component></My-Component>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script>
// 注册时:PascalCase (首字母大写命名)、camelCase (驼峰命名)、kebab-case (短横线命名) 都可以
Vue.component('MyComponent', {
template: '<div>Hello Vue</div>'
});
new Vue ({
el: '#app'
});
</script>
</html>
同样,组件 props 属性也是如此
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符,
下面的例子不会被正确渲染, 一打开会被浏览器解析成 posttitle,但是注册的 vue 的组件是 postTitle,因此也无法渲染
<!DOCTYPE <html>
<head>
<meta charset="utf-8">
<title>Vue Component</title>
</head>
<body>
<div id="app">
<!-- 在 HTML 中是 PascalCase (首字母大写命名) 的不会被渲染 -->
<my-component PostTitle="Hello Vue"></my-component>
<!-- 在 HTML 中是 camelCase (驼峰命名) 的不会被渲染 -->
<my-component postTitle="Hello Vue"></my-component>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script>
Vue.component('MyComponent', {
// 注册时:PascalCase (首字母大写命名)、camelCase (驼峰命名)、kebab-case (短横线命名) 都可以
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
new Vue ({
el: '#app'
});
</script>
</html>
下面的例子就可以正常渲染了,这意味着当你使用 DOM 中的模板时,prop 名需要使用其等价的 kebab-case (短横线分隔命名) 的方式书写
<!DOCTYPE <html>
<head>
<meta charset="utf-8">
<title>Vue Component</title>
</head>
<body>
<div id="app">
<!-- 在 HTML 中是 kebab-case (短横线命名) 的会被渲染 -->
<my-component post-title="Hello Vue"></my-component>
<my-component Post-title="Hello Vue"></my-component>
<my-component post-Title="Hello Vue"></my-component>
<my-component Post-Title="Hello Vue"></my-component>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script>
Vue.component('MyComponent', {
// 注册时:PascalCase (首字母大写命名)、camelCase (驼峰命名)、kebab-case (短横线命名) 都可以
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
new Vue ({
el: '#app'
});
</script>
</html>
$emit 方法
<!DOCTYPE <html>
<head>
<meta charset="utf-8">
<title>Vue Component</title>
</head>
<body>
<div id="app">
<!-- 在 HTML 中只能是 kebab-case (短横线命名) 的会被渲染 -->
<my-component @component-click="myClick"></my-component>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script>
Vue.component('MyComponent', {
template: '<h3 @click="titleClick">Hello Vue</h3>',
methods: {
titleClick(){
// 当使用 $emit 时,命名规则只能使用 kebab-case (短横线命名)
this.$emit("component-click", 123)
}
}
})
new Vue ({
el: '#app',
methods: {
myClick(val){
console.log(val)
}
}
});
</script>
</html>
模板语法
1、文本插值 (会把带有元素标签的字符串原原本本的渲染出来)
rawHtml = '<span style="color: red">This should be red.</span>'
<p>Using mustaches: {{ rawHtml }}</p>
2、html插值(会把带有元素标签的字符串解析渲染出来)
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
3、属性 动态绑定属性
<div v-bind:id="dynamicId"></div>
<button v-bind:disabled="isButtonDisabled">Button</button>
4、使用javascript 表达式
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
这些表达式会在所属的Vue实例的数据作用域下作为JavaScript被解析 每个限制就是,每个绑定都只能包含单个表达式, 所以下面的例子都不会生效
<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}
<!-- 流控制也不会生效,请使用三元表达式 -->
{{ if (ok) { return message } }}
5、指令 指令时带有v-前缀的特殊attribute。 指令attribute的值预期是单个javascript 表达式(v-for特殊) 指令的职责是, 当表达式的值改变时,将其产生的连带影响,响应式的作用与DOM
v-if
<p v-if="seen">现在你看到我了</p>
这里,v-if 指令将根据表达式 seen 的值的真假来插入/移除 <p> 元素。
v-bind
一些指令能够接收一个“参数”,在指令名称之后以冒号表示。 例如v-bind指令可以用于响应式的更新HTML attribute
<a v-bind:href="url">...</a>
v-on
v-on指令 用于监听DOM事件 这里的参数是事件名
<a v-on:click="doSomething">...</a>
动态参数
<a v-bind:[attributeName]="url"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>
修饰符
<form v-on:submit.prevent="onSubmit">...</form>
.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault():
缩写
v-bind 缩写
<!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a :[key]="url"> ... </a>
v-on缩写
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>
<!-- 缩写 -->
<a @click="doSomething">...</a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a @[event]="doSomething"> ... </a>
计算属性
模板内的表达式非常便利,但是设计他们的初衷是用于简单的运算的。
在模板中放入太多的逻辑会让模板过重且难以维护
对于复杂的逻辑 应当使用计算属性
computed: {
reversedMessage(){
return this.message.split('').reverse().join('')
}
}
计算属性是基于它们的响应式依赖进行缓存的.只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。
这也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖:
computed: {
now: function () {
return Date.now()
}
}
方法
<p>Reversed message: "{{ reversedMessage() }}"</p>
// 在组件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
计算属性传参
<div class="number">{{ interNum(oneLevel) }}</div>
computed: {
interNum() {
return function (oneLevel) {
let num = 0
oneLevel.departmentList.forEach(it => {
num += it.departmentList.length
})
return num
}
}
},
计算属性 vs 侦听属性
通常更好的做法是使用计算属性,有响应式依赖的时候
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。 这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// 如果 `question` 发生改变,这个函数就会运行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...' // 没有响应式依赖
this.debouncedGetAnswer()
}
},
watch高阶使用 立即执行
watch 是在监听属性改变时才会触发,有些时候,我们希望在组件创建后 watch 能够立即执行
可能想到的的方法就是在 create 生命周期中调用一次,但这样的写法不优雅,或许我们可以使用这样的方法
export default {
data() {
return {
name: 'Joe'
}
},
watch: {
name: {
handler: 'sayName',
immediate: true
}
},
methods: {
sayName() {
console.log(this.name)
}
}
}
复制代码
watch高阶使用 深度监听
在监听对象时,对象内部的属性被改变时无法触发 watch ,我们可以为其设置深度监听
export default {
data: {
studen: {
name: 'Joe',
skill: {
run: {
speed: 'fast'
}
}
}
},
watch: {
studen: {
handler: 'sayName',
deep: true
}
},
methods: {
sayName() {
console.log(this.studen)
}
}
}
条件渲染 v-if key
用 key 管理可复用的元素 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换:
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>
那么在上面的代码中切换 loginType 将不会清除用户已经输入的内容。因为两个模板使用了相同的元素,< input> 不会被替换掉——仅仅是替换了它的 placeholder。
这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key attribute 即可:
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>
// 注意,<label> 元素仍然会被高效地复用,因为它们没有添加 key attribute。
列表渲染
渲染数组
// 也可以用 `of` 替代 `in` 作为分隔符,因为它更接近 JavaScript 迭代器的语法:
<li v-for="item in items">
{{ item.message }}
</li>
data() {
return {
items: [{ message: 'Foo' }, { message: 'Bar' }]
}
}
渲染对象
<li v-for="(value, name) in myObject">
{{ name }}: {{ value }}
</li>
data() {
return {
myObject: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
}
使用值的范围
<div id="range" class="demo">
<span v-for="n in 10" :key="n">{{ n }} </span>
</div>
数组更新检测
1、变更方法
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:非纯函数 push() pop() shift() unshift() splice() sort() reverse() 你可以打开控制台,然后对前面例子的 items 数组尝试调用变更方法。比如
example1.items.push({ message: 'Baz' })
2、替换数组
变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法,例如 纯函数map()、filter()、concat() 和 slice()。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组:
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
注意事项
Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property
Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue可以用splice等其他方法。但vue3中验证可以更新啊 - 当你修改数组的长度时,例如:
vm.items.length = newLength
数据不更新时操作方法
(1)、数组内部更新检测不到,用数组的非纯函数方法更新
(2)、对象内部更新检测,可以重新赋值该对象
(3)、DOM模板没有key,导致vue认为是相同的,这时候加个key,key的值随数据变化而变化(update++) (4)、watch不到数组变动 加 deep和immediate
<template>
<div>
hello world
<ul>
<li v-for="item in list" :key="item">
{{item}}
</li>
</ul>
<div @click="change">change</div>
</div>
</template>
<script>
export default {
data(){
return {
list: [1,2,3,4,5]
}
},
methods:{
change: function(){
console.log('click change')
// this.list[0] = 8888 //vue检测不到这个变化
this.list.pop() // vue可以检测到这种变化 ]
//变更方法 Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:push()pop()shift()unshift()splice()sort()reverse()
//替换数组 变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法,例如 filter()、concat() 和 slice()。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组
}
}
}
</script>
<style scoped>
</style>
data() {
return {
// basisList: {} 或者 null 以后 设置 this.basisList.plan = {xxxx}, 这时添加一个plan, vue 检测不到basisList变动,只有设置 this.basisList = {xxxx} vue才可以监测到basisList变动 触发重新渲染
basisList: {
plan: null // plan没值写个空也要写,这样才能响应式
} // 以后设置 this.basisList.plan = {xxxx} vue可以检测到数据变化 触发渲染
// 原因是 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 `data` 选项,
// Vue 将遍历此对象所有的 property,并使用 [`Object.defineProperty`]
// 这样才能形成响应式,追踪数据变化,触发重新渲染,所有要多写初始值
// 这里需要注意的是,如果数据传入子组件,子组件的created中对数据做了处理
// 当数据再改变的时候,要再子组件中监听watch父组件传过来的数据的变化做类似created的处理
// 或者在父组件内调用子组件的方法更新
}
v-for 和 v-if
v-for 和 v-if同时使用的时候v-for有更高的优先级,这意味着在每个v-for中运行一遍v-if,所以不推荐 v-for和v-if一起使用
插槽
组件 current-user
// 写在父组件中间的内容会渲染覆盖对应名称的slot
// 接受来自父组件的user属性,在这个组件中使用
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
使用组件
// 用template 用于匹配子组件中的具名slot, 这里的default匹配子组件中的default或者默认无名的slot
// 这里的v-slot:default="slotProps" 可写成v-slot="slotProps", slotProps是使用组件的组件的所有属性的集合
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
// 注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确,有多个template的情况下必须写明 v-slot:default
<template v-slot:other="otherSlotProps"> ... </template>
</current-user>
解构插槽prop
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
自定义组件的双向数据绑定v-model 语法糖
input 的 v-model是vue作者内置写好的
如果我们自己的组件要实现v-model功能 要我们自己在子组件中编写触发数据改变的操作 组件XInput
<template>
<input type="text"
:value="title"
@change="handleChange"
/>
</template>
<script>
export default {
// model prop event 写死的
// 用input事件的时候不用写model
// 一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,
// 但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。
// model 选项可以用来避免这样的冲突
model: {
prop: 'title', //需要与props中的title相同 ,因为父组件中是v-model='xx' 所以这里prop名字可以随便写,只要和props中一致即可
event: 'change' // 需要与$emit中的change同名
},
// 还是要写 props
props: {
title: String
},
data() {
return {}
},
methods: {
handleChange(event) {
this.$emit('change', event.target.value)
}
}
}
</script>
使用组件
<template>
<x-input v-model="data" />
<x-input :title="data" @change="data = $event"/>
// 这里change input方法名都行 只要子组件 emit响应相应的方法就行
</template>
<script>
import XInput from '../components/XInput'
export default {
components: {
XInput
},
updated(){
console.log('mhModel updated this.data', this.data);
},
data() {
return { data: '123'}
}
}
</script>
sync 使用 彩虹糖
它能对一个 prop 做双向绑定。它的事件名是 update: 前缀加上 prop 名称
.sync 与 v-model 非常类似。当我们使用 .sync 封装组件时,只需调整 prop 和事件的名称update即可,其余逻辑与 v-model 保持不变
sync 子组件内部数据变更的实现都要由我们自己写
当v-model使用了,还有其他属性需要双向,就可以用sync
//父组件
<template>
<p>{{title}}</p>
<syncDemo :title.sync="title"> </syncDemo>
<!--等同于 这里的方法是update:title-->
<syncDemo :title="title" @update:title="title = $event"> </syncDemo>
</template>
//子组件
<template>
<div>
<!--约定该事件名,必须为update:后加上需要更新的props即title-->
<!--el-dialog 使用 :visible.sync="dialogVisible" el-dialog里 有 this.$emit('update:visible', false); 的处理-->
<button @click="$emit('update:title','aaa123')">点我</button>
</div>
</template>
<script>
export default {
props:{
title:{
type:String,
}
}
}
</script>
过滤器
过滤器中不能用this,vue3中移除过滤器,推荐使用函数或computed
过滤器注册在vue实例之前,所以this指向了window,但是因为严格模式原因,为 undefined;
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({
// ...
})
当全局过滤器和局部过滤器重名时,会采用局部过滤器。
下面这个例子用到了 capitalize 过滤器:
John
过滤器函数总接收表达式的值 (之前的操作链的结果) 作为第一个参数。在上述例子中,capitalize 过滤器函数将会收到 message 的值作为第一个参数。
过滤器可以串联:
{{ message | filterA | filterB }}
在这个例子中,filterA 被定义为接收单个参数的过滤器函数,表达式 message 的值将作为参数传入到函数中。然后继续调用同样被定义为接收单个参数的过滤器函数 filterB,将 filterA 的结果传递到 filterB 中。
过滤器是 JavaScript 函数,因此可以接收参数:
{{ message | filterA('arg1', arg2) }}
这里,filterA 被定义为接收三个参数的过滤器函数。其中 message 的值作为第一个参数,普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数。
用函数实现类似过滤器功能
this.demandTypes 不是初始化就准备好的常量的时候
<span>{{ filterDemandType(scope.row.type) }}</span>
/**
* 需求类型转为中文
* @param {string} value 需求类型id
* @returns {string} 需求类型中文名称
*/
filterDemandType(value) {
const length = this.demandTypes.length;
for (let i = 0; i < length; i++) {
if (this.demandTypes[i].value === value) {
return this.demandTypes[i].label;
}
}
},
template
在 <template> 元素上使用 v-if 条件渲染分组
因为 v-if 是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 <template> 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含 <template> 元素。
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
render函数
cn.vuejs.org/v2/guide/re… 当用vue模板写组件较为繁杂的时候 就可以使用render函数(jsx语法)
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
'div',
// {Object}
// 一个与模板中 attribute 对应的数据对象。可选。
{
// 与 `v-bind:class` 的 API 相同,
// 接受一个字符串、对象或字符串和对象组成的数组
'class': { foo: true, bar: false },
// 与 `v-bind:style` 的 API 相同,
// 接受一个字符串、对象,或对象组成的数组
style: { color: 'red', fontSize: '14px' },
// 普通的 HTML attribute attrs: { id: 'foo' },
// 组件 prop props: { myProp: 'bar' },
// DOM property domProps: { innerHTML: 'baz' },
// 事件监听器在 `on` 内,
// 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
// 需要在处理函数中手动检查 keyCode。
on: { click: this.clickHandler },
// 仅用于组件,用于监听原生事件,而不是组件内部使用
// `vm.$emit` 触发的事件。
nativeOn: { click: this.nativeClickHandler },
// 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
// 赋值,因为 Vue 已经自动为你进行了同步。
directives: [ { name: 'my-custom-directive', value: '2', expression: '1 + 1', arg: 'foo', modifiers: { bar: true } } ],
// 作用域插槽的格式为
// { name: props => VNode | Array<VNode> } scopedSlots: { default: props => createElement('span', props.text) },
// 如果组件是其它组件的子组件,需为插槽指定名称 slot: 'name-of-slot',
// 其它特殊顶层 property key: 'myKey', ref: 'myRef',
// 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
// 那么 `$refs.myRef` 会变成一个数组。 refInFor: true
},
// {String | Array} 子节点
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
[
'先写一些文字',
createElement('h1', '一则头条'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
例子1
(h, params) => { return h('span', params.row.mindSize + ' kb'); }
例子2
(h, params) => {
return h('span', [
params.row.mindSize,
h('span', 'kb')
]);
}
例子3
(h, params) => {
if (params.row.view) {
return h('div', {
on: {
click: this.getMindList
},
}, [
h('span', '允许'),
'查看'
]);
}
return null;
}
@vue/babel-preset-jsx 用于在 Vue 中使用 JSX 语法,它可以让我们回到更接近于模板的语法上。
// 例子1
(h, params) => {
return (
<span>{ params.row.mindSize }kb</span>
);
}
// 例子2
(h, params) => {
if (params.row.view) {
return (
<el-button onClick={this.getMindList}>查看</el-button>
);
}
return null;
}
vue使用过程出现的问题解决及注意事项
1、给子组件传递参数 如果是父组件中的变量 applist 一定要用: :list="applist"
2、$emit('函数名' ,参数1,参数2)
3、{{xxxxx&&xxxxx .yyyy}} 是条件 无法在页面上展示xxxxx .yyyy 可以写
<div v-if="xxxxx&&xxxxx .yyyy"
>{{xxxxx .yyyy}}
</div>
4、靠右 如果不用浮动可以用flex justify-content
display: flex;
justify-content: flex-end;
5、form清空 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果
this.$refs[formName].resetFields();
6、form表单校验 可以是正则表达式也可以是函数 required: true 可以出现必填星号
validateField 可以触发联动校验 b切换时校验a
form校验项必须在 form的model对象里
data() {
var validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入密码'));
} else {
if (this.ruleForm.checkPass !== '') {
this.$refs.ruleForm.validateField('checkPass');
}
callback();
}
};
return {
ruleForm: {
pass: ''
},
rules: {
pass: [
{ required: true, validator: validatePass, trigger: 'blur' }
],
name: [
{ required: true, message: '请输入活动名称', trigger: 'blur' },
{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
],
region: [
{ required: true, message: '请选择活动区域', trigger: 'change' }
],
}
};
},
7、动态表单校验
<a-form-model ref="ruleForm" :model="form" :rules="formRules" :labelCol="{ span: 4}" :wrapperCol="{ span: 16}" v-loading="formLoading">
<a-form-model-item label="模板名称" prop="name"> <a-input v-model="form.name" placeholder="请输入模板名称" /> </a-form-model-item>
<a-tab-pane v-for="(child, index) in form.subTemplates" :key="index" :tab="child.name">
<a-form-model-item label="子模板名称" :prop="`subTemplates.${index}.name`" :labelCol="{ span: 4 }" :wrapperCol="{ span: 19}">
<a-input v-model="child.name" placeholder="请输入子模板名称" />
</a-form-model-item>
<a-form-model-item label="模板取数口径" :prop="`subTemplates.${index}.shellPath`" :labelCol="{ span: 4 }" :wrapperCol="{ span: 19}">
<a-textarea v-model="child.shellPath" :auto-size="{ minRows: 5}"/>
</a-form-model-item>
<a-row>
formRules: {
name: [{ required: true, message: '请输入模板名称', trigger: 'blur' }]
paramMap: [{ required: true, validator: this.checkParamMap, trigger: 'blur' }],
subTemplates: [ // 可以有多个
{
name: [{ required: true, validator: this.checkName, trigger: 'blur' }],
shellPath: [{ required: true, validator: this.checkShellPath, trigger: 'blur' }],
datasourceId: [{ required: true, message: '请选择环境', trigger: 'blur' }],
}
],
orgName: [{ required: true, message: '请输入所属部门', trigger: 'blur' }],
},
7、清空el-cascader
<el-cascader
ref="orgIdCascader"
:props="props"
v-model="orgId"
:key="isResouceShow"
style="width: 300px"
placeholder="请选择关联组织"
clearable
@change="name=''"
>
clearOrgId() {
++this.isResouceShow;
this.orgId = [];
}
8、key的使用
如果key改变了 子组件销毁重建,如果key 没有改变 会根据data的改变更新子组件,不会触发子组件created等生命周期,如果这些生命周期中写了初始化内容就无法执行
key 要唯一 不要用index 在使用非文本节点的组件,且这个组件没有依赖于响应式的props,此时使用index作为key,那么此时对于列表的删除操作会导致非文本节点的组件用的还是老index位置没更新前的组件的内容
v-for默认使用就地复用策略,列表数据修改的时候,他会根据key值去判断key对应的数据和当前dom中的数据是否改变某个值是否修改,如果修改,则重新渲染这一项,否则复用之前的元素。
虚拟dom库在进行新旧虚拟DOM比较的时候 如果两个节点的key 内容 等其他数据data信息都一样就认为他们相同不做改变
例如循环一个数组,如果key用数组的索引,如果数组中插入一个元素,元素后面的元素的索引都会改变,这个该元素和后面的元素都要重新渲染很浪费性能,如果用元素的id就好了,后面的元素的id不变也就不会重新渲染
所以循环中的key 最好写成item唯一的属性id
<script setup>
import { reactive, ref } from 'vue'
import Dog from './dog.vue'
const state = reactive({
dogs: [{
id: 1,
name: '哈士奇',
age: 1,
sex: '男',
friends: ['aa', 'bb']
},{
id: 2,
name: '金毛',
age: 2,
sex: '男',
friends: ['cc', 'dd']
},{
id: 3,
name: '边牧',
age: 3,
sex: '男',
friends: ['ee', 'ff']
}]
})
const changeHandler = () => {
// state.dogs[0].friends[0] = 'uuuuu'
// state.dogs = state.dogs.filter(item => { //key 为 id 剩下的哈士奇和边牧组件没有更新 input也正常 应该是判断了data没变且key也匹配的上就没有更新
// return item.age !== 2
// })
//key 为 index 哈士奇组件没更新 边牧组件更新 边牧的index1和之前金毛的1 的 data不同所以更新 但也只更新了props传过去的data东西 input不正常 边牧用了金毛遗留的直
// state.dogs = [{
// id: 5,
// name: '哈士奇1',
// age: 1,
// sex: '男',
// friends: ['aa', 'bb1']
// },{
// id: 1,
// name: '边牧1',
// age: 3,
// sex: '男',
// friends: ['ee', 'ff1'],
// }] // key是id时 如果id不相同,直接子组件删除重建,如果id相同 用新数据更新老id组件 input正常
// key是 index 在老id为0 和 1的组件上更新 input不正常 用老组件的内容 data的其他属性更新正常
// state.dogs[0].other = '0000000' // 由于other在初始化data中未声明,本组件可以监测到更新,但子组件监测不到更新
// state.dogs[0] = {
// id: 1,
// name: '哈士奇11111',
// age: 1111,
// sex: '男111',
// friends: ['aa111', 'bb111']
// } // 都可以更新
}
</script>
// 子组件
<template>
<p v-for="item,index in state.dogs" :key="item.id">
{{item.name}}{{item.age}}{{item.sex}}{{item.other}}
<span v-for="friend in item.friends">{{ friend }}</span>
</p>
<Dog v-for="item, index in state.dogs" :key="item.id" :dog="item"></Dog>
<div @click="changeHandler">点击</div>
</template>
<script setup>
import { computed } from '@vue/reactivity';
import { ref, onMounted, onUpdated, reactive } from 'vue'
const props = defineProps({
dog: Object,
})
onMounted(() => {
console.log('onMounted,,,,,', props.dog);
})
onUpdated(() => {
console.log('onUpdated,,,,,', props.dog);
})
const state = reactive({
input1: '',
inputt2: ''
})
</script>
<template>
<div>
<div>{{ dog.name }}</div>
<div>{{ dog.age }}</div>
<div>{{ dog.sex }}</div>
<div v-for="item in dog.friends">{{ item }}</div>
<input v-model="state.input1" />
</div>
</template>
10、vue router query中params改变不刷新页面
11、Vue插值文本换行问题
后端返回的字符串带有\n换行符,但Vue将其插值渲染成div内部文本后,文本并不换行,换行符显示为一个空格。
让文本在换行符处换行。
思路:实现文本换行有两种方法,一是HTML方法,即
标签;二是CSS方法,即white-space属性。
方法1.使用v-html
首先,将字符串里的\n替换为
,然后用v-html指令渲染字符串为innerHTML。
// JS部分
this.text = res.data.replace(/\n/g, '<br>')
// HTML部分
<div v-html="text"></div>
这种方法比较麻烦,而且存在安全问题,故不推荐使用。
方法2设置white-space属性(推荐)
将div容器的white-space属性设置为pre-wrap即可解决问题。
// CSS部分
.text-wrapper {
white-space: pre-wrap;
}
// HTML部分
<div class="text-wrapper">{{text}}</div>
pre-wrap值的意思是保留空白并且正常换行。
white-space各属性值详见这里。其实设置为pre即可使换行符发挥作用,但这时文本在div宽度不足时不会自动换行,而是撞破边界延伸到div外部去,所以还得加上wrap。
12、样式穿透
在开发中修改第三方组件样式是很常见,但由于 scoped 属性的样式隔离,可能需要去除 scoped 或是另起一个 style 。这些做法都会带来副作用(组件样式污染、不够优雅),样式穿透在css预处理器中使用才生效。
我们可以使用 >>> 或 /deep/ 解决这一问题:
<style scoped>
外层 >>> .el-checkbox {
display: block;
font-size: 26px;
.el-checkbox__label {
font-size: 16px;
}
}
</style>
<style scoped>
/deep/ .el-checkbox {
display: block;
font-size: 26px;
.el-checkbox__label {
font-size: 16px;
}
}
</style>
13、el-dialog 弹框关闭再打开时有上次遗留的form表单校验等问题 用v-if 就可以消除
<el-dialog
v-if="dialogVisible"
:title="title"
:visible.sync="dialogVisible"
>
14、打印空格
<span>你好 你好</span>
或者
a = '你好 你好'
或<span v-html="a"></span>
换行 \n
a = 'aaaaa\nbbbbbb'
<span style="white-space: pre-line;">{{ a }} </span> // 输出内容自动换行,否则会直接把\n打印出来
15、antD vue 的select组件 option为 true或false的时候有问题,可以转成其他 wrapper-col 和 label-col 加起来最大24 这样充满整行 再多就换行
16、input 的 input事件 和 change事件区分开
<input
ref="input"
@input="handleInput"
@change="handleChange"/>
17、$attrs 除了写在子组件props中的所有父组件的写在子组件上的属性
// 父组件
<el-input-mh v-model="value" placeholder="请输入内容" :maxlength="10"/>
// 子组件
<input
ref="input"
v-bind="$attrs"
@input="handleInput"
@change="handleChange"/>
props: {
value: [Number, String],
type: {
type: String,
default: 'text'
}
},
// this.$attrs { maxlength: 10, placeholder: "请输入内容"}
18、给条件校验的form item 前面添加必填标记* (required: true,然后再执行validator里的逻辑)
<template>
<div>
<el-form ref="databaseForm" :rules="rules" :model="cConfig" label-width="80px" class="form">
<el-form-item label="地址:" prop="fullAddr">
<el-input v-model="cConfig.fullAddr" placeholder="请输入地址,如: 10.10.10.10:8080"/>
</el-form-item>
<el-form-item :class="{'require': dbType === 0 || dbType === 1 }" label="库名:" prop="name" >
<el-input v-model="cConfig.name" placeholder="请输入库名"/>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
props: {
configType: {
type: Number,
default: 0
},
config: {
type: Object,
default: {
addr: '',
name: '',
username: '',
password: '',
params: ''
}
},
dbType: Number
},
data() {
const checkName = (rule, value, callback) => {
// mysql、oracle 库名必填
if (this.dbType === 0 || this.dbType === 1) {
if (!value) {
return callback(new Error('请输入库名'));
}
}
callback();
};
return {
cConfig: {
...this.config,
fullAddr: this.config.addr ? `${this.config.addr}:${this.config.port}` : ''
},
rules: {
fullAddr: [
{ required: true, message: '请输入地址', trigger: 'blur' }
],
name: [
{ required: true, validator: checkName, trigger: 'blur' }
],
}
};
}
};
</script>
<style scoped>
.require >>> .el-form-item__label:before {
content: '*';
color: #F56C6C;
margin-right: 4px;
}
</style>
19 修改props传过来的参数 众所周知,vue是单向数据流,一般我们也不会在子组件里面修改父组件传进来的值,但总有需要修改的时候。
前段时间一个项目中有遇到上述情况,假设我直接传进来一个list, 当时我直接在里面改了list,但是却惊讶的发现没有报错,以前好像有遇到直接修改抛出错误的,但这次却没有,当时也没有多想,最近空闲下来又想了下,发现自己确实错了。原因如下:
因为我传进来的list是个数组,属于引用类型,修改子组件相当于把父组件也同时修改了,所以没有报错,如果是个基本类型的数据直接修改那么vue会报错。
在子组件修改props的方法:
(1)data 中重定义数据
这种情况适用于用 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。期间父组件不会改变这个值 在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:
但是像弹窗dialog再次打开dialog时 initialCounter可能改变了,但是counter不会改变依然是上次dialog中的值,这是个问题,
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
复制代码
(2)通过计算属性 或者watch
这个 prop 以一种原始的值传入且需要进行转换。 在这种情况下,最好使用这个 prop 的值来定义一个计算属性 如果要改变 normalizedSize 依然要通过emit改变父组件中的size,否则会报错 Computed property "normalizedSize" was assigned to but it has no setter.
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase() // Computed property "normalizedSize" was assigned to but it has no setter.
}
}
// 如果父组件会改变传过来的属性
<sc-switch
v-model="isAuth"
:disabled="isAuthTrusteeship"
@change="() => { this.$emit('takeLock')}"
>
props: {
isAuthTrusteeship: {
type: Boolean,
default: false
}
},
// 不可以使用 Computed property "isAuth" was assigned to but it has no setter.
computed: {
isAuth () {
return this.isAuthTrusteeship
}
},
// 可以使用
computed: {
isAuth : {
get() {
return this.isAuthTrusteeship
},
set(v) {
}
}
},
// 可以使用
watch: {
isAuthTrusteeship: {
handler () {
this.isAuth = this.isAuthTrusteeship
},
immediate: true
},
},
(3)通过 $emit 事件传递
父组件:
<template>//父组件
<CommonDialog
:title="dialogTitle"
:showDialog.sync="isShowDialog"
:footer="true"
:width="dialogWidth"
>
....
</CommonDialog>
</template>
复制代码
子组件
//子组件 弹框是否打开props: showDialog
<el-dialog :title="title" :visible="showDialog" :show-close="false" :width="width">
<i class="el-dialog__headerbtn" @click="closeModal">
<span class="iconfont iconclose"></span>
</i>
<div class="dialog-body">
<slot></slot>
</div>
<div v-if="!footer" slot="footer" class="dialog-footer">
<slot name="footer"></slot>
</div>
</el-dialog>
复制代码
事件:
closeModal() {
this.$emit("update:showDialog", false);
},
4. 通过研究大佬们的写法又发现了一种修改方式: sync修饰符 不推荐使用
父组件 传进去的时候加上 .sync
子组件 通过this.$emit(‘update:xxx’, params)
// 父组件
<todo-list :list.sync="list" />
// 子组件
methodName(index) {
this.$emit('update:list', this.newList)
5、v-model
mixin
1.mixin就是方法变量抽离的合集,引用在组件内,相当于组件的拓展,和组件内其他方法变量一样使用。
2.多个组件调用同一个mixin,每个变量都是单独独立的,不会项目影响污染。
3.组件的方法、变量会覆盖mixin的方法、变量。
4.事件钩子中,mixin优先组件执行
5.mixin和组件 方法名同名:- mixin方法不执行被覆盖,组件方法正常执行
mouseout和mouseleave
1.不论鼠标指针离开被选元素还是任何子元素,都会触发 mouseout 事件。
2.只有在鼠标指针离开被选元素时,才会触发 mouseleave 事件。
动态样式:style和动态:class
注意后面的值是 对象(key为样式名或者class名)还是字符串(计算出的结果拼成正常的class或style) 还是数组
:class="[dialogClass, { 'form-view-only': formDisabled }]"
:class="`${fixed} ${needMask ? 'mask' : ''}`"
:class="phaseData.movementIsChange ? 'is-change' : ''"
:class="`sp-icon ${this.className}`"
:class="{ active: activeIndex === item.path }"
:class="{ 'node-select': one.isOpen }"
:class="{ normalbg }"
:style="{ width: propWidth, backgroundImage: propBackgroundImage?`url(${propBackgroundImage})`:null }"
:style="`font-size: ${this.fontSize}`"
:style="moveStyleObj"
computed: {
moveStyleObj() {
return {
height: '.' + (this.width - 2 + 'rem'),
width: '.' + (this.width - 2 + 'rem'),
'background-size':
'.' + this.width / 8 + 'rem ' + '.' + this.width / 4 + 'rem'
}
}
},