Vue2

简介: Vue2

theme: fancy

highlight: a11y-light

mvvm设计模式,m ->model数据,v->view视图,Vm数据视图连接层,组件之间的数据都是单向流动的,子组件不能直接修改传递过来的值

e30408d7ca12a14d10da83c0ce8588b.jpg

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script>
    const app = Vue.createApp({
   
   //初始化vue
      template: '<div>{
   
   {content}}</div>',
      data() {
   
   
        return {
   
   
          content: 1,
        }
      },
      mounted() {
   
   
        setInterval(() => {
   
   
          this.$data.content++// this.$data.content=this.content
        }, 500)
      },
    })
    const vm = app.mount('#root')//挂载到root上,vm让数据视图相关联
  </script>
</html>

生命周期

lifecycle.png

 // 生命周期函数:在某一时刻会自动执行的函数
  const app = Vue.createApp({
   
   
    data() {
   
   
      return {
   
   
        message: 'hello world'
      }
    },
    // 在实例生成之前会自动执行的函数
    beforeCreate() {
   
   
      console.log('beforeCreate')
    },
    // 在实例生成之后会自动执行的函数
    created() {
   
   
      console.log('created')
    },
    // 在组件内容被渲染到页面之前自动执行的函数
    beforeMount() {
   
   
      console.log(document.getElementById('root').innerHTML, 'beforeMount')
    },
    // 在组件内容被渲染到页面之后自动执行的函数
    mounted() {
   
   
      console.log(document.getElementById('root').innerHTML, 'mounted')
    },
    // 当数据发生变化时会立即自动执行的函数
    beforeUpdate() {
   
   
      console.log(document.getElementById('root').innerHTML, 'beforeUpdate');//输出未更改的数据
    },
    // 当数据发生变化,页面重新渲染后,会自动执行的函数
    updated() {
   
   
      console.log(document.getElementById('root').innerHTML, 'updated');
    },
    // 当 Vue 应用失效时,自动执行的函数
    beforeUnmount() {
   
   
      console.log(document.getElementById('root').innerHTML, 'beforeUnmount');
    },//输出dom结构
    // 当 Vue 应用失效时,且 dom 完全销毁之后,自动执行的函数
    unmounted() {
   
   
      console.log(document.getElementById('root').innerHTML, 'unmounted');
    },//没有内容
  });
  const vm = app.mount('#root');

v-html v-once 插值表达式 阻止默认行为@click.prevent .self子元素不会触发父元素事件 .stop停止冒泡

message:'<strong>hello</strong>'
v-html="message"

双括号里只能放js表达式,不能放语句,也可以调用函数

与计算属性相比可以实现异步

    watch:{
   
   
        price(current,prev){
   
   
           setTimeout(()=>{
   
   console.log(123)},1000)
        }
      },

样式绑定语法

image.png

<div>
<demo :class="classstr"></demo></div>
//给父组件绑定一个样式,如果里面有两个子元素样式不会生效,一个才会生效,因为不知道赋值给谁。或者使用$attrs.class接收
 app.component('demo', {
   
   
      template: '<div :class="$attrs.class">123</div><div>456</div>',
    })

修饰符 v-model.lazy不改变输入框数据失去焦点再改变 .trim .number转数字

image.png
精确修饰符表示不能同时按除这个键之外的键 .enter.exact

局部组件

//先声明组件 首字母大写,驼峰命名表示局部组件
    const Demo={
   
   
      template: '<div>123</div><div>456</div>',

    }
    //再到组件中声明 components:{demo}= components:{demo:demo},就可以使用了

父子组件传值,通过props

   const demo = {
   
   
      props: ['number'],
      template: '<div>{
   
   {number}}</div>',
    }
    const app = Vue.createApp({
   
   
      data() {
   
   
        return {
   
   
          num: 123,
        }
      },
      components: {
   
    demo },
      template: '<div ><demo :number=num></demo></div>',
    })

    const vm = app.mount('#root')
    //如果要对传入参数做限制props写出对象形式。
      props: {
   
   
        number: {
   
   
          type: Number,
          // required:true,
          //default:234
          default: function () {
   
   
            return 234
          },
          validator: function (value) {
   
   
            return value < 1000
          },
        },
      },

如果父组件传值太多,可以写进一个对象里v-bind="params",props再一个一个接收

   const demo = {
   
   
      props: ['a', 'b', 'c'],
      template: '<div>{
   
   {a}}</div><div>{
   
   {b}}</div><div>{
   
   {c}}</div>',
    }
    const app = Vue.createApp({
   
   
      data() {
   
   
        return {
   
   
          params: {
   
    a: 1, b: 2, c: 3 },
        }
      },
      components: {
   
    demo },
      template: '<div ><demo v-bind="params"></demo></div>',
    })

    const vm = app.mount('#root')

属性传的时候,使用content-abc这种命名,props接的时候,使用contentAbc接收

Non-props

父组件传值子组件不用props接收例如

<demo  msg=content></demo>
//传入的属性会当成div的属性,<div mag=content></div>
//不想要这个属性,在子组件里加一个inheritAttrs:false
//用来设置style,class
//如果子组件有多个根元素,v-bind="$attrs"把父元素传入的所有Non-props属性都加在这个元素上,如果只要某一个:msg="$attrs.msg"

子组件修改父组件传入的值通过emit

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script>
    const demo = {
   
   
      props: ['count'],
      //emits:['addOne'],
      emits: {
   
   
        addOne: (count1, count2) => {
   
   
          if (count1 > 0 && count2 > 0) {
   
   
            return true
          }
          return false
        },
      }, //对象形式会检验子组件向父组件传的参数
      template: '<div @click="handleClick">{
   
   {count}}</div>',
      methods: {
   
   
        handleClick() {
   
   
          this.$emit('addOne', 1, -2) //驼峰命名,这里还可以传参,或者直接在这里算出count再返回count
        },
      },
    }
    const app = Vue.createApp({
   
   
      data() {
   
   
        return {
   
   
          count: 1,
        }
      },
      components: {
   
    demo },
      template:
        '<div ><demo @add-one="handleAddOne" :count="count"></demo></div>', //横杆命名
      methods: {
   
   
        handleAddOne(a, b) {
   
   
          //直接在这里使用参数
          this.count = this.count + a + b
        },
      },
    })

    const vm = app.mount('#root')
  </script>
</html>

父子组件v-model

可以看到我们通过props,emit可以接收修改父组件传递的值,这就类似于v-model,通过v-model来实现父子组件的双向绑定

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script>
    const app = Vue.createApp({
   
   
      template: '<counter v-model="count"></counter>',
      data() {
   
   
        return {
   
   
          count: 1,
        }
      },
    })
    app.component('counter', {
   
   
      props: ['modelValue'], //规定这么写
      methods: {
   
   
        handleClick() {
   
   
          this.$emit('update:modelValue', this.modelValue + 3) //规定这么写update:modelValue意味着更新modelValue
        },
      },
      template: `<div @click="handleClick">{
    
    {modelValue}}</div>`,
    })
    const vm = app.mount('#root')
  </script>
</html>
//如果不想用规定的名字,v-model:app=count,props:['app'],'update:app'
//通过自己定义的名字可以v-model多个数据,v-model:名字

自定义v-model修饰符(转大写字母)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script>
    const app = Vue.createApp({
   
   
      template: '<counter v-model.uppercase="word"></counter>',
      data() {
   
   
        return {
   
   
          word: 'a',
        }
      },
    })
    app.component('counter', {
   
   
      props: {
   
   
        modelValue: String,
        modelModifiers: {
   
   
          default: () => ({
   
   }), //不传默认返回空对象
        },
      },
      methods: {
   
   
        handleClick() {
   
   
          let newVal = this.modelValue + 'b'
          if (this.modelModifiers.uppercase) {
   
   
            newVal = newVal.toUpperCase()
          }
          this.$emit('update:modelValue', newVal) //规定这么写update:modelValue
        },
      },
      template: `<div @click="handleClick">{
    
    {modelValue}}</div>`,
    })
    const vm = app.mount('#root')
  </script>
</html>

关于v-model的相关拓展官方文档

插槽

插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的,即父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script>
    const demo = {
   
   
      data() {
   
   
        return {
   
   
          count: 2,
        }
      },
      props: ['count'],
      template: '<div><input /><slot></slot></div>',//父组件在子组件里面写的dom,会被替换到slot的位置,如果这个传入的dom使用了变量,虽然他被替换到slot的位置,但还是使用父组件的数据。
    }
    const app = Vue.createApp({
   
   
      data() {
   
   
        return {
   
   
          count: 1,
        }
      },
      components: {
   
    demo },
      template:
        '<demo><button>{
   
   {count}}</button></demo><demo><div>{
   
   {count}}</div></demo>', //横杆命名
    })

    const vm = app.mount('#root')
  </script>
</html>

具名插槽

将传入子组件的dom进一步拆分,以方便在子组件不同位置进行使用

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script>
    const app = Vue.createApp({
   
   
      data() {
   
   
        return {
   
   }
      },
      template: `<demo1><template v-slot:head><div>head</div></template><template #footer><div>footer</div></template></demo1>`,//简写#footer
    })
    app.component('demo1', {
   
   
      template: `<slot name="head"></slot><div>content</div><slot name="footer"></slot>`,
    })
    const vm = app.mount('#root')
  </script>
</html>

作用域插槽

子传父

<!-- <MyComponent> 的模板 -->
<div>
  <slot :text="greetingMessage" :count="1"></slot>
</div>


<MyComponent v-slot="slotProps">
  {
   
   {
   
    slotProps.text }} {
   
   {
   
    slotProps.count }}
</MyComponent>
//当然更推荐直接结解构出来v-slot="{ text, count }"

多层组件传值

传统方法需要一层一层传值非常麻烦

//在父组件data同层,声明
provide:{
   
   
    count:1,
}
//如果想传data里的值,需要把provide写出一个函数
provide(){
   
   
    return{
   
   
        msg:'hi',
        count:this.count,
        getInfo(data){
   
   
        console.log(data)}
    }
},//如果后续父组件count改变,子组件不会感知。
//在子组件inject就可以使用传来的值
inject:['msg','count','getInfo']

动态组件异步组件

动态组件

同一个位置按照要求显示不同组件 keep-alive可以缓存

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script>
    const demo1 = {
   
   
      data() {
   
   
        return {
   
   }
      },
      template: '<input />',
    }
    const demo2 = {
   
   
      data() {
   
   
        return {
   
   }
      },
      template: '<div>123</div>',
    }
    const app = Vue.createApp({
   
   
      data() {
   
   
        return {
   
   
          current: 'demo1',
        }
      },
      components: {
   
    demo1, demo2 },
      methods: {
   
   
        change() {
   
   
          this.current === 'demo1'
            ? (this.current = 'demo2')
            : (this.current = 'demo1')
        },
      },
      template:
        '<keep-alive><component :is="current"></component></keep-alive><button @click="change">切换</button>',
    })

    const vm = app.mount('#root')
  </script>
</html>

异步组件

需要的时候再展示的组件

components:{
   
   
demo:()=>{
   
   '../demo'}
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script>
    const app = Vue.createApp({
   
   
      data() {
   
   },
      template: '<div>123</div><demo1></demo1>',
    })
    app.component(
      'demo1',
      Vue.defineAsyncComponent(() => {
   
   
        return new Promise((resolve, reject) => {
   
   
          setTimeout(() => {
   
   
            resolve({
   
   
              template: `<div>这是异步组件</div>`,
            })
          }, 3000)
        })
      })
    )
    const vm = app.mount('#root')
  </script>
</html>

transition

appear第一次展示就带动画
image.png
加一个.v-move{transition all .5s ease}

image.png

mixin

image.png

image.png
子组件不能使用父组件引入的mixin,可以定义全局的mixin,
修改混入的自定义属性高于组件的自定义属性优先级

image.png
image.png
在data,methods同级声明的是自定义属性,调用方法this.\$options.number
替代方案使用组合式api

自定义指令操作dom

全局指令 使用v-focus

image.png

局部指令,要在组件中声明directives:directives

image.png

指令接收参数

对于v-pos:abc="top" binding.arg可以拿到abc,binding.value可以拿到top变量
image.png
当这个200是动态变化的,页面并不会改变,因为没有再触发mounted
加一个updated

upadted(el,binding){
   
   
    el.style.top=(binding.value+'px')
}

简写:指令中如果只有mounted,updated且内容都一样,可以简写为

app.directive('pos',(el,binding)=>{
   
   
        el.style.top=(binding.value+'px')
    })

Teleport 传送门

Teleport 是一种能够将我们的组件html结构移动到指定位置的技术

将mask从组件中传送到html标签下,传送给body就好了,这样mask就可以相对于全局进行绝对定位了,不必管父元素的定位

<teleport to="#hello">
<div class="mask" v-show="show">{
   
   {
   
   message}}</div>
</teleport>

插件

主要是用于增强功能,可以把他看作是一个工具库,可以提供很多强大的功能,比如一些强大的自定义指令,一些强大的工具方法,过滤器等。app.use来使用option是传入的参数,app是vue的实例
image.png
this.\$sayHello
数据检验插件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script>
    const app = Vue.createApp({
   
   
      template: '<div></div>',
      data() {
   
   
        return {
   
   
          name: 'zh',
          age:12
        }
      },
      rules: 
        {
   
   
          name: {
   
   
            validate: (item) => item.length > 3,
            message: '输入的长度应该大于3',
          },
          age: {
   
   
            validate: (item) => item < 18,
            message: '你已经不是小孩了',
          },
        },
    })
    const vaildatorPlugin = (app, options) => {
   
   
      app.mixin({
   
   
        created() {
   
   
          for (let key in this.$options.rules) {
   
   
            const item = this.$options.rules[key]
            this.$watch(key, (value) => {
   
   
              const result = item.validate(value)
              if (!result) {
   
   
                console.log(item.message)
              }
            })
          }
        },
      })
    }
    app.use(vaildatorPlugin)
    const vm = app.mount('#root')
  </script>
</html>

ref reactive toRefs toRef

原理,通过proxy对数据进行封装,当数据变化是,触发模板等内容的更新

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script>
    const app = Vue.createApp({
   
   
      setup(props) {
   
   
        //'zhang'变成proxy({value:'zhang'})这样的一个响应式引用
        const {
   
    ref,reactive } = Vue
        let name = ref('zhang')
        let collection = reactive([1,2,3])
        setTimeout(() => {
   
   
          name.value = 'fang'
          collection[0]=999
        }, 2000)
        return {
   
   
          name,collection
        }
      },

      template: '<div>{
   
   {name}}</div><div>{
   
   {collection[0]}}</div>',
    })
    const vm = app.mount('#root')
  </script>
</html>

toRefs可以让解构出来的数据也变成响应式的,toRef用于原数据里不存在的元素

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script>
    const app = Vue.createApp({
   
   
      setup(props) {
   
   
        //'zhang'变成proxy({value:'zhang'})这样的一个响应式引用
        const {
   
    reactive, toRefs } = Vue
        let collection = reactive({
   
    name: 'zhang' })
        //toRefs proxy({name:'zhang'}),{name:proxy({value:'zhang'})}
        let {
   
    name } = toRefs(collection)
        setTimeout(() => {
   
   
          collection.name = 'fang'
        }, 2000)
        return {
   
   
          name,
        }
      },

      template: '<div>{
   
   {name}}</div>',
    })
    const vm = app.mount('#root')
  </script>
</html>

路由

beforeEnter 进入这个路由时会触发。

path:'/login',
    name:'Login',
    component:Login,
    beforeEnter: (to, from, next) => {
   
   
        const {
   
   isLogin}=localStorage
        isLogin?next({
   
   name:'Home'}):next()

    }

beforEach每次切换路由都会执行

//登陆注册不需要登陆
  router.beforeEach((to,from,next)=>{
   
   
        const {
   
   isLogin}=localStorage
        (isLogin||(to.name!=="login"&&to.name!=="register"))?next():next({
   
   name:'Login'})
    })

动态路由

image.png

路由元信息

路由表
meta:{
   
   auth:true}
this.$route.meta.auth可以拿到

路由传参

//通过query
<router-link to="/about?username=zhang" />
<router-link :to="{name:'about',query:{username:'zhang'}}" />
this.route.query可以拿到对象
//通过params隐式
<router-link :to="{name:'about',params:{username:'zhang'}}" />
this.route.params可以拿到对象,刷新拿不到值.
//通过params显示
<router-link to="/about/123" />
path:about/:id
this.route.params.id

Vue原理

Object.definrProperty的基本使用

const data={
   
   }
let name='zhang'
Object.defineProperty(data,"name",{
   
   
    get:function(){
   
   
        console.log('get')
        return name
    },
    set:function(newVal){
   
   
        console.log('set')
        name=newVal
    }
})
console.log(data.name)
data.name='fang'
console.log(data.name)

深度监听对象,数组

  • 深度监听,需要递归到底,一次性计算量大
  • 无法监听新增属性/删除属性( Vue.set Vue.delete )
  • 无法原生监听数组,需要特殊处理
const data = {
   
   
  name: 'zhang',
  age: 22,
  info: {
   
   
    address: '比奇堡',
  },
  list: [1, 2, 3],
}
//重新定义数组原型,不能再原型上更改,会污染数组原型
const oldArrayProperty = Array.prototype
//创建新对象,原型指向oldArrayProperty,再扩展新的方法不会影响原型
//Object.create方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach((methodName) => {
   
   
  arrProto[methodName] = function () {
   
   
    updateView() //触发视图更新
    oldArrayProperty[methodName].call(this, ...arguments)
    //等于Array.prototype.push.call(this, .. .arguments)
  }
})
function updateView() {
   
   
  console.log('视图更新')
}
function observer(target) {
   
   
  //不是对象或数组
  if (typeof target !== 'object' || typeof target === null) {
   
   
    return target
  }
  //新原型
  if (Array.isArray(target)) {
   
   
  //target的原型指向以数组原型为原型的对象arrProto
    target.__proto__ = arrProto
  }
  //重新定义各个属性(forin也可以遍历数组)
  for (const key in target) {
   
   
    defineReactive(target, key, target[key])
  }
}
//重新定义属性监听起来
function defineReactive(target, key, value) {
   
   
  //深度监听对象
  observer(value)
  //核心API
  Object.defineProperty(target, key, {
   
   
    get: function () {
   
   
      return value
    },
    set: function (newVal) {
   
   
      //设置新值
      //value一直在闭包里,此处设置完,再get时也会获取最新值
      if (newVal !== value) {
   
   
        //要对新值进行监听,如果data.name={num:100},后面修改data.name.num=200不会监听到
        observer(newVal)
        value = newVal
        //更新视图
        updateView()
      }
    },
  })
}
//一进入页面就监听data
observer(data)
data.name = 'fang'
data.age = '18'
data.info.address = '羊村'
data.list.push(4)

vDom即虚拟Dom

react首创,js运行速度比操作Dom不是一个量级,将html结构用js数据结构模拟出来,计算出最小变更操作Dom

<div id="div1" class="container">
        <p>vdom</p>
        <ul style="font-size: 20px;">
        <li>a</li></ul>
    </div>
//load编译,h是createElement的别称
h('div', {
   
    
  attrs: {
   
   
    id: 'div1'
  },
  class: 'container'
}, [
  h('p', 'vdom'),
  h('ul', {
   
    
    style: {
   
   
      fontSize: '20px'
    }
  }, [
    h('li', 'a')
  ])
])
======================================>>>>>>执行h函数获得虚拟DOM
//可能react,vue具体划分不同,但是大致是按照这样划分的
 {
   
   
        tag:'div',//或者是选择器名字
        props:{
   
   //标签上的属性 对象
            id:'div1',
            className:'container'
        },
        children:[//子元素 数组
            {
   
   
                tag:'p',
                children:'vdom',
            },{
   
   
                tag:'ul',
                props:{
   
   style:'font-size:20px'},
                children:[
                    {
   
   
                        tag:'li',
                        children:'a'
                    }
                ]
            }

        ]
    }
//render函数将虚拟dom转化为真实dom

diff算法

image.png

image.png

image.png

image.png
pathVnode用来比较两个vdom,如果他们的key和tag相同,就执行updateChildren,新节点有children进行addVnodes,没有进行removeVnodes

模板编译

image.png

  • 模板编译为render 函数,执行render函数返回vnode
  • 使用webpack vue-loader ,会在开发环境下编译模板(重要)
    image.png
    vue组件可以用render代替template

image.png

渲染更新流程

image.png
image.png

image.png

image.png

vue路由模式

image.png
history,后端只需要返回一个主文件地址

image.png
image.png

image.png

beforedestroy使用场景

image.png
自定义DOM事件是通过addEventListener生成的

nexttick

image.png
异步渲染无法,无法在data发生变化之后拿到DOM的变化

proxy

proxy代理,reflect反射一一对应,名字参数也一致,正在逐渐替换object上的工具函数

当我们期望监听代理对象的 getter 和 setter 时,不应该使用 target[key],因为它在某些时刻下是不可靠的。receiver是代理之后的对象。而应该使用 Reflect ,借助它的 get 和 set 方法,使用receiver (proxy 实例) 作为 this ,达到期望的结果

// const data = {
   
   
//   name: 'zhang',
//   age: 22,
// }
const data = [1, 2, 3]
//返回一个已经被监听的对象
const proxyData = new Proxy(data, {
   
   
  get(target, key, receiver) {
   
   
    //只处理非原型(自身)的属性
    const ownKeys=Reflect.ownKeys(target)
    if(ownKeys.includes(key)){
   
   
      console.log('get', key)
    }
    //下面不管是不是原型上的属性都要执行,要不然用不了原型上的方法
    const result = Reflect.get(target, key, receiver)
    return result //返回结果
  },
  set(target, key, val, receiver) {
   
   
    //重复的数据不处理
    if(val===target[key])
    {
   
   
      return true
    }
    const result = Reflect.set(target, key, val, receiver)
    console.log('set', key, val)
    return result //是否设置成功
  },
  deleteProperty(target, key) {
   
   
    const result = Reflect.deleteProperty(target, key)
    console.log('delete', key)
    return result //是否删除成功
  },
})
//proxyData.age=100
//console.log(proxyData.age)
//delete proxyData.name
proxyData.push('4')
//get push 没必要劫持,原型的属性不处理
// get length
// set 3 4 length已经变成4
// set length 4 没必要再设置一遍

proxy实现响应式

const data = {
   
   
  name: 'zhang',
  age: 22,
  info:{
   
   address:'nanjing'},
  list:[1,2,3]
}
//返回响应式数据
const proxyData=reactive(data)
function reactive(target={
   
   }){
   
   
  if(typeof target!=='object'||target==null){
   
   
    return target
  }
  //配置代理
  const proxyConf = {
   
   
    get(target, key, receiver) {
   
   
      //只处理非原型(自身)的属性
      const ownKeys=Reflect.ownKeys(target)
      if(ownKeys.includes(key)){
   
   
        console.log('get', key)
      }
      //下面不管是不是原型上的属性都要执行,要不然用不了原型上的方法
      const result = Reflect.get(target, key, receiver)
      //深度监听
      //性能优化,什么时候get才深度监听,惰性的
      return reactive(result) //返回结果
    },
    set(target, key, val, receiver) {
   
   
      //重复的数据不处理
      if(val===target[key])
      {
   
   
        return true
      }
      const ownKeys=Reflect.ownKeys(target)
      //监听是否是新增属性
      if(ownKeys.includes(key)){
   
   
        console.log('已有的key', key)
      }else{
   
   
        console.log('新增的key', key)
      }
      const result = Reflect.set(target, key, val, receiver)
      console.log('set', key, val)
      return result //是否设置成功
    },
    deleteProperty(target, key) {
   
   
      const result = Reflect.deleteProperty(target, key)
      console.log('delete', key)
      return result //是否删除成功
    },
  }
  const observed=new Proxy(target,proxyConf)
  return observed
}
proxyData.age=10000
proxyData.nickname='dong'
proxyData.list.push(4)
console.log(proxyData.list)

为什么data是一个函数

因为一个.vue文件编译出来是一个class,在不同地方使用相当于实例化类,如果不是函数数据会共享

父子组件生命周期调用

create从外到内,渲染mount从内到外。beforemounted mounted之间需要调用子组件,先执行完子组件(深度优先)

image.png

自己实现v-model

https://blog.csdn.net/qq_42753705/article/details/122960165

自定义事件

不同于click,keyup

https://blog.csdn.net/qq_40616529/article/details/93652453

suspense 处理异步组件

<Suspense> 
  <template #default> 
  <!--异步组件,当setup方法中的异步方法执行完后显示-->
  </ template>
  <template #fallback>
  <!--默认加载的内容,比如Loading提示或者图标-->
  </ template>
 </Suspense>

v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销

相关文章
|
JavaScript 前端开发 数据处理
vue2、vue3中使用$forceUpdate()
vue2、vue3中使用$forceUpdate()
1917 0
|
5月前
|
JavaScript 前端开发 API
什么是vue
什么是vue
63 4
|
JavaScript 前端开发 算法
Vue day01
Vue day01
60 0
|
6月前
|
JavaScript 前端开发 API
使用Vue
使用Vue
37 0
|
资源调度 JavaScript Linux
VUE使用总结
VUE使用总结
50 0
|
监控 JavaScript 前端开发
vue v-for
vue v-for
62 0
|
JavaScript
vue为什么避免v-if和v-for一起使用?
vue为什么避免v-if和v-for一起使用?
|
JavaScript 前端开发 API
详解——Vue小总结
详解——Vue小总结
96 0
|
JavaScript
Vue相关知识
Vue相关知识
66 0