Vue官方文档笔记(二)
23、$refs是什么东东?
通过在标签上设置ref属性,然后在Vue实例方法中可以通过$refs拿到这些标签,如:
<input ref="input">
methods: { focus: function () { //拿到文本框标签,调用其获取焦点方法 this.$refs.input.focus() } }
24、对于多级嵌套组件,后代组件如何拿到父级或祖父级,设置更高级别的组件的数据或方法?
使用依赖注入。
provide: function () { return { getMap: this.getMap } }
然后在任何后代组件里,我们都可以使用inject 选项来接收指定的我们想要添加在这个实例上的属性:
inject: ['getMap']
- 通过$on(eventName, eventHandler) 侦听一个事件
- 通过$once(eventName, eventHandler) 一次性侦听一个事件
- 通过$off(eventName, eventHandler) 停止侦听一个事件
先看一个示例:
mounted: function () { //Pikaday是一个第三方日期选择器的库。这里是将这个日期选择器附加到一个输入框上,最后挂载到DOM上 var picker = new Pikaday({ field: this.$refs.input, format: 'YYYY-MM-DD' }) //设置一次性的侦听事件,在组件销毁之前,销毁这个日期选择器 this.$once('hook:beforeDestroy', function () { picker.destroy() }) }
mounted: function () { this.attachDatepicker('startDateInput') this.attachDatepicker('endDateInput') }, methods: { attachDatepicker: function (refName) { var picker = new Pikaday({ field: this.$refs[refName], format: 'YYYY-MM-DD' }) this.$once('hook:beforeDestroy', function () { picker.destroy() }) } }
26、如果组件之间出现了互相引用,如何处理?
先看一个示例:
组件<tree-foler>的模板是这样的:
<p> <span>{{ folder.name }}</span> <tree-folder-contents :children="folder.children"/> </p>
另外一个组件<tree-folder-contents>的模板是这样的:
<ul> <li v-for="child in children"> <tree-folder v-if="child.children" :folder="child"/> <span v-else>{{ child.name }}</span> </li> </ul>
这两个组件互相引用,导致出现了循环引用。这种情况如何去解决呢?
假设这两个组件<tree-folder>是父组件,<tree-folder-contents>是子组件,则产生问题的是子组件<tree-folder-contents>, 因此我们在生命周期钩子beforeCreate里面去注册子组件
beforeCreate: function () { this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default }
components: { TreeFolderContents: () => import('./tree-folder-contents.vue') }
这样问题就解决了。
27、Vue项目编译关于内存溢出的处理
随着项目中组件的增加,终于到了一个临界点,每次修改页面保存自动编译时,都会报内存溢出导致编译中断:
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
解决办法,增加increase-memory-limit插件,增加node服务器内存限制,步骤:
1、在 package.json文件的scripts中增加一行配置:"fix-memory-limit": "cross-env LIMIT=8096 increase-memory-limit"
"scripts": { "dev": "cross-env POST_API=dev vue-cli-service serve", "test": "cross-env POST_API=test vue-cli-service build --mode test", "pre": "cross-env POST_API=pre vue-cli-service build --mode pre", "hd": "cross-env POST_API=hd vue-cli-service build --mode hd", "build": "cross-env POST_API=pro vue-cli-service build --mode pro", "fix-memory-limit": "cross-env LIMIT=8096 increase-memory-limit" },
2、在项目终端输入新增依赖包命令:npm install --save-dev increase-memory-limit
3、接着执行命令:npm run fix-memory-limit
4、重新运行项目就ok了
28、为什么要自定义指令?如何自定义指令?
虽然在Vue2.0中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通DOM元素进行底层操作,这时候就会用到自定义指令。
- bind : 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
- inserted : 被绑定元素插入父节点时调用(仅保证父节点存在,但不一定被插入文档中)。
- 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编译生成的虚拟机节点。
- oldVnode :上一个虚拟节点,仅在update和componentUpdated钩子中可用
<div v-my-custom-directive:foo.a.b="message"></div>
//自定义指令 , 写在new Vue前面 Vue.directive('my-custom-directive', { bind: function (el, binding, vnode) { console.log(el); console.log(binding); console.log(vnode); console.log(".......end.....") var s = JSON.stringify el.innerHTML = 'name: ' + s(binding.name) + '<br>' + 'value: ' + s(binding.value) + '<br>' + 'expression: ' + s(binding.expression) + '<br>' + 'arg: ' + s(binding.arg) + '<br>' + 'modifiers: ' + s(binding.modifiers) + '<br>' + 'vnode keys: ' + Object.keys(vnode).join(', ') } }) var vm = new Vue({ el: "#app", data:{ message: "hello", } });
动态指令:指令的参数可以是动态的。例如,在v-mydirective:[argument]="value"中,argument参数可以根据组件实例数据进行更新,举例:
<div id="dynamicexample">
<h3>Scroll down inside this section ↓</h3>
<p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
</div>
Vue.directive('pin', { bind: function (el, binding, vnode) { el.style.position = 'fixed' var s = (binding.arg == 'left' ? 'left' : 'top') el.style[s] = binding.value + 'px' } }) new Vue({ el: '#dynamicexample', data: function () { return { direction: 'left' } } })
29、什么叫渲染函数?
Vue推荐在绝大多数情况下使用模板来创建HTML, 然而在一些场景中,我们也需要JavaScript的完全编程的能力。这时需要我们使用渲染函数,这比模板更接近编译器。
举个例子来对比某种场景下使用模板和渲染函数的区别,先用模板来创建一个带锚点的标题组件,组件编译后的HTML标签大概是这样:
<h1>
<a name="hello-world" href="#hello-world">
Hello world!
</a>
</h1>
对于上面编译后的HTML, 我们决定这样定义组件接口:
<anchored-heading :level="1">Hello world!</anchored-heading>
<script type="text/x-template" id="anchored-heading-template"> <h1 v-if="level === 1"> <slot></slot> </h1> <h2 v-else-if="level === 2"> <slot></slot> </h2> <h3 v-else-if="level === 3"> <slot></slot> </h3> <h4 v-else-if="level === 4"> <slot></slot> </h4> <h5 v-else-if="level === 5"> <slot></slot> </h5> <h6 v-else-if="level === 6"> <slot></slot> </h6> </script>
Vue.component('anchored-heading', { template: '#anchored-heading-template', props: { level: { type: Number, required: true } } })
我们 看到,这里使用模板并不是最好的选择,不但代码冗长,而且在每一级别的标题中书写<slot></slot>, 在要插入的锚点元素时还要再次重复。
接着我们来尝试使用渲染(render)函数重写上面的例子:
Vue.component('anchored-heading', { render: function (createElement) { return createElement( 'h' + this.level, // 标签名称 this.$slots.default // 子节点数组 ) }, props: { level: { type: Number, required: true } } })
这个就是渲染函数,看起来简单多了。
30、啥叫虚拟DOM ?
看看下面这段HTML,说说浏览器是怎么操作它的
<div>
<h1>My title</h1>
Some text content
<!-- TODO: Add tagline -->
</div>
当浏览器读到这些代码时,它会建立一个”DOM节点“树来保持追踪所有内容,如同你会画一张家谱树来追踪家庭成员的发展一样。
每个元素都是一个节点,每段文字也是一个节点。甚至注释也都是节点。一个节点就是页面的一个部分。就是家谱树一样,每个节点都可以由孩子节点(也就是说每个部分可以包含其他的一些部分)。
高效的更新所有节点会是比较困难的,不过所幸你不必手动完成这个工作,你只需要告诉Vue你希望页面上的HTML是什么,这可以是在一个模板里:
<h1>{{ blogTitle }}</h1>
或者一个渲染函数里:
render: function (createElement) { return createElement('h1', this.blogTitle) }
在这两种情况下,Vue都会自动保持页面的更新,即便blogTitle发生了改变。
Vue怎么做到这些的呢?这就出现”虚拟DOM“这个概念了!Vue通过建立一个虚拟DOM来追踪自己要如何改变真实DOM, 请仔细看这行代码:
return createElement('h1', this.blogTitle)
createElement到底返回什么呢?其实不是一个实际的DOM元素,它更准确的名字可能是createNodeDescription, 因为它所包含的信息会告诉Vue页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为”虚拟节点(Virtual node)", 也常简写它为“VNode" 。”虚拟DOM" 是我们对Vue组件树建立起来的这个VNode树的称呼。
接下来我们对createElement函数进行研究一下,它需要传递哪些参数?如何使用?先看看它的参数定义:
// @returns {VNode} createElement返回一个虚拟DOM createElement( 'div', //第一个参数(必填),可以是HTML标签名(String)、组件选项对象(Object)、async函数(Function) { /* ... */ }, //第二个参数(可选),一个与模板中属性对应的数据对象(Object) //第三个参数(可选),子级虚拟节点(VNodes), 由‘createElement()’构建而成,也可以使用字符串来生成”文本虚拟节点“ [ '先写一些文字', createElement('h1', '一则头条'), createElement(MyComponent, { props: { someProp: 'foobar' } }) ] )
对createElement函数的参数了解了后,我们来看一个完整的示例:
Vue.component('anchored-heading', { render: function (createElement) { console.log("this.$slots.default: ", this.$slots.default); /* this.$slots.default, 插槽里每加一个标签,this.$slots.default里面就多一个undefined的VNode, 比如上面anchored-heading的slot里面直接子节点3个(div、span、h3), 则this.$slots.default数组里面的VNode为5个(3个+2个undefined VNode) [ { tag: 'div', children: Array(5) }, { tag: undefined, children: undefined }, { tag: 'span', children: Array(1) }, { tag: undefined, children: undefined }, { tag: 'h3', children: Array(1)} ] */ // 创建 kebab-case 风格的 ID var childrenStr = getChildrenTextContent(this.$slots.default); var headingId = childrenStr .toLowerCase() .replace(/\W+/g, '-') //非字母、数字、下划线字符,全部替换成“-”,比如中文/标点符号全部换成“-” .replace(/(^-|-$)/g, '') //以“-”开头或结尾,替换为空 console.log("orialStr: ", childrenStr); console.log("headingId: ", headingId); var myVnode = createElement( 'h' + this.level, [ //这里有对三个参数使用的例子 createElement('a', { attrs: { name: headingId, href: '#' + headingId, id: 'tantan' }, // style: { //与`v-bind:style`的API相同,接受一个字符串、对象,或对象组成的数组 // color: 'red', fontSize: '16px', borderTop: '1px dashed black', textDecoration: 'underline' // }, // domProps: { //设置DOM属性 // innerHTML: '<div class="sub-div">nihao</div>', // // innerText: '王大锤', // }, // on: { //渲染函数事件 // click: function(){ // console.log("点我啦了。, 。。 ") // alert("ni8你好") // }, // focus: function(){ // console.log("....获取焦点。。。") // }, // blur: function(){ // console.log(".....失去焦点。。。") // }, // } }, this.$slots.default) ] ) console.log(".............最后createElement返回的数据是......") console.log(myVnode) console.log("............ok end.................") return myVnode; }, props: { level: { type: Number, required: true } } })
注意:组件树中的所有VNode必须是唯一的。这意味着,下面的渲染是不合法的:
render: function (createElement) { var myParagraphVNode = createElement('p', 'hi') return createElement('div', [ // 错误 - 重复的 VNode myParagraphVNode, myParagraphVNode ]) }
如果真的需要重复很多次的元素/组件, 可以使用工厂函数来实现。例如,下面这渲染函数用完全合法的方式渲染了20个相同的段落:
render: function (createElement) { return createElement('div', Array.apply(null, { length: 20 }).map(function () { return createElement('p', 'hi') }) ) }