Vue2转Vue3快速上手全篇

2,697 阅读8分钟

我正在参加「码上掘金挑战赛」详情请看:码上掘金挑战赛来了!

Vue3

v2-v3的学习成本不高,只要有v2基础,基本可以上手vue3

一、setup语法

setup中不能访问v2的配置比如data,methods等

二、ref响应数据

使用ref可以创建一个对象,可以是基本类型,也可以是对象 例如:

<template>
  <div class="home">
    <!-- 渲染 -->
	{{a}}
  </div>
</template>

<script>
import { ref} from 'vue'
export default {
  name: 'Home',
  setup(){
    // 创建
	const a = ref('')
    // 使用方法  xxx.value
	a.value = 'NanChen'
	return{
		a,
	}
  },
}
</script>

请添加图片描述

三、reactive创建响应式数据

这是一个对象类型的响应数据,例:

<template>
  <div class="home">
    {{a.sex}}
    {{a.name}}
  </div>
</template>

<script>
import { reactive } from "vue";
export default {
  name: "Home",
  setup() {
    const a = reactive({
      sex: "",
      name: "",
    });
    a.sex = "男";
    a.name = "NanChen";
    return {
      a,
    };
  },
};
</script>

请添加图片描述

四、computed 计算属性

例:

<template>
  <div>
    <div>
      姓:{{per.firstName}}
    </div>
    <div>
      名:{{per.lastName}}
    </div>
    <div>
      加起来:{{per.fullName}}
    </div>
  </div>
</template>
 
<script>
import { reactive, computed } from "vue";

export default {
  setup() {
    let per = reactive({
      firstName: "Nan",
      lastName: "Chen",
    });
    per.fullName = computed(() => {
      return per.firstName + "++" + per.lastName;
    });
    return {
      per,
    };
  },
};
</script>
 
<style>
</style>

请添加图片描述

五、watch 监听器

例(一):监听单个ref的响应式数据

<template>
  <div>
	监听<button @click="num++">+1查看监听器变化</button>
	<div>{{num}}</div>
  </div>
</template>
 
<script>
import { ref, watch } from "vue";

export default {
  setup() {
	let num = ref(0);
	watch(num,(newValue,oldValue)=>{
		console.log('newValue: '+newValue,' oldValue: '+oldValue);
	})
	return{
		num
	}
  },
};
</script>

请添加图片描述

例(二):监听ref定义的多个响应式数据

<template>
  <div>
    <div>
      <button @click="num++">+1查看监听器变化</button>
      <div>num:{{num}}</div>
    </div>

    <div>
      <button @click="num2+='改动了'">改变</button>
      <div>num2:{{num2}}</div>
    </div>
  </div>
</template>
 
<script>
import { ref, watch } from "vue";

export default {
  setup() {
    let num = ref(0);
    let num2 = ref("监听多个ref响应式的使用");
    watch(
      [num, num2],
      (newValue, oldValue) => {
        console.log("num and num2 ", newValue, oldValue);
      },
      /* immediate:第一次就监听 */
      { immediate: true }
    );
    return {
      num,
      num2,
    };
  },
};
</script>

请添加图片描述

例(三):监听reactive所定义的一个响应式数据

<template>
  <div>
    <div>
      <button @click="a.name+='XX'">更改姓名</button>
      <div>{{a.name}}</div>
    </div>
    <div>
      <button @click="a.age++">更改年龄</button>
      <div>{{a.age}}</div>
    </div>
  </div>
</template>
 
<script>
import { reactive, watch } from "vue";

export default {
  setup() {
    let a = reactive({
      name: "NanChen",
      age: 20,
    });
     watch(a,(newValue, oldValue) => {
        console.log(newValue, oldValue);
      },{immediate:true,deep:false}//deep无效
    );
    return {
      a,
    };
  },
};
</script>

请添加图片描述

例(四):监听reactive定义的一个响应式数据中的某一个属性

<template>
  <div>
    <div>
      <button @click="a.name+='XX'">更改姓名</button>
      <div>{{a.name}}</div>
    </div>
    <div>
      <button @click="a.age++">更改年龄</button>
      <div>{{a.age}}</div>
    </div>
  </div>
</template>
 
<script>
import { reactive, watch } from "vue";

export default {
  setup() {
    let a = reactive({
      name: "NanChen",
      age: 20,
    });
    watch(() => a.name,(newValue, oldValue) => {
        console.log(newValue, oldValue);
      }
    );
    return {
      a,
    };
  },
};
</script>

请添加图片描述

例(五):监听reactive定义的一个响应式数据中的某些属性

<template>
  <div>
    <div>
      <button @click="a.name+='XX'">更改姓名</button>
      <div>{{a.name}}</div>
    </div>
    <div>
      <button @click="a.age++">更改年龄</button>
      <div>{{a.age}}</div>
    </div>
  </div>
</template>
 
<script>
import { reactive, watch } from "vue";

export default {
  setup() {
    let a = reactive({
      name: "NanChen",
      age: 20,
    });
    watch([() => a.name, () => a.age],(newValue, oldValue) => {
        console.log(newValue, oldValue);
      }
    );
    return {
      a,
    };
  },
};
</script>

请添加图片描述

例(六):监听reactive定义的嵌套对象

<template>
  <div>
    <div>
      <button @click="a.name+='XX'">更改姓名</button>
      <div>{{a.name}}</div>
    </div>
    <div>
      <button @click="a.age++">更改年龄</button>
      <div>{{a.age}}</div>
    </div>
    <div>
      <button @click="a.obj.objList.num++">更改数字</button>
      <div>{{a.obj.objList.num}}</div>
    </div>
  </div>
</template>
 
<str>
import { reactive, watch } from "vue";

export default {
  setup() {
    let a = reactive({
      name: "NanChen",
      age: 20,
      obj: {
        objList: {
          num: 100,
        },
      },
    });
    watch(() => a.obj,(newValue, oldValue) => {
        console.log(newValue, oldValue);
      },{ deep: true }); // 这里是坚挺的reactive对象中的某个属性,因此deep生效
    // deep:其值是true或false;确认是否深入监听。(一般监听时是不能监听到对象属性值的变化的,数组的值变化可以听到。
    return {
      a,
    };
  },
};
</script>

请添加图片描述

六、watchEffect 监听器

特性,也可以说成和watch的区别

  • 不需要手动传入依赖
  • 每次初始化时会执行一次回调函数来自动获取依赖
  • 无法获取到原值,只能得到变化后的值
<template>
  <div>
    <div>
      <button @click="a.name+=' hello'">更改姓名</button>
      <div>{{a.name}}</div>
    </div>
    <div>
      <button @click="a.age++">更改年龄</button>
      <div>{{a.age}}</div>
    </div>
  </div>
</template>
 
<script>
import { reactive, watchEffect } from "vue";

export default {
  setup() {
    let a = reactive({
      name: "NanChen",
      age: 20,
    });
    watchEffect(() => {
      console.log(a.name);
      console.log(a.age);
    });
    setTimeout(() => {
      a.name += " hi";
      a.age++;
    }, 1000);
    return {
      a,
    };
  },
};
</script>

请添加图片描述


七、Vue3生命周期

所有的声明周期要放在setup中 Vue3的生命周期如下: 1、beforeCreate -> 使用 setup()

2、created -> 使用 setup()

3、beforeMount -> onBeforeMount

4、mounted -> onMounted

5、beforeUpdate -> onBeforeUpdate

6、updated -> onUpdated

7、beforeDestroy -> onBeforeUnmount

8、destroyed -> onUnmounted

9、errorCaptured -> onErrorCaptured

语法

setup() {
    onMounted(() => {
      console.log('mounted')
    })
}

八、toRef

说白话文就是不用写前面的对象名称直接渲染里面的属性即可

<template>
  <div>
    <div>{{name}}</div>
    <div>{{age}}</div>
    <div>{{num}}</div>
  </div>
</template>
 
<script>
import { reactive, toRef } from "vue";

export default {
  setup() {
    let a = reactive({
      name: "NanChen",
      age: 20,
      list: {
        num: 0,
        num1: 1,
        num2: 2,
        num3: 3,
        num4: 4,
      },
    });
    return {
      name: toRef(a, "name"),
      age: toRef(a, "age"),
      num: toRef(a.list, "num"),
    };
  },
};
</script>

请添加图片描述

九、toRefs 响应式转换

一键给对象中的多个属性全部响应转换

<template>
  <div>
    <div>{{name}}</div>
    <div>{{age}}</div>
    <div>{{num}}</div>
    <div>{{num1}}</div>
    <div>{{num2}}</div>
    <div>{{num3}}</div>
    <div>{{num4}}</div>
  </div>
</template>
 
<script>
import { reactive, toRefs } from "vue";

export default {
  setup() {
    let a = reactive({
      name: "NanChen",
      age: 20,
      list: {
        num: 0,
        num1: 1,
        num2: 2,
        num3: 3,
        num4: 4,
      },
    });
    return {
      ...toRefs(a),
      ...toRefs(a.list),
    };
  },
};
</script>

请添加图片描述


十、shallowReactive (浅响应式)

只处理对象最外面一层的响应式数据(浅响应式)

<template>
  <div>
    <h1>姓:{{name}}</h1>
    <h2>岁数:{{age}}</h2>
    <h3>对象{{obj.objList.name}}</h3>
    <button @click="name += '+'">修改姓名</button>
    <button @click="age++">修改年龄</button>
    <button @click="obj.objList.name += '!'">修改对象</button>
  </div>
</template>
 
<script>
import { reactive, toRefs, shallowReactive } from "vue";
export default {
  name: "App",
  setup() {
    // 定义了一段数据
    let a = shallowReactive({
      // 只将第一层数据做了响应式处理
      name: "NanChen",
      age: 20,
      obj: {
        objList: {
          name: "Jia", // 深层次的数据将会是一个普通的对象
        },
      },
    });
    // 将数据返回出去
    return {
      ...toRefs(a),
    };
  },
};
</script>

请添加图片描述

十一、shallowRef 不进行对象响应式

只处理基础数据类型的响应式,不进行对象类型的响应式。

<template>
  <div>
    <h1>姓:{{a}}</h1>
    <button @click="a += '+'">修改姓名</button>
    <h2>{{b.num}}</h2>
    <button @click="b++">修改num</button>
  </div>
</template>
 
<script>
import { shallowRef } from "vue";
export default {
  name: "App",
  setup() {
    // 定义了一段数据
    let a = shallowRef("NanChen");
    let b = shallowRef({
      num: 1,
    });
    console.log(b.value.num);
    // 将数据返回出去
    return {
      a,
      b,
    };
  },
};
</script>

请添加图片描述

这里可以看到,修改数据后将不会在触发页面的更新 因为监测不到了


十二、readonly(深只读)

const a = shallowRef({
  name: 'NanChen', // 只读
  obj: {
    objList: 2 // 也是只读
  }
})

十三、shallowReadonly(浅只读)

const a = shallowReadonly({
  name: 'NanChen', // 只读
  obj: {
    objList: 2 // 不是只读
  }
})

十四、toRaw 将响应式对象转换成普通对象

例:

<template>
  <h2>姓名:{{a.name}}</h2>
  <h2>年龄:{{a.age}}</h2>
  <button @click="showRawA">点我显示原始a</button>
</template>

<script>
import { reactive, toRaw } from "vue";
export default {
  name: "Demo",
  setup() {
    let a = reactive({
      name: "NanChen",
      age: 20,
    });
    function showRawA() {
      console.log("a=", a);
      let p = toRaw(a);
      console.log("raw a = ", p);
    }
    return {
      a,
      showRawA,
    };
  },
};
</script>

请添加图片描述

这里可以看到使用toRaw后,响应式对象变成了一个普通的对象

十五、markRaw 永久不响应

这里看一下使用markRaw和不使用markRaw的区别

不使用markRaw

看这个例子:

<template>
  <h2>姓:{{a.name}}</h2>
  <div v-if="a.other" style="border: 1px solid #000;width: 200px;padding: 10px;margin-bottom: 10px;">
    <h3>开发岗位:{{a.other.kaifa}}</h3>
    <h3>待遇:{{a.other.money}}K</h3>
    <button @click="changeOne">更换工程师</button>&nbsp;
    <button @click="changeTwo">加薪资</button>
  </div>
  <button @click="addOther">添加信息</button>
</template>

<script>
import { reactive } from "vue";
export default {
  name: "Demo",
  setup() {
    let a = reactive({
      name: "NanChen",
      age: 20,
    });
    function addOther() {
      a.other = {
        kaifa: "web开发",
        money: 1,
      };
    }
    function changeOne() {
      a.other.kaifa = "java开发";
    }
    function changeTwo() {
      a.other.money++;
    }
    return {
      a,
      addOther,
      changeOne,
      changeTwo,
    };
  },
};
</script>

效果: 请添加图片描述


可以看到这里的数据是可以进行响应

添加markRaw

<template>
  <h2>姓名:{{a.name}}</h2>
  <div v-if="a.other" style="border: 1px solid #000;width: 200px;padding: 10px;margin-bottom: 10px;">
    <h3>开发岗位:{{a.other.kaifa}}</h3>
    <h3>待遇:{{a.other.money}}K</h3>
    <button @click="changeOne">更换工程师</button>&nbsp;
    <button @click="changeTwo">加薪资</button>
  </div>
  <button @click="addOther">添加信息</button>
</template>

<script>
import { reactive, markRaw } from "vue";
export default {
  name: "Demo",
  setup() {
    let a = reactive({
      name: "NanChen",
      age: 20,
    });
    function addOther() {
      a.other = markRaw({
        kaifa: "web开发",
        money: 1,
      });
    }
    function changeOne() {
      a.other.kaifa = "java开发";
    }
    function changeTwo() {
      a.other.money++;
    }
    return {
      a,
      addOther,
      changeOne,
      changeTwo,
    };
  },
};
</script>

效果: 请添加图片描述

因为markRaw将{kaifa: "web开发",money: 1,}变成了一个非响应式对象。因此,当修改 a.other.kaifa 或 a.other.money时,界面不会更新

十六、provide / inject

在组合式 API 中使用 provide/inject,两个只能在 setup 期间调用 provide 函数是有两个接受参数,是用来提供和发送数据

provide(name,value)

祖先组件:

import { provide,reactive } from "vue"
export default {
  setup(){
    let obj = reactive({
        name:'NanChen',
        age:20
    }
    provide('obj',obj)
  }
}

inject则是用来接受数据 后代组件:

import { inject } from "vue"
export default {
  setup(){
    const obj = inject('car')
    return{obj}
  }
}

十七、isRef

判断值是否为ref对象

let name = ref('NanChen')
console.log(isRef(name)); // true

十八、isReactive

判断值是否为isReactive对象

let num = isReactive({})
console.log(isRef(val)); // true

十九、inReadonly

检查对象是否是由readonly创建的只读代理

const state = reactive({
  name: 'NanChen'
})
console.log(isReactive(state)) // true

二十、isProxy

检查对象是否是由reactive或者readonly创建的proxy

const state = reactive({
  name: 'NanChen'
})
console.log(isProxy(state)) // true

teleport 传送门

这里说一下传送,这个传送就是我可以把teleport标签通过to="名称"放在我想要放在的标签里面 看个例子: Home.vue

<template>
  <div id="one">
      <h3>第一个div</h3>
  </div>
  <div class="two">
      <h3>第二个div</h3>
  </div>
  <ModalButton></ModalButton>
</template>
<script>
import { defineComponent } from "vue";
import ModalButton from '../components/ModalButton.vue';

export default defineComponent({
  components: {
    ModalButton,
  },
  setup() {
    return {};
  },
});
</script>

子组件ModalButton.vue

<template>
  <div>
    <button @click="modalOpen = true">弹出框</button>
    <teleport to="body">
      <div v-if="modalOpen" class="modal">
        <div>
          传送
          <button @click="modalOpen = false">关闭</button>
        </div>
      </div>
    </teleport>
  </div>
</template>
<script>
import { defineComponent, ref } from "vue";
export default defineComponent({
  setup() {
    const modalOpen = ref(false);
    return { modalOpen };
  },
});
</script>

<style scoped>
.modal {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.modal div {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-around;
  background-color: white;
  width: 350px;
  height: 300px;
  padding: 5px;
}
</style>

请添加图片描述

我们来具体看一下效果: 请添加图片描述

把to中的body替换成#one 请添加图片描述

再看效果: 请添加图片描述

最后再尝试把to中的#one替换成.two,可想而知效果是一致的 请添加图片描述

效果: 请添加图片描述

注意:这个传送是需要通过找到上方层级的盒子才能进行传送

请添加图片描述

如果写成这样:(是进行不了传送的)

<template>
  <ModalButton></ModalButton>
  <div id="one">
    <h3>第一个div</h3>
  </div>
  <div class="two">
    <h3>第二个div</h3>
  </div>
</template>

请添加图片描述

请添加图片描述

为什么我们需要 Teleport Teleport 是一种能够将我们的模板移动到 DOM 中 Vue app 之外的其他位置的技术,就有点像哆啦A梦的“任意门”。

场景:像 modals,toast 等这样的元素,很多情况下,我们将它完全的和我们的 Vue 应用的 DOM 完全剥离,管理起来反而会方便容易很多

原因在于如果我们嵌套在 Vue 的某个组件内部,那么处理嵌套组件的定位、z-index 和样式就会变得很困难

另外,像 modals,toast 等这样的元素需要使用到 Vue 组件的状态(data 或者 props)的值

这就是 Teleport 派上用场的地方。我们可以在组件的逻辑位置写模板代码,这意味着我们可以使用组件的 data 或 props。然后在 Vue 应用的范围之外渲染它

Suspense组件

官网中有提到他是属于实验性功能: <Suspense> 是一项实验性功能。它不一定会最终成为稳定功能,并且在稳定之前相关 API 也可能会发生变化。 <Suspense> 是一个内置组件,用来在组件树中协调对异步依赖的处理。它让我们可以在组件树上层等待下层的多个嵌套异步依赖项解析完成,并可以在等待时渲染一个加载状态。

意思就是此组件用来等待异步组件时渲染一些额外内容,让应用有更好的用户体验

要了解 <Suspense> 所解决的问题和它是如何与异步依赖进行交互的,我们需要想象这样一种组件层级结构:

<Suspense>
└─ <Dashboard>
   ├─ <Profile>
   │  └─ <FriendStatus>(组件有异步的 setup())
   └─ <Content>
      ├─ <ActivityFeed> (异步组件)
      └─ <Stats>(异步组件)

在这个组件树中有多个嵌套组件,要渲染出它们,首先得解析一些异步资源。如果没有 <Suspense>,则它们每个都需要处理自己的加载、报错和完成状态。在最坏的情况下,我们可能会在页面上看到三个旋转的加载态,在不同的时间显示出内容。

有了 <Suspense> 组件后,我们就可以在等待整个多层级组件树中的各个异步依赖获取结果时,在顶层展示出加载中或加载失败的状态。 接下来看个简单的例子: 首先需要引入异步组件

import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))

简洁一些就是用组件实现异步的加载的这么一个效果 Home父组件代码如下:

<template>
  <div class="home">
    <h3>我是Home组件</h3>
    <Suspense>
       <template #default>
        <Child />
      </template>
      <template v-slot:fallback>
        <h3>Loading...</h3>
      </template>
    </Suspense>
  </div>
</template>
 
<script >
// import Child from './components/Child'//静态引入
import { defineAsyncComponent  } from "vue";
const Child = defineAsyncComponent(() => import("../components/Child"));
export default {
  name: "",
  components: { Child },
};
</script>
 
<style>
.home {
  width: 300px;
  background-color: gray;
  padding: 10px;
}
</style>

自组件Child

<template>
  <div class="child">
    <h3>我是Child组件</h3>
    name: {{user.name}}
    age: {{user.age}}
  </div>
</template>

<script>
import { ref } from "vue";
export default {
  name: "Child",
  async setup() {
    const NanUser = () => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve({
            name: "NanChen",
            age: 20,
          });
        },2000);
      });
    };
    const user = await NanUser();
    return {
      user,
    };
  },
};
</script>

<style>
.child {
  background-color: skyblue;
  padding: 10px;
}
</style>

根据插槽机制,来区分组件, #default 插槽里面的内容就是你需要渲染的异步组件; #fallback 就是你指定的加载中的静态组件。


效果如下: 请添加图片描述


使用Ref来获取DOM

Vue3中没有this,所以不能使用$refs来获取dom元素 在V3中的写法可以使用**ref(null)**来获取dom元素

<template>
  <div class="home">
    <h3 ref="name">NanChen</h3>
  </div>
</template>
 
<script >
// import Child from './components/Child'//静态引入
import {onMounted, ref} from 'vue'
export default {
  name: "",
  setup(){
    const name = ref(null)
    onMounted(()=>{
      console.log(name.value);
    })
    return{
      name
    }
  }
};
</script>
 
<style>
</style>
这里注意一下要放在onMounted中,因为它需要在组件挂载时调用!!!

获取动态循环生成的ref

<template>
  <div v-for="(item,index) in list" :key="index" :ref="setItemRef">
    {{item}}
  </div>
  <el-button @click="getRefData">点我获取</el-button>
</template>

<script setup>
import { ref, reactive, onMounted } from "vue";
const refList = ref([]); //定义ref数组
const list = reactive(["我是第一行", "我是第二行", "我是NanChen"]);
// 给动态ref一个灵活的函数
const setItemRef = (el) => {
  console.log(el);
  if (el) {
    refList.value.push(el);
  }
};

//通过获得的函数并执行接下来操作
const getRefData = () => {
  for (let i = 0; i < refList.value.length; i++) {
    console.log(refList.value[i]);
  }
};
</script>

实现效果: 请添加图片描述

打印结果: 请添加图片描述

Fragments(片段)

我们都知道在vue2中,不支持多个根组件,意外创建根组件时会提示警告,所以我们在写组件时,必须要用div来包裹多个组件才行,

<template>
  <div>
    <header>header</header>
    <main>
      main
      <aside>aside</aside>
    </main>
    <footer>footer</footer>
  </div>
</template>

在Vue3中就不需要用div来包裹,意思就是组件可以有多个节点,想怎么用就怎么使用

<template>
  <header>header</header>
  <main>
    main
    <aside>aside</aside>
  </main>
  <footer>footer</footer>
</template>

单文件组件状态驱动的 CSS 变量

<template>
  <div class="parent">
  I am Parent
  <div class="child">
    I am Child
    <div>NanChen</div>
  </div>
</div>
</template>

<script setup>
</script>
<style scoped>
.parent {
  /*  变量的作用域就是它所在的选择器的有效范围,所以.parent 读取不到 child 中的变量  */
  color: var(--child-color);
  /*  定义变量  */
  --parent-color: #f00;
}
.child {
  /*  通过 var 读取变量  */
  color: var(--parent-color);
  --child-color: green;
}
.child div{
  color: var(--child-color);
}
</style>

注意以下几点:

  1. CSS变量的命名是敏感的,也就是说--child-color 和--child-Color是有区别的
  2. var() 参数可以使用第二个参数设置默认值,当该变量无效的时候,就会使用这个默认值
  3. CSS变量提供了JavaScript与CSS通信的一种途径,可以通过js去操作我们所用的css,这里了解一下即可。
// 获取DOM元素节点上的css名
element.style.getPropertyValue('--child-color');

// 获取任意DOM节点上的CSS名
getComputedStyle(element).getPropertyValue('--child-color');

这里提一下为什么出现这种写法,这样写的意义在什么地方? 现在许多复杂的网站都有大量的css代码,重复的值也比较多,是用这种方式可读性比较强,而且在性能上也是要好许多,通过css变量,可以通过在组件的根元素设置变量,在组件内部<style>中直接使用即可。 还有一点就是我们无法在动态的style中设置伪元素样式,而css变量就可以。

div::first-list {
  color: var(--parent-color);
}