Vue官方文档笔记(二)

23、$refs是什么东东?

  通过在标签上设置ref属性,然后在Vue实例方法中可以通过$refs拿到这些标签,如:

<input ref="input">
methods: {
  focus: function () {
    //拿到文本框标签,调用其获取焦点方法
    this.$refs.input.focus()
  }
}

 

24、对于多级嵌套组件,后代组件如何拿到父级或祖父级,设置更高级别的组件的数据或方法?

  使用依赖注入

      provide选项允许我们在当前组件指定我们想要提供给后代组件的数据/方法,比如:
provide: function () {
  return {
    getMap: this.getMap
  }
}

  然后在任何后代组件里,我们都可以使用inject 选项来接收指定的我们想要添加在这个实例上的属性:

inject: ['getMap']
  实际上,可以把依赖注入看做是一部分”大范围有效的prop", 但是这个依赖注入祖先组件不需要知道哪些后代组件使用它提供的属性,同时后代组件不需要知道被注入的属性来自哪一级祖先组件。
 
25、对于某些我们创建的对象,如何程序化的清理?
  程序化事件侦听器
  • 通过$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()
  })
}
  对于多个这种输入框,可以让多个输入框使用不同的Pikady, 每个新的实例都程序化地在后期清理它自己
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
}
  或者,在本地注册组件的时候,我们使用webpack的异步import:
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钩子中可用
注意:除了el之外,其他参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的dataset来进行
 使用例子:
<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>
View Code
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
                }
            }
        })
View Code

   注意:组件树中的所有VNode必须是唯一的。这意味着,下面的渲染是不合法的:

render: function (createElement) {
  var myParagraphVNode = createElement('p', 'hi')
  return createElement('div', [
    // 错误 - 重复的 VNode
    myParagraphVNode, myParagraphVNode
  ])
}
View Code

  如果真的需要重复很多次的元素/组件, 可以使用工厂函数来实现。例如,下面这渲染函数用完全合法的方式渲染了20个相同的段落:

render: function (createElement) {
  return createElement('div',
    Array.apply(null, { length: 20 }).map(function () {
      return createElement('p', 'hi')
    })
  )
}
View Code

 

posted @ 2019-10-09 23:01  谈晓鸣  阅读(641)  评论(0编辑  收藏  举报