紙上得來終覺淺,Vue3 新語法練起來


搜集資源

Vue3 入門指南與實戰案例
Vue在線演練場
Vue3.0中Ref與Reactive的區別是什么
Composition API RFC
Vue.js 中使用defineAsyncComponent 延遲加載組件
2022年必會Vue3.0學習 (強烈建議)

Vue2與Vue3的對比

  • 對TypeScript支持不友好(所有屬性都放在了this對象上,難以推倒組件的數據類型)
  • 大量的API掛載在Vue對象的原型上,難以實現TreeShaking。
  • 新推出了CompositionAPI
  • 更方便的支持了 jsx
  • Vue 3 的 Template 支持多個根標簽,Vue 2 不支持
  • 對虛擬DOM進行了重寫、對模板的編譯進行了優化操作

24. 在less中如何使用響應式變量

width:v-bind(styleProgressWidth);

23. @eventName="foo" 后面有無括號的差別

@click有括號:傳指定參數
@click無括號:默認傳event參數

22. 判斷slot命名插槽是否定義,定義才展示

      <slot v-if="$slots.customButton" name="customButton"></slot>
      <Button v-else :type="buttonType" :disabled="disabled" :loading="loading">
        <template #icon><UploadOutlined /></template> {{ btnText }}
      </Button>

21. 用toRefs解構ref定義的對象會丟失響應式

   <MaterialModal
            ref="materialModalRef"
            title="選擇素材"
            :contentId="materialModalInfo.contentId"
            :isEdit="materialModalInfo.isEdit"
            :onSave="handleMaterialSave"
            :validateMaterialMaxLimit="validateMaterialMaxLimit"
          />

  // 處理點擊添加內容素材按鈕事件
  const handleAddMaterial = () => {
    // 重點,不會立即生效,在nextTick之后才會生效
    materialModalInfo.isEdit = false;
    console.log('handleAddMaterial', materialModalInfo);
    handleOpenMaterialModal();
  };

  // 素材彈窗引用
  const materialModalRef = ref();
  // 打開素材彈窗
  const handleOpenMaterialModal = () => {
    nextTick(() => {
      const initFormData = materialModalInfo.isEdit ? chooseMaterialList.value : [];
      materialModalRef.value?.init(initFormData, TYPE.picture, ALL_TYPES.slice(1));
    });
  };

20. 用toRefs解構ref定義的對象會丟失響應式

import {toRefs} from 'vue';
const foo=ref({bar:'xxx'});
const {bar}=toRefs(foo);
<template>{{bar}}</template>

19. 獲取更加詳細的報錯

app.config.errorHandler = (err, vm, info) => {
    console.log('[全局異常]', err, vm, info)
}

18. useVModel語法糖

import { useVModel } from '@vueuse/core'

const props = defineProps<{
  modelValue: string
}>();
const emit = defineEmits(['update:modelValue']);

// 帶有類型定義的寫法
const emit = defineEmits<{
  (e: 'update:value', params: any): void;
}>();


const data = useVModel(props, 'modelValue', emit);

17. vue組件 即便用withDefaults設置了默認值, TProps對屬性要寫設置為可選,否則會有告警提示

16. keep-alive用法示例

<template>
  <router-view v-slot="{ Component }">
    <transition>
      <keep-alive :include="includeList">
        <component :is="Component" />
      </keep-alive>
    </transition>
  </router-view>
</template>

<script lang="ts"> export default {name:'AppEntry'} </script>

<script lang="ts" setup>
  const router = useRouter();
  const includeList = ref(['AuthByManagerList']);
  router.beforeEach((to, from) => {
    if (from.path === '/authByManagerResult' && to.path === '/authByManagerList') {
      // 從客戶認證結果頁跳列表頁,要清除緩存
      includeList.value = [];
    } else if (from.path === '/authByManagerList' && to.path === '/authByManagerDetail') {
      // 從列表頁進入詳情頁,要緩存列表頁
      includeList.value = ['AuthByManagerList'];
    }
  });
</script>

15.vue3中的props在模板中會同步更新,在setup函數中只更新一次。當props值再次發生改變是,在setup中要用watch才能監聽到props的變化,如果props某個屬性是對象,要設置深度監聽,才能監聽到變化。

14.v-model可以定義多個

<van-list
  v-model:loading="loading"
  v-model:error="error"
  error-text="請求失敗,點擊重新加載"
  @load="onLoad"
>
  <van-cell v-for="item in list" :key="item" :title="item" />
</van-list>

13.context的屬性

setup(props, context) {
    context.attrs
    context.slots
    context.parent
    context.root
    context.emit
    context.refs
    
    return {
        
    }
  }

12.computed屬性完整寫法

watch的套路是:既要指明監聽的屬性,也要指明監聽的回調函數。
watchEffect的套路是:不需要指明監聽的屬性,監聽中的回調函數用到了那個屬性,就監聽那個屬性。

watchEffect跟computed有點像:
computed注重是計算出來的值,所以必須要有返回值。
watchEffect更注重是過程,所以不用寫返回值。

const person = reactive({
   fistName:"Mr",
   lastName:"long"
}) 

// 計算屬性簡寫
let fullName = computed(()=>{
  return person.fistName + '-' + person.lastName
})

// 計算屬性完整寫法
let fullName = computed({
  get(){
    return person.fistName + '-' + person.lastName
  },
  set(value){
    const newArr = value.split('-')
    person.fistName = newArr[0]
    person.lastName = newArr[1]
  }
})

const sltEle = computed( ()=>{ 
 return function(index){ 
  console.log('index',index); 
 } 
}) 

computed 接收參數的語法

<template> 
 <div> 
  <div v-for="(item,index) in arr" :key="index" @click="sltEle(index)"> 
   {{item}} 
  </div> 
 </div> 
</template>
const sltEle = computed(()=>(index)=>{ 
  console.log('index',index); 
}) 

11.watch和watchEffect的區別

  • watch 是需要傳入偵聽的數據源,而 watchEffect 是自動收集數據源作為依賴。
  • watch 可以訪問偵聽狀態變化前后的值,而 watchEffect 沒有,watchEffect獲取的改變后的值。
  • watch 是屬性改變的時候執行,當然也可以immediate,而 watchEffect 是默認會執行一次,然后屬性改變也會執行。

    let count = ref(0)
    let countObj = reactive({count: 0})

    watch(count, (newVal, oldVal) =>{console.log(newVal, oldVal)} )
    // 只能監聽ref、reactiveObject, function, array, 想監聽reactive的某個屬性,需要轉換成函數
    watch(() => countObj.count, (newVal, oldVal) => {console.log(oldVal, newVal)}, {})
    // watch監聽reactiveObject時,舊值無法獲取正確獲取
    watch (countObj, (newVal, oldVal) => {console.log(newVal, oldVal);})
    // 監聽多個值,可以寫成數組的形式
    watch ([oneName, twoName], ([oneNewName, twoNewName], [oneOldName, twoOldName]) => {
      console.log(oneNewName, oneOldName, twoNewName, twoOldName)
    })

watch監聽ref值,不用加.value,watch監聽對象的某個屬性值時,書寫方式是watch([()=>obj.propA],()=>{})

// 情況一監聽ref響應式數據
 watch(count,(newValue,oldValue)=>{
     console.log(newValue,oldValue)
 },{immediate:true}) // immediate 立即監聽

// 情況二 監聽多個ref響應式數據
watch([count,name],(newValue,oldValue) =>{
    console.log(newValue,oldValue) // 此時value的數據是數組
})

// 情況三 監聽reactvie響應式數據
// 如果watch監聽是reactive定義的響應式數據,則無法獲取正確的oldValue,且強制開啟深度監聽。
   watch(person,(newValue,oldValue)=>{
       console.log(newValue,oldValue) // 兩個值一致都是一樣的
   })
  
  // 情況四 監聽reactive定義的響應式數據的某個屬性(基礎數據類型)
  watch(()=>person.name,(newValue,oldValue) =>{
      console.log(newValue,oldValue)
  })
  
 // 情況五 監聽多個reactive定義的多個響應式數據的屬性(基礎數據類型)
 watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
  console.log(newValue,oldValue)
 })
 
 // 情況六 監聽reactive定義的響應式數據的某個屬性(復雜數據類型)
 watch(() => person.class,(newValue,oldValue) =>{
   // 此時的class 為 { b:{c:20 } }, 想要監聽c值的變化 則需要開啟deep深度監聽
   console.log(newValue,oldValue)
 },{deep:true}) 

10.useSlots用法

父組件

<template>
  <!-- 子組件 -->
  <ChildTSX>
    <!-- 默認插槽 -->
    <p>I am a default slot from TSX.</p>
   
    <!-- 命名插槽 -->
    <template #msg>
      <p>I am a msg slot from TSX.</p>
    </template>
  </ChildTSX>

</template>
<script setup lang="ts">
import ChildTSX from '@cp/context/Child.tsx'
</script>

子組件

<script lang="ts" setup>
// 注意:這是一個 .tsx 文件
import { useSlots } from 'vue'
const slots = useSlots()
const ChildTSX = ()=>{
    // 渲染組件
    return () => (
      <div>
        {/* 渲染默認插槽 */}
        <p>{ slots.default ? slots.default() : '' }</p>

        {/* 渲染命名插槽 */}
        <p>{ slots.msg ? slots.msg() : '' }</p>
      </div>
    )
  }
}
export default ChildTSX
</script>

9.jsx語法,要借助插件@vitejs/plugin-vue-jsx

<script lang="tsx" setup>
const jsxNode = () => {
  return <div>text</div>;
};
</script>
<template>
  <jsxNode />
</template>

8. 動態class的設置方法

<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
  <h1 :class="`red ${msg === 'Hello World!' && 'green'}`">{{ msg }}</h1>
  <input v-model="msg">
</template>
<style scoped>
  .red{
    color:red;
  }
  .green{
    color:green
  }
</style>

7.父子組件通信 props/emit方式

父組件

<template>
  <Child
    ref="child"
    title="用戶信息"
    :index="1"
    :uid="userInfo.id"
    :user-name="userInfo.name"
    @update-age="updateAge"
  />
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import Child from '@cp/Child.vue'

type TMember={
  id: number,
  name: string
}
const child = ref<typeof Child | null>()

const userInfo: TMember = {
  id: 1,
  name: 'Petter'
}

// 父組件調用子組件的方法
onMounted(() => child.value?.childMethod)

const updateAge = (age: number) => {
  console.log(age);
}


</script>

子組件

<template>
  <p>標題:{{ title }}</p>
  <p>索引:{{ index }}</p>
  <p>用戶id:{{ uid }}</p>
  <p>用戶名:{{ userName }}</p>
</template>
<script lang="ts" setup>
import { withDefaults, defineProps, defineEmits, toRefs, defineExpose } from 'vue';

const props = withDefaults(defineProps<{
  title: string;
  index: number;
  uid: number;
  userName: string;
}>(), { userName: 'zhangsan' });

  // 復雜類型賦初始值
  const props = withDefaults(defineProps<PropsType>(), {
    value: () => {
      return {
        memberList: [],
        deptList: [],
        positionList: [],
      };
    },
  });


const { title, index, uid, userName } = toRefs(props);
// 接受子組件傳進來的方法
const emit = defineEmits(['update-age']);

setTimeout(() => {
  emit('update-age', 22);
}, 2000);

const childMethod = () => {
  console.log('我是子組件的方法')
}

// 子組件暴露方法給父組件
defineExpose({ childMethod })
</script>

6.使用ref操作dom

<template>
  <!-- 掛載DOM元素 -->
  <p ref="msg">留意該節點,有一個ref屬性</p>
  <!-- 掛載DOM元素 -->
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";

// 定義掛載節點,聲明的類型詳見下方附表
const msg = ref<HTMLElement | null>();

// 請保證視圖渲染完畢后再執行節點操作 e.g. onMounted / nextTick
onMounted(() => {
  // 比如獲取DOM的文本
  console.log(msg?.value?.innerText);
});
</script>

5.props用法

<script lang="ts" setup>
  import {withDefaults,defineProps} from 'vue';
  const props = withDefaults(defineProps<{
    btnName: string;
    noBtn?: boolean;
    doAction: () => void;
    classStyle?: string;
    config:any[];
  }>(),{
    noBtn:true,      // 設置默認值
    classStyle:''
    config:()=>[],
  });
  
  const mActionClass=`m-action ${props.classStyle}`;
<script>

4.生命周期函數

vue2和vue3生命周期對比

beforeCreate  -> 使用 setup()
created       -> 使用 setup()
beforeMount   -> onBeforeMount
mounted       -> onMounted
beforeUpdate  -> onBeforeUpdate
updated       -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed     -> onUnmounted
errorCaptured -> onErrorCaptured
<template>
  <div class="home">
    <p>{{ count }}</p>
    <p>{{ state.a }}</p>
    <button @click="add">加1</button>
  </div>
</template>

<script lang="ts" setup>
import {
  ref,
  reactive,
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
  onErrorCaptured,
  onRenderTracked,
  onRenderTriggered,
  onActivated,
  onDeactivated,
} from "vue";
const count = ref(1);
const state = reactive({ a: 10 });
const add = () => {
  count.value += 1;
  state.a = state.a + 1;
};

onBeforeMount(() => {
  console.log("onBeforeMount");
});
onMounted(() => {
  console.log("onMounted");
});

onBeforeUpdate(() => {
  console.log("onBeforeUpdate");
});

onUpdated(() => {
  console.log("onUpdated");
});

onBeforeUnmount(() => {
  console.log("onBeforeUnmount");
});

onUnmounted(() => {
  console.log("onUnmounted");
});

onErrorCaptured((evt) => {
  console.log("onErrorCaptured", evt);
});

// 只執行一次,有幾個響應式api,執行幾次
onRenderTracked((evt) => {
  console.log("onRenderTracked", evt);
});

// 行為如同onUpdated,每次有數據更新都會執行
onRenderTriggered((evt) => {
  console.log("onRenderTriggered", evt);
});

// keep-alive要用到的函數
onActivated(() => {
  console.log("onActivated");
});

onDeactivated(() => {
  console.log("onDeactivated");
});
</script>

4. shallowReactive, shallowRef, readonly,shallowReadonly,toRaw,markRaw函數區別

4.1 shallowReactive、shallowRef的之間的區別

shallowReactive:淺監視
shallowRef:不做監視

4.2 readonly和shallowReadonly

readonly:只讀屬性的數據,深度只讀
const state2 = readonly(state)
shallowReadonly:只讀的數據,淺只讀的

4.3 toRaw和markRaw

toRaw將代理對象變成普通對象,數據變化,界面不會進行更新
const user = toRaw(state);
markRaw標記的對象數據,從此以后都不能在成為代理對象了
const likes = ['吃',‘喝’]
state.likes = markRaw(likes)
<template>
  
  <h4>
    <button @click="triggerShallowReactiveRender">
      改變第一層才會渲染
    </button>
  </h4>
  <p>{{ shallowReactiveState.a }}</p>

    <h4>
    <button @click="triggerShallowRefRender">
      失去響應式
    </button>
  </h4>
  <p>{{ shallowRefState.a }}</p>
  
</template>
<script setup>
import { readonly,reactive, shallowReactive, shallowRef, toRaw } from "vue";

// shallowRef 與shallowReactive
// shallowRef 與shallowReactive創建的是非遞歸的響應對象,shallowReactive創建的數據第一層數據改變會重新渲染dom
const shallowReactiveState = shallowReactive({
  a: "initShallowReactiveState",
  b: {
    c: "c",
  },
});

//如果不改變第一層 只改變其他的數據 頁面不會重新渲染,例如:
shallowReactiveState.b.c = 2;

//改變第一層的數據會導致頁面重新渲染
const triggerShallowReactiveRender=()=>{
  shallowReactiveState.a = "changeShallowReactiveState";
}

// shallowRef創建的對象沒有響應式特性
const shallowRefState = shallowRef({
  a: "initShallowRefState",
  b: {
    c: "c",
  },
});
  
const triggerShallowRefRender=()=>{
  //失去響應式--除非對整個對象重新賦值或者使用triggerRef(shallowRefState)觸發頁面更新
  shallowRefState.a = "changeShallowRefState";
}

// toRaw ---只修改數據不渲染頁面
var obj = { name: "test" };
var toRawState = reactive(obj);
var raw = toRaw(toRawState);
//並不會引起頁面的渲染,而obj.name,toRawState.name的值均已改變
setTimeout(()=>raw.name = "zs",2000);


// 不允許修改對象的值 
const readonlyState = readonly({a:1,b:2});
// 賦值會引起警告
readonlyState.a=2;

</script>

3. toRefs和toRef的用途

借助toRefs可以在template部分,直接使用結構后的對象單個鍵值,寫法簡潔,卻不失響應式。
toRef是轉換reactive對象的單個值

<template>
  <ul class="user-info">
    <li class="item">
      <span class="key">ID:</span>
      <span class="value">{{ id }}</span>
    </li>

    <li class="item">
      <span class="key">name:</span>
      <span class="value">{{ name }}</span>
    </li>

    <li class="item">
      <span class="key">age:</span>
      <span class="value">{{ age }}</span>
    </li>

    <li class="item">
      <span class="key">gender:</span>
      <span class="value">{{ gender }}</span>
    </li>
  </ul>
</template>
<script lang="ts" setup>
import { reactive, toRef, toRefs } from "vue";

interface Member {
  id: number;
  name: string;
  age: number;
  gender: string;
}

// 定義一個reactive對象
const userInfo: Member = reactive({
  id: 1,
  name: "Petter",
  age: 18,
  gender: "male",
});

// 結構reactive對象,它的字段全部是ref變量
const { id, age, gender } = toRefs(userInfo);
const name: string = toRef(userInfo, 'name');

// 2s后更新userInfo
setTimeout(() => {
  userInfo.id = 2;
  userInfo.name = "Tom";
  userInfo.age = 20;
}, 2000);

</script>

2. 響應式數據

ref可以定義任何類型的數據,但是定義數據和對象時,在script部分使用時,賦值和取值,不如reactive方便
reactive只能用於數組,對象類型。
ref的本質是通過reactive創建的,Ref(10)=>Reactive({value:10});
reactive的本質是將每一層的數都解析成proxy對象,reactive 的響應式默認都是遞歸的,改變某一層的值都會遞歸的調用一遍,重新渲染dom。

<template>
    <p>{{count}}</p>
    <button @click="add">加1</button>
    <p>{{info.name}}</p>
    <button @click="changeName">改姓名</button>
</template>

<script lang="ts" setup>
import { reactive, ref } from 'vue';
interface Info{
  name:string;
  age:number;
}
// 響應式基本類型用ref
const count = ref<number>(0);
// 響應式引用類型用reactive
const info:Info = reactive({ age: 100,name:'zhangsan' });

const add = () => {
  // 基本類型賦值時,是賦值給value屬性
  count.value += 1;
};

const changeName = () => {
  info.name="lisi";
};
</script>

1.路由取值和跳轉

<script lang="ts" setup>
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
// 獲取路由信息
console.log(route.query.id);
// 編程式路由跳轉
const jump = ()=> {
  router.push({ path: `/about` , query:{id:'xxx}});
};
</script>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM