setup
ref 和 reactive
isRef
toRefs
toRefs() 函数可以将 reactive() 创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每个属性节点,都是 ref() 类型的响应式数据
computed
watch
新的生命周期函数
Vue3 | Vue2 |
---|---|
use setup() | |
use setup() | |
onBeforeMount | beforeMount |
onMounted | mounted |
onBeforeUpdate | beforeUpdate |
onUpdated | updated |
onBeforeUnmount | beforeDestroy |
onUnmounted | destroyed |
onErrorCaptured | errorCaptured |
onRenderTracked | renderTracked |
onRenderTriggered | renderTriggered |
文档: https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create
## 安装或者升级
npm install -g @vue/cli
## 保证 vue cli 版本在 4.5.0 以上
vue --version
## 创建项目
vue create my-project
然后的步骤
::: tip Vue3 初始项目直观差异
Vue3 与 Vue2 项目细节差异
:::
Vue3
main.ts
createApp(App).use(store).use(router).mount('#app');
Vue2
main.js
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');
Vue3
router/index.ts(函数化单个拆分引入,方便摇树去除代码,减少项目大小提高性能)
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
mode 切换成了函数化声明
const router = createRouter({
history: createWebHashHistory(),
routes,
});
Vue2
router/index.js(整个模块引入,导致项目大小增加,增大损耗)
import VueRouter from 'vue-router';
mode 是字符串设置
const router = new VueRouter({
routes,
});
Vue3
store/index.ts
import { createStore } from 'vuex';
export default createStore({});
Vue2
store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({});
Vue3
views/home.vue
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src
@Options({
components: {
HelloWorld
}
})
export default class Home extends Vue {}
</script>
上述代码可以修改成以下代码内容,定义组件的方式也可以利用 defineComponent 来实现
<script lang="ts">
import { defineComponent } from 'vue'
import HelloWorld from "@/components/HelloWorld.vue";
export default defineComponent({
name:'Home',
components:{
HelloWorld
}
})
</script>
Vue2
views/home.vue
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'Home',
components: {
HelloWorld
}
}
</script>
Vue3
components/helloworld.vue
<script lang="ts">
import { Options, Vue } from "vue-class-component";
@Options({
props: {
msg: String
}
})
export default class HelloWorld extends Vue {
msg!: string;
}
</script>
上述代码可以修改成以下代码内容,定义组件的方式也可以利用 defineComponent 来实现
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name:"HelloWorld",
props:{
msg:String
}
})
</script>
Vue2
components/helloworld.vue
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
文档: https://v3.cn.vuejs.org/guide/installation.html
Vite 是一个由原生 ESM 驱动的 Web 开发构建工具。在开发环境下基于浏览器原生 ES imports 开发,在生产环境下基于 Rollup 打包。
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
文档:
https://v3.cn.vuejs.org/api/basic-reactivity.html
https://v3.cn.vuejs.org/api/composition-api.html
https://composition-api.vuejs.org/zh/
setup
函数是一个新的组件选项。作为在组件内使用 Composition API 的入口点。
创建组件实例,然后初始化 props
,紧接着就调用setup
函数。从生命周期钩子的视角来看,它会在 beforeCreate
钩子之前被调用
入参:
{Data} props
{SetupContext} context
<script lang="ts">
export default {
setup(props,context){
console.log(props,context)
}
}
</script>
接受一个参数值并返回一个响应式且可改变的 ref 对象
ref 对象拥有一个指向内部值的单一属性 .value
如果传入 ref 的是一个对象,将调用 reactive
方法进行深层响应转换。
当 ref 作为渲染上下文的属性返回(即在setup()
返回的对象中)并在模板中使用时,它会自动解套,无需在模板内额外书写 .value
<script lang="ts">
export default {
beforeCreate(){
console.log('beforeCreate()',this) // beforeCreate在setup之后执行,具有this对象
},
setup(props,context){
console.log('setup()',this) // setup在beforeCreate之前执行,没有this对象
}
}
</script>
::: tip Vue3 与 Vue2 的 ref 差异
与 Vue2 的 ref 对象指向不同(找 DOM 以及找 Component 组件),Vue3 中的 ref 是创建包含响应式数据的引用对象
:::
::: tip 对象需返回才能使用
如果目前 count 对象没有进行 return 返回处理,在模板页中插值显示 count 是无法显示的
:::
{{ count }}
使用 getter 函数,并为从 getter 返回的值返回一个不变的响应式 ref 对象。
或者,它可以使用具有 get
和 set
函数的对象来创建可写的 ref 对象。
count: {{ count }}
double: {{ double }}
count: {{ count }}
double: {{ double }}
double2: {{ double2 }}
接收一个普通对象然后返回该普通对象的响应式代理器对象
响应式转换是“深层的”:会影响对象内部所有嵌套的属性
基于 ES2015 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
:::tip reactive 区别 ref
ref 的作用就是将一个原始数据类型(primitive data type)转换成一个带有响应式特性(reactivity)的数据类型,原始数据类型共有 7 个,分别是:String、Number、Boolean、Null、Undefined、Symbol、BigInt
ref 与 reactive 定义基本元素类型数据时,ref 定义的是包装后的响应式数据,而 reactive 定义的还是原来的类型(也就是 reactive 定义基本类型不是响应式的,修改数据不能更新到模板)
使用 ref 还是 reactive 可以选择这样的准则
第一:像平常写普通的 js 代码,选择原始类型和对象类型一样来选择是使用 ref 还是 reactive。
第二:所有场景都使用 reactive,但是要记得使用 toRefs 保证 reactive 对象属性保持响应性。
:::
<template>
<div class="about">
<p>msg:{{ state.msg }}</p>
<p>numbers:{{ state.numbers }}</p>
<p>name:{{ state.person.name }}</p>
</div>
</template>
<script lang="ts">
import { reactive } from "vue";
export default {
setup() {
// state 对象是reactive中原始对象的代理对象
// 一旦操作代理对象的属性,内部操作的是原始对象的属性
const state = reactive({
msg: "hello Vue3",
numbers: [1, 2, 3],
person: {
name: "Vane",
},
});
return {
state,
};
},
};
</script>
<template>
<div class="about">
<p>msg:{{ state.msg }}</p>
<p>numbers:{{ state.numbers }}</p>
<p>name:{{ state.person.name }}</p>
<button @click="state.update">update</button>
</div>
</template>
<script lang="ts">
import { reactive } from "vue";
export default {
setup() {
// state 对象是reactive中原始对象的代理对象
// 一旦操作代理对象的属性,内部操作的是原始对象的属性
const state = reactive({
msg: "hello Vue3",
numbers: [1, 2, 3],
person: {
name: "Vane",
},
update: () => {
state.msg = "Vue3 is very good";
// 通过下标直接替换数组元素
// Vue3会自动更新,Vue2中不会自动更新,需要用转换后的数组函数
state.numbers[1] = 100;
// 通过路径直接替换对象属性
// Vue3会自动更新,Vue2中需要利用Vue.set进行对象属性更新
state.person.name = "Chinavane";
},
});
return {
state,
};
},
};
</script>
<template>
<div class="about">
<p>msg:{{ msg }}</p>
<p>numbers:{{ numbers }}</p>
<p>name:{{ person.name }}</p>
<button @click="update">update</button>
</div>
</template>
<script lang="ts">
import { reactive } from "vue";
export default {
setup() {
// state 对象是reactive中原始对象的代理对象
// 一旦操作代理对象的属性,内部操作的是原始对象的属性
const state = reactive({
msg: "hello Vue3",
numbers: [1, 2, 3],
person: {
name: "Vane",
},
update: () => {
state.msg = "Vue3 is very good";
// 通过下标直接替换数组元素
// Vue3会自动更新,Vue2中不会自动更新,需要用转换后的数组函数
state.numbers = [1, 100, 3];
// 通过路径直接替换对象属性
// Vue3会自动更新,Vue2中需要利用Vue.set进行对象属性更新
state.person = { name: "Chinavane" };
},
});
return {
// 问题:如果 reactive 响应式对象一旦解析出来,其属性则不再是响应式属性
...state,
};
},
};
</script>
...
<script lang="ts">
import { reactive, toRefs } from "vue";
export default {
setup() {
...
return {
// 问题:如果 reactive 响应式对象一旦解析出来,其属性则不再是响应式属性
// 解决:利用 toRefs 将 reactive 中的每个属性转成 ref 响应式数据对象
...toRefs(state),
};
},
};
</script>
...
<script lang="ts">
import { reactive, toRefs } from "vue";
interface State {
msg: string;
numbers: number[];
person: {
name?: string;
};
update: () => void;
}
export default {
setup() {
const state: State = reactive({
...
});
return {
...toRefs(state),
};
},
};
</script>
问题: reactive 对象取出的所有属性值都是非响应式的
解决: 利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性
与选项 API this.$watch (以及相应的 watch 选项) 完全等效
侦听一个数据: ref 或 reactive 属性值(getter 函数)
侦听多个数据
...
count:{{ count }}
<script lang="ts">
...
export default {
...
// 监视一个ref值
watch(count, (newVal, oldVal) => {
console.log("watch count", newVal, oldVal);
document.title = count.value.toString();
});
return {
...toRefs(state),
count,
};
},
};
</script>
<script lang="ts">
...
export default {
...
// 监视 reactive 对象中的某个属性,注意:指定返回它的getter
watch(
() => state.msg,
(newVal, oldVal) => {
console.log("watch msg", newVal, oldVal);
}
);
return {
...toRefs(state),
count,
};
},
};
</script>
export default {
...
// 监视多个对象,利用解构方式输出
watch(
[count, () => state.msg],
([countNewVal, msgNewVal], [countOldValue, msgOldVal]) => {
console.log("watch 多值解构count", countNewVal, countOldValue);
console.log("watch 多值解构msg", msgNewVal, msgOldVal);
}
);
// 监视多个对象,利用单一对象输出
const stateRef = toRefs(state);
watch([count, () => state.msg, stateRef.msg, state], (values) => {
console.log("watch 多值", values);
});
return {
...stateRef,
count,
};
},
};
</script>
Object.defineProperty(data, 'count', {
get() {},
set() {},
});
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>介绍Object.defineProperty的作用title>
head>
<body>
<script>
const obj = {
name: '张三',
age: 18,
};
// 取值操作
console.log(obj.name);
// 赋值操作
obj.name = '李四';
console.log(obj.name);
// 通过Object.defineProperty可以拦截取值赋值属性操作
Object.defineProperty(obj, 'name', {
enumerable: true, // 当前属性允许被循环
// 如果不允许for-in的时候会被跳过
configurable: true, // 当前属性允许被配置
get() {
// getter
console.log('有人获取了obj.name的值');
return '我不是张三';
},
set(newVal) {
// setter
console.log('我不要你给的值', newVal);
},
});
console.log(obj.name); // get的演示
obj.name = '李四'; // set的演示
console.log(obj);
script>
body>
html>
new Proxy(data, {
// 拦截读取属性值
get(target, prop) {
return Reflect.get(target, prop);
},
// 拦截设置属性值或添加新属性
set(target, prop, value) {
return Reflect.set(target, prop, value);
},
// 拦截删除属性
deleteProperty(target, prop) {
return Reflect.deleteProperty(target, prop);
},
});
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Proxy 与 Reflecttitle>
head>
<body>
<script>
const user = {
name: 'Vane',
age: 42,
};
/*
proxyUser是代理对象, user是被代理对象
后面所有的操作都是通过代理对象来操作被代理对象内部属性
*/
const proxyUser = new Proxy(user, {
get(target, prop) {
console.log('劫持get()', prop);
return Reflect.get(target, prop);
},
set(target, prop, val) {
console.log('劫持set()', prop, val);
return Reflect.set(target, prop, val); // (2)
},
deleteProperty(target, prop) {
console.log('劫持delete属性', prop);
return Reflect.deleteProperty(target, prop);
},
});
// 读取属性值
console.log(proxyUser === user);
console.log(proxyUser.name, proxyUser.age);
// 设置属性值
proxyUser.name = 'Chinavane';
proxyUser.age = 18;
console.log(user);
// 添加属性
proxyUser.sex = '男';
console.log(user);
// 删除属性
delete proxyUser.sex;
console.log(user);
script>
body>
html>
Vue3 | Vue2 |
---|---|
use setup() | |
use setup() | |
onBeforeMount | beforeMount |
onMounted | mounted |
onBeforeUpdate | beforeUpdate |
onUpdated | updated |
onBeforeUnmount | beforeDestroy |
onUnmounted | destroyed |
onErrorCaptured | errorCaptured |
onRenderTracked | renderTracked |
onRenderTriggered | renderTriggered |
msg: {{ msg }}
作用:对多个组件重复的功能进行提取封装
在 vue2 中,可以使用 mixin 技术,在 vue3 中使用自定义 hooks 函数
x:{{ x }}
y:{{ y }}
hooks/useMousePosition.ts
/*
自定义hooks: 收集用户鼠标点击的页面坐标
*/
import { ref, onMounted, onUnmounted } from 'vue';
export default function useMousePosition() {
// 初始化坐标数据
const x = ref(-1);
const y = ref(-1);
// 用于收集点击事件坐标的函数
const updatePosition = (e: MouseEvent) => {
x.value = e.pageX;
y.value = e.pageY;
};
// 挂载后绑定点击监听
onMounted(() => {
document.addEventListener('click', updatePosition);
});
// 卸载前解绑点击监听
onUnmounted(() => {
document.removeEventListener('click', updatePosition);
});
return { x, y };
}
x:{{ x }}
y:{{ y }}
hooks/useUrlLoader.ts
/*
使用axios发送异步ajax请求
*/
import { ref } from 'vue';
import axios from 'axios';
export default function useUrlLoader(url: string) {
const result = ref(null);
const loading = ref(true);
const errorMsg = ref(null);
axios
.get(url)
.then((response) => {
loading.value = false;
result.value = response.data;
})
.catch((e) => {
loading.value = false;
errorMsg.value = e.message || '未知错误';
});
return {
loading,
result,
errorMsg,
};
}
x:{{ x }}
y:{{ y }}
Loading...
{{ errorMsg }}
{{ result }}
hooks/useUrlLoader.ts
export default function useUrlLoader<T>(url: string) {
const result = ref<T | null>(null)
...
}
...
<script lang="ts">
import { watch } from "vue";
import useMousePosition from "../hooks/useMousePosition";
import useUrlLoader from "../hooks/useUrlLoader";
interface DogResult {
message: string;
status: string;
}
export default {
setup() {
const { x, y } = useMousePosition();
const { result, loading, errorMsg } = useUrlLoader<DogResult>(
"https://dog.ceo/api/breeds/image/random"
);
watch(result, (newVal, oldVal) => {
console.log(result.value?.message); // 原来没有使用接口类型约束没有提示,有接口类型则可以提示
});
return { x, y, result, loading, errorMsg };
},
};
</script>
...
<script lang="ts">
import { watch } from "vue";
import useMousePosition from "../hooks/useMousePosition";
import useUrlLoader from "../hooks/useUrlLoader";
interface CatResult {
breeds: any[];
id: string;
url: string;
width: number;
height: number;
}
export default {
setup() {
const { x, y } = useMousePosition();
const { result, loading, errorMsg } = useUrlLoader<CatResult[]>(
"https://api.thecatapi.com/v1/images/search"
);
watch(result, (newVal, oldVal) => {
if (result.value) {
console.log(result.value[0].url);
}
});
return { x, y, result, loading, errorMsg };
},
};
</script>
eds/image/random"
);
watch(result, (newVal, oldVal) => {
console.log(result.value?.message); // 原来没有使用接口类型约束没有提示,有接口类型则可以提示
});
return { x, y, result, loading, errorMsg };
},
};
### 3) 定义接口,约束信息,数组对象
```typescript {7-13,18-26}
...