纸上得来终觉浅,Vue3 新语法练起来

搜集资源

Vue3相对Vue的变化:

  • 对TypeScript支持不友好(所有属性都放在了this对象上,难以推倒组件的数据类型)
  • 大量的API挂载在Vue对象的原型上,难以实现TreeShaking。
  • 新推出了CompositionAPI
  • 更方便的支持了 jsx
  • Vue 3 的 Template 支持多个根标签,Vue 2 不支持
  • 对虚拟DOM进行了重写、对模板的编译进行了优化操作

24. 在less中如何使用响应式变量

css 复制代码
width:v-bind(styleProgressWidth);

23. @eventName="foo" 后面有无括号的差别

@click有括号:传指定参数 @click无括号:默认传event参数

22. 判断slot命名插槽是否定义,定义才展示

html 复制代码
      <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定义的对象会丢失响应式

HTML 复制代码
   <MaterialModal
            ref="materialModalRef"
            title="选择素材"
            :contentId="materialModalInfo.contentId"
            :isEdit="materialModalInfo.isEdit"
            :onSave="handleMaterialSave"
            :validateMaterialMaxLimit="validateMaterialMaxLimit"
          />
<script>
  // 处理点击添加内容素材按钮事件
  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));
    });
  };
</script>

20. 用toRefs解构ref定义的对象会丢失响应式

js 复制代码
import {toRefs} from 'vue';
const foo=ref({bar:'xxx'});
const {bar}=toRefs(foo);
html 复制代码
<template>{{bar}}</template>

19. 获取更加详细的报错

js 复制代码
app.config.errorHandler = (err, vm, info) => {
    console.log('[全局异常]', err, vm, info)
}

18. useVModel语法糖

js 复制代码
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用法示例

html 复制代码
<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可以定义多个

html 复制代码
<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的属性

js 复制代码
setup(props, context) {
    context.attrs
    context.slots
    context.parent
    context.root
    context.emit
    context.refs
    
    return {
        
    }
  }

12.computed属性完整写法

watch的套路是:既要指明监听的属性,也要指明监听的回调函数。 watchEffect的套路是:不需要指明监听的属性,监听中的回调函数用到了那个属性,就监听那个属性。

watchEffect跟computed有点像: computed注重是计算出来的值,所以必须要有返回值。 watchEffect更注重是过程,所以不用写返回值。

js 复制代码
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 接收参数的语法

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

11.watch和watchEffect的区别

  • watch 是需要传入侦听的数据源,而 watchEffect 是自动收集数据源作为依赖。
  • watch 可以访问侦听状态变化前后的值,而 watchEffect 没有,watchEffect获取的改变后的值。
  • watch 是属性改变的时候执行,当然也可以immediate,而 watchEffect 是默认会执行一次,然后属性改变也会执行。
js 复制代码
    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],()=>{})

js 复制代码
// 情况一监听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用法

父组件

HTML 复制代码
<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>

子组件

html 复制代码
<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

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

8. 动态class的设置方法

HTML 复制代码
<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方式

父组件

HTML 复制代码
<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>

子组件

HTML 复制代码
<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

HTML 复制代码
<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用法

HTML 复制代码
<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生命周期对比

bash 复制代码
beforeCreate  -> 使用 setup()
created       -> 使用 setup()
beforeMount   -> onBeforeMount
mounted       -> onMounted
beforeUpdate  -> onBeforeUpdate
updated       -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed     -> onUnmounted
errorCaptured -> onErrorCaptured
HTML 复制代码
<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

js 复制代码
readonly:只读属性的数据,深度只读
const state2 = readonly(state)
shallowReadonly:只读的数据,浅只读的

4.3 toRaw和markRaw

ini 复制代码
toRaw将代理对象变成普通对象,数据变化,界面不会进行更新
const user = toRaw(state);
markRaw标记的对象数据,从此以后都不能在成为代理对象了
const likes = ['吃','喝']
state.likes = markRaw(likes)
HTML 复制代码
<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对象的单个值

HTML 复制代码
<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。

HTML 复制代码
<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.路由取值和跳转

JavaScript 复制代码
<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>
相关推荐
susu10830189116 分钟前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
天天进步20152 小时前
Vue+Springboot用Websocket实现协同编辑
vue.js·spring boot·websocket
疯狂的沙粒3 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员3 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
想自律的露西西★4 小时前
用el-scrollbar实现滚动条,拖动滚动条可以滚动,但是通过鼠标滑轮却无效
前端·javascript·css·vue.js·elementui·前端框架·html5
白墨阳4 小时前
vue3:瀑布流
前端·javascript·vue.js
程序媛-徐师姐6 小时前
Java 基于SpringBoot+vue框架的老年医疗保健网站
java·vue.js·spring boot·老年医疗保健·老年 医疗保健
余道各努力,千里自同风6 小时前
前端 vue 如何区分开发环境
前端·javascript·vue.js
PandaCave6 小时前
vue工程运行、构建、引用环境参数学习记录
javascript·vue.js·学习
软件小伟6 小时前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js