Vue2笔记

指令

v-bind (绑定特性)

简介

动态地绑定一个或多个特性,: 后的为传递的参数,简写 为( : )

绑定一个属性
<img :src="imageSrc">

<img v-bind:src="imageSrc">

<script>
export default {
  data(){
    return{
      imageSrc:'值',
    }
  }
}
</script> 

对象/数组 语法

class

<!--对象语法-->
	如果isRed为真,那么class就会被设置为active
  <div id="app" :class="{'active':isRed}"></div>

<!--数组语法-->
	可以把一个数组传给 v-bind:class,应用一个 class 列表
	<div id="app" :class="[classList]"></div>
	也可以使用三目运算符来进行切换,v-bind可以和普通class共存
	<div id="app" class="index" :class="[isActive?'ok':'no']"></div>
	在数组语法中也可以使用对象语法
	<div id="app" :class="[classList,{'active':isActive,'red':isRed}]"></div>

<script>
export default {
  data(){
    return{
      isRed:false,
      isActive:true,
      classList:'arr1 arr2 arr3'
    }
  }
}
</script> 

style

看着比较像CSS,但其实是一个JavaScript对象
CSS属性名可以用驼峰式或者短横线分隔来命名

绑定style时,有的css属性需要添加前缀的,Vue会自动添加

  <div id="app" :style="{
    fontSize: '20px',
    color:'red'
  }">v-bind</div>

	也可以直接绑定一个样式对象
	<div id="app" :style={styleObj1}>v-bind</div>
	数组语法可以将多个样式对象应用到同一个元素
	<div id="app" :style=[styleObj1,styleObj2]>v-bind</div>

<script>
export default {
  data(){
    return{
      styleObj1:{
        fontSize:'20px'
      },
      styleObj2:{
        color:'red'
      }
    }
  }
}
</script>

v-for (循环渲染)

  1. 注意key 唯一且独一无二,重复会导致错误
  2. 不要用数据索引作为key,不然会引发很多奇葩的BUG
  3. 永远不要把 v-if 和 v-for 同时用在同一个元素上。v-for比v-if优先级高,所以使用的话,每次v-for都会执行v-if,造成不必要的计算,影响性能但是这情况可以使用计算属性computed**

v-on (事件)

  1. 监听 DOM 事件,事件类型由参数指定,简写@后面跟事件名

  2. $event: 为事件对象

  3. 可以动态绑定事件,但是必须使用全称 v-on:[‘事件类型’]=‘事件参数’

    <div v-on:[event]="btnChange($event)">点击</div>  
    <script>
    export default {
      data(){
        return{
          event:'click'
        }
      },
      methods:{
        btnChange(e){
          console.log('事件对象:',e);
        }
      }
    }
    </script>
    
    
  4. 可以绑定一个对象,但是不能传参,不能使用事件修饰符

    <div v-on="{ mousedown: doThis, mouseup: doThat }"></div>
    <script>
    export default {
      methods:{
        doThis(){
          console.log('不能传参,不能使用事件修饰符');
        },
        doThat(){
          console.log('不能传参,不能使用事件修饰符');
        }
      }
    }
    </script>
    
    

事件修饰符

  1. [ .stop ],调用 event.stop,阻止事件冒泡
  2. [ .prevent ],调用 event.preventDefault(),阻止默认事件
  3. [ .capture ],事件捕获模式
  4. [ .self ],.self事件是从侦听器绑定的元素本身触发时才触发回调,也就是你绑定那个,你点那个那个才生效
  5. [ .once ],事件只触发一次,再次触发无效
  6. [ .passive ],设置 addEventListener 中的 passive 选项,能够提升移动端的性能,加了就是你滑动,网页也就跟着滑动,不加,等你滑动完了网页在滑动。.passive 会告诉浏览器你不想阻止事件的默认行为

按键修饰符

​ 这玩意用到查

<!-- 只有在 `key` 是 `Enter` 时调用 -->
<!-- 这玩意的修饰符就按键的key-->
<!-- keyCode 也可以,很简单-->
<input v-on:keyup.enter="submit">

按键码key参考地址


v-if

没什么好解释的,就是不能和v-for一起使


v-show

根据表达式之真假值,切换元素的 display CSS 属性。

v-if 对比 v-show

  1. v-if 如果在初始渲染时条件为假,则什么也不做,直到条件第一次变为真时,才会开始渲染条件块。v-show则不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
  2. v-if 有更高的切换开销,v-show 有更高的初始渲染开销,如果需要非常频繁地切换,则使用 v-show 较好,如果在运行时条件很少改变,则使用 v-if 较好
  3. v-show不支持<template>元素
  4. v-show不支持v-else/v-else-if
v-if能够控制是否生成vnode,也就间接控制了是否生成对应的dom。当v-if为true时,会生成对应的vnode,并生成对应的dom元素;当其为false时,不会生成对应的vnode,自然不会生成任何的dom元素。
v-show始终会生成vnode,也就间接导致了始终生成dom。它只是控制dom的display属性,当v-show为true时,不做任何处理;当其为false时,生成的dom的display属性为none。
使用v-if可以有效的减少树的节点和渲染量,但也会导致树的不稳定;而使用v-show可以保持树的稳定,但不能减少树的节点和渲染量。
因此,在实际开发中,显示状态变化频繁的情况下应该使用v-show,以保持树的稳定;显示状态变化较少时应该使用v-if,以减少树的节点和渲染量。

v-slot(插槽)

东西不多,文字不太好表达,缩写为#

基础用法

<template>
  <div id="app">
    <slot> 	<!-- 插槽 -->
      <demo/>
    </slot>
  </div>
</template>
<script>
import demo from "./components/demo";
export default {
  components: {
    demo
  },
</script>
<!-- demo内容 -->
<template>
  <p>插入到指定容器</p>
</template>

<!-- 解析结果-->

<div  id="app">
    <p>插入到指定容器</p>
</div>

具名插槽

  1. 如果某个组件中需要父元素传递多个区域的内容,也就意味着需要提供多个插槽
  2. 为了避免冲突,就需要给不同的插槽赋予不同的名字
  3. 一个不带 name 的 <slot> 出口会带有隐含的名字“default”。 在使用插槽的时候不提供名称默认渲染
  4. 注意v-slot (#)只能添加在<template>
<template>
  <div class="layout-container" >
      <slot name="left"/>
      <slot name="main"/>
      <slot name="right"/>
  </div>
</template>
<!-- 这个组件导出名称为Layout -->
<template>
    <Layout>
      <template #left>
		<div>我是左边的内容</div>
      </template>
       <template #right>
          <div>我是右边的内容</div>
      </template>
      <template #main>
		 <div>我是中间内容</div>
      </template>
    </Layout>
</template>

<!-- 解析结果-->
<template>
  <div class="layout-container" >
      <div>我是左边的内容</div>
      <div>我是中间内容</div>
      <div>我是右边的内容</div>
  </div>
</template>

v-model (双向数据绑定)

语法什么的没啥好说的,几乎都用在表单上面,但是面试十成一百会问原理(原理我也不太懂)

实现原理

简单阐述v-model原理

  1. v-model 就是一个语法糖 !!!!!!!!!!

用于表单元素时,vue会根据表单元素类型生成合适的属性和事件,用于文本输入框的情况下,vue会生成一个value属性,一个input事件,如果用于单选框或者多选框 ,vue会生成checked属性 和 change事件

v-model 也可作用于自定义组件,当其作用于自定义组件时,默认情况下,它会生成一个value属性和input事件。

<!--简易实现v-model,虚拟DOM里面就这么写的-->
<template>
<div id="app">
 <input type="text" :value="text" @input="myModel($event)">
 <p>{{text}}</p>
</div>
</template>
<script>
export default {
data(){
 return{
   text:''
 }
},
methods:{
 myModel(e){
   console.log(e);
   this.text = e.target.value
 }
},
}
</script>

在自定义组件使用v-model

v-model 也可作用于自定义组件,当其作用于自定义组件时,默认情况下,它会生成一个value属性和input事件。

在自定义组件内可以通过 model 的配置来改变生成的属性和事件

<!--这一段代码是模拟默认情况-->
<!--父组件-->
<template>
  <div id="app">
    <HelloWorld :number="number" @input="numberClickNumber($event)"/>
    <!--上面这句话就等效于下面这句话-->  
    <HelloWorld :number="number" v-model="number"/>
  </div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";

export default {
  components: {
    HelloWorld
  },
  data(){
    return{
      number:0
    }
  },
  methods:{
    numberClickNumber(data){
      this.number = data
    }
  },
  mounted() {
    console.log(this._vnode);
  },
}
</script>
<!--HelloWorld 子组件-->
<template>
  <div>
    <button type="button" @click="numberChange(number -1)">-</button>
    <span>{{ number }}</span>
    <button type="button" @click="numberChange(number +1)">+</button>
  </div>
</template>
<script>
export default {
  name: 'HelloWorld',
  props: {
    number: {
      type: Number,
      default: 0
    }
  },
  methods: {
    numberChange(data) {
      //这里抛出的事件也是一个input事件
      return this.$emit('input', data)
    }
  }
}
</script>
model配置

在自定义组件内可以通过 model 的配置来改变生成的属性和事件

<script>
export default {
  name: 'HelloWorld',
  model:{
    prop:'number',//修改生成属性的名字,默认为value 在vNode -> componentOptions里面可以看到
    event:'click'//修改事件类型,默认是input
  }
}
</script>

v-model修饰符

  1. [ lazy ],在默认情况下,v-model在每次input事件触发后将输入框的值与数据进行同步。如果要变为使用change事件同步可以添加lazy修饰符: (人话就是加了lazy 输入框的内容不会实时变化,等输入框失去焦点才会触发变化)
  2. [ number ],自动将用户的输入值转为数值类型
  3. [ trim ],自动过滤用户输入的**首尾**空白字符,失去焦点就触发

自定义指令

其他指令


v-html

  1. 更新元素的innerHTML
  2. 内容按普通 HTML 插入,不会作为 Vue 模板进行编译
  3. 容易被XSS攻击,不能用在用用户提交的内容上
  4. 可以用于渲染富文本数据

v-pre

  1. 跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache(大胡子) 标签。跳过大量没有指令的节点或者静态资源会加快编译。

v-cloak

  1. 可以解决闪烁的问题,在网速不好的情况下会直接显示插值表达式
  2. 和 CSS 规则如 [v-cloak] { display: none } 一起用时,在网速不好的情况下,不会显示插值表达式,也就是页面不显示内容,等待编译结束,才会显示内容

v-once

  1. 只渲染元素一次。随后的重新渲染,元素及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能

v-text

  1. 更新元素的 textContent
  2. v-text替换元素中所有的文本,Mustache(大胡子)只替换自己,不清空元素内容
  3. v-text 优先级高于 Mustache
  4. 渲染字符串有性能优势 ???鸡肋?

配置

data

和界面相关的数据

components

局部注册组件

methods

任何可能需要调用的方法(事件)


computed

计算属性

  1. 计算属性是基于响应式依赖进行缓存的

  2. 计算属性的值一直存于缓存中,只要它依赖的data数据不改变,每次访问计算属性,都会立刻返回缓存的结果,而不是再次执行函数。(比如某个要渲染的值经常变,我就用计算属性来搞,不然每次都得重新渲染

  3. 计算属性除了写成一个函数之外,还可以写成一个对象,对象内有两个属性,getter 和 setter,这两个属性皆为函数,他们的this都指向vue实例,写法如下:

      computed: {
        fullName: {
          getter () {
            // getter 读取
            //在获取某个属性值的时候执行
          },
          setter () {
            //  setter 设置
            //set函数在给计算属性重新赋值时会执行。参数,为被重新设置的值。
          }
        }
      }
    
  4. 给计算属性赋了值,计算属性也不会改变,只有当计算的属性变化了,计算属性才会重新计算。


watch

  1. 侦听的属性数据变化时,会立刻执行对应函数,
  data: {
    msg: 'hello,你好呀',
  },
  watch: {
  //侦听器函数,会接收两个参数,第一个参数为newVal(被改变的数据),第二个参数为oldVal(赋值新值之前的值)。
  msg (newVal,oldVal) {
    conosle.log('被改变的数据:',newVal, '赋值新值之前的值:',oldVal);
  }
  deep: true   // 开启深度侦听
}
//更改msg的值就会触发 

this.$watch()

  1. 用法与watch选项部分一致,略有不同。以下为使用方法。
// 1. 三个参数,一参为被侦听的数据;二参为数据改变时执行的回调函数;三参可选,为设置的选项对象
vm.$watch(
  'msg', 
  function () {
    
  }, 
  {
    deep: Boolean, 
    immediate: Boolean
  }
)

// 2. 二个参数,一参为被侦听的数据;二参为选项对象,其中handler属性为必需,是数据改变时执行的回调函数,其他属性可选。
vm.$watch(
  'msg', 
  {
    handler () {
      
    },
    deep: Boolean, 
    immediate: Boolean
  }
)

计算属性computed vs 侦听器watch

  1. 两者都可以观察和响应Vue实例上的数据的变动。

  2. watch擅长处理的场景是:一个数据影响多个数据。计算属性擅长处理的场景是:多个数据影响一个数据。

  3. 在侦听器中可以执行异步,但是在计算属性中不可以

computed 和 methods 区别

简单的标准答案

  1. 在使用时,computed当做属性使用,而methods则当做方法调用
  2. computed具有getter和setter,因此可以赋值,而methods不行
  3. computed无法接收多个参数,而methods可以
  4. computed具有缓存,而methods没有,(比如computed定义一个a,这个a在组件内多个地方使用,但是a只会运行一次)

重点来了,把面试官整懵逼,直呼大佬的回答

vue对methods的处理比较简单,只需要遍历methods配置中的每个属性,将其对应的函数使用bind绑定当前组件实例后复制其引用到组件实例中即可

而vue对computed的处理会稍微复杂一些。

当组件实例触发生命周期函数beforeCreate后,它会做一系列事情,其中就包括对computed的处理

它会遍历computed配置中的所有属性,为每一个属性创建一个Watcher对象,并传入一个函数,该函数的本质其实就是computed配置中的getter,这样一来,getter运行过程中就会收集依赖

但是和渲染函数不同,为计算属性创建的Watcher不会立即执行,因为要考虑到该计算属性是否会被渲染函数使用,如果没有使用,就不会得到执行。因此,在创建Watcher的时候,它使用了lazy配置,lazy配置可以让Watcher不会立即执行。

收到lazy的影响,Watcher内部会保存两个关键属性来实现缓存,一个是value,一个是dirty

value`属性用于保存Watcher运行的结果,受`lazy`的影响,该值在最开始是`undefined
dirty`属性用于指示当前的`value`是否已经过时了,即是否为脏值,受`lazy`的影响,该值在最开始是`true

Watcher创建好后,vue会使用代理模式,将计算属性挂载到组件实例中

当读取计算属性时,vue检查其对应的Watcher是否是脏值,如果是,则运行函数,计算依赖,并得到对应的值,保存在Watcher的value中,然后设置dirty为false,然后返回。

如果dirty为false,则直接返回watcher的value

巧妙的是,在依赖收集时,被依赖的数据不仅会收集到计算属性的Watcher,还会收集到组件的Watcher

当计算属性的依赖变化时,会先触发计算属性的Watcher执行,此时,它只需设置dirty为true即可,不做任何处理。

由于依赖同时会收集到组件的Watcher,因此组件会重新渲染,而重新渲染时又读取到了计算属性,由于计算属性目前已为dirty,因此会重新运行getter进行运算

而对于计算属性的setter,则极其简单,当设置计算属性时,直接运行setter即可


Axios

Axios是一个基于promise的HTTP库

config 配置对象

axios({
  method: 'get', // post、get、put....
  baseURL: '', // 请求的域名,基本地址
  url: '', // 请求的路径
  params: {}, // 会将请求参数拼接在url上
  data: {}, // 会将请求参数放在请求体中
  headers: {}, // 设置请求头,例如设置token等
  timeout: 1000, // 设置请求超时时长,单位:ms
})

拦截器

interceptors,在发起请求之前做一些处理,或者在响应回来之后做一些处理。

请求拦截器

axios.interceptors.request.use(config => {
  // 在发送请求之前做些什么
  return config;
})

响应拦截器

axios.interceptors.response.use(response => {
  // 对响应数据做点什么
  return response;
})

移除拦截器

const myInterceptor = axios.interceptors.request.use(config => {});
axios.interceptors.request.eject(myInterceptor);

为axios实例添加拦截器

const instance = axios.create();
instance.interceptors.request.use(config => {});

取消请求

用于取消正在进行的http请求

const source = axios.CancelToken;
const source = CancelToken.source();

axios.get('/getUserInfo', {
  cancelToken: source.token
}).then(res => {
  console.log(res);
}).catch(error => {
  if(axios.isCancel(error)) {
    // 取消请求
    console.log(error.message);
  } else {
    // 处理错误
  }
})

// 取消请求 参数 可选
source.cancel('取消请求');

错误处理

在请求错误时进行的处理
request / response 是error的上下文,标志着请求发送 / 得到响应
在错误中,如果响应有值,则说明是响应时出现了错误。
如果响应没值,则说明是请求时出现了错误。
在错误中,如果请求无值,则说明是请求未发送出去,如取消请求。

axios.get('/user/12345')
  .catch(function (error) {
    // 错误可能是请求错误,也可能是响应错误
    if (error.response) {
      // 响应错误
    } else if (error.request) {
      // 请求错误
    } else {
      console.log('Error', error.message);
    }
    console.log(error.config);
  });

在实际开发过程中,一般在拦截器中统一添加错误处理
请求拦截器中的错误,会当请求未成功发出时执行,但是要注意的是:取消请求后,请求拦截器的错误函数也不会执行,因为取消请求不会抛出异常,axios对其进行了单独的处理。
在更多的情况下,我们会在响应拦截器中处理错误。

const instance = axios.create({});
instance.interceptors.request(config => {

}, error => {
  return Promise.reject(error);
})

instance.interceptors.response(response => {

}, error => {
  return Promise.reject(error);
})

axios 预检

当axios的请求为非简单请求时,浏览器会进行预检,及发送OPTIONS请求。请求到服务器,询问是否允许跨域。如果响应中允许预检中请求的跨域行为,则浏览器会进行真正的请求。否则会报405错误。

Vue 生命周期

在这里插入图片描述

创建前后

beforeCreate

实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性

create

  1. 实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板
  2. 在实例创建完成后被立即调用。
  3. 如果要在第一时间调用methods中的方法,或者操作data中的数据,可在此钩子中进行操作。
    需要注意的是,执行此钩子时,挂载阶段还未开始,$el 属性目前不可见。
  4. 此时,可以进行数据请求,将请求回来的值赋值给data中的数据。

挂载前后

beforeMount

  1. 在挂载开始之前被调用,此时模板已经编译完成,只是未将生成的模板替换el对应的元素。(也就是模板编译完成,没挂到页面上),在这里操作DOM节点最终都无效
  2. 在此钩子函数中,可以获取到模板最初始的状态。
  3. 此时,可以拿到$el,只不过为旧模板

mount

  1. 此时,已经将编译好的模板,挂载到了页面指定的容器中显示
  2. 可以操作DOM节点;
  3. 此时组件已经脱离初始化阶段,进入运行阶段,一般在此阶段:开启定时器、发送网络请求等准备工作

更新前后

beforeUpdate

  1. 页面数据发生更变时触发,此时data内的数据是最新的,但页面上的数据还是旧的,因为此时还没开始重新渲染

update

  1. 当数据更变完毕触发,此时data内的数据和页面上的已经完成更新,是同步的
  2. 此时数据和DOM是同步的

销毁前后

beforeDestroyed

  1. 实例销毁前调用,此时并没有到真正的销毁过程
  2. 此时实例里面的data,methods,指令等等都还还处于工作状态
  3. 一般情况下此钩子用于关闭定时器,取消一系列操作等等

destroyed

  1. 实例销毁后调用
  2. 此时指示的所有东西都会解除绑定,所有的事件监听会被移除。所有的子实例也都会被销毁
  3. 此时并没有销毁自身的事件监听

组件

分为全局组件和局部组件还有函数式组件,没啥好赘述的


父子组件通信

通信方式很多:比如这个列表

但是最好是用 props和$emit,因为他们俩遵守了单向数据流,其余的了解下就好

prop

没什么好说的,基础操作

prop验证

几个注意点:

  1. 名称最好避开 type这些有特殊意义的,如果你给input使用,原本的的type为text,你传了参数只会破坏原本的属性值,但是class和style会合并,但最好还是不要这样操作

我们可以为组件的 prop 指定验证要求,例如你可以要求一个 prop 的类型为什么,默认值是什么。如果说需求没有被满足的话,VUE会给一个报错,开发组件给别人用的时候有用

prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。例如:

<template>
  <div :class="myClass">{{myNumber}}</div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    myClass: {
      type:String,//类型为String
      required:true//必传参数,不然报错
    },
    myNumber:{
      type:[String,Number],//可以使用数组规定可能是多种类型
      default:1,//默认值
      validator(value) {//自定义验证规则,value为传过来的值,验证完成后返回一个布尔值,也可以对数据进行操作返回
        return value>99//这里只是演示,这里会报错
      }
    }
  }
}
</script>
单向数据流

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

总结成人话就是,爹打儿子天经地义,儿子打爹五雷轰顶,也就是子组件不能修改父组件传过来的数据。当然子组件可以抛出一个事件给父组件,让父组件来处理更变,也就是爹可以给儿子任何东西,但是你儿子没权利改,想改给爹申请

$emit()

子组件无权更改父组件数据,此时子组件可以抛出一个事件给父组件处理

示例:

<!--子组件抛出事件-->
<template>
  <div @click="change('我是子组件抛出的数据')"></div>
</template>

<script>
export default {
  name: 'HelloWorld',
  methods: {
    change(i) {
        //子组件抛出了一个名为childComponent的事件,i 为子组件抛出的数据
      return this.$emit('childComponent', i)
    }
  }
}
</script>
<!--父组件接收-->
<template>
  <div id="app">
      <!--父组件接收子组件抛出的事件,并指定一个处理函数-->
    <HelloWorld  @childComponent="childCli"/>
  </div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
  components: {
    HelloWorld
  },
  methods:{
    childCli(data){
      //这里的data 是子组件抛出的数据
      console.log('父组件接收的数据:',data);
    }
  }
}
</script>
<!---->

style和class

父组件可以向子组件传递styleclass,它们会合并到子组件的根元素中

$attrs

如果父组件传递了一些属性到子组件,但子组件并没有声明这些属性,则它们称之为attribute,这些属性会直接附着在子组件的根元素上,但是不包括style和class

没有通过props声明的属性,传过来了,就叫attribute

子组件可以通过inheritAttrs: false配置,禁止将attribute附着在子组件的根元素上,但不影响通过$attrs获取,(就是看不到,能获取到,inheritAttrs写在默认导出里面)

<!--父组件 传递一个data-index,abc到子组件-->
<HelloWorld data-index="1" abc="888"/>

<!--子组件-->
<template>
  <div></div>
</template>
<script>
export default {
  name: 'HelloWorld',
  inheritAttrs:false,// inheritAttrs:false 传过来的自定义属性不会附着在子组件的根元素上面
  created() {
    console.log(this.$attrs);//此时控制台输出了一个对象 {data-index:1,abc:888}
  }
}
</script>
<!---->

.natvie 修饰符

在注册事件时,父组件可以使用native修饰符,将事件注册到子组件的根元素上(没啥卵用,父组件传过来的东西,子组件貌似是不能修改这些数据的

<!--父组件-->
<template>
  <div id="app">
    <HelloWorld @click.native="childClick(childArr)"/>
  </div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
  components: {
    HelloWorld
  },
  data() {
    return {
      childArr:[1,2,3,4,5,6,7]
    }
  },
  methods:{
    childClick(data){
      console.log(data);
    }
  }
}
</script>

<!--子组件-->
<template>
  <div>子组件</div>
</template>

<script>
export default {
  name: 'HelloWorld',
}
</script>

$listeners

子组件可以通过**$listeners**获取父组件传递过来的所有事件处理函数

$listeners还有其他用法,可以把子组件抛出的事件持续向上抛

<!--父组件-->
<template>
  <div id="app">
      <!--父组件传递两个事件-->
    <HelloWorld @event1="childEvent1" @event2="childEvent2"/>
  </div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
  components: {
    HelloWorld
  },
  methods:{
    childEvent1(){
      console.log('event1');
    },
    childEvent2(){
      console.log('event2');
    },
  }
}
</script>
<!--子组件-->
<template>
  <div @click="event">子组件</div>
</template>

<script>
export default {
  name: 'HelloWorld',
  methods:{
    event(){
      //子组件可以通过this.$listeners一次性接收
      this.$listeners.event1()
      this.$listeners.event2()
    }
  }
}
</script>

v-model

这个也可以

.sync 修饰符

v-model的作用类似,用于双向绑定,不同点在于v-model只能针对一个数据进行双向绑定,而sync修饰符没有限制

<!--子组件-->
<template>
  <div>
    <p>
      <!--注意 !!!!!!!!!
	 	使用.sync这个修饰符,子组件抛出事件的时候必须用这个奇葩语法
	  -->
      <button @click="$emit(`update:num1`, num1 - 1)">-</button>
      {{ num1 }}
      <button @click="$emit(`update:num1`, num1 + 1)">+</button>
    </p>
    <p>
      <button @click="$emit(`update:num2`, num2 - 1)">-</button>
      {{ num2 }}
      <button @click="$emit(`update:num2`, num2 + 1)">+</button>
    </p>
  </div>
</template>

<script>
export default {
  props: ["num1", "num2"],
};
</script>

<!--父组件-->
<template>
  <div id="app">
    <Numbers :num1.sync="n1" :num2.sync="n2" />
    <!-- 等同于 -->
    <Numbers :num1="n1" @update:num1="n1 = $event" :num2="n2" @update:num2="n2 = $event"/>
  </div>
</template>
<script>
import Numbers from "./components/Numbers.vue";
export default {
  components: {
    Numbers,
  },
  data() {
    return {n1: 0,n2: 0,};
  },
};
</script>

p a r e n t 和 parent和 parentchildren

在组件内部,可以通过$parent$children属性,分别得到当前组件的父组件和子组件实例,实例里面包含所有数据还有DOM节点

$slots 和 $scopedSlots

ref

<div id="app" ref="appElement"></div>
<!--获取-->
<script>
	this.$refs.appElement //返回的是拿到的DOM元素的实例,里面啥都有
</script>

跨组件通信

Provide和Inject

provide定义数据,lnject接收数据

示例

<!--祖先组件-->
<template>
  <div id="app">
    <HelloWorld />
  </div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
  components: {
    HelloWorld
  },
  provide:{ //给孙子组件传递 a 和 b
    a:1,
    b:2
  }
}
</script>

<!--孙子组件-->
<template>
  <p>孙子组件</p>
</template>
<script>
export default {
  name: "demo",
  inject:['a','b'],//接收
  created() {
    console.log(this.a, this.b);
  }
}
</script>

详见:https://cn.vuejs.org/v2/api/?#provide-inject

router

如果一个组件改变了地址栏,所有监听地址栏的组件都会做出相应反应

最常见的场景就是通过点击router-link组件改变了地址,router-view组件就渲染其他内容

vuex

适用于大型项目的数据仓库

store模式

适用于中小型项目的数据仓库

直接建个文件,各个组件都可以使用…,只要一个组件修改了数据,所有的组件都会跟着改变

// store.js
const store = {
  loginUser: ...,
  setting: ...
}

// compA
const compA = {
  data(){
    return {
      loginUser: store.loginUser
    }
  }
}

// compB
const compB = {
  data(){
    return {
      setting: store.setting,
      loginUser: store.loginUser
    }
  }
}

eventbus

组件通知事件总线发生了某件事,事件总线通知其他监听该事件的所有组件运行某个函数


虚拟DOM

什么是虚拟dom?

虚拟dom本质上就是一个普通的JS对象,用于描述视图的界面结构

在vue中,每个组件都有一个render函数,每个render函数都会返回一个虚拟dom树,这也就意味着每个组件都对应一棵虚拟DOM树

在这里插入图片描述

为什么需要虚拟dom?

vue中,渲染视图会调用render函数,渲染不仅发生在组件创建时,同时视图上的数据更新时。如果在渲染时,直接使用真实DOM,由于真实DOM的创建、更新、插入等操作会带来大量的性能损耗,从而就会降低渲染效率。

所以,vue在渲染时,使用虚拟dom来替代真实dom,主要为解决渲染效率的问题。

虚拟dom是如何转换为真实dom的?

如果一 个组件实例首次被渲染时,它先生成虚拟dom树,然后根据虚拟dom树创建真实dom,并把真实dom挂载到页面中合适的位置,此时,每个虚拟dom便会对应一个真实的dom。

如果一个组件受响应式数据变化的影响,需要重新渲染时,就会重新调用render函数,创建出一个新的虚拟dom树,用新树和旧树对比,通过对比,vue会找到最小更新量,然后更新必要的虚拟dom节点,最后,这些更新过的虚拟节点,会去修改它们对应的真实dom

这样一来,就保证了对真实dom达到最小的改动。

Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。

在这里插入图片描述

模板和虚拟dom的关系

vue框架中有一个compile模块,它主要负责将模板转换为render函数,而render函数调用后将得到虚拟dom。

编译的过程分两步:

  1. 将模板字符串转换成为AST
  2. AST转换为render函数

如果使用传统的引入方式,则编译时间发生在组件第一次加载时,这称之为运行时编译。

如果是在vue-cli的默认配置下,编译发生在打包时,这称之为模板预编译。

编译是一个极其耗费性能的操作,预编译可以有效的提高运行时的性能,而且,由于运行的时候已不需要编译,vue-cli在打包时会排除掉vue中的compile模块,以减少打包体积

模板的存在,仅仅是为了让开发人员更加方便的书写界面代码

vue最终运行的时候,最终需要的是render函数,而不是模板,因此,模板中的各种语法,在虚拟dom中都是不存在的,它们都会变成虚拟dom的配置

Vue响应式原理

数据变化去做一些事情,Object.defineProperty

router(路由)

基本语法没什么说的

路由模式

路由模式决定了:

  1. 路由从哪里获取访问路径
  2. 路由如何改变访问路径

vue-router提供了三种路由模式:

  1. hash:默认值。路由从浏览器地址栏中的hash部分获取路径,改变路径也是改变的hash部分。该模式兼容性最好。

    http://localhost:8081/#/blog  -->  /blog
    http://localhost:8081/about#/blog  --> /blog
    
  2. history:路由从浏览器地址栏的location.pathname中获取路径,改变路径使用的H5的history api。该模式可以让地址栏最友好,但是需要浏览器支持history api

    http://localhost:8081/#/blog  -->  /
    http://localhost:8081/about#/blog  --> /about
    http://localhost:8081/blog  --> /blog
    
    //当你使用 history 模式时,URL 就像正常的 url,例如 http://yoursite.com/user/id, 也好看!
    //不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。
    //所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。
    
  3. abstract:路由从内存中获取路径,改变路径也只是改动内存中的值。这种模式通常应用到非浏览器环境中。

    内存: /			-->   /
    内存: /about	--> /about
    内存: /blog	  --> /blog
    

命名路由

使用命名路由可以解除系统与路径之间的耦合

  {
    //现在不管path路径怎么变,点击首页依然会匹配到
    path: '/activity/personal',
    name: 'Home',
    component: Personal,
  }

要链接到一个命名路由,可以给 router-link 的 to 属性传一个对象:

<router-link :to="{ name: 'Home' }">首页</router-link>

激活状态

exact 开启路由精确匹配

<!-- 这个链接只会在地址为 / 的时候被激活 -->
<router-link active-class="active" exact-active-class="" to="/" exact></router-link>

<!-- 这个链接会在地址为 /aacc 开头的时候被激活 -->
<router-link active-class="active" exact-active-class="" to="/aacc" :exact=false></router-link>

<!--这里也没什么说的 开启exact后把这两个粘贴到link上面,匹配的路由类名为active,active-class="active" exact-active-class=""-->

$router

$router.push

// 字符串
this.$router.push('home')

// 对象
this.$router.push({ path: 'home' })

// 命名的路由
this.$router.push({ name: 'user' })

$router.replace

跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是替换掉当前的 history 记录。

$router.go(n)

这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)。

$route 只读,路由信息对象

$route.path

字符串,对应当前路由的路径,总是解析为绝对路径,如 “/foo/bar”。

$route.params

一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。

$route.query

一个 key/value 对象,表示 URL 查询参数。例如,对于路径 /foo?user=1,则有 $route.query.user == 1,如果没有查询参数,则是个空对象。

$route.hash

路由的 hash 值 (带 #) ,如果没有 hash 值,则为空字符串。

$route.fullPath

完成解析后的 URL,包含查询参数和 hash 的完整路径。

$route.matched

一个数组,包含当前路由的所有嵌套路径片段的路由记录 。路由记录就是 routes 配置数组中的对象副本 (还有在 children 数组)。

$route.name

当前路由的名称,如果有的话

$route.redirectedFrom

如果存在重定向,即为重定向来源的路由的名字。

动态路由

同一个组件,渲染不同内容

参数值会被设置到 this.$route.params

    // 动态路径参数 以冒号开头
    { path: '/user/:id',name:'User', component: User }
          <router-link :to="{
            name:'User',
            params:{
              id:item.id
            }
          }">
          </router-link>

导航守卫

导航守卫被分成三种:全局的、单个路由独享的、组件内的。

参数说明:

  • to 目标路由对象 (去哪里)
  • from 即将要离开的路由对象 (哪里来的)
  • next 三个参数中最重要的参数。
    • 必须调用next(),才能继续往下执行一个钩子,否则路由跳转会停止
    • 若要中断当前的导航,可以调用next(false)。
    • 可以使用next跳转到一个不同的地址。终端当前导航,进入一个新的导航。next参数值和$routet.push一致。
    • next(error)。2.4+,如果传入的参数是一个Error实例,则导航会被终止,且该错误会被传递给router.onError() 注册过的回调。

Vuex(数据仓库)

杂项

过滤器

Vue 官方过滤器地址

组件内过滤器

<template>
  <div id="app">
      <!--语法,一个竖线代表name要过滤处理,没什么难得-->
      {{name | isName}}
  </div>
</template>
<script>
export default {
  data(){
    return{
      name:['ding','shuai']
    }
  },
  filters:{
    isName:(value)=>{
      let name = [],str=''
      value.forEach(i=>name.push(i.charAt(0).toUpperCase()+i.slice(1)))
      name.forEach(i=>str+=i)
      return str
    }
  }
}
</script>

全局过滤器

//第一个参数是 过滤器名字.....第二个参数处理函数,形参是需要处理的值
Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

串联过滤器

//message 代表要处理的值,filterA处理完了,再给feilterB拿去处理
{{ message | filterA | filterB }}
//这玩意是可以接收参数的,第一个参数就是message,也就是需要过滤的值,第二个参数,第三个参数自定义
{{ message | filterA('arg1', arg2) }}

过滤器和计算属性的区别

计算属性
1、计算属性适合用在单个属性的计算;
2、计算属性只能在单个vue实例中使用;
3、计算属性不能接收参数,只能使用data中定义的变量进行计算;
4、计算属性有缓存机制,可减少调用次数;
5、计算属性相当于定义一个变量

过滤器
1、过滤器适合多个同样计算方法的属性的计算;
2、过滤器可以定义为全局过滤器,在多个vue实例中使用;
3、过滤器可以接收多个参数进行计算;
4、过滤器没有缓存机制,每调用一次都会计算一次;
**5、过滤器相当于定义一个特殊的方法

还没完呢

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值