vue 实例属性:
data、computed、methods、props
中的属性都挂载到 vue 实例的一级属性上。computed
可以依赖多个vue 实例属性,并根据依赖进行缓存。指令:指令可以带参数和修饰符,指令绑定的表达式。指令的值是多少?
什么是字符串模板:单文件中的template 和 实例化vue对象时的template 属性。
DOM 模板:在 html 文件中编写的html标签(包含自定义标签),直接在DOM中使用组件。
html 属性名和属性值
Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器。
Vue 的模板实际上被编译成了渲染函数
组件
组件是可复用的 Vue 实例,根实例特有选项: el
创建组件的方式有哪些?
使用 Vue.component 或 Vue.extend 定义组件
全局注册
vueVue.component('my-component-name', { // ... options ... })
函数式组件和类组件:函数式组件只是函数,所以渲染开销也低很多
vue-class-component 和 vue-property-decorator
vm 表示实例,h 表示 createElement
组件的选项对象
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。
// 定义一个名为 button-counter 的新组件
// 全局注册:全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生
html 的属性名不区分大小写,都是小写,html 属性值只能是字符串?
Vue.component('button-counter', {
data() {
return {
count: 0
}
},
props: ['title'],
template: <button v-on:click="count++">You clicked me {{ count }} times.</button>
})
// ***********************************************************
props: ['initialCounter'],
data(){
return {
counter: this.initialCounter
}
}
动态组件
<tbody is="my-cmp"></tbody>
tbody
在渲染时被替换为my-cmp
组件
$emit ==> $event
自定义事件(非click)
- $emit:触发一个事件
- $event:
vue组件三大核心概念
- 原生组件(也叫 HTML 元素)的构成:
<div id='app' style='width:70%'>内容</div>
,开始标签、内容、结束标签、属性(特性)、属性值;自定义组件也由这些部分构成。
<!--使用自定义组件-->
<my-component name='czl'>
<!-- 此处为组件内容,通过slot插槽传递 -->
<p></p>
<div></div>
</my-component>
- 一个 Vue 应用由一个通过 new Vue 创建的根 Vue 实例,以及可选组件树组成;
- 在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例,每个组件都会各自独立维护它的 data对象。因为你每用一次组件,就会有一个它的新实例被创建,所以data对象是通过函数返回;
- 组件有自己独立的作用域,数据不会传递到组件内 ,因此使用props向组件内部传递数据;
- 不要在选项属性或回调上使用箭头函数,箭头函数的
this
不是指向组件实例; - Vue 将模板编译成虚拟 DOM 渲染函数;
Vue最核心的功能:数据的双向绑定,实时同步数据,数据驱动DOM。
Q:数据双向绑定是哪两方数据?
- A:HTML元素数据(表单元素)--Vue实例中的data将元素的属性与属性值分离,其值存放在Vue实例中;用v-model、v-bind将元素属性与其属性值相互绑定。
Vue创建的实例代理了data和method属性,所以可以直接通过实例(this.**)访问;
实例属性与方法用$开头来访问,如vueApp.$el可以访问元素;
Vue构成
- 组件化
- 数据驱动 & 状态管理(vuex)
- 指令
计算属性 & watch
- 计算属性只能执行同步代码,计算属性根据响应式依赖进行缓存,当依赖的数据没有更新时,多次访问计算属性直接取缓存的值;
- watch 可以执行异步操作;
1. 属性
1.1 分类
- 自定义属性props
- 原生属性attrs
- 插槽props
- 特殊属性class、style
data与props的区别?
单向数据流?
Vue为什么禁止修改父组件传递进来的props, 又是如何监控到子组件修改了props?
- 因为Vue遵循单向数据流,模型(比如props数据)渲染视图,如果允许子组件修改父组件传递的值,那么同一父组件的其他子组件就会受影响,导致数据流向很混乱,所以禁止子组件修改props。可以通过父组件传递一个回调函数实现。Vue通过
defineProperty
定义属性的属性描述符中的get
和set
,当修改属性时判断是否是父组件触发的修改。
组件内部修改 props 的方法
- 拷贝至 data 或 计算属性,结合 emit 实现父组件更新;
sync
修饰符;- 将 props 包装为对象,因为 props 传递是引用;
2. 事件
- 事件驱动 & 数据驱动
- 普通事件 & 修饰符事件
在自定义组件上注册监听原生事件(如click),使用 native 事件修饰符 <my-componenet @click.native="handle"></my-component>
;因为 my-component 不在DOM树上,添加的是组件内模板的代码(组件模板默认会继承非props特性),如果没有 native 修饰,则原生的监听器(click)没有作用;注意自定义事件this.$emit不需要native修饰。
3. 插槽
- 普通插槽
- 作用域插槽
参考链接: 详解vue组件三大核心概念
组件构成
props
props
的 key & value,类型、默认值、验证
props属性:定义组件时,在组件实例的props选项上声明的属性;可以通过
this.$props
获取该属性,直接使用v-bind=$props
在<template>
中的元素上进行绑定。非props属性:使用组件时,定义了未在props选项上声明的属性;
$attrs
可以访问非props
属性。默认情况下,所有的非props属性被组件<template>
的根元素继承;通过inheritAttrs
设置根元素是否继承非props
属性javascript<my-component name="czl" age="22"></my-component> 局部注册 new Vue({ el:"#app", components:{ myComponent:{ inheritAttrs:false, 默认为true template: '<div id="root"><input v-bind=$attrs></div>' } } })
无参bind
v-bind:参数='表达式'
使用无参的bind指令可以一次动态绑定多个值,等价于将属性名作为参数
html<MyCom v-bind="obj"/> obj: { name: 'czl', age: 22 } // 等价于 <MyCom v-bind:name="obj.name" :age="obj.age">
slot
- slot:分发__内容__(元素由开始标签、结束标签和内容构成),当有多个内容需要分发时,通过指定slot.name即具名slot实现。
- 插槽prop: 绑定在
<slot>
上的特性
watch监听引用数据
监听复杂数据(object、array)
jswatch: { obj: { deep: true, handle: function(newValue, oldValue){ ... } } }
keep-alive
将组件
<my-component>
进行缓存,避免每次重新渲染组件(create, mounte等),用于路由及组件切换(动态组件<component v-bind:is="cmp">
)。html<keep-alive> <my-component></my-component> </keep-alive>
native
若没有修饰符native,那下面的@click就是自定义事件click而非原生click
html<mycomponent @click.native='handleClick'></mycomponent>
key
vue和react的虚拟DOM的Diff算法大致相同,其核心是基于两个简单的假设:
- 两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构。
- 同一层级的一组节点,他们可以通过唯一的id进行区分。
基于以上这两点假设,使得虚拟DOM的Diff算法的复杂度从O(n^3)降到了O(n)。
key
的作用是给予一个节点唯一的身份识别,有相同父元素的子元素必须有独特的key
,特别是列表渲染时的节点,因为相同的组件产生类似的DOM结构 。这样在使用Diff算法对新旧虚拟DOM进行比较时,计算出哪些节点是可以就地复用或者调整顺序,可以更高效的重用排序现有的元素;比较时,依次对比两个节点的类型、属性、子节点。- key的作用主要是为了高效的更新虚拟DOM。
组件通信
- props & emit:自定义属性和自定义事件
- $refs、$parent、$children
- provide & inject
- $attrs & $listeners
- Event Bus
- vuex
Virtual DOM
Diff 算法
Diff 算法将时间复杂度从O(n3)减少到O(n);
diff 算法包括几个步骤:
- 用 JS 对象表示 DOM 树的结构,然后用这个树构建一个真正的 DOM 树,插到文档当中;
- 当状态变更的时候,重新构造一棵新的对象树,然后用新的树和旧的树进行比较(同层比较),记录两棵树差异;
- 把所记录的差异应用到所构建的真正的DOM树上,视图就更新了;
参考:
render & JSX & 函数组件
如何描述组件的UI和状态信息?
- 字符串模板
<template>
- render 函数
这三中方法的作用都是一样的(可以相互替换)
1.render
render 函数
- js
new Vue({ render(h){ h('p', 子节点); // h 即 createElemnet } })
createElement 的返回值是什么?
createElement的返回的不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription
,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。
- template 模板的来源? 当 Vue 选项对象中有 render 渲染函数时,Vue 构造函数将直接使用渲染函数渲染 DOM 树,当选项对象中没有 render 渲染函数时,Vue 构造函数首先通过将 template 模板编译生成渲染函数,然后再渲染 DOM 树,而当 Vue 选项对象中既没有 render 渲染函数,也没有 template 模板时,会通过 el 属性获取挂载元素的 outerHTML 来作为模板,并编译生成渲染函数。
2.函数式组件
特点:
- 声明了functional: true;
- render(h, context); // 由第二个参数提供状态数据, h-->createElement
3.JSX
JSX只是JS的语法扩展,在JS文件中使用。
插件:@vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props
https://github.com/vuejs/jsx#installation
Demo:
render(h, ctx)中第一个参数在某些版本下只能用名称h,详细见**Vue-函数式组件**
js<template> <VNode :vnodes="getJSXSpan()" info="info"/><hr> </template> <script> export default { components: { // 函数组件 VNode: { functional: true, // 第一种使用方法 render: (h, ctx) => { return h('div', { style: { color: 'blue' }, attrs: { id: "funCom" } }, [ctx.props.vnodes]) } // 第二种使用方法 render: (h, ctx) => ctx.props.vnodes // 第三种使用方法 render: (h, ctx) => { let {type, info} = ctx.props; // return h(type, ctx.data, ctx.children); return FC(); function FC(){ info = info || '未定义'; return ( <type class="func-comp" {...ctx.data}> <span>{ ctx.children }</span> </type> ) } } } }, methods: { getJSXSpan() { return <span>Message: {this.msg}</span>; }, } </script>
参考
render 函数与模板编译:运行时构建 & 独立构建
当使用 vue-loader
或 vueify
的时候,*.vue
文件内部的模板会在构建时预编译成 JavaScript。你在最终打好的包里实际上是不需要编译器的,所以只用运行时版本即可。
vue实例
当一个 Vue 实例被创建时,它将 data
对象中的所有的属性加入到 Vue 的响应式系统中。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。当这些数据改变时,视图会进行重渲染。值得注意的是只有当实例被创建时就已经存在于 data
中的属性才是响应式的。也就是说如果你添加一个新的属性,
模板
绑定的数据对象不必内联定义在模板里
js<div v-bind:class="classObject"></div>
注意这里的
is="todo-item"
属性。这种做法在使用 DOM 模板时是十分必要的,因为在<ul>
元素内只有<li>
元素会被看作有效内容。这样做实现的效果与<todo-item>
相同,但是可以避开一些潜在的浏览器解析错误。查看 DOM 模板解析说明 来了解更多信息。
需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的:
字符串 (例如:
template: '...'
)<script type="text/x-template">
当直接在 DOM 中使用一个组件 (而不是在字符串模板或单文件组件) 的时候,我们强烈推荐遵循 W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)。这会帮助你避免和当前以及未来的 HTML 元素相冲突。
不要在选项属性或回调上使用箭头函数,比如
created: () => console.log(this.a)
或vm.$watch('a', newValue => this.myMethod())
。因为箭头函数并没有this
,this
会作为变量一直向上级词法作用域查找,直至找到为止;计算属性:基于响应式依赖进行缓存,当依赖的数据没有更新时,不会执行函数; 计算属性默认只有getter
jscomputed: { 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] } } }
vue 响应式原理
创建一个vue 实例时,vue 遍历data 对象的属性,并使用 defineProperty 将属性转换为 getter\setter (vue3.0 使用 Proxy),当属性被访问或修改时触发更新(使用了发布订阅模式)。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。