Vue2 到 Vue3 有哪些变化

10 篇文章 0 订阅

6.20

vue3 升级

1.data 初始化数据

  • vue2,根组件中可以使用对象,vue3 中必须使用函数定义
  data(){
    return {}
  }

2.vue3 初始化实例

  • 在 vue3 中创建一个新的应用实例使用 Vue.createApp()
<div id="counter">Counter: {{ counter }}</div>
const Counter = {
  data() {
    return {
      counter: 0,
    };
  },
};

Vue.createApp(Counter).mount("#counter");

3.组件实例

  • 每个 Vue 应用都是通过用 createApp 函数创建一个新的应用实例开始的:
const app = Vue.createApp({});
  • 该应用实例是用来在应用中注册全局z 组件的
const app = Vue.createApp({});
app.component("SearchInput", SearchInputComponent); //注册全局组件
app.directive("focus", FocusDirective); //自定义指令
app.use(LocalePlugin); //引入插件

应用实例暴露的大多数方法都会返回该同一实例,允许链式:

Vue.createApp({})
  .component("SearchInput", SearchInputComponent)
  .directive("focus", FocusDirective)
  .use(LocalePlugin);

4.生命周期(beforeUnmount,unmounted)

  • vue3 对于 vue2 来说生命周期改变了销毁的生命周期,不过用法还是一样
  • vue3 里边多了一个销毁的方法 app.unmount()
const app = Vue.createApp({
  data() {
    return {
      title: "hello Vue3.",
    };
  },

  beforeUnmount() {
    console.log("beforeUnmount");
  },

  unmounted() {
    console.log("unmounted");
  },
});

app.mount("#root");

setTimeout(() => {
  app.unmount(); // 销毁这个实例
}, 3000);

5.Data Property 和 方法

  • vue3 中的 data 只能定义为函数
  • 防抖和节流,vue 中没有内置的防抖和节流,不过可以使用第三方的库 lodash 来实现防抖和节流
  • lodash
    • 如果某个组件仅使用一次,可以在 methods 中直接应用防抖:
<script src="https://unpkg.com/lodash@4.17.20/lodash.min.js"></script>
<script>
  Vue.createApp({
 methods: {
   // 用 Lodash 的防抖函数
   click: _.debounce(function() {
  // ... 响应点击 ...
   }, 500)
 }
  }).mount('#app')
</script>
  • 但是,这种方法对于可复用组件有潜在的问题,因为它们都共享相同的防抖函数。为了使组件实例彼此独立,可以在生命周期钩子的 created 里添加该防抖函数:
app.component("save-button", {
  created() {
    // 使用 Lodash 实现防抖
    this.debouncedClick = _.debounce(this.click, 500);
  },
  unmounted() {
    // 移除组件时,取消定时器
    this.debouncedClick.cancel();
  },
  methods: {
    click() {
      // ... 响应点击 ...
    },
  },
  template: `
    <button @click="debouncedClick">
      Save
    </button>
  `,
});

6.v-ifv-for

  • 在 vue2 中,当v-ifv-for同时使用时,v-for的权重高
  • 在 vue3 中,v-if的权重更高,而且在 vue3 中 v-for 也不需要我们写 v-key 来使用了
  • v-for 中的 Ref 数组
    • 在 Vue 2 中,在 v-for 中使用的 ref attribute 会用 ref 数组填充相应的 $refs property。当存在嵌套的 v-for 时,这种行为会变得不明确且效率低下。
    • 在 Vue 3 中,此类用法将不再自动创建 $ref 数组。要从单个绑定获取多个 ref,请将 ref 绑定到一个更灵活的函数上 (这是一个新特性):

7.多事件处理器

  • vue3 中允许绑定多个方法,事件处理程序中可以有多个方法,这些方法由逗号运算符分隔:
<!-- 这两个 one() 和 two() 将执行按钮点击事件 -->
<button @click="one($event), two($event)">Submit</button>
// ...
methods: {
  one(event) {
    // 第一个事件处理器逻辑...
  },
  two(event) {
   // 第二个事件处理器逻辑...
  }
}

8.按键修饰器

  • 在 vue2 中,可以通过 keyCode 来修改 v-on 的方法

    • 使用键码 <input v-on:keyup.13="submit" />

    • 使用按键别名 <input v-on:keyup.enter="submit" />

    • 也可使使用 config.keyCodes,自定义自己的别名

      Vue.config.keyCodes = {
        f1: 112,
      };
      
  • 在 vue3 中 config.keyCodes被废弃

    • 使用按键修饰符 <input v-on:keyup.page-down="nextPage">
    • 同时匹配 q 和 Q <input v-on:keypress.q="quit">

9.$attrs包含class&style

  • vue2 中,在子组件标签中的 class 不会出现在 ``

  • Vue3 中,$attrs 现在包含了所有传递给组件的 attribute,包括 classstyle

    • 当属性值 inheritAttrs: false 是 falsed 的时候 ,子组件标签上的属性不会继承到子组件内部元素上,当是 true 的时候,会继承到子组件最外层元素上
    • 子组件标签所有属性值都会在子组件的 props 中获取到
    • 也会在 $attrs 中存在
    <div id="root">
      <date-picker data-status="activated" class="a"></date-picker>
    </div>
    
    <script>
      const app = Vue.createApp({
    
      })
    
      app.component('date-picker', {
        inheritAttrs: false,
        template: `
          <div v-bind:class="$attrs.class">
            <input type="datetime-local" v-bind:class="$attrs.class"/>
          </div>
        `,
    
        created() {
          // console.log(this.$attrs.class)
        }
      })
    
      app.mount('#root')
    </script>
    

10.自定义事件

  • 事件名

    • 与组件和 prop 一样,事件名提供了自动的大小写转换。如果在子组件中触发一个以 camelCase (驼峰式命名) 命名的事件,你将可以在父组件中添加一个 kebab-case (短横线分隔命名) 的监听器。
    <div id="root">
      <parent-com @aaa="handleClick"></parent-com>
    </div>
    
    const app = Vue.createApp({
      methods: {
        handleClick() {
          console.log("click.");
        },
      },
    });
    
    app.component("parent-com", {
      emits: ["aaa"],
      template: `
          <div>
            <button @click="$emit('aaa')">click</button>
          </div>
        `,
    });
    
    app.mount("#root");
    
  • 验证抛出的事件

    • 与 prop 类型验证类似,如果使用对象语法而不是数组语法定义发出的事件,则可以对它进行验证。
    • 要添加验证,请为事件分配一个函数,该函数接收传递给 $emit 调用的参数,并返回一个布尔值以指示事件是否有效。
    emits:{
      aaa:(e)=>{
        if(e>10){
          return true
        }else{
          return false
        }
      }
    },
    
  • v-model 参数

    • 默认情况下,组件上的 v-model 使用 modelValue 作为 prop 和 update:modelValue 作为事件。我们可以通过向 v-model 传递参数来修改这些名称:
    // 理解:s 将自定义事件 aaa 的返回值做了动态绑定
    <div id="root">
      {{ counted }}
      <parent-com v-model:aaa="counted"></parent-com>
    </div>
    
    // props 中的 aaa 就是 每次从父组件传下来的值
    // emits 使用 update:aaa 来定义事件
    // this.emit 触发事件,返回值到父组件
     <script>
        const app = Vue.createApp({
          data() {
            return {
              counted:1
            }
          }
        });
        app.component("parent-com", {
    
          data(){
            return{
              bbb:1
            }
          },
          props:['aaa'],
          emits:['update:aaa'],
           template: `
          <button @click="handleClick">click</button>
        `,
        methods:{
          handleClick(){
            this.bbb++
            this.$emit('update:aaa',this.bbb)
          }
        }
        });
    
        app.mount("#root");
      </script>
    

11.插槽

  • 插槽内容
<todo-button> Add todo </todo-button>
<!-- todo-button 组件模板 -->
<button class="btn-primary">
  <slot></slot>
</button>
  • 新增 v-slot 简写为 #

    <div id="root">
      // v-slot:名字=“传递参数”
      <todo-list v-slot:list="{ index, item }">
        <span>{{index}}</span> -
        <span>{{item}}</span>
      </todo-list>
    </div>
    
    const app = Vue.createApp({});
    app.component("todo-list", {
      data() {
        return {
          items: ["feed a cat", "Buy milk"],
        };
      },
    
      // 渲染插槽,这里用的是具名插槽,name 绑定插槽,item,index用来传递参数
      template: `
          <ul>
            <li v-for="(item, index) in items">
              <slot name="list" :item="item" :index="index"></slot>
            </li>
          </ul>
        `,
    });
    app.mount("#root");
    

12.Provide / Inject

  • Vue3 的开发文档中将这个提出来放到了 深入组件 这一栏。

  • 基础使用

    • 在根组件中使用 Provide 来声明一个变量

      provide: {
        user: 'John Doe'
      },
      
    • 在需要用到改参数的子组件中使用inject来获取
      inject: ['user'],

  • 处理响应式

    • 使用 Vue3 ,存储在实例中的 computed

      const { createApp, computed } = Vue
      provide() {
          return {
            msg: Vue.computed(() => this.str)
          }
        },
      
    • 获取的方法还是使用 inject
      inject: ['msg'],

13.动态组件 && 异步组件

  • 使用挂载在 vue 实例上的方法 defineAsyncComponent,需要做异步的组件返回一个 promise 对象。

    const { createApp, defineAsyncComponent } = Vue;
    const app = createApp({});
    
    const AsyncComp = defineAsyncComponent(
      () =>
        new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve({
              template: "<div>I am async!</div>",
            });
          }, 3000);
        })
    );
    
    app.component("async-example", AsyncComp);
    app.mount("#root");
    
  • 使用 suspense 标签实现页面的呈现

    • 如果组件没有返回值就显示 fallback,否则就显示 default
    <div id="root">
      <suspense>
        <template #default>
          <async-example></async-example>
        </template>
        <template #fallback>
          <div>loading...</div>
        </template>
      </suspense>
    </div>
    

14.$children

  • $children 实例 property 已从 Vue 3.0 中移除,不再支持。
  • 如果想访问子组件实例,vue3 中使用 $refs

15.自定义指令

<div id="root">
  <h1>scroll down the page</h1>
  <input type="range" min="0" max="500" v-model="pinPadding" />
  <p v-pin:[direction]="pinPadding">text</p>
</div>
const app = Vue.createApp({
  data() {
    return {
      pinPadding: 200,
      direction: "right",
    };
  },
});

// app.directive('pin', {
//   mounted(el, binding) {
//     // console.log(binding)
//     el.style.position = 'fixed'
//     const s = binding.arg || 'top'
//     el.style[s] = binding.value + 'px'
//   },

//   updated(el, binding) {
//     const s = binding.arg || 'top'
//     el.style[s] = binding.value + 'px'
//   },
// })

// app.directive 第二个参数传一个方法 结果等同于上边 mouthed和updated的结合

app.directive("pin", (el, binding) => {
  el.style.position = "fixed";
  const s = binding.arg || "top";
  el.style[s] = binding.value + "px";
});

app.mount("#root");

16.与自定义元素的互操作性

  • 定制内置元素

    • 在普通元素上使用时,它将作为 is attribute 传递给 createElement 调用,并作为原生 attribute 渲染。这支持了自定义内置元素的用法。

      <button is="plastic-button">点击我!</button>
      
      • 2.x 的行为:渲染 plastic-button 组件。

      • 3.x 的行为:通过调用以下函数渲染原生的 button

        document.createElement("button", { is: "plastic-button" });
        
  • 使用 vue: 前缀来解决 DOM 内模板解析问题,可以渲染组件

    <div id="root">
      <div :is="myDiv"></div>
      <table>
        <tr is="vue:mytr"></tr>
      </table>
    </div>
    
    const app = Vue.createApp({
      data() {
        return {
          myDiv: "abc",
        };
      },
    });
    
    app.component("mytr", {
      template: `
            <tr><td>abc111</td></tr>
          `,
    });
    app.mount("#root");
    

17.组合式 API

  • 1> setup()

    • 基本使用

      • setup 里边没有 this,不能访问 this
      • setup 函数,可以把他理解成 生命周期函数
      <template>
        <div>{{count}}</div>
      </template>
      
      import { ref } from "vue";
      export default {
        setup() {
          const count = ref(0);
          return {
            count,
          };
        },
      };
      
    • 访问 Prop

      • setup 的第一个参数 props 接收
      • 直接解构 props 里的内容不是响应式的,这时候需要使用,vue 里的方法 toRef toRefs
      // props: ['title', 'obj']
      const x = toRef(props.obj, "x");
      const title = toRef(props, "title");
      const {
        title: title2,
        obj: { value },
      } = toRefs(props);
      
    • setup 的上下文

      • setup 的第二个参数 context 接收,有四个参数slots emit attrs expose
      // 不给父组件暴露任何数据
      // 正常情况下父组件可以通过 $refs 获取子组件的值,这样的情况下,父组件是无法获取内容的
      expose();
      
      // attrs 类似于 $attrs ,包含通过子组件标签传来的内容
      // emit 就是 $emit
      
    • 与渲染函数裿使用

      <template>
        <h1>{{count}}</h1>
      </template>
      
      <script>
      import { ref, h, onMounted } from 'vue'
      export default {
      setup(props, { expose }) {
        const count = ref(0)
        // return {
        //   count
        // }
      
        onMounted(() => {
          setTimeout(() => {
            count.value = 100
          }, 2000)
        })
      
        expose({count})
      
        return () => h('div', {class: 'abc'}, h('a', {href: 'http://www.baidu.com'}, '百度'))
      }
      }
      </script>
      
  • 2> 核心

    • ref()

      • 用来申明 响应式 变量
      • 需要在 vue 中引入
      • 可以是任何数据,但是 Map 不可以,尽量用 ref 声明简单数据类型,复杂数据类型交给 reactive 去做
      const couter = ref(0);
      const obj = ref({
        x: 1,
        t: {
          a: 2,
        },
      });
      
    • reactive()

      • 用来申明 响应式 变量
      • 需要在 vue 中引入
      const obj = reactive({
        count,
      });
      obj.count = 200;
      console.log(count.value); // 200
      
      const books = reactive([ref("JavaScript学习指南")]);
      books[0].value = "html学习指南";
      console.log(books[0].value); // html学习指南
      
    • computed()

      • 计算属性
      • 需要在 vue 中引入
      const count = ref(0);
      const addOne = computed(() => count.value + 1);
      console.log(addOne.value); // 1
      count.value++;
      console.log(addOne.value); // 2
      
    • readonly()

      • 设置只读属性
      const original = reactive({ count: 0 });
      const copy = readonly(original);
      original.count = 100;
      // copy.count = 200
      console.log(copy.count);
      
    • watchEffect()

      • 监听属性
      • 加载完成时会执行一次,总要执行一次
      • 有返回值,是一个函数,执行返回值会停止监听
      • 多次更改会一起合并执行
      const count = ref(0);
      const stop = watchEffect(() => console.log(count.value));
      count.value = 100; // 会打印  100
      stop();
      
      // const title = ref('line1')
      // const stop = watchEffect(() => console.log(count.value + title.value))
      // count.value = 100
      // count.value = 100
      // title.value = 'line2'  // 只打印一次  100line2
      
    • watch()

      • 监听属性
      • 有两个参数,都是函数,第一个返回要坚挺的参数,第二个参数是新值和旧值,返回要执行的 js 代码
      const state = reactive({ count: 0 });
      watch(
        () => state.count,
        (count, prevCount) => {
          console.log(count);
          console.log(prevCount);
        }
      );
      state.count++; // 1 ,0
      
  • 3> 工具

    • isRef()

      • 检查一个值是否为一个 ref 对象
      const count = ref(0);
      console.log(isRef(count));
      
    • unRef()

      • 如果参数是一个 ref 则返回它的 value,否则返回参数本身
      • 它是 val = isRef(val) ? val.value : val 的语法糖
      const count = ref(10);
      const value = unref(count);
      
    • toref()

      • 可以用来为一个 reactive 对象的属性创建一个 ref。这个 ref 可以被传递并且能够保持响应性
      const countObj = reactive({ x: 0, y: 100, z: 300 });
      const countObj = reactive({ x: 0, y: 100, z: 300 });
      const x = toRef(countObj, "x");
      const { x: x1, y, z } = toRefs(countObj);
      
    • toRefs()

      • 把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref ,和响应式对象 property 一一对应。
      //  看上面
      
    • isProxy()

      • 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理
      const msg = reactive({});
      const msg2 = {};
      console.log(isProxy(msg)); // true
      console.log(isProxy(msg2)); // false
      
    • isReactive()

      • 检查一个对象是否是由 reactive 创建的响应式代理
      • 如果这个代理是由 readonly 创建的,但是又被 reactive 创建的另一个代理包裹
        了一层,那么同样也会返回 true
      const count = ref(0);
      const count2 = reactive({ x: 0, y: 0 });
      console.log(isReactive(count)); // false
      console.log(isReactive(count2)); // true
      
    • isReadonly()

      • 检查一个对象是否是由 readonly 创建的只读代理。
      const msg = readonly(reactive({}));
      const msg2 = ref(0);
      console.log(isReadonly(msg)); // true
      console.log(isReadonly(msg2)); // false
      
  • 4> 声明周期钩子

    • 用法和之前也一样只不过,与组合式 API 结合使用,js 逻辑代码是以参数的形式书写的
    • beforeCreate -> setup()
    • created -> setup()
    • beforeMount -> onBeforeMount
    • mounted -> onMounted
    • beforeUpdate -> onBeforeUpdate
    • updated -> onUpdated
    • beforeDestroy -> onBeforeUnmount
    • destroyed -> onUnmounted
      setup() {
      const count = ref(0)
    
      onMounted(() => {
        console.log('onMounted')
        count.value = 100
      })
    
      onUpdated(() => {
        console.log('onUpdated')
      })
    
      onUnmounted(() => {
        console.log('onUnmounted')
      })
    
      onBeforeMount(() => {
        console.log('onBeforeMount')
      })
    
      onBeforeUpdate(() => {
        console.log('onBeforeUpdate')
      })
    
      onBeforeUnmount(() => {
        console.log('onBeforeUnmount')
      })
    
      return {
        count
      }
    }
    
  • 5> 依赖注入- provide & inject

    • 和之前的 provide & inject 用法是一样的

    • provide()

      • 存入内容,两个参数,第一个是这个上下文的名字,第二个为参数
        const title = ref('hello111')
        provide('title1',title)
    • inject()

      • 第二个参数是一个可选的的默认值。如果未提供默认值,并且在 provide 上下文中未找到该属性,则 inject 返回 undefined。
        const title = inject('title1')
  • 6> <script setup>

    • 在 Vue3 中 script 标签中写了 setup 之后,就不需要在 里面写 setup 了
  • 7> Vue Router 4.x 组合式API

    • 在 vite 下安装 Vue Router 4.x
      pnpm i vue-router@4

    • 定义路由
      import { createRouter, createWebHistory } from 'vue-router'
      createWebHistory history 模式路由
      createWebHashHistory hash 模式路由

    • 引入路由

      • 在 main.js 中引入
        import router from './router'
        app.use(router)
    • useRouter(),useRoute()

      • 页面中要是用 原来的 router 和 route 需要引入
        import { useRouter, useRoute } from 'vue-router'

        const router = useRouter();
        const route = useRoute();
        
        router.push({
          name: "home",
          query: {
            ...route.query,
          },
        });
        
    • watch 路由变化

      • 可以监听动态路由的变量
      watch(
        () => route.params.id,
        (newId) => {
          console.log(newId);
        }
      );
      
    • onBeforeRouteLeave() 守卫

      • 添加一个导航守卫,在当前位置的组件将要离开时触发。类似于 beforeRouteLeave,但它可以在任何组件中使用。当组件被卸载时,导航守卫将被移除。
    • onBeforeRouteUpdate() 守卫

      • 添加一个导航守卫,在当前位置即将更新时触发。类似于 beforeRouteUpdate,但它可以在任何组件中使用。当组件被卸载时,导航守卫将被移除。
  • 8> 21、Vuex 4.x 组合式API

    • 在 vite 下安装 Vuex
      pnpm install vuex@next --save

    • 定义 store

      • 在 store 中引入 Vuex
      import { createStore } from "vuex";
      const store = createStore({
        state() {
          return {
            count: 0,
          };
        },
      
        mutations: {
          increment(state) {
            state.count++;
          },
        },
      });
      
      export default store;
      
    • 引入 store

      • 在 main.js 入口文件引入
      import store from "./store";
      app.use(store);
      
    • useStore()

      • 页面是用 vuex 中的数据 ,需要引入 useStore
        import { useStore } from 'vuex'
      • 使用
      const store = useStore();
      // 取值
      const count = computed(() => {
        return store.state.count;
      });
      // 使用方法
      const increment = () => {
        store.commit("increment");
      };
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值