Vue3知识大纲

333 阅读6分钟

Vue.js起步

数据到视图映射

image.png

<!DOCTYPE html>
<html lang="en">
<head>
  <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
  <div id="app">
    <fieldset>
      <legend>数据驱动视图</legend>
      {{ message }}
    </fieldset>
  </div>

  <script>
    const App = {
      data() {
        return {
          message: 'Hello World'
        }
      }
    }

    Vue.createApp(App).mount('#app')
  </script>
</body>
</html>

修改数据

image.png

<!DOCTYPE html>
<html lang="en">
<head>
  <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
  <div id="app">
    <fieldset>
      <legend>数据驱动视图</legend>
      <button @click="add">Click Me</button>
      {{ counter }}
    </fieldset>
  </div>

  <script>
    const App = {
      data() {
        return {
          counter: 0
        }
      },
      methods: {
        add() {
          this.counter++
        }
      },
    }

    Vue.createApp(App).mount('#app')
  </script>
</body>
</html>

属性绑定

image.png

<!DOCTYPE html>
<html lang="en">
<head>
  <script src="https://unpkg.com/vue@next"></script>
</head>

![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/735ebe023180428c86fde3988d5398a6~tplv-k3u1fbpfcp-watermark.image?)
<body>
  <div id="app">
    <fieldset>
      <legend>属性的绑定</legend>
      <a :href="url" :title="message">百度</a>
      {{ message }}
    </fieldset>
  </div>

  <script>
    const App = {
      data() {
        return {
          url: 'https://baidu.com',
          message: 'Hello World'
        }
      }
    }

    Vue.createApp(App).mount('#app')
  </script>
</body>
</html>

双向绑定

image.png

<!DOCTYPE html>
<html lang="en">
<head>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="app">
    <fieldset>
      <legend>双向绑定</legend>
      <input type="text" v-model="search">
      {{search}}
    </fieldset>
  </div>

  <script>
    const App = {
      data() {
        return {
          search: "hello"
        }
      }
    }

    Vue.createApp(App).mount('#app')
  </script>
</body>
</html>

条件渲染

image.png

<!DOCTYPE html>
<html lang="en">
<head>
  <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
  <div id="app">
    <fieldset>
      <legend>条件渲染</legend>
      <p v-if="isShow"> 子组件</p>
      <button @click="isShow = !isShow">Click</button>
    </fieldset>
  </div>

  <script>
    const App = {
      data() {
        return {
          isShow: true
        }
      },
      // methods: {
      //   toggle() {
      //     this.isShow = !this.isShow
      //   }
      // },
    }

    Vue.createApp(App).mount('#app')
  </script>
</body>
</html>

循环渲染

image.png

<!DOCTYPE html>
<html lang="en">
<head>
  <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
  <div id="app">
    <fieldset>
      <legend>列表渲染</legend>
      <ul>
        <li v-for="value in items"> {{value}}</li>
      </ul>
      <button @click="add">Click</button>
    </fieldset>
  </div>

  <script>
    const App = {
      data() {
        return {
          items: ['item0', 'item1', 'item2']
        }
      },
      methods: {
        add() {
          this.items.push('item' + this.items.length)
        }
      },
    }

    Vue.createApp(App).mount('#app')
  </script>
</body>
</html>

自定义组件

image.png

<!DOCTYPE html>
<html lang="en">
<head>
  <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
  <div id="app">
    <fieldset>
      <legend>自定义组件</legend>
      <todo-item></todo-item>
      <button @click="add">Click</button>
    </fieldset>
  </div>

  <script>
    const App = {
      data() {
        return {
        }
      }
    }


    const app = Vue.createApp(App)
    app.component('todo-item', {
      data() {
        return { message: 'hello' }
      },
      template: "<div>{{message}} </div>"
    })
    app.mount('#app')
  </script>
</body>
</html>

组件实例、生命周期

1、Vue有哪些⽣命周期钩⼦?

  • beforeCreate 在实例初始化之后,数据观测(data observer)和event/watcher事件配置之前被调用
  • created 在实例创建完成后被立即调用。在这一步,实例已完成以下的配置︰数据观测(data observer),property和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el property目前尚不可用
  • beforeMount 在挂载开始之前被调用∶相关的render函数首次被调用
  • mounted 实例被挂载后调用,这时 Vue.createApp({}).mount()被新创建的vm.$el替换了。如果根实例挂载到了一个文档内的元素上,当mounted被调用时vm.$el也在文档内。
  • beforeUpdate 数据更新时调用,发生在虚拟DOM打补丁之前
  • updated 由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子
  • beforeUnmount 在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的
  • unmounted 卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载

image.png

2、 如果需要发送Ajax请求,最好放在哪个钩⼦内?

一般情况下,都放在mounted中,保证逻辑的统一性。因为生命周期是同步执行的,ajax是异步执行的。

服务端渲染不支持mounted方法,所以在服务端渲染的情况下统一放在created中。

3、 ⽗⼦组件嵌套时,⽗组件视图和⼦组件视图渲染完成谁先谁后?

不确定

4、 ⽗⼦组件嵌套时,如果希望在所有组件视图都渲染完成后再执⾏操
作,该如何做?

   mounted() { 
     this.$nextTick(function () { 
   // 仅在渲染整个视图之后运⾏的代码 
   });
   }

应用实例与组件实例

image.png

<!DOCTYPE html>
<html lang="en">
<head>
  <script src="https://unpkg.com/vue@next"></script>
</head>

<body>

  <div id="app">
    <fieldset>
      <legend>组件实例</legend>
      {{ message }}
      <button @click="change">Click</button>
    </fieldset>
  </div>

  <script>
    const App = {
      data() {
        return {
          message: 'Hello Vue!!'
        }
      },
      methods: {
        change() {
          console.log(this)
          this.message += '!'
        }
      },
    }

    const app = Vue.createApp(App)
    console.log(app)
    const vm = app.mount('#app')
    console.log(vm)
  </script>


</body>
</html>

问题:左侧代码中的this是什么?

vm对象

生命周期

image.png

<!DOCTYPE html>
<html lang="en">
<head>
  <script src="https://unpkg.com/vue@next"></script>
</head>
  <div id="app">
    <fieldset>
      <legend>生命周期钩子</legend>
      <span id="msg">{{ message }}</span>
      <button @click="change">Click</button>
    </fieldset>
  </div>
  <script>
    const App = {
      data() {
        return { message: 'Hello Vue'}
      },
      methods: {
        change() {
          this.message += '!'
        }
      },
      beforeCreate() {
        console.log('beforeCreate', this.message, this.$el)
      },
      created() {
        console.log('created', this.message, this.$el)
      },
      beforeMount() {
        console.log('beforeMount', this.message, this.$el)
      },
      mounted() {
        console.log('mounted', this.message, this.$el) 
      },
      beforeUpdate() {
        console.log('beforeUpdate', this.message, document.querySelector('#msg').innerText)
      },
      updated() {
        console.log('updated', this.message, document.querySelector('#msg').innerText)
      }
    }

    Vue.createApp(App).mount('#app')
  </script>

</body>
</html>

组件销毁生命周期代码演示

image.png

<!DOCTYPE html>
<html lang="en">
<head>
  <script src="https://unpkg.com/vue@next"></script>
</head>

<body>

  <div id="app">
    <fieldset>
      <legend>生命周期钩子</legend>
      <child v-show="isShow"></child>
      <button @click="isShow = !isShow">Click</button>
    </fieldset>
  </div>

  <script>
        const App = {
      data() {
        return { isShow: true }
      },
      beforeCreate() {
        console.log('root beforeCreate', this.message, this.$el)
      },
      created() {
        console.log('root created', this.message, this.$el)
      },
      beforeMount() {
        console.log('root beforeMount', this.message, this.$el)
      },
      mounted() {
        console.log('root mounted', this.message, this.$el) 
      }
    }
    const app = Vue.createApp(App)
    app.component('child', {
      template: '<div>{{text}}</div>',
      data() { 
        return {text: 'I am child'}
      }, 
      created() {
        console.log('child  created')
      },
      mounted() {
        console.log('child mounted')
      },
      beforeUnmount() {
        console.log('child  beforeUnmount')
      },
      unmounted() {
        console.log('child  unmounted')
      },
    })

    app.mount('#app')

  </script>


</body>
</html>

data、methods、computed、watch

指令

参考文档

image.png

data methods

image.png

computed

image.png

methods和computed的区别

  1. 调用方式不同。computed直接以对象属性方式调用,不需要加括号,而methods必须要函数执行才可以得到结果。
  2. 绑定方式不同。methods与compute纯get方式都是单向绑定,不可以更改输入框中的值。compute的get与set方式是真正的双向绑定。
  3. 是否存在缓存。methods没有缓存,调用相同的值计算还是会重新计算。competed有缓存,在值不变的情况下不会再次计算,而是直接使用缓存中的值。

watch

image.png

小结

  • v-text 和 v-html 有什么区别
  • data 为什么要是函数
  • 计算属性缓存是什么,如果不想缓存该怎么做
  • watch、计算属性有什么区别

image.png

响应式原理

什么是响应式

数据响应式

image.png 范例

var obj = {}
var age 
Object.defineProperty(obj, 'age', {
    get: function(){
        console.log('get age...')
        return age
    },
    set: function(val){
        console.log('set age...')
        age = val
    }
})
obj.age = 100  // 'set age...'
console.log(obj.age) // 'get age...', 100

Object.defineProperty实现响应式

function observe(data) {
  if(!data || typeof data !== 'object') return
  for(var key in data) {
    let val = data[key]  
    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: true,
      get: function() {
        track(data, key)
        return val
      },
      set: function(newVal) {
        trigger(data, key, newVal)
        val = newVal
      }
    })
    if(typeof val === 'object'){
      observe(val)
    }
  }
}

function track(data, key) {
  console.log('get data ', key)
}

function trigger(data, key, value) {
  console.log('set data', key, ":", value)
}

var data = {
  name: 'hunger',
  friends: [1, 2, 3]
}
observe(data)

console.log(data.name)
data.name = 'valley'
data.friends[0] = 4
data.friends[3] = 5 // 非响应式
data.age = 6  //非响应式

image.png

Proxy和Reflect

image.png

const dinner = {
  meal: 'tacos'
}

const handler = {
  get(target, prop) {
    console.log('get...', prop)
    //return target[prop]
    return Reflect.get(...arguments)
  },
  set(target, key, value) {
    console.log('set...', key, value)
    //target[key] = value
    return Reflect.set(...arguments)
  }
}

const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)

Proxy实现响应式

 function reactive(obj) {
    const handler = {
      get(target, prop, receiver) {
        track(target, prop)
        const value = Reflect.get(...arguments)
        if (typeof value === 'object') {
          return reactive(value)
        } else {
          return value
        }
      },
      set(target, key, value, receiver) {
        trigger(target, key, value)
        return Reflect.set(...arguments)
      }
    }
    return new Proxy(obj, handler)
  }
  function track(data, key) {
    console.log('get data ', key)
  }

  function trigger(data, key, value) {
    console.log('set data', key, ":", value)
  }


  const dinner = {
    meal: 'tacos'
  }
  const proxy = reactive(dinner)
  proxy.meal = 'apple'
  proxy.list = []
  proxy.list.push(1) //响应式

小结

  • Vue3和Vue2的响应式原理分别是什么,二者有什么差异
  • 手写reactive 实现track trigger
  • 用Proxy和Object.defineProperty相比有什么优点和缺点

image.png

条件、列表、事件、组件、双向绑定、单向数据流

v-if和v-show的区别

image.png

<div id="app">
  <button @click="isShow=!isShow">Toggle</button>
  <p v-if="isShow">v-if content</p>
  <p v-show="isShow">v-show content</p>
  <child v-if="isShow" title="v-if"></child>
  <child v-show="isShow" title="v-show"></child>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
  const app = Vue.createApp({
    data() {
      return { isShow: true }
    }
  })
  app.component('child', {
    props: ['title'],
    template: `<div>component {{title}}</div>`,
    created() {
      console.log('child created', this.title)
    },
    unmounted() {
      console.log('child unmounted', this.title)
    }
  })
  app.mount('#app')
</script>

v-for列表渲染

image.png

<div id="app">
  <div v-for="(item,index) in items" >
    <child></child>
    <button @click="remove(index)">delete</button>
  </div>
  <button @click=add>add</button>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
  //[{id:1}, {id:2}, {id:3}]
  //[child1, child2, child3]
  const app = Vue.createApp({
    data() { return { items: [] } },
    methods:{
      remove(index) { 
        console.log('remove ', index)
        this.items.splice(index,1) 
      },
      add() { this.items.push({id: Math.random()}) }
    }
  })
  app.component('child', {
    data() { return { counter: Math.random() } },
    template: `<span>{{counter}} </span>` ,
    unmounted() {
      console.log(this.counter + ' unmounted')
    }, 
  })
  app.mount('#app')
</script>

事件处理

image.png


<div id="app">
<span v-on:click="sayHello">click</span>
<!-- <span @click="sayHello">click</span>
<span @click="sayHello('hunger')">click</span>
<span @click="sayHello($event), sayHi('hunger')">click</span>
<span @click.once="sayHello(name)">click</span> -->
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>

  Vue.createApp({
    data() {
      return {
        name: 'hunger'
      }
    },
    methods: {
      sayHello(name) {
        console.log(name)
      },
      sayHi(name) {
        console.log(name)
      }
    },
  }).mount('#app')
</script>

v-model

image.png

<div id="app">
  <fieldset>
    <legend> value 和 input </legend>
    <!-- value 和 input -->
    <input v-model="message" /> {{ message }}
  </fieldset>
  <fieldset>
    <legend> value 和 change </legend>
    <!-- value 和 change -->
    <textarea v-model.lazy="message"></textarea> {{ message }}
  </fieldset>
  <fieldset>
    <legend> 复选框 </legend>
    <input type="checkbox" v-model="checked" /> {{checked}}
  </fieldset>
  <fieldset>
    <legend> 多个复选框 </legend>
    <input type="checkbox" value="a" v-model="list" />
    <input type="checkbox" value="b" v-model="list" /> {{list}}
  </fieldset>
  <fieldset>
    <legend> 单选框 </legend>
    <input type="radio" value="a" v-model="theme" />
    <input type="radio" value="b" v-model="theme" /> {{theme}}
  </fieldset>

  <fieldset>
    <legend> select </legend>
    <select v-model="selected" multiple>
      <option value="AA">A</option>
      <option value="BB">B</option>
      <option value="CC">C</option>
    </select>
    <br />
    <span>Selected: {{ selected }}</span>
  </fieldset>

</div>

<script src="https://unpkg.com/vue@next"></script>
<script>
  Vue.createApp({
    data() {
      return {
         message:  'aa',
         checked: false,
         list: [],
         theme: '',
         selected: []
      }
    }
  }).mount('#app')
</script>

组件基础

image.png

<div id="app">
  <font-size step="1" :val="fontSize" @plus="fontSize += $event"    
             @minus="fontSize -= $event"></font-size>
  <font-size step="3" :val="fontSize" @plus="fontSize += $event" 
             @minus="fontSize -= $event"></font-size>
  <p :style="{fontSize:fontSize+'px'}">Hello {{fontSize}}</p>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
  const app = Vue.createApp({
    data() { return { fontSize: 18 } }
  })
  app.component('font-size', {
    props: ['val', 'step'],
    template: `
      <div>step: {{step}}
        <button @click="onPlus">+</button>
        <button @click="$emit('minus', step)">-</button>
      </div>`,
    methods: {
      onPlus() { this.$emit('plus', parseInt(this.step)) }
    }
  })
  app.mount('#app')
</script>

组件的v-model双向绑定

image.png

  • 父组件通过v-model="属性"把属性传递给子组件。
  • 子组件内有一个modelvalue的ProP,接收父组件传递的数据。
  • 子组件通过触发update:modelValue修改父组件绑定的属性
<div id="app">
  <font-size step="1" v-model="fontSize"></font-size>
  <font-size step="4" v-model="fontSize"></font-size>
  <p :style="{fontSize:fontSize+'px'}">Hello {{fontSize}}</p>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
  const app = Vue.createApp({
    data() { return { fontSize: 16 } }
  })
  app.component('font-size', {
    props: ['modelValue', 'step'],
    template: `
      <div>step: {{modelValue}}
        <button @click="$emit('update:modelValue', +step+modelValue)">+</button>
        <button @click="$emit('update:modelValue', modelValue-step)">-</button>
      </div>`
  })
  app.mount('#app')
</script>

单向数据流

  • 什么是单向数据流?
    很多框架都使用单向数据流,指的是父组件能直接传递数据给子组件,子组件不能随意修改父组件状态。
  • 为什么要单向?
    单向数据流的目的是让数据的传递变得简单、可控、可追溯。假设都是用双向数据流,父组件维护一个状态,并且传递给所有的子组件。当其中一个子组件通过双向数据流直接修改父组件的这个状态时,其他子组件也会被修改。当这个结果产生时我们无法得知是拿个子组件修改了数据,在项目庞大时数据的管理和追湖变得更复杂。
  • 如果要双向如何实现?
    一般来说,父组件可用通过设置子组件的props直接传递数据给子组件。子组件想传递数据给父组件时,可以在内部mit一个自定义事件,父组件可在子组件上绑定该事件的监听,来处理子组件mit的事件和数据。 在Vue里,v-models实现的所谓双向绑定,本质上就这种方式。

小结

1、列表循环时key的作用?

  • 简单通俗地讲,没有key时,状态默认绑定的是位置,有key时,状态根据key的属性值绑定到了响应的数组元素。
  • key是为了更高效的更新虚拟DOM
  • 推荐使用数组内的字段(保证唯一性)作为key的唯一标识,不建议直接使用index 2、@click="handler"和@click="handler()"哪个正确?

3、父子组件如何传递数据?

4、单向数据流指的是什么?有什么好处? 5、v-model是如何实现的

Vue3组件注册、Props、Attribute、自定义事件

组件注册

image.png 组件注册

Props

image.png

非Prop的属性

image.png

自定义事件

image.png

v-model语法糖

image.png

小结

  • 在Vue中组件的全局注册和局部注册有什么区别,如何局部注册组件?\
  • 如何传递一个字符串类型的prop给子组件?数字类型呢?如何动态给prop赋值?\
  • 对于组件来说非prop的attribute怎么处理?\
  • v-model:foo="bar”与:foo="bar"有什么区别?如何实现v-model:foo="bar” ?\
  • Vue中的插槽是什么?\
  • 如何实现多层级嵌套的父子组件数据传递?\
  • keep-alive有什么作用

插槽、具名插槽、作用域插槽

slot插槽

image.png

具名插槽

image.png

作用域插槽

image.png

keep-alive与生命周期

image.png

image.png

小结

  • Vue中的插槽是什么?
  • 具名插槽怎么用?
  • 作用域插槽是什么?

爷孙组件数据传递

Provide和Inject

image.png

image.png

过渡与动画

使用class切换实现过渡和动画

image.png

修改style实现过渡和动画

image.png

transition组件创建的class

image.png

transition组件和animate.css便捷实现特

多个元素轮流切换过渡实现

image.png

动态组件专场效果实现

image.png

transition-group

image.png

使用Vue CLI4搭建Vue项目

image.png

目录结构与执行流程

image.png

引入Sass解决各种bug

image.png

scoped

image.png

@vue:cli-service# @vue:cli-service

image.png

vue.config.js配置

image.png

webpack

image.png

Vite 搭建项目

vite是什么解决什么问题

image.png

诞生思路

image.png

vite体验

image.png

Composition API

选项式API的痛点

image.png

组合式API的优势和用法

image.png

用法

选项式API改造成组合式API

生命周期钩子概览

image.png

Setup、ref、reactive、toRef、toRefs用法详解

image.png

image.png

image.png

VueRouter

VueRouter4的安装

image.png

VueRouter4的使用

image.png

原生JS造一个Hash模式的Router

image.png

原生JS造一个History模式的Router

image.png

VueRouter4的Hash模式和History模式

image.png

动态路由与响应参数变化(路由匹配)

image.png

导航守卫

image.png

在组合式API中使用VueRouter

image.png

beforeRouteUpdate路由守卫的使用场景

路由懒加载

image.png

路由切换过渡动效

image.png

Vuex4.0的使用和原理

组件间数据传递的几种方法、为什么需要Vuex

image.png

EventBus的不便之处

image.png