Vue开发、学习笔记,持续记录

Vue每天学一些,慢慢的也学下来了。记一些笔记

生命周期图:https://nicen.cn/collect/life.png

Vue-Router学习:https://nicen.cn/vue-router.html

开始

文档理解

1. is 命令

用于动态组件且基于 DOM 内模板的限制来工作。就是扩展 html标签的限制,动态指定组件。

2. slot ,插槽

组件内定义了该标签时,调用组件时,组件标签中间的内容将会替换该标签。<AB>我是插入的内容</AB>。插槽的内容是在父级进行渲染的。vm.$scopedSlots,作用类似$ref,用于获取组件接收到的所有插槽对象。

  1. 需要多个插槽时,可以利用 <slot> 元素的一个特殊的特性:name 来定义具名插槽。
  2. 在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:
  3. v-slot (简写#)只能添加在 template上(#slotName),一个不带 name 的 slot出口会带有隐含的名字 “default” 。只存在一种例外情况,当组件只有一个默认插槽时,可以把v-slot直接写在组件上。
    <main>
        <slot>中间可以定义默认内容</slot>
    </main>
    
    /*独占默认插槽*/
    <current-user v-slot:default="slotProps"> 
         {{ slotProps.user.firstName }} 
    </current-user>
  4. <slot>可以在标签之间定义默认内容。当使用组件未添加插槽内容时,该默认内容会显示。
  5. 作用域插槽:在组件内可以给插槽动态绑定一些变量,然后父组件传递插槽内容的时候,插槽内容内可以调用,子组件内插槽绑定的这些变量。
提示
作用域插槽的作用,就是让传递的插槽内容,可以调用子组件的状态

3. 动态指令参数

可以给指令添加动态的参数;如v-bind的绑定的属性名、v-on的事件名、v-slot的插槽名;([任意JS表达式])

v-mydirective:[argument]="value"
<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>
</base-layout>

argument 参数可以根据组件实例数据进行更新!这使得自定义指令可以在应用中被灵活使用。

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'
  }
})

4. scoped和>>>、v-deep、depp()

可以在一个组件中同时使用有 scoped 和非 scoped 样式;如果希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用 >>> 操作符

<style scoped>
     .a >>> .b { /* ... */ }
</style>

5. v-bind.sync

修饰符的详细解释:https://www.jianshu.com/p/6b062af8cf01

  •  .sync 修饰符作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 监听器。
    <comp :foo.sync="bar"></comp>
    /* 拓展为 */
    <comp :foo="bar" @update:foo="val => bar = val"></comp>
    /* 当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件 */
    this.$emit('update:foo', newValue)
    

6. Vue数据响应式

对于data内的数组和对象初始时定义的元素和属性,都支持响应式,但是对于属性或元素的新增(除特定的被重写的数组对象方法之外的修改)需要使用set接口添加响应式。(深度监视)。

对于整个对象或者数组的替换也会保持响应式。

7. Key详解

使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。也可以用于强制替换元素/组件而不是重复使用它。不使用key时,Vue只会就地更新现有的Dom,最大限度的复用已存在的dom。和v-for一起使用时,key需要使用bind绑定,否则key值只是字符串。

8. 虚拟 DOM

Vue 通过建立一个虚拟 DOM 来追踪自己要如何改变真实 DOM。

return createElement('h1', this.blogTitle)

createElement 到底会返回什么呢?其实不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。

创建 VNode 的方法是 createElement,如 createElement(tag,{},[])或者 createElement(tag,{},string),其中 tag 是创建元素的标签名,「{}」是创建元素的属性,「[]」是创建元素的子元素列表,string 是文本。

9. 渲染函数和模板

官方文档:https://cn.vuejs.org/v2/guide/render-function.html

  • Vue 的模板(template)实际上被编译成了渲染函数(render),render和模板不会同时存在。template会解析为render,然后得到Vnode,然后再Update到页面。
  • Vue中的Render函数中有一个参数,这个参数是一个函数通常我们叫做h。其实这个h叫做createElement。Render函数将createElement的返回值放到了HTML中createElement这个函数中有3个参数

    • 第一个参数(必要参数):主要用于提供DOM的html内容,类型可以是字符串、对象或函数
    • 第二个参数(类型是对象,可选):用于设置这个DOM的一些样式、属性、传的组件的参数、绑定事件之类
    • 第三个参数(类型是数组,数组元素类型是VNode,可选):主要是指该结点下还有其他结点,用于设置分发的内容,包括新增的其他组件。注意,组件树中的所有VNode必须是唯一的
    /* @return {VNode} */
    createElement(
     /* {String | Object | Function} */
     /* 一个HTML标签字符串,组件选项对象,或者一个返回值类型为String/Object的函数。该参数是必须的 */
     'div',
    
     /* {Object} */
     /* 一个包含模板相关属性的数据对象,这样我们可以在template中使用这些属性,该参数是可选的。 */
    {
         attrs: {
            name: headingId,
            href: '#'+headingId
        },
         style: {
            color: 'red',
            fontSize: '20px'
        },
         'class': {
            foo: true,
            bar: false
          },
          /* DOM属性 */
          domProps: {
             innerHTML: 'baz'
          },
          /* 组件props */
           props: {
              myProp: 'bar'
          },
           /* 事件监听基于 'on' */
           /* 所以不再支持如 'v-on:keyup.enter' 修饰语 */
           /* 需要手动匹配 KeyCode */
           on: {
               click: function(event) {
                    event.preventDefault();
                    console.log(111);
              }
            }
      }
    
     /* {String | Array} */
     /* 子节点(VNodes)由 createElement() 构建而成。可选参数 */
     /* 或简单的使用字符串来生成的 "文本节点"。 */
    [
       'xxxx',
       createElement('h1', '一则头条'),
       createElement(MyComponent, {
         props: {
           someProp: 'xxx'
        }
      }),
    
      /*获取默认插槽中的内容*/
       this.$slots.default
    ]
    )
    
  • 两者的区别
    • Template适合逻辑简单,render适合复杂逻辑。
    • 使用者template理解起来相对容易,但灵活性不足;自定义render函数灵活性高,但对使用者要求较高。
    • render的性能较高,template性能较低。这一点我们可以看一下,下图中vue组件渲染的流程图可知。
    • 基于上一点,我们通过vue组件渲染流程图知道,使用render函数渲染没有编译过程,相当于使用者直接将代码给程序。所以,使用它对使用者要求高,且易出现错误
    • Render 函数的优先级要比template的级别要高,但是要注意的是Mustache(双花括号)语法就不能再次使用

实际应用:

render: function (createElement) {
  // `<div><slot></slot></div>`
  return createElement('div', this.$slots.default)
}

10. directives

用于注册一个自定义指令。

11.vue mixins 合并策略 

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

混入策略详解:https://cn.vuejs.org/v2/guide/mixins.html 

组件的深入理解

  • 分类:非单文件组件(一般直接在html中定义)、单文件组件(CLI下的.Vue),组件:创建、注册、使用。
  • Vue.extend({}),用于创建一个组件(每次调用都会生成并返回一个单独的VueComponent类)。data配置项只能是函数式,使用对象形式在组件复用时会导致引用重复的对象。
  • Vue.component(),用于注册一个全局组件。可直接传入一个配置项对象,该语句被调用时,Vue将自己调用extend函数。
  • Vue实例对象是Vue类的对象(配置项对象中的this是Vue),组件全部是VueComponent类的对象(配置项对象中的this为VueComponent)。
  • 在Vue2.x中程序结构为:Vm对象->Vc对象->单个或多个Vc对象->单个或多个Vc对象;
  • 组件内的使用的组件对象都可以在组件对象的children属性中找到。
  • 父组件给子组件传递值使用props,子组件给父组件传递数据使用自定义事件绑定父组件的对象方法进行事件处理。
  • v-if和v-show的区别:如果使用v-show,切换组件,只不过是相应组件的显示和隐藏;而v-if则会销毁之前的组件并渲染新组建。如果我们在v-if切换的组件之外,套上标签,那么本该销毁的组件则会被缓存起来。当我们重新切换回来时依旧会重新渲染(确实找不到对应的dom元素),但是之前的vue实例没有被销毁,vue实例中的属性和变量都还在,这个标签非常适合做单页面应用。
  • 所有组件都有完整的生命周期。
  • Vue.extends(),用于继承一个组件的配置。

1.动态组件

  1. keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 transition 相似, 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。可通过props设置匹配的组件。
  2. 当组件在 <keep-alive> 内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行。
  3. <!-- 动态组件由 vm 实例的 `componentId` property 控制 -->
    <component :is="componentId"></component>
    
    <!-- 也能够渲染注册过的组件或 prop 传入的组件 -->
    <component :is="$options.components.child"></component>
    component,渲染一个“元组件”为动态组件。依 is 的值,来决定哪个组件被渲染。

2.异步组件

Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。(不要要渲染时,相当于无视,普通组件则是直接解析)。

把 webpack 2 和 ES2015 语法加在一起,我们可以这样使用动态导入:

Vue.component(
  'async-webpack-example',
  // 这个动态导入会返回一个 `Promise` 对象。
  () => import('./my-async-component')
)

详细说明:https://blog.csdn.net/Liu_Jun_Tao/article/details/81913946

3.拓展

凡是函数都有一个prototype(显式原型对象),函数本身也是一个对象,有__proto__属性。

凡是对象都会有一个属性那就是__proto__(指向构造函数的prototype,隐式原型对象)

原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。

Props属性

1.完整的prop声明

app.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() {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator(value) {
        // 这个值必须与下列字符串中的其中一个相匹配
        return ['success', 'warning', 'danger'].includes(value)
      }
    },
    // 具有默认值的函数
    propG: {
      type: Function,
      // 与对象或数组的默认值不同,这不是一个工厂函数——这是一个用作默认值的函数
      default() {
        return 'Default function'
      }
    }
  }
})

2.传递对象

如果想要将一个对象的所有 property 都作为 prop 传入,可以使用不带参数的 v-bind (用 v-bind 代替 :prop-name)。

<blog-post v-bind="post"></blog-post>
//等价于
<blog-post v-bind:id="post.id" v-bind:title="post.title"></blog-post>

问题总结

1. 计算属性和自定义方法的区别

  • methods方法和computed计算属性,两种方式的最终结果确实是完全相同;
  • 不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值,多次访问计算属性会立即返回之前的计算结果,而不必再次执行函数。
  • methods方法,每当触发重新渲染时,调用方法将总会再次执行函数。
  • 所以,官网的一句话,对于任何复杂逻辑,你都应当使用计算属性。
  • 虽然计算属性可以通过闭包进行传参,但它已经违背了计算属性,所以使用计算属性传参不如使用methods

2. 实战笔记

/* 开始模板渲染 */
this.$nextTick(function() {
     /* 使添加的input自动获取焦点 */
     this.$refs.addInput.focus();
 })				

3. 动态设置元素的ref并获取元素对象

  1. 第一,获取ref一定要注意是在dom元素生成之后,否则获取到的是undefined,或者报没有“getAtrribute”方法的错误,解决办法是使用$nextTick
  2. 没必要给循环列表的每一个元素加上不一样的ref,而只用给他们都加上一样的ref,根据此ref获取到的是一个数组列表,然后根据index即可定位该元素
  3. 微信小程序无法操作Dom,所以$refs无法获取内置组件的节点。
  4. 当 ref 和 v-for 一起使用的时候,你得到的 ref 将会是一个包含了对应数据源的这些子组件的数组。$refs 只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问 $refs

补充知识

1.全局事件总线

总线:组件绑定事件,另一个组件触发事件,通过事件传递数据。

Vue2.x 可以使用inject、provide 接口替代全局事件总线。

Vue 2 当中事件总线是通过在现有的 Vue 应用实例中新建一个新的 Vue 实例,通过这个实例来传递事件触发行为。 可更宽范围地跨组件状态通信
但显然从使用上就可以看出这个方案是相对比较笨重的,在 Vue 3 版本中,子孙组件之间的状态通信有了一种新的方案:Provide / Inject

前提:

  1. 必须拥有$on、$off、$emit组件事件方法。
  2. 必须让所有组件都可以访问到。
  3. 最简洁的实现方法。
/*创建vm*/
new Vue({
	el:'#app',
	render: h => h(App),
	beforeCreate() {
		Vue.prototype.$bus = this /*安装全局事件总线*/
	},
})

全局事件总线适用于 父子、子父以外的数据传输情况。可以在Vue的beforeCreate事件里,将Vue的实例作为Vue的prototype对象的一个属性,即可满足上方所有条件。所有组件都可以使用this.$bus访问到作为总线的对象。

使用时应注意避免事件名冲突。组件销毁之前,应解绑在总线上绑定过的事件。

2.监视属性

  1. watch API 的flush选项可以更好地控制回调的时间。它可以设置为 'pre''post' 或 'sync'
  2. 官方文档:https://v3.cn.vuejs.org/api/instance-methods.html#watch
  3. 在变更 (不是替换) 对象或数组时,旧值将与新值相同,因为它们的引用指向同一个对象/数组。Vue 不会保留变更之前值的副本。对于基本类型以外的值,赋值都是引用,所以会一样
  4. user.user = res.data.data;
      /* res.data.data = news = olds  = user.user */
      /* 他们引用的都是一个对象 */
     watch(user.user,function (news,olds) {
    }
    

3.provide、inject

provide() {
    return {
      foo: this.foo,
      bar: this.bar
   }
}