1vue实例
数据与方法
所有的 Vue 组件都是 Vue 实例,并且接受相同的选项对象 (一些根实例特有的选项除外)。当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。当这些数据改变时,视图会进行重渲染。值得注意的是只有当实例被创建时就已经存在于 data 中的 property 才是响应式的。也就是说如果你添加一个新的 property,比如:
vm.b = 'hi'
那么对 b 的改动将不会触发任何视图的更新。如果你知道你会在晚些时候需要一个 property,但是一开始它为空或不存在,那么你仅需要设置一些初始值。这里唯一的例外是使用 Object.freeze(),这会阻止修改现有的 property,也意味着响应系统无法再追踪变化。
除了数据 property,Vue 实例还暴露了一些有用的实例 property 与方法。它们都有前缀 $,以便与用户定义的 property 区分开来。
var data = { a: 1 }
var vm = new Vue({
el: '#example',
data: data
})
vm.$data === data // => true
vm.$el === document.getElementById('example') // => true
// $watch 是一个实例方法
vm.$watch('a', function (newValue, oldValue) {
// 这个回调将在 `vm.a` 改变后调用
})
实例生命周期钩子
- beforeCreate
- created
beforecreated: el和data并未初始化 created:完成了data 数据的初始化,el没有
- beforeMount
- mounted
beforeMount:完成了el和 data初始化,mounted :完成挂载
- beforeUpdate
- updated
- beforeDestroy
- destroyed
注意:不要在选项 property 或回调上使用箭头函数,比如 created: () => console.log(this.a) 或 vm.$watch('a', newValue => this.myMethod())。因为箭头函数并没有 this,this 会作为变量一直向上级词法作用域查找,直至找到为止,经常导致 Uncaught TypeError: Cannot read property of undefined 或 Uncaught TypeError: this.myMethod is not a function 之类的错误。
二模板语法
插值
文本
<span>Message: {{ msg }}</span>
原始 HTML
双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html 指令:
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
注意:你的站点上动态渲染的任意 HTML 可能会非常危险,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值。
Attribute
Mustache 语法不能作用在 HTML attribute 上,遇到这种情况应该使用 v-bind 指令
<button v-bind:disabled="isButtonDisabled">Button</button>
如果 isButtonDisabled 的值是 null、undefined 或 false,则 disabled attribute 甚至不会被包含在渲染出来的 元素中。
使用 JavaScript 表达式
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
注意每个绑定都只能包含单个表达式,所以下面的例子都不会生效。
<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}
<!-- 流控制也不会生效,请使用三元表达式 -->
{{ if (ok) { return message } }}
指令
<!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a :[key]="url"> ... </a>
[
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>
<!-- 缩写 -->
<a @click="doSomething">...</a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a @[event]="doSomething"> ... </a>
三计算属性和侦听器
计算属性
对于任何复杂逻辑,你都应当使用计算属性。
计算属性若没有放到视图中是不会执行的
计算属性放到视图中第一次执行的时机是created之后,mounted之前。
除了第一次执行的时机外后面每次执行频率是reversedMessage方法里面的某个data值发生了变化或者是引用类型的地址发生变化就会执行。
<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: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
})
再看一个搜索的例子吧
<template>
<div>
<input type="text"
v-model='state.keywords'
placeholder="请输入搜索关键字"
>
<br/>
<ul>
<li v-for="star in filterStars" :key="star.id">
{{star.name}}-{{star.age}}
</li>
</ul>
</div>
</template>
<script>
import { computed, reactive } from '@vue/reactivity'
export default {
setup(){
const state = reactive({
stars:[
{ id:"01", name:'马东梅', age:18},
{ id:"02", name:'周冬雨', age:20},
{ id:"03", name:'周杰伦', age:30},
{ id:"04", name:'温兆伦', age:40},
],
keywords:''
})
const filterStars = computed(()=>{
return state.stars.filter(v=>v.name.includes(state.keywords))
})
return {
state,
filterStars,
}
}
}
</script>
计算属性缓存 vs 方法
虽然计算属性缓存和方法都能达到相同效果,第一次执行时机都是created之后,mounted之前。
但是后面每次执行时机不一样:计算属性缓存的执行是方法里面的某个值发生了变化或者是引用类型的地址发生变化就会执行;而方法的执行是模板重新渲染时就会执行。
方法是没有缓存的,当一个组件多次使用同一个方法{{ reversedMessage() }}时,方法会执行多次;而计算属性是具有缓存机制,多个地方使用同一个计算属性,计算属性只会执行一次 总结:方法的执行频率 >= 计算属性缓存
注意啦:如果需要传参请使用方法,不用绕一圈在计算属性中返回函数,其实执行频率是一样的,搞复杂了(实际项目中遇到的)
计算属性 vs 侦听属性
共同点:计算属性和侦听属性都可以实现当某一个数据(称它为依赖数据)发生变化的时候,所有依赖这个数据的“相关”数据“自动”发生变化
不同点: 计算属性,是一个计算值,具有缓存性;常用到有复杂计算逻辑,需要用到缓存时
侦听属性,监听某一个值变化执行某个操作时,不具有缓存性;常用到需要在数据变化时执行异步或开销较大的操作时
侦听器
watch: {
// 监听基本数据类型
age(newVal) {
console.log('age', newVal)
},
// 监听引用类型
userInfo: {
handler(newVal) {
console.log('userInfo', newVal)
},
// deep不配置或者值为false,都表示只有userInfo这个对象地址值发生变化时才会执行handler回调
deep: true,
// immediate不配置或者值为false,都表示初始值时不会执行handler的函数;只有配置为true,才在beforeCreate之后created之前执行
immediate: true
},
// 监听某个对象里面的某个属性
'userInfo.name'(newVal){
console.log('userInfo.name', newVal)
}
},
四Class 与 Style 绑定
传对象,与普通class并存
<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>
数组中含有对象
<div :class="[ { active: isActive }, 'errorClass' ]">456</div>
根据条件切换列表中的 class,可以用三元表达式:
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
实际常用的做tab的切换添加动态类
<li v-for="(nav,index) in navs" :key="nav.id" @click="handleChangeTab(index)" :class="[index===currentIndex?'active':'']">{{nav.category}}</li>
<li v-for="(nav,index) in navs" :key="nav.id" @click="handleChangeTab(index)" :class="{active:index===currentIndex}">{{nav.category}}</li>
绑定内联样式
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
},
isActive : true
}
内联样式的数组语法
<div v-bind:style="[baseStyles, overridingStyles]"></div>
五条件渲染
v-if vs v-show
v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
实际场景中比如子组件中有定时器轮询事件,dom操作事件等副作用操作,应该用v-if,因为v-show并不会真正销毁子组件,不会触发beforeUnmount生命周期函数,导致不会关闭定时器。工作中弹窗中展示一个表格,表格中的状态需要不断去轮询后端接口,关闭后应该停止轮询,实际并没有,就是这个原因,写个demo感受一下吧
<button @click="show = !show">点击切换显示和隐藏组件</button>
<IfShow v-if='show'></IfShow>
//子组件内容
<template>
<div class="test-show">
我是if show组件中的内容
<span>{{count}}</span>
</div>
</template>
<script>
import {ref, onBeforeMount , onMounted ,onBeforeUnmount,onUnmounted} from 'vue'
export default {
setup(){
const count = ref(0)
let timerId = null;
onBeforeMount(()=>{
console.log('onBeforeMount')
})
onMounted(()=>{
console.log('onMounted')
timerId = setInterval(() => {
count.value +=1
}, 1000)
})
onBeforeUnmount(()=>{
clearInterval(timerId);
console.log('onBeforeUnmount')
})
onUnmounted(()=>{
console.log('onUnmounted')
})
return {
count,
}
}
}
</script>
v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好
v-if可以配合v-else-if v-else使用,v-if还可以与template一起使用,不破坏dom结构
六列表渲染
基本用法
// 遍历数组
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
// 遍历对象
<div v-for="(value, name) in object">
{{ name }}: {{ value }}
</div>
key理解
key的唯一性能提高渲染的速度
key的工作原理: 每次渲染真实dom之前,会进行新的虚拟Dom和上一次虚拟Dom比较。 若key值不同,则它本身以及子元素会直接创建新的真实Dom, 从而渲染到页面; 若key值相同,则会递归元素本身显示属性和其子节点,属性键名键值都相同部分复用,子节点完全一样会复用,复用下就不会创建新的真实Dom,只有不一样的才会创建。从而提高性能。
可以用index做key吗?随机数呢 用index做key在有添加删除元素的逆序操作中,会产生没有必要的真实dom的更新,效率低;如果结构中还包含输入类的dom,比如input框,会 产生错误dom更新,界面有问题。
数组更新检测
变更方法,变更调用了这些方法的原始数组,直接调用这些方法可以触发视图更新
push() pop() shift() unshift() splice() sort() reverse()
替换数组,它们不会变更原始数组,而总是返回一个新数组
当使用非变更方法时,可以用新数组替换旧数组:
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
显示过滤/排序后的结果,利用计算属性
<li v-for="n in evenNumbers">{{ n }}</li>
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}
在计算属性不适用的情况下 (例如,在嵌套 v-for 循环中) 你可以使用一个方法:
<ul v-for="set in sets">
<li v-for="n in even(set)">{{ n }}</li>
</ul>
data: {
sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}
在组件上使用 v-for
cn.vuejs.org/v2/guide/li… 注意这里的 is="todo-item" attribute。这种做法在使用 DOM 模板时是十分必要的,因为在
- 元素内只有
- 元素会被看作有效内容。这样做实现的效果与 相同,但是可以避开一些潜在的浏览器解析错误
七事件处理
事件处理方法
在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法:
<button v-on:click="warn('Form cannot be submitted yet.', $event)"> Submit </button>// ... methods: { warn: function (message, event) { // 现在我们可以访问原生事件对象 if (event) { event.preventDefault() } alert(message) } }事件修饰符
.stop .prevent .capture .self .once .passive 这个 .passive 修饰符尤其能够提升移动端的性能。
按键修饰符
<input v-on:keyup.enter="submit"> <input v-on:keyup.page-down="onPageDown">按键码
keyCode 的事件用法已经被废弃了并可能不会被最新的浏览器支持。
<input v-on:keyup.13="submit">为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名: .enter .tab .delete (捕获“删除”和“退格”键) .esc .space .up .down .left .right
你还可以通过全局 config.keyCodes 对象自定义按键修饰符别名:
// 可以使用 `v-on:keyup.f1` Vue.config.keyCodes.f1 = 112.exact 修饰符
.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 --> <button v-on:click.ctrl="onClick">A</button> <!-- 有且只有 Ctrl 被按下的时候才触发 --> <button v-on:click.ctrl.exact="onCtrlClick">A</button> <!-- 没有任何系统修饰符被按下的时候才触发 --> <button v-on:click.exact="onClick">A</button>八表单输入绑定
多个复选框,绑定到同一个数组:
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames"> <label for="jack">Jack</label> <input type="checkbox" id="john" value="John" v-model="checkedNames"> <label for="john">John</label> <input type="checkbox" id="mike" value="Mike" v-model="checkedNames"> <label for="mike">Mike</label> <br> <span>Checked names: {{ checkedNames }}</span>new Vue({ el: '...', data: { checkedNames: [] } })多个复选框 type='checkbox' v-model绑定一个数组,给定value值 单选按钮 type="radio" v-model绑定一个字符串,给定value值
选择框
<select name="" id="" v-model="selected" @change="handleChangeSelect"> <!-- <option disabled value="">请选择</option> --> <option v-for='option in options' :key='option.value' :value="option.value">{{option.name}}</option> </select> <br /> selected:{{selected}} new Vue({ el: '...', data: { selected: '', options: [ { value: 'one', name: 1 }, { value: 'two', name: 2 }, { value: 'three', name: 3 }, ] }, <!-- 通过这种方式设置select默认值 --> created:{ this.selected = this.options[0].value }, methods:{ handleChangeSelect () { console.log('handleChangeSelect', this.selected) } } })注意:如果 v-model 表达式的初始值未能匹配任何选项,select 元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。
FIX 选择框多选的例子有问题?
修饰符 .lazy 在“change”时而非“input”时更新 .number .trim
自定义组件的 v-model cn.vuejs.org/v2/guide/co…
九组件基础&深入了解组件
组件名
使用 kebab-case
Vue.component('my-component-name', { /* ... */ })使用 PascalCase
Vue.component('MyComponentName', { /* ... */ })当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 和 都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。
全局注册
Vue.component('my-component-name', { // ... 选项 ... })局部注册
全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:
var ComponentA = { /* ... */ } var ComponentB = { /* ... */ } var ComponentC = { /* ... */ }然后在 components 选项中定义你想要使用的组件:
new Vue({ el: '#app', components: { 'component-a': ComponentA, 'component-b': ComponentB } })注意局部注册的组件在其子组件中不可用。例如,如果你希望 ComponentA 在 ComponentB 中可用,则你需要这样写:
var ComponentA = { /* ... */ } var ComponentB = { components: { 'component-a': ComponentA }, // ... }或者如果你通过 Babel 和 webpack 使用 ES2015 模块,那么代码看起来更像:
import ComponentA from './ComponentA.vue' export default { components: { ComponentA }, // ... }Prop
Prop 的大小写
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:
Vue.component('blog-post', { // 在 JavaScript 中是 camelCase 的 props: ['postTitle'], template: '<h3>{{ postTitle }}</h3>' })<!-- 在 HTML 中是 kebab-case 的 --> <blog-post post-title="hello!"></blog-post>单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行. 这里有两种常见的试图变更一个 prop 的情形: 1这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:
props: ['initialCounter'], data: function () { return { counter: this.initialCounter } }2这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } }注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。
Prop 验证
Vue.component('my-component', { props: { // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证) propA: Number, // 多个可能的类型 propB: [String, Number], // 必填的字符串 propC: { type: String, required: true }, // 带有默认值的数字 propD: { type: Number, default: 100 }, // 带有默认值的对象 propE: { type: Object, // 对象或数组默认值必须从一个工厂函数获取 default: function () { return { message: 'hello' } } }, // 自定义验证函数 propF: { validator: function (value) { // 这个值必须匹配下列字符串中的一个 return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } } })自定义事件
事件名
不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。举个例子,如果触发一个 camelCase 名字的事件:
this.$emit('myEvent')则监听这个名字的 kebab-case 版本是不会有任何效果的:
<!-- 没有效果 --> <my-component v-on:my-event="doSomething"></my-component>不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或 property 名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。
因此,我们推荐你始终使用 kebab-case 的事件名。
自定义组件的 v-model
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:
Vue.component('base-checkbox', { model: { prop: 'checked', event: 'change' }, props: { checked: Boolean }, template: ` <input type="checkbox" v-bind:checked="checked" v-on:change="$emit('change', $event.target.checked)" > ` })将原生事件绑定到组件
有时候我们需要在一个组件的根元素上直接监听一个原生事件,一般情况下我们可以使用.native修饰符
<base-input v-on:focus.native="onFocus"></base-input>但是像下面这种情况,根元素实际是label标签,这个时候直接用.native,组件上的原生事件是会失效的,所以我们需要借助listeners"` 将所有的事件监听器指向这个组件的某个特定的子元素.
<label> {{ Label }} <input type="text" ref="inputRef" :value="value" v-on="inputListeners"/> </label>computed: { inputListeners: function () { console.log('this.$listeners',this.$listeners) return Object.assign({}, this.$listeners, { // 这里确保组件配合 `v-model` 的工作 input: (event) => { this.$emit("input", event.target.value); }, }); }, },<BaseInput v-model="inputVal" @focus="handleFarFocus"/>.sync修饰符
通常情况下props都是自上而下传递的,即父传向子。但有时候我们可能需要对prop进行“双向的数据绑定”,即子组件需要修改父组件的数据。通常有2种方式:
第一:子组件维护一份自己的data或computed,修改自己的数据。记住,computed中返回的数据若会被修改,需要设置set函数
computed: { accepetTitle:{ get(){ return this.title }, set(val){ console.log('set被调用了',val) this.title=val } } }, methods: { changeTitle(){ this.accepetTitle='111' } }第二:父组件上定义方法,子组件触发方法并将自己的数据传递过去。一般我们可能会写成一个方法,但有时候像下面这样的简单逻辑,我们推荐以
update:myPropName的模式触发事件取而代之methods: { changeTitle(){ this.$emit('update:title','111') } }<TextDocument :title='doc.title' :content='doc.content' v-on:update:title="doc.title=$event"></TextDocument> 可利用.sync修饰符简写为: <TextDocument :title.sync='doc.title' :content='doc.content'></TextDocument>十插槽的使用(项目中常用)
- 具名插槽(一个组件中有几个地方需要用到插槽,可以在插槽中定义name属性(定义插槽的地方)使用插槽的地方使用v-slot:header接收
- 具名插槽的缩写 v-slot:header="slotProps" -> #header="slotProps"
- 作用域插槽(如果父组件想要使用子组件中的数据,可以在定义插槽的地方使用v-bind把数据传过来,使用插槽的地方像v-slot:header="slotProps"接收
- 解构插槽 Prop Layout.vue 定义插槽的子组件
<template> <div> <header> header <slot name='header' v-bind:user="user"> </slot> </header> <main> main <slot name='main'></slot> </main> <footer> footer <slot name='footer'></slot> </footer> </div> </template>Home 使用插槽的父组件
<Layout> <template v-slot:header="slotProps"> <h1>Here might be a page title-{{slotProps.user.firstName}}</h1> </template> <template v-slot:main> <p>A paragraph for the main content.</p> <p>And another one.</p> </template> <template v-slot:footer> <p>Here's some contact info</p> </template> </Layout>解构插槽 Prop
<template #header="{user}"> <h1>Here might be a page title-{{user.firstName}}</h1> </template>十一 Mixin cn.vuejs.org/v2/guide/mi…
2个组件共享同一个配置;2个类似的页面,如果写成一个组件复用需要很多判断逻辑,这时可以写成2个组件,相同的功能用mixin混入,其他逻辑各写各的,方便后期维护。
基础
Mixin 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个 mixin 对象可以包含任意组件选项。
选项合并
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。 比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。 同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。 值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
全局混入
一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。
十二 渲染函数 & JSX
这里类似于react的jsx语法,文件命名为js或ts。在大部分情况下我们都是推荐.vue单文件组件,但有些场景使用JavaScript 的完全编程的能力却是非常方便的,比如动态返回某个组件。
import EmptyList from './EmptyList.vue' import TableList from './TableList.vue' import OrderedList from './OrderedList.vue' import UnorderedList from './UnorderedList.vue' import Vue from 'vue' export default Vue.extend({ functional: true, props: { items: { type: Array, required: true }, isOrdered: Boolean }, render(createElement, context) { console.log(createElement, context) function appropriateListComponent() { var items = context.props.items if (items.length === 0) return EmptyList if (typeof items[0] === 'object') return TableList if (context.props.isOrdered) return OrderedList return UnorderedList } return createElement( appropriateListComponent(), context.data, context.children ) } })上面例子我们需要了解的点有:
- 全局api:Vue.extend 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
<div id="mount-point"></div> // 创建构造器 var Profile = Vue.extend({ template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>', data: function () { return { firstName: 'Walter', lastName: 'White', alias: 'Heisenberg' } } }) // 创建 Profile 实例,并挂载到一个元素上。 new Profile().$mount('#mount-point')- 选项/DOM:render
字符串模板的代替方案,允许你发挥 JavaScript 最大的编程能力。该渲染函数接收一个
createElement方法作为第一个参数用来创建VNode。
如果组件是一个函数组件,渲染函数还会接收一个额外的
context参数,为没有实例的函数组件提供上下文信息。Vue 选项中的
render函数若存在,则 Vue 构造函数不会从template选项或通过el选项指定的挂载元素中提取出的 HTML 模板编译渲染函数。-
createElement通常会简写为h
-
可以添加functional: true,意味着该组件是函数式组件,意味它无状态(没有响应式数据),没有实例(没有this上下文),读取数据可以通过第二个参数context
十三 插件
- 思考:往原型上添加方法,vue3里面都没有this了呀 ,but可以通过getCurrentInstance()!.proxy获取vue实例
- 功能:插件通常用来为 Vue 添加全局功能
- 本质:包含install方法的一个对象,这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象 定义插件
MyPlugin.install = function (Vue, options) { // 1. 添加全局方法或 property Vue.myGlobalMethod = function () { // 逻辑... } // 2. 添加全局资源 Vue.directive('my-directive', { bind (el, binding, vnode, oldVnode) { // 逻辑... } ... }) // 3. 注入组件选项 Vue.mixin({ created: function () { // 逻辑... } ... }) // 4. 添加实例方法 Vue.prototype.$myMethod = function (methodOptions) { // 逻辑... }使用插件
Vue.use(MyPlugin, { someOption: true })插件通常用来为 Vue 添加全局功能。
使用插件
通过全局方法 Vue.use() 使用插件。它需要在你调用 new Vue() 启动应用之前完成:
Vue.use(Router) Vue.use(vuex) Vue.use 会自动阻止多次注册相同插件,届时即使多次调用也只会注册一次该插件。
开发插件
Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:
MyPlugin.install = function (Vue, options) { // 1. 添加全局方法或 property Vue.myGlobalMethod = function () { // 逻辑... } // 2. 添加全局资源 Vue.directive('my-directive', { bind (el, binding, vnode, oldVnode) { // 逻辑... } ... }) // 3. 注入组件选项 Vue.mixin({ created: function () { // 逻辑... } ... }) // 4. 添加实例方法 Vue.prototype.$myMethod = function (methodOptions) { // 逻辑... } }一些常见库的用法
Portal-Vue (portal-vue.linusb.org/guide/getti…)
Setup
Install Package:
npm install --save portal-vue # or with yarn yarn add portal-vueAdd it to your application:
import PortalVue from 'portal-vue' Vue.use(PortalVue)Usage
The Basics
<portal to="destination"> <p>This slot content will be rendered wherever the <portal-target> with name 'destination' is located. </p> </portal> <portal-target name="destination"> <!-- This component can be located anwhere in your App. The slot content of the above portal component will be rendered here. --> </portal-target>Enabling/Disabling the Portal 给portal增加属性:disabled="true",内容就不会移动到别的区域。
Conditional rendering with v-if 给portal增加v-if="usePortal"条件判断,为true时才会移动。
Multiple Portals, one Target
The component has a multiple mode the components has a order prop ,order决定哪个内容渲染顺序
Use Cases(使用场景)
Positioning Modals & Overlays(模态框)
Advanced Usage
"Slim" - removing the wrapper(还没定位到怎么用的 fix)
Scoped Slots(相当于父向子传参)
PortalVue can also be used with Scoped Slots! This allows you to send a scoped slot to a PortalTarget, which can then provide props for the slot content:
<portal to="destination"> <p slot-scope="{message}">{{message}}</p> </portal> <portal-target name="destination" :slot-props="{message: 'Hello from the Target to You!'}" />Result:
<div class="vue-portal-target"> <p>Hello from the Target to You!</p> </div>