学习vue3.0

vue3

Vite

  • 安装
  • npm init vite@latest -- --template vue
  • npm install
  • npm run dev

动态参数

  • 动态属性

  • v-bind绑定

  • <button @click="attributeName = 'class'">改变属性

  • 动态事件

  • <button @[mouseEvent]="attributeName = 'class'">改变属性

  • <button @click="mouseEvent = 'mouseover'">改变事件

  • computed 计算属性,只要依赖值不变,那么不会重新计算,计算属性基于它们的响应依赖关系缓存,提高性能

  • methids 方法,每当触发重新渲染时,调用方法将始终会再次执行函数

  • computed 简写

      computed: {
        reverseMsg: function() {
          return this.message.split('').reverse().join('')
        }
      }
    
  • computed 完整写法

      computed{
        // 每一个计算属性都一个getter和setter
        reverseMsg: {
          get: function() {
            return this.message.split('').reverse().join('')
          },
          // 计算属性一般是没有set方法,计算属性是只读属性
          set: function(newValue) {
            // 在设置或者更改计算属性的时候调用
            this.message = newValue
          }
        }
      }
      <button @click="reverseMsg = '你好'">改变reverseMsg</button>
    
  • watch 简写

      watch: { // 监听数据的变化
        // 每当message发生变化时,就会调用这个函数
        message: function(newValue,oldValue) {
          console.log(newValue,oldValue)
          // 执行异步操作,或者复杂逻辑代码
        }
      }
    
  • watch 完整写法

      watch: {
        message: {
          immediate: true,// 初始化的时候调用函数
          handler: function(newValue) {
            console.log(newValue)
          }
        }
      }
    
  • watch 监听不到对象的属性变化,需要使用深度监听

      watch: {
        user: {
          handler: function(newValue) {
            console.log(newValue)
          },
          deep: true // 表示是否深度监听,侦听器会一层层的向下遍历,给对象每个属性都加上侦听器
        },
        "user.name": {  // 使用字符串的形式进行优化,只会单独监听对象中对应的属性
          handler: function(newValue) {
            console.log(newValue)
          },
          deep: true
        }
      }
    
  • v-show 只是简单地切换元素的 display CSS property,带有 v-show 的冤死始终会被渲染并保留在 DOM 中 频繁切换状态

  • v-if 只有后面为 false,对应的元素以及子元素都不会被渲染,控制 dom 元素的创建和销毁, 运行时条件很少改变,一次性

  • key 唯一标识 快速找到节点,减少渲染次数,提升渲染性能

数组更新检测

变更方法

  • push() // 给数组末尾添加元素

  • pop() // 给数组末尾删除元素

  • shift() // 给数组的第一位删除

  • unshift() // 给数组首位开始添加元素

  • splice() // 删除//插入/替换元素

  • sort() // 排序

  • reverse() // 翻转

  • splice 第一个参数,表示插入或者开始删除的元素的位置下标

  • 删除元素

  • 第二个参数,表示传入要删除几个元素(如果没有传,就删除后面所有的元素)

  • 插入元素

  • 第二个元素,传入 0,并且后面接上要插入的元素

  • 替换元素

  • 第二个元素 ,表示替换几个元素,后面的参数表示用于替换前面的元素

list.splice(index,1)

  • 传递事件 @click="xxx(5,$event)"
  • 多事件处理器
  • 由逗号运算符分隔
  • <button @click="one($event),two($event)">

事件修饰符

  • .stop // 阻止事件冒泡

  • .prevent // 阻止默认行为

  • .capture

  • .self

  • .once // 只触发一次回调

  • .passive

        <!-- 阻止单击事件继续传播 -->
        <a v-on:click.stop="doThis"></a>
    
        <!-- 提交事件不再重载页面 -->
        <form v-on:submit.prevent="onSubmit"></form>
    
        <!-- 修饰符可以串联 -->
        <a v-on:click.stop.prevent="doThat"></a>
    
        <!-- 只有修饰符 -->
        <form v-on:submit.prevent></form>
    
        <!-- 添加事件监听器时使用事件捕获模式 -->
        <!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
        <div v-on:click.capture="doThis">...</div>
    
        <!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
        <!-- 即事件不是从内部元素触发的 -->
        <div v-on:click.self="doThat">...</div>
    

按键修饰符

  • .enter
  • .tab
  • .delete (捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right
  • keyCode (键盘编码) keyAlias (键盘的简写) 监听键盘的某个键帽

v-model

  • v-model 基本使用

      <input type="text" v-model="msg" />
      <h2>{{msg}}</h2>
    
  • v-model 的原理

  • 本质是两个操作

  • 1、v-bind 绑定一个 value 属性

  • 2、v-on 给当前元素添加一个 input 事件

      <input type="text" :value="msg" @input="changeValue" />
      changeValue(e) {
        this.msg = e.target.value
      }
    

修饰符使用

  • .lazy 当输入框失去焦点,再去同步输入框中的数据
  • <input type="text" v-model.lazy="msg"/>
  • .number 将输入框的内容自动转为数字类型
  • trim 自动过滤用户输入的首尾空白字符

vue 组件

  • 组件的首字母一般大写
  • 组件是带有名称的可复用实例,单独功能模块的封装
  • data 为什么是个函数并且返回 return {}

  • 让每一个组件对象都返回一个新的对象,不会造成数据污染

  • 子传父

  • 在子组件中,通过$emit 来触发事件

  • this.$emit('自定义事件的名称','发送的事件参数')

父子组件的访问方式

  • 父组件访问子组件$refs (常用) $children(vue2.0)
  • ref 用来给元素或者子组件注册引用信息
  • 子组件访问父组件 $parent (尽量少用) 因为组件的复用性很高
  • 子组件访问跟根据 $root

插槽 slot

  • <slot></slot>
  • 如果有多个值,同时放入组件替换时,一起作为替换元素

具名插槽

  • <slot name="xxx"></slot>
  • 使用 <template v-slot:header>xxx</template>
  • v-slot 只能添加到 template
  • <button slot="xxx">按钮</button>
  • 作用域插槽,父组件替换插槽的标签,但是数据由子组件来提供
  • provide/inject 并不是响应式

组合式 API(vue3 新增)

  • 将同一个逻辑关注点相关代码收集在一起
  • setup
  • setup 组件选项
  • beforeCreate 和 created 里的代码应该直接在 setup 函数中编写
  • setup 组件被创建之前执行,不需要使用 this,this 不会指向实例

  • 带 ref 的响应式变量

  • import { ref, reactive, toRefs,watch } from "vue"

  • const count = res(0)

      // 组合式API,将同一个逻辑关注点相关代码收集在一起
      setup() {
        // 组件被创建之前执行,不需要使用this,this不会指向实例
        // msg 的逻辑代码
        // 没有响应式
        let msg = 'hello';
        function changeMsg() {
          msg = 'nihao' // 数据不是响应式
        }
    
    
        // 通过ref定义响应式变量
        // counter的逻辑代码
        const counter = res(0) // ref()返回带有value属性的对象
        function changeCounter() {
          counter.value++
        }
    
        // 通过reactive定义响应式引用类型的数据
        // obj的逻辑代码
        const obj = reactive ({
          name: '张三',
          age: 18,
          children: {
            name: '小张'
          }
        })
        function changeObjName() {
          obj.name = "李四"
        }
        // toRefs(obejct)使结构后的数据重新获得响应式
        let { name,children} = toRefs(obj)
    
        // 通过es6扩展运算符进行结构使得
        return {msg,changeMsg,counter,changeCounter,obj,changeObjName,...toRefs(obj),name,children}
      }
      <!--模板会自动解析value值-->
      <div>{{counter}}</div>
      <div>{{obj.name}}</div>
      <div>{{obj.children.name}}</div>
    
  • ref 为我们的值创建了一个响应式引用。在整个组合式 API 中会经常使用引用的概念。

  • watch 响应式更改

        import { ref, reactive, watch,watchEffect } from "vue"
        setup() {
          const counter = ref(0);
          function changeCounter() {
            counter.value ++;
          }
    
          const user = reactive({
            name: '张三',
            age: 18
          })
          function changeUserName() {
            user.name = '李四'
          }
          // watch(侦听的响应式引用,回调函数)
          watch(counter,(newValue,oldValue)=> {
              console.log(newValue)
              console.log(oldValue)
          })
          watch(user,(newValue,oldValue)=> {
            console.log(newValue)
            console.log(oldValue)
          })
          // watchEffect(回调函数)注意不需要指定监听的属性,组件初始化的时候会执行一次回调函数,自动收集依赖
          watchEffect(() => {
            console.log(user.name)
          })
          return { counter,changeCounter,user,changeUserName }
    
        }
    
  • watch 和 watchEffect 的区别

  • 1、watchEffect 不需要监听的属性,自动收集依赖,只要在回调中引用到了响应式的属性,只要这些属性发生改变,回调就会执行,watch 只能监听指定的属性,做出回调函数的执行,可以侦听多个,vue3 开始后

  • 2、watch 可以获取到新值和旧值,watchEffect 拿不到

  • 3、watchEffect 在组件初始化的时候就会自动执行一次,用来收集依赖,watch 不需要,一开始就指定了

  • setup 使用 computed

      import { ref, computed } from "vue"
      setup() {
        const counter = ref(0);
        const msg = ref("helloworld");
        const reverseMsg = computed(() => {
          // 返回一个带有value属性的对象
          return msg.value.split("").reverse().join("")
        })
        console.log(reverseMsg.value)
        const user = reactive({
          name: '张三',
          age: 18,
          reverseMsg: computed(() => {
            // 返回一个带有value属性的对象
            return msg.value.split("").reverse().join("");
          })
          console.log(user.reverseMsg)
          return { counter,user,msg }
        })
      }
    
  • setup 使用生命周期

      import { onBeforeMount, onMounted, onBeforeUpdate,onUpdated } from "vue";
    
      setup() {
        // 生命周期函数可以使用多次
        //生命周期调用: 函数中有一个参数,回调函数
        onBeforeMount(() => {
          console.log('onBeforeMount')
        })
        onBeforeMount(() => {
          console.log('onBeforeMount')
        })
        return {}
      }
    
  • setup 函数它将接收两个参数

  • 1、props

  • 2、context

  • 注意: props 是响应式的,你不能使用 ES6 解构,它会消除 props 的响应性

  • 如果需要解构 prop,可以在 setup 函数中使用 toRefs 函数来完成此操作

      import { onUpdated,toRefs } from "vue"
      setup(props) {
        console.log(props)
        console.log(props.xxx)
        const { message } = toRefs(props); // 响应式
        console.log(message.value)
        onUpdated(() => {
          console.log(message.value)
    
        })
      }
    
  • 如果 title 是可选的 prop,则传入的 props 中可能没有 title,在这种情况下,toRefs 将不会为 title 创建一个 ref,你需要使用 toRef 替代它

      import { toRef } from "vue"
      setup(props) {
        const title = toRef(props,'title')
        console.log(title.value)
      }
    
  • Context 是 setup 函数的第二个参数

      setup(props,context) {
        console.log(context)
        // Attribute(非响应式对象,等同于$attrs)
        console.log(context.attrs)
        // 插槽 (非响应式对象,等同与$slots)
        console.log(context.slots)
        // 触发事件(方法,等同于$emit)
        console.log(context.emit)
        // 暴露公共property(函数)
        console.log(context.expose)
      }
    
  • h 渲染函数

      import { ref,h} from "vue"
      setup(props,context) {
        const counter = ref(20);
        function sendParent() {
          context.emit('injectCounter', counter.value)
        }
        context.expose({
          sendParent, counter
        })
        return () => h('div',counter.value)
      }
    
  • provide 和 inject 函数

  • 父组件

      import {provide,ref } from "vue"
      setup() {
        const name = ref('张三')
        provide('name',name)
        function changeName() {
          name.value = '李四'
        }
        return {changeName }
      }
    
  • 子组件

      import { inject } from "vue";
      setup() {
        const name = inject('name')
        console.log(name)
      }
    
  • setup 语法糖

      <script setup>
        // 顶层的绑定会被暴露给模板
        // 定义响应式的变量,还是需要从vue中引入
        import { ref } from "vue";
        // 引入组件,不需要注册
        import Content from "./Content.vue";
        // 定义变量,在模板使用不需要暴露出去,模板直接使用
        const a = 20;
        console.log(a)
        const b= ref(10);
        function addB() {
          b.value ++
        }
      </script>
      <template>
        <div>
          <h2>{{a}}</h2>
          <h2>{{b}}</h2>
          <button @click="addB">改变b</button>
          <Content />
        </div>
      </template>
    

Vue Router

  • 安装
  • npm install vue-router@4
  • 路由核心:改变 URL,但是页面不进行整体刷新
  • 路由理解为指向
  • 路由表,是一个映射表,一个路由就是一组映射关系,key:value,key:表示路由,value:可以为 function 或者 Component(组件)
  • setup 拿到路由参数

      <script setup>
        import { useRoute } from "vue-router"
        console.log(userRoute().params.id)
      </script>
    
  • vue-router-404 页面

  • path: '/:path(.*)' // 使用正则的方式,匹配任意的

      {
        path: '/:path(.*)',
        component: xxx
      }
    
  • 路由正则与重复参数

  • path: "/news/:id(\d+)" // 动态路由的参数一定是数字

  • path: "/news/:id+" // 有多个参数

  • path: "/news/:id*" // 参数是可有可无 * 参数可以重复叠加

  • path: "/news/:id?" // 参数是可有可无 ? 参数不可以重复叠加

编程式导航

  • <router-link :to="xxx" /> // 声明式
  • router.push(xxx) // 编程式
  • this.$router.push
  • $router 路由实例对象 push, forword,go 方法
  • $route 当前活跃的路由对象,拿到 path,params,query,name
  • router.push("/users/deuardo") // 字符串路径
  • router.push({path: '/users/deuardo'}) // 带有路径的对象
  • router.push({name: 'user',params: {username: 'eduardo'}}) // 命名的路由,并加上参数,让路由建立 url
  • router.push({ path: '/register', query: { plan: 'private' } }) // 带查询参数,结果是 /register?plan=private
  • router.push({path: '/about',hash: '#team'}) // 带 hash,结果是 /about#team

替换当前位置

  • <router-link :to="..." replace> // 声明式

  • router.replace(...) // 编程式

      router.push({path: '/home',replace: true})
      // 相当于
      router.replace({path: '/home'})
    
  • 横跨历史

  • router.go(1) // 向前移动一条记录,与 router.forward()相同

  • router.go(-1) // 返回一条记录,与 router.back()相同

  • router.go(3) // 前进 3 条记录

  • router.go(-100) // 如果没有那么多记录,静默失败

  • router.go(100)

命名视图

  • 一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置带上 s

      <router-view class="view left-sidebar" name="LeftSidebar"></router-view>
      <router-view class="view main-content"></router-view>
      <router-view class="view right-sidebar" name="RightSidebar"></router-view>
      const router = createRouter({
        history: createWebHashHistory(),
        routes: [
          {
            path: '/',
            components: {
              default: Home,
              // LeftSidebar: LeftSidebar 的缩写
              LeftSidebar,
              // 它们与 `<router-view>` 上的 `name` 属性匹配
              RightSidebar,
            },
          },
        ],
      })
    

重定向

const routes = [
  {
    path: '/',
    // 重定向
    redirect: '/home',
    // 命名路由
    redirect: { name: "home"},
    // 方法
    redirect:(to) => {
      console.log(to)
      return { path: '/home'}
    }
  }
]

别名

  • const routes = [{ path: '/', component: Homepage, alias: '/home' }]
  • alias: ['/people', 'list'] // 起多个名

路由组件传参

  • setup 中接收参数

      <script>
        const props = defineProps({
          id: String
        })
        console.log(props)
      </script>
    

导航守卫

全局前置守卫

  • 注册一个全局前置守卫 router.beforeEach

      const router = createRouter({...})
      router.beforeEach((to,from,next) => {
        // 返回false已取消导航
       //  return false
        next() // 通行证
      })
    
  • to: 即将要进入的目标
  • from: 当前导航正要离开的路由

路由独享的守卫

    const routes = [
      {
        path: '/users/:id',
        component: UserDetails,
        beforeEnter: (to, from) => {
          // reject the navigation
          return false
        },
      },
    ]

组件内的守卫

  • beforeRouteEnter

  • beforeRouteUpdate

  • beforeRouteLeave

      const UserDetails = {
        template: `...`,
        beforeRouteEnter(to, from,next) { // 路由进入组件之前
          // 在渲染该组件的对应路由被验证前调用
          // 不能获取组件实例 `this` !
          // 因为当守卫执行时,组件实例还没被创建!
          next((vm) => { // 拿不到实例对象,通过next的回调函数
            console.log(vm.age)
          })
        },
        beforeRouteUpdate(to, from) { // 路由更新组件之前
          // 在当前路由改变,但是该组件被复用时调用
          // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
          // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
          // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
        },
        beforeRouteLeave(to, from) { // 路由离开组件之前
          // 在导航离开渲染该组件的对应路由时调用
          // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
        },
      }
    

路由懒加载

  • 路由懒加载,用到时在加载

      // 将
      // import UserDetails from './views/UserDetails.vue'
      // 替换成
      const UserDetails = () => import('./views/UserDetails.vue')
    
      const router = createRouter({
        // ...
        routes: [{ path: '/users/:id', component: UserDetails }],
      })
    

状态管理

  • store/index.js

      // 状态集中管理
      // 数据实现响应式
      // ref reactive ---> 对象中存储着状态msg,age,counter
      import { reactive } from "vue"
      const store = {
        state: reactive({// 定义状态
          msg: 'helloworld'
        }),
        updateMsg: function () {
          this.state.msg = '你好'
        }
      }
      // 如何在App组件通过provide提供
      export default store
    
  • App.vue

      <script>
        import Home from "./Home.vue";
        import store from "./store";
        // vue3中如何设置状态管理
        // provide/inject 跨级通信
        export default {
          provide: {
            store
          },
          components: {
            Home
          }
        }
      </script>
      <template>
        <Home />
      </template>
    
  • Home.vue

      <template>
        <div>
          <div>{{store.state.msg}}</div>
          <button @click="updateMsg">改变msg</button>
        </div>
      </template>
      <script>
        export default {
          inject: ['store'],
          methods: {
            updateMsg: function() {
              this.store.updateMsg()
            }
          }
        }
      </script>
    

通过 proxy 解决跨域问题

  • 跨域请求数据,浏览器同源策略的保护机制,通过 proxy 实现跨域请求数据

      proxy: {
        "/": {
          target: 'http:xxx',
          ws: false,
          changeOrigin: true, // 开启代理允许跨越
        },
      },
    

VUEX

  • store.js

      import { createStore } from "vuex";
      import axios from "axios"
      const moduleA = {
        state() {// 通过$store.state.模块名.状态名获取
          return {
            userName: '用户张三'
          }
        },
        getters: { // 通过$store.getters可以直接获取
          // state局部状态
          userNameAge: function(state,getters,rootState) {
          // getters 是store实例的getters对象
          // rootState 第三个参数 获取跟节点实例的state状态
            return state.userName+',今年18岁了'
          }
        },
        mutations: {// 触发提交函数,直接通过$store.commint即可,actions也是一样
        // state 局部状态
          updateUserName: function(state) {
            state.userName = '李四'
          }
    
        }
      }
      // 创建store 实例
      const store = createStore({
        state() { // 是存储的单一状态,是存储的基础数据
          return {
            count: 0,
            msg: 'helloworld',
            hotList: []
          }
        },
        // 如果state的状态需要过滤或者其他的操作
        getters: {// 认为是store中的计算属性
          reverMsg: function(state) {
            return state.msg.split("").reverse().join('')
          },
          reverMsgLength: function(state,getters) { // getters表示当前store中的getters对象
            return getters.reverMsg.length
          }
        },
        mutations: { // 通过store.commit 方法触发对应函数状态变更,mutation是同步函数
          increment(state,value) { // state表示上面state 返回的对象,value 表示传过去的参数
            state.count += value
          },
          updateHotList: function(state,vlaue) {
            state.hotList = value;
          }
        },
        actions: {
          getHot: function() {
            fetch('xxx').then((res) => {
              console.log(res)
            })
          },
          //payload 第二个参数
          getHot1: function(context,payload) { // context:与store实例具有相同的属性和方法的对象
            axios.get('xxx').then((res) => {
              console.log(res)
              context.commit('updateHotList',res.data)
            })
          }
        },
        modules: {
          a: moduleA
        }
      })
      export default store
    
  • 使用

      <div>{{$store.state.count}}</div>
      <button @click="$store.commit('increment',5)">count++</button>
      <h2>{{$store.getters.reverMsg}}</h2>
      <h2>{{$store.getters.reverMsgLength}}</h2>
      <h2>{{$store.getters.userNameAge}}</h2>
      <h2>{{$store.state.a.userName}}</h2>
      <button @click="$store.commit('updateUserName')">改变名字</button>
    
      mouted() {
        this.$store.dispatch('getHot')
        this.$store.dispatch('getHot1','hh')
      }
    
  • vuex 的基本使用-辅助函数

  • mapState 辅助函数

      <h2>{{count}}<h2>
      <script>
      import { mapState, mapMutations,mapGetters,mapActions  } from "vuex"
      export default {
        computed: mapState({
          // count: state => state.count
          count: 'count'
        }),
        computed: mapState(['count']),
        methods: {
          addCount(num) {
            this.$store.commit('increment',num) // 使用store.commit方法改变state的状态
            this.increment(5)
          },
          changeName() {
            this.$store.commit('a/updateUserName')
            this.updateUserName()
          },
          ...mapMutations(['increment])
          ...mapMutations('a',['updateUserName'])
          ...mapActions(['getHot'])
        }
    
      }
      </script>
    
  • 对象展开运算符

        computed: {
          xxx() {},
          // 使用对象展开运算符将此对象混入到外部对象中
          ...mapState({})
          ...mapState(['count','msg'])
          ...mapGetters(['reverMsg'])
        },
        mouted() {
          console.log(this.count)
          console.log(this.msg)
        }
    
  • module 命名空间

  • namespaced: true 的方式使其成为带命名空间的模块

  • $store.getters['a/userNameAge']

  • v-for 和 v-if 为什么不建议在一起使用?

  • v-for 优先级高于 v-if,如果将两者放在一块,会先执行 v-for 循环列表,再执行 v-if 去判断,造成性能浪费

  • 一般用计算属性替换掉

  • vue 中如何使用自定义指令?

  • 指令 v-bing v-if v-for v-model v-on 不同的指令实现不同的功能

  • 局部注册和全局注册

  • 局部注册

      <input type="text">
      // 使用
      <input type="text" v-focus>
    
      directives: { // 自定义指令, 不需要写v-,使用的时候添加v-
        focus: {
          inserted: function (el) {// 表示被绑定的元素插入父节点的时候调用
            //el 表示指令所绑定的元素
            // binding 对象,属性name:指令名  value: 指令的绑定值
            el.focus() // 获取焦点
          }
        }
      }
    
  • 全局注册

  • main.js

      // 全局注册自定义指令
      Vue.direactive('指令的名字','对象数据也可以是一个指令函数')
      Vue.direactive('focus',{
        inserted: function (el) {// 表示被绑定的元素插入父节点的时候调用
          //el 表示指令所绑定的元素
          // binding 对象,属性name:指令名  value: 指令的绑定值
          el.focus() // 获取焦点
        }
      })
    
  • vue 中什么是$nextTick?

  • 是将回调函数延迟在下一次 dom 更新数据之后调用

  • vue 是异步渲染的框架,数据更新之后,dom 是不会立刻渲染,$nextTick 会在 dome 渲染之后被触发,用来获取最新的 dom 节点

  • 使用场景:

  • 1、在生命周期函数 create 进行 dom 操作,一定要放到$nextTick 函数中执行(created()中进行 DOM 操作)

  • 2、在数据变化后要执行某个操作,而这个操作需要使用数据变化而变化的 dom 结构时,这个操作需要放到 nextTick 中 (获取数据更新之后的 DOM)

  • 3、获取元素宽度

posted @ 2022-11-22 17:10  不完美的完美  阅读(44)  评论(0编辑  收藏  举报