为什么要使用vue3,vue3带来什么优势
Vue3 在语法方面进行了优化,主要是提供了
Composition【[ˌkɑːmpəˈzɪʃn] 成分、构成】 API(组合式API)(自由式编程)替换原本的Options API (选项式API)(填鸭式编程)
vue2目前API使用选项来组织代码,现在vue3是使用逻辑来组织代码,这样更有意义
vue3对
typescript的支持非常好打包大小减少、初次渲染加快、更新加快、内存使用减少
引入
tree-shaking减少打包体积vue2使用
Object.defineProperty进行数据劫持,必须预先知道劫持的key是什么,导致对象的新增属性或者使用数组下表的形式修改数据,视图不能跟着更新,只能通过拦截数组的原型方法,监听数组的变化
vue3使用Proxy进行数据劫持
vue2使用$set也就是全局Vue.set方法的别名,来添加一个新的响应式数据
1. ref 语法
ref接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property,指向该内部值。
setup(props) {
// 基本数据类型
const count = ref(0);
// 定义对象
const myObj = ref({});
myObj.value.name = '小蔡';
myObj.value.age = '22';
// 定义数组
const myArr = ref(['唱', '跳', 'rap']);
myArr.value[3] = '打篮球';
// 如果使用的不是TS,还可以直接变化数据类型
count.value = [1, 2, 3];
return { count, myObj, myArr };
},
2. reactive()函数,参数是一个对象
reactive()函数和ref,如何进行选择,官方并没有给出明确的答案,但是目前大致可以理解为
基本数据类型使用ref,引用数据类型使用reactive
但这也不是绝对的,根据编程习惯来选择就行了
setup(props) {
const myName = ref('陈明辉');
// 使用reactive函数,定义块级变量,里面还可以定义函数,等类型
const myData = reactive({
myDataName: myName,
count: 1,
// 如果想调用reactive函数里面的变量,需要加上对象名.键名的形式
handleAdd: () => myData.count++,
double: computed(() => myData.count * 2),
});
// 定义完之后,需要使用toRefs,转化成响应式对象,才能被使用
const refMyData = toRefs(myData);
// 打印一下,会发现是完全相等的
console.log('两个名字是否完全相等?', myName.value === myData.myDataName); // true
return { ...refMyData, myName, handleChangeName };
},
3. computed计算属性
接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象。
// 定义一个ref响应式的常量
const count = ref(5);
// 使用计算属性将它翻倍
const doubleCount = computed(() => count.value * 2);
// 设置点击事件
const handleAdd = () => count.value++;
doubleCount.value++; // 会打印警告:Write operation failed: 【computed value is readonly】
return { count, doubleCount, handleAdd };
或者,接受一个具有 get 和 set 函数的对象,用来创建可写的 ref 对象。
const myName = ref('陈明辉');
const changeName = computed({
get: () => myName.value + '123',
set: (val) => {
console.log('val', val); // 输出:你赋予的那个值,这里输出'蔡徐坤'
myName.value = val;
},
});
changeName.value = '蔡徐坤';
4. vue3生命周期与vue2的生命周期
注意点:
- vue3中的生命周期,比vue2的生命周期,先一步执行
- vue3修改声明周期名称,纯粹是为了更加的语义化,因为组件更像是一个挂载与卸载的过程,而不是创建与销毁的过程!
- vue3的生命周期函数,都要在
setup()函数中定义,且因为vue3能向下兼容vue2,所以在setup()函数外,我们还是可以使用vue2中的生命周期函数的 - vue3的生命周期函数,
参数都是一个函数
onBeforeMount(() => {
console.log('vue3===onBeforeMount');
});
vue2的生命周期函数,本身就是一个函数,可以直接在后面写逻辑
beforeCreate() {
console.log('vue2--beforeCreate');
},
| vue2 | vue3 | 简述 |
|---|---|---|
| beforeCreate | setup() | setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们 |
| created | setup() | 在setup()函数中强制定义beforeCreate,created 的话,会出现报错 |
| beforeMount | onBeforeMount | |
| mounted | onMounted | |
| beforeUpdate | onBeforeUpdate | 【数据更新】前后的钩子,只有当数据发生变化时才会触发 |
| updated | onUpdated | 【数据更新】前后的钩子,只有当数据发生变化时才会触发 |
| beforeDestroy | onBeforeUnmount | 相当于vue2中的beforeDestroy |
| destroyed | onUnmounted | 相当于vue2中的destroyed |
| errorCaptured | onErrorCaptured | |
| renderTracked | onRenderTracked | 调试用的,deBug钩子函数,【观察数据的变化】,函数有一个参数【event】 |
| renderTriggered | onRenderTriggered | |
| activated | onActivated | |
| deactivated | onDeactivated |
5. watch监听属性
跟vue2的作用一样,侦听特定的数据源,并在回调函数中执行副作用。
// 侦听ref对象值
const inputNum1 = ref(0);
const inputNum2 = ref(0);
const inputSum = ref(0);
// 侦听多个ref对象值,使用数组形式包过,当然,也可以写多个watch进行分别监听
watch([inputNum1, inputNum2], (newValue, oldValue) => {
console.log('newValue:', newValue);
console.log('oldValue:', oldValue);
inputSum.value = Number(inputNum1.value) + Number(inputNum2.value);
});
// 侦听reactive对象值
const mousePosition = reactive({
xPosition: 0,
yPosition: 0,
});
const updateMousePosition = (e) => {
mousePosition.xPosition = e.pageX;
mousePosition.yPosition = e.pageY;
};
onMounted(() => {
document.addEventListener('click', updateMousePosition);
});
// 如果侦听reactive对象值,直接监听这个值的话,无法获得oldValue
// watch(mousePosition, (newValue, oldValue) => {
// 必须得单独拿出来,使用函数的形式返回这个值,如果是多个值,必须用数组包起来
watch([() => mousePosition.xPosition, () => mousePosition.yPosition], (newValue, oldValue) => {
console.log('newValue:', newValue);
console.log('oldValue:', oldValue);
});
// 监听reactive深层属性
const deepData = reactive({
person: {
sex: {
male: {
name: '陈明辉',
},
},
},
});
const handleChangeDeepData = () => {
deepData.person.sex.male.name = prompt('请输入你的姓名!');
};
// 就算开启deep深度监听,也无法侦听前后值的变化,无法获取oldValue
// 两种解决方法
// 1. 使用深克隆的形式,返回一个深克隆对象
// watch(
// () => deepClone(deepData),
// (newValue, oldValue) => {}
// );
// 2. 使用属性链式.的形式,依次.到你想侦听的属性值上
watch(
() => deepData.person.sex.male.name,
(newValue, oldValue) => {
console.log('newValue:', newValue);
console.log('oldValue:', oldValue);
}
);
6. Provide / Inject 父子组件传值
解决了深度嵌套组件之间的父子传值问题
父组件 provide 提供数据
子组件 inject 获取并使用数据
// 父组件
const myLikeSport = ref(['唱', '跳', 'rap', '蓝球']);
// provide函数向子组件提供数据,第一个参数是键名,第二个参数是键值
// 键值可以传递一个【常量】也可以传递一个ref的【响应式变量】,父组件对响应式变量进行更新,子组件也会更新
provide('myLikeSport', myLikeSport);
// 子组件
// 使用inject函数,加上键名可以获取到
// 使用起来类似localStorage
const myLikeSport = inject('myLikeSport');
7. 子组件使用emits触发父组件的函数
注意点:
emits可以是数组 或 对象(用法就好似 vue2 中的 props)- 如果是数组,那就将要发送的函数以字符串的形式放进去就行了
- 如果是对象,每个值可以是 null 也就是什么都不做,也可以是一个验证函数,有一个参数是要传递给父组件的参数,这个验证函数结束时需要返回一个true,否则会报警告。(但是他这个验证函数,无论返回true还是false,都会继续调用父组件的函数,我也不知道这个验证是干嘛的?)
- emits是要写在setup函数外面的
// 第一种,数组形式,非常简单
emits: ['closeDialog'],
// 第二种,对象形式,只给一个null就可以了
emits: {
closeDialog: null
},
// 第三种,对象形式,验证函数的形式这到底验证个啥?
emits: {
closeDialog: (params) => {
if (params == 'close') {
return true;
}
return false;
},
},
8. vue-router路由系统
由于vue是一个渐进式框架,路由系统是可以按照自己的需求,安装与卸载的,但是我们几乎所有项目,都会用到
router文件夹:
// 导入两个函数
// createRouter 类似 main.js 中的 createApp ,用于创建一个router实例
// createRouter 有两个必填的参数,一个是 history,定义采用哪种形式的路由
// routes 接收一个路由配置项,是一个数组对象
// 对象中至少有两个属性,一个path,一个component
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router';
import HomeView from '../views/HomeView.vue';
import Login from '../views/Login.vue';
const routes = [
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/login',
name: 'login',
component: Login,
},
];
// 创建一个 router 实例对象
const router = createRouter({
history: createWebHashHistory(), // 这种路由在url上会有一个#,有诸多的缺点,唯一的有点就是适配各种旧版的浏览器
// history: createWebHistory(), // 这种就没有
routes,
});
export default router;
页面路由跳转:
// 两种【声明式导航】
// 最基本的形式
<router-link to="/login">跳转到登录页</router-link>
// 带参数形式
<router-link :to="{ name: 'login', query: { name: '蔡徐坤' } }">跳转到登录页,带参数</router-link>
// 编程式导航
// 首先要导入useRouter这个函数,相当于vue2中的router
import { useRouter } from 'vue-router';
export default {
setup(props) {
// 创建一个实例
const myRouter = useRouter();
console.log('函数外面的 useRouter', useRouter());
const handleLinkTo = function () {
// 不知道为什么,在箭头函数里面没发获取到useRouter()的实例,外面就可以
console.log('函数里面的 useRouter', useRouter()); // undefined
// 进行路由跳转,其他的跟vue2都是一样的
myRouter.push({ name: 'login' });
};
return { handleLinkTo };
},
};
接收路由参数,或者获取路由信息
// 引入 useRoute ,相当于vue2中的 route 记录当前路由的各种信息,包括上一页面传递过来的参数
import { useRoute } from 'vue-router';
export default {
setup(props) {
const myRoute = useRoute();
console.log('myRoute', myRoute.query);
return { myRoute };
},
};
keep-alive的写法规则变更
vue3里面要这样写,router-view 包裹着 keep-alive 跟 vue2 正好相反
include 属性还是跟之前保持一样
<router-view v-slot="{ Component }">
<keep-alive include="HomeView">
<component :is="Component" />
</keep-alive>
</router-view>
vue2写法
<keep-alive include="SEMextension">
<router-view></router-view>
</keep-alive>
9. vuex状态管理工具
用法跟vue2基本都是一样的,唯一的不同是使用方式
可以说基本跟vue-router都差不多
// 使用时,使用createStore来创建实例对象,最后导出
// 一个非常简单的例子
import { createStore } from 'vuex';
export default createStore({
state: {
userInfo: {
name: '陈明辉',
sex: 'male',
hobby: 'CTRL C V',
favoriteStar: '蔡徐坤',
token: 'H13KJ3LS1ADH132LK2OJ5PSA2MC45AS1C5AS1D3153FA',
userId: 1,
},
city: '杭州',
},
actions: {
changeCityName({ commit }, newCityName = '宁波') {
commit('changeCityName', newCityName);
},
},
mutations: {
changeCityName(state, newCityName) {
state.city = newCityName;
},
},
getters: {
city: (state) => state.city + '西湖',
},
modules: {},
});
// 在.vue文件中使用,需要引入,并创建实例
import { useStore } from 'vuex';
setup(props) {
// 创建一个vuex的实例
const myStore = useStore();
console.log('myStore', myStore.state.city);
myStore.dispatch('changeCityName');
console.log('myStore', myStore.getters.city);
console.log('useStore', useStore());
const logVuex = () => {
// 跟vue-router一样,不能用在函数里面,只能在setup函数中定义
console.log('useStore', useStore());
};
return { logVuex };
},