4

1. Vue2到Vue3

vue3项目搭建

  • 1.安装vue3的cli
    cnpm i -g @vue/cli@next

    @next 是因为没有正式发布,后面正式发布可能会去掉

    2.创建vue3项目
    vue create <name>

    3.vue3项目


    vue3跟vue2差不多 多了一个composition API

    vue3和ts的配合更好了

vue3与vue2的区别

Vue3与Vue2的不同:
1.vue实例化方式变了

//vue2
import Vue from 'vue';
import App from './App.vue';

new Vue({
  render: h => h(App)
}).mount('#app');

//vue3
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

2.Vue全局方法变了
//vue2
import Vue from 'vue';
Vue.component('name', {});

//vue3
import {createApp} from 'vue';
let app=createApp(App);
app.component('name', {});
app.mount('#app');


3.vue3取消了filter 用computed、函数替代

4.v-model
//vue2
v-model = :value + @input

//vue3
[html组件]
v-model = :value + @input

[自定义组件]
v-model = :modelValue + @update:modelValue


5.函数组件的写法变了
//vue2
render(h){
  return h(...);
}

//vue3
import {h} from 'vue';
render(props, context){
  return h(...);
}


6.data变了
//vue的data统一了,只有函数一种
data(){
  return {};
}

7.异步组件(分包加载)
//vue2
const cmp1=()=>import('./cmp1');

//vue3
import {defineAsyncComponent} from 'vue';
const cmp1=defineAsyncComponent(()=>import('./cmp1'));

8.vue3事件简化了
//vue2
this.$emit('name', ...);
this.$on();
this.$off();
this.$once();

//vue3
this.$emit('name', ...);

9.其他
自定义directive变了——beforeMount、mounted、beforeUpdate、updated、...
template不需要根元素

2.详解composition API

composition API究竟是什么

​ 1.数据、方法、computed注入到一个组件当中

​ 2.重用组件的代码---vue2.x种组件里面东西特别多、特别乱(data、method、computed、...)


​ composition API——注入
​ 把东西(数据、方法、computed、生存周期函数、...)注入到组件当中——集中到一个地方写


//普通方式
export default {
  data(){ a: 12 },
  methods: {
    fn(){
      ....
    }
  },
  computed: {}
}
//composition API
export default {
  setup(){
    return {
      a: 12,
      fn(){
        ...
      },
    }
  }
}

setup、可响应数据—reactive

  1. setup
    beforeCreate
    setup
    created

    setup执行顺序是在beforeCreate之后created之前,这个时候组件刚刚被new出来,组件里面什么也没有,所以无法使用this及其他的东西

    setup无法使用组件的其他东西(data、methods、computed、...)、也不要使用this

    setup执行的过程中,组件中其他东西都没有创建;不能用this
    setup是同步的(不能用async)

  2. 可响应数据
    普通的变量,无法完成响应操作(检测到数据变化,重新渲染组件)

    使用可响应数据(reactive)

    import { reactive, isReactive } from "vue";
    const state = reactive({
        xxx:xxx,
        xxx:xxx
    })
    // isReactive 可以判断一个值是不是reactive
    console.log(isReactive(state)); // true

    reactive的参数必须是一个object

    reactive(number|string|boolean) ×
    reactive({...}|[...]) √

    必须是对象(json、数组)如果是其他对象(Date、RegExp、...),修改不会被监听(proxy的原因),如果非要使用可以创建一个新的来覆盖。例如:

    <template>
      <div>a={{a.date}}</div>
      <button type="button" @click="fn">++</button>
    </template>
    
    <script>
    import { reactive } from "vue";
    
    export default {
      setup() {
        const a = reactive({ date: new Date() });
        return {
          a,
          fn() {
            //这样修改数据改变页面不会刷新
            // a.date.setDate(a.date.getDate() + 1);
            
            //需要直接覆盖
            const newDate = new Date(a.date.getTime() + 86400000);
            a.date = newDate;
            console.log(a.date);
          },
        };
      },
    };
    </script>

ref、readonly

  1. ref

    1. 使用基本类型

      • 使用

        isRef判断一个值是不是ref

        import { ref, isRef } from "vue";
        let a = ref(12);  //基本等价于 reactive({ value: 12 }),只是会自动加value
        console.log(isRef(a)); //true - 判断是否ref

        template中ref会自动加.value,例如使用a

        <template>{{a}}</template>

        在js中需要自己加.value 例如自己修改

        <script>fn(){ a.value++ }</script>

    2. 引用节点(html原生、自定义组件)

      <template>
        <div ref="a">dsfasdfdas</div>
      </template>
      <script>
      import { ref, onMounted } from "vue";
      export default {
        setup() {
          const a = ref(null);
      
          //需要在onMounted中使用,不然在setup执行过程中 a是不存在
          onMounted(() => {
            a.value.style.border = "1px solid black";
          });
      
          return {
            a,
          };
        },
      };
      </script>
  2. readonly

    类似于const,不同的是: readonly 保护所有操作、递归保护; const----仅保护赋值、非递归

    isReadonly判断一个值是不是readonly

    <template>
      {{a.count}}
      <button type="button" @click="a.count++">++</button>
    </template>
    
    <script>
    import { readonly, isReadonly } from "vue";
    
    export default {
      setup() {
        const a = readonly({ count: 12 });
        // a.count++的时候const会报警告,readonly是递归保护多层 
          
        //判断一个值是不是readonly
        console.log(isReadonly(a)); //true
          
        // const a = { count: 5 };
        // a.count++的时候const会增加,const只保护赋值操作,只保护一层
    
        return { a };
      },
    };
    </script>

递归监听、shallow、trigger

  1. 递归监听(深度监听)

    在vue2.x中修改对象属性页面是无法完成刷新的,例如this.data.obj.a = 12页面并不会刷新(需要使用$set),vue3.x中实现了递归监听,修改data.obj.a = 12页面也会刷新

  2. shallow&trigger

    vue3递归监听的实现原理是把多层数据全部改为Proxy,例如:

    import { reactive, shallowRef, triggerRef } from "vue";
    // reactive、ref、readonly 都是递归监听,这里使用ref举例
    // 非递归版本
    // shallowReactive
    // shallowRef
    // shallowReadonly
    export default {
        setup(){
            const json = reactive({
                arr:[ { a:12 } ]
            });
            console.log(json); // Proxy
            console.log(json.arr); // Proxy
            console.log(json.arr[0]); // Proxy
            
            return {json}
        }
    }

    当数据特别庞大时,例如几万、几十万条数据时,性能可能会有影响(普通对象转成proxy),这个需要使用非递归版本,例如:

    import { shallowRef, triggerRef } from "vue";
    export default {
        setup(){
            const json = shallowRef({
              arr: [ { a:12 } ]
            })
            console.log(json); // Proxy
            console.log(json.arr); // [...]
            console.log(json.arr[0]); // {...}
            
            setTimeout(()=>{
              json.value.arr[0].a++;
              console.log(json.value.arr[0].a);
              // 如果没有这一行,数据变,页面不会刷新
              triggerRef(json); //通知页面刷新
            }, 500)
            
            return {json}
        }
    }

raw原始数据

  1. toRaw

    如果数据有大量的操作,一直被监听可能会影响性能,可以先把数据转换成原始数据,改完之后再变回去

    //reactive、ref、readonly -> 原始数据
    let json = reactive({ ... }; //proxy
    let obj = toRow(json);  // {...}
    //原始数据 ->  reactive、ref、readonly
    reactive(obj); //proxy

    把创建reactive、ref的参数返回来 对原始数据进行操作,不会被监听的(性能高)

  2. markRaw 保持一个数据永远是原始的

    可能写库的时候会用,数据不会被转成proxy,比如返回一个数据,不希望这个数据被监听

    let json = markRaw({xxx:xxx});
    reactive(json); // object 不会被转成proxy

深入ref操作

  1. unRef 或如ref的原始数据

    例如let res = ref(12);

    使用toRaw获取 => { value:12 }

    使用unRef获取 => 12

    toRaw是连着value一块获取的,unRef是ref专用的

  2. toRef

    const json = { a:12 };
    let res = ref(json.a);
    
    res.value = 13;
    //这个时候res的value会变为13,但是json的内容不会变,相当于 ref和原始数据没有关系,
    // let res = ref(json.a) 等价于 let res = ref(12);
    
    //toRef
    let res = toRef(json, "a");
    res.value = 13;
    // 这个时候res的value和json的a都会变为13,但是页面不会刷新
    
    // ref - 普通ref对象
    //    1. 不会跟原始数据挂钩
    //    2. 会触发页面渲染
    // toRef - 特殊的ref对象
    //    1. 创建的ref对象,跟原始数据对象挂钩
    //    2. 不会触发渲染
    reftoRef
    跟原始数据无关(相当于复制)会引用原始数据(改的话都会变)
    会触发页面渲染不会触发页面渲染
  3. toRefs

    相当于toRef的批量操作

    const json = { a:12, b:5 }
    // toRef
    let res1 = toRef(json, "a");
    let res2 = toRef(json, "b");
    
    // toRefs
    let res = toRefs(json)
    //相当于:
    //toRefs({a: xx, b: xxx}) => {a: toRef(json, 'a'), b: toRef(json, 'b')}
  4. customRef - 自定义ref

    function myRef(...){
        //code...
        return customRef((track, trigger)=>{
            // track == 整个ref开始
            // trigger == 流程结束,通知vue渲染
            return {
                get(){
                    track();
                    return xxxx;
                },
                set(newVal){
                    // code..
                    trigger();
                }
            }
        })
    }
    
    setup(){
        //返回的内容就是普通的ref对象
        let data = myRef(...);
    }

可响应数据的本质

vue2.x是基于Object.defineProprty(),vue3.x是基于Proxy

  1. shallowReactive基本实现(非深度监听)

    function shallowReactive(data) {
        if (typeof data == 'object') {
          return new Proxy(data,{
            get(data, key){
              return data[key]
            },
            set(data, key, val){
              console.log('重新渲染');
              data[key] = val;
              // 这里必须return true,因为如果是数组修改会进行两个操作,操作数组,修改长度
              // 这里return true 表示第一次成功
              return true;
            }
          })
        } else {
          console.warn('args is not object:' + data);
        }
      }
      let arr = shallowReactive([1,2,3]);
      arr.push(4)
      console.log(arr); // 会刷新页面,并且是两次(1.数组元素修改,2.数组length修改)
    
      let d = shallowReactive({ a:12 });
      d.a++;
      console.log(d); // 会刷新页面
    
      let obj = shallowReactive({ a: { b:12 } });
      obj.a.b++;
      console.log(obj); // 不会刷新页面
  2. reactive基本实现(深度监听)

    function reactive(data) {
        if (typeof data == 'object') {
          if( Array.isArray(data) ){
            data.forEach((item, idx)=>{
              if(typeof item == 'object'){
                data[idx] = reactive(item)
              }
            })
          }else{
            for(item in data){
              if(typeof data[item] == 'object'){
                data[item] = reactive(data[item]);
              }
            }
          }
          return new Proxy(data,{
            get(data, key){
              return data[key]
            },
            set(data, key, val){
              console.log('重新渲染');
              data[key] = val;
              // 这里必须return true,因为如果是数组修改会进行两个操作,操作数组,修改长度
              // 这里return true 表示第一次成功
              return true;
            }
          })
         } else {
          console.warn('args is not object:' + data);
         }
     }
     let d = reactive({ a:{ b:12 } });
     d.a.b++;
     console.log(d.a); // 可以监听到

setup与函数组件

  1. setup

    setup有两种用法

    • 普通用法

      setup(){
        // code...
        return {
              //....
        }
      }
    • 函数组件用法

      import {h} from "vue";
      // h('type', props, children)
      // type -> 标签名称(可以是组件)
      // props -> 属性内容(json格式)
      // children -> 标签子元素(数组格式),标签里面的东西
      // 下面的代码等价于
      // <div title="aaaa" id="div1"> <a href="xxx">aaaaa</s> </div>
      setup(){
          return ()=>{
              return h('div', { title:'aaaa', id:'div1' }, [
                  h('a', { href:'xxx' }, ['aaaaa']) 
              ])
          }
      }
  2. 组件

    组件分为单文件组件(用的最多),和函数组件。每种组件又分为局部组件和全局组件

    • 单文件组件+局部

      import xxx from "...";
      export default {
          components:{xxx}
      }
    • 单文件组件+全局

      // main.ts
      import xxx from 'xxx.vue';
      // app为createApp(App)的返回内容
      app.component("xxx", xxx);
    • 函数组件+局部

      import {h} from "vue";
      const xxx = {
        setup(){
          return ()=>{
            return h('type', {}, [])
          }
        }
      }
      export default {
          components:{xxx}
      }
    • 函数组件+全局

      const xxx={
        setup(){
          return ()=>{
            return h('type', {}, []);
          };
        }
      };
      // app为createApp(App)的返回内容
      app.component('name', xxx);

computed

computed——虚拟数据,自身只是一个转换——对变量的操作,转换为调用;本身不是响应数据

  1. 普通用法

    在vue3.x中仍然可以使用vue2,x的用法

    data(){
      return { a:12 }
    },
    computed:{
      b(){
        // code...
        return "xxx";
      }
    }
  2. setup中的用法

    import { computed, ref } from "vue";
    setup(){
      // 只读的,只有get,无法修改
      const a = computed(()=>{
        // code...
        return "xxx";
      })
      
      // 完整版
      // computed本身不是可响应数据(修改不会触发页面刷新),所以需要使用ref
      const _b = ref(11);
      const b = computed({
        get(){
          return _b.value;
        },
        set(newVal){
          _b.value = newVal;
        }
      })
      
      //setup中必须把数据返回出去
      return { a,b }
    }

watch和watchEffect

  1. watchEffect

    • 自动监听watchEffect中使用到的数据

      import {watchEffect, reactive} from "vue";
      setup(){
        const a = reactive({ count:5 });
        const b = reactive({ count:8 });
        const c = reactive({ count:12 });
        // watchEffect返回一个函数,用来停止监听
        const stop = watchEffect(()=>{
          // 这里使用了a和b,让a和b改变时会触发这个watch,c改变不会触发
            console.log('变了',a,b);
        });
           
        stop(); // 停止监听
      }
    • 清理

      import {watchEffect, ref} from "vue";
      setup(){
        const count = ref(0);
        // 当监听失效时(调用返回方法、组件销毁)会调用invalidate
        watchEffect((invalidate)=>{
          const timer = setInterval(()=>{
            count.value ++;
            console.log(count.value);
          }, 500);
          // 当监听失效时,清除定时器
          invalidate(()=>{
            clearInterval(timer);
          })
        })
      }
  2. watch

    • 手动指定监听的数据,watch同样会返回 停止的方法

      import { watch, ref } from "vue";
      setup(){
        const a = ref(12);
        
          // 用法1 - 监听单个数据
        const stop = watch(a, ()=>{  // 当a改变时触发回调
            // code...
        })
          stop(); //停止
        
          // 用法2 - 监听多个数据
          watch([a,b], ()=>{})
        
        // 用法3 - 自定义
        // 在函数中返回true就会执行后面的回调,返回false就不会执行,自定义是否监听
        watch(()=>{
              // code...
          return true|false
        },()=>{ /* code.. */ })
      }

inject

注入类似于函数的传参,这边声明,那边使用,和传参不同的是provideinject可以跨组件层级传参

注入传参
不同都写出来需要按顺序写出完整的参数列表
容易修改修改费劲
穿透(父组件声明,里面的组件都可以使用)一层一层传
  • 用法

    //父组件
    import { provide } from "vue";
    setup(){
      // provide(名字,值);
      provide("name", "xxx");
    }
    //子组件
    import { inject } from "vue";
    setup(){
      // inject(名字, 默认值);
      const name = inject("name", "defaultName");
      return {name}
    }

    inject尽量少用,用的多了会导致程序逻辑混乱(比如看代码突然出来一个inject,根本不知道在哪个组件声明的),可以用来存储一些配置类的东西,比如主题、权限

3.Vue3生态圈

router路由

// 使用 router/index.ts
import { createRouter, createWebHistory } from "vue-router";
import Home from '@/views/home.vue';
import News from '@/views/news.vue';
// 创建路由实例 ------------ createRouter
// 创建history模式路由 ----- createWebHistory        √
// 创建hash模式路由--------- createWebHashHistory
// 创建内存模式路由--------- createMemoryHistory

export default createRouter({
  // 指定路由模式,参数:指定网站根路径
  // process.env.NODE_ENV == 'devlopment' ? '/' : 'http://aaa.com/news/'
  history: createWebHistory("/"),
  routes: [
    { path: '/', component: Home },
    { path: '/news', component: News },
  ],
})

//挂载  main.ts
import router from './router';
createApp(App).use(router).mount('#app');

vuex数据管理

// 使用 store/index.ts
import { createStore } from "vuex";
export default createStore({
  state:{ a:12 },
  momutaions:{
        addA(state, payload){
            state.a += playload;
    }
  }
})

// 挂载 main.ts
import router from './router';
import store from './store';
createApp(App).use(router).use(store).mount('#app');

// 修改
<template>
  {{$store.state.a}}
    <button @click="fn">+5</button>
</template>
setup(){
  return {
        fn(){
            this.$store.commit("addA", 5)
    }
  }
}

undefined
289 声望17 粉丝

技术的力量