一、官网
二、Vue3的新特性
三、Vue-cli 搭建 Vue3 开发环境
四、项目结构
五、setup() — 组合式API 的入口点
六、数据响应式
01、ref() — 单值数据响应式
02、reactive() — 对象数据响应式
03、readonly() — "深层”的只读代理
七、响应式系统工具集 - 辅助函数
01、unref() — 拆出原始值的语法糖
02、toRef() — 为reactive对象的属性创建一个ref
03、toRefs() — 解构响应式对象数据
04、isRef、isProxy、isReactive、isReadonly
八、Vue3.x的生命周期和钩子函数
九、onRenderTracked() 和onRenderTriggered() 钩子函数的使用
01、onRenderTracked 状态跟踪
02、onRenderTriggered 状态触发
十、watch 和 watchEffect — 侦听器
01、watch 侦听单个数据源
02、watch 侦听多个数据源
03、watchEffect
十一、独立的 computed 计算属性
01、只传 getter
02、同时传 getter、setter
十二、Vue3中模块化
十三、axios
十四、teleport — 瞬间移动组件
十五、 Suspense — 异步请求组件
十六、onErrorCaptured — 处理理异步请求错误
十七、全局配置
十八、配置404页面
Vue3 官网: https://v3.cn.vuejs.org/
Vue-cli 官网:https://cli.vuejs.org/zh/
01、Vue3 采用渐进式开发,向下兼容
02、性能提升
- 打包大小减少41%
- 初次渲染快55%
- 更新快133%
- 内存使用减少54%
03、Composition API集合,解决Vue2组件开发问题
04、新的API加入
- Teleport 瞬移组件
- Suspense 解决异步加载组件问题
05、更好TypeScript 支持
【更好的性能、更小的bundle体积、更好的TypeScript支持】
01、版本的要求
必须是最新版本(V4.5.4 以上版本)才有创建 Vue3 的选项
检查当前vue-cli的版本号:
vue --version
02、 创建项目
npm install -g @vue/cli
或者 yarn global add @vue/cli
vue create hello-vue3
自定义配置:
或者:
vue-cli 图形搭建环境:
vue ui
然后根据提示一步步创建即可
|-node_modules -- 所有的项目依赖包都放在这个目录下
|-public -- 公共文件夹
---|favicon.ico -- 网站的显示图标
---|index.html -- 入口的html文件
|-src -- 源文件目录,编写的代码基本都在这个目录下
---|assets -- 放置静态文件的目录,比如logo.png就放在这里
---|components -- Vue的组件文件,自定义的组件都会放到这
---|App.vue -- 根组件,这个在Vue2中也有
---|main.ts -- 入口文件,因为采用了TypeScript所以是ts结尾
---|shims-vue.d.ts -- 类文件(也叫定义文件),因为.vue结尾的文件在ts中不认可,所以要有定义文件
|-.browserslistrc -- 在不同前端工具之间公用目标浏览器和node版本的配置文件,作用是设置兼容性
|-.eslintrc.js -- Eslint的配置文件,不用作过多介绍
|-.gitignore -- 用来配置那些文件不归git管理
|-package.json -- 命令配置和包管理文件
|-README.md -- 项目的说明文件,使用markdown语法进行编写
|-tsconfig.json -- 关于TypeScript的配置文件
|-yarn.lock -- 使用yarn后自动生成的文件,由Yarn管理,安装yarn包时的重要信息存储到yarn.lock文件中
这就是基本目录结构和用处了
官网地址:https://vue-composition-api-rfc.netlify.app/zh/api.html#setup
一个组件选项,在创建组件实例之前执行,一旦
props
被解析,并作为 组合式 API 的入口点。
由于在执行 setup
时未创建组件实例,因此在 setup
选项中没有 this
这意味着,除了 props
之外,你将无法访问组件中声明的任何属性——本地状态、计算属性或方法。
使用
setup
函数时,它将接受两个参数:
props
context
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup(props) {
console.log(props) // { user: '' }
return {} // 这里返回的任何内容都可以用于组件的其余部分
}
// 组件的“其余部分”
}
setup
函数中的第一个参数是props,
正如在一个标准组件中所期望的那样,setup
函数中的props
是响应式的,当传入新的 prop 时,它将被更新。但是,因为
props
是响应式的,你不能使用 ES6 解构,因为它会消除 prop 的响应性。
如果需要解构 prop,可以通过使用 setup
函数中的 toRefs
来完成此操作:
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
使用setup函数 代替Vue2中的data 和 methods属性的好处:
不用暴露在界面中的变量和方法就可以不进行 return 了,这样子可以精确地控制暴露出去的变量和方法。
总结:
setup - 组件内使用 Composition API 的入口点
{{ username }}
01、创建组件实例,然后初始化props
02、紧接着,调用 setup 函数(作为组合式API的入口)
03、从生命周期钩子的视角来看,它会在 beforeCreate 钩子之前被调用
接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property.
value(生成响应式对象)
ref
函数使任何响应式变量在任何地方起作用
ref
接受参数并返回它包装在具有value
property 的对象中,然后可以使用该 property 访问或更改响应式变量的值:
import { ref } from 'vue'
const counter = ref(0)
console.log(counter) // { value: 0 }
console.log(counter.value) // 0
counter.value++
console.log(counter.value) // 1
换句话说,
ref
对我们的值创建了一个响应式引用。在整个组合式 API 中会经常使用引用的概念。
当 ref 作为渲染上下文 (从 setup() 中返回的对象) 上的 property 返回并可以在模板中被访问时,
它将自动展开为内部值。不需要在模板中追加
.value
:
{{ count }}
接收一个普通对象,然后返回该普通对象的响应式副本(生成响应式对象)
给 data 添加类型注解:
在无添加类型注解的情况下,默认是采用了TypeScript
的类型推断,这样子是不合理的。
我们先定义一个接口,用接口(interface
)来作类型注解。
编写完成后,为 data
变量作一个类型注解,这时候我们的代码才是严谨的。
有时我们想跟踪响应式对象 (
ref
或reactive
) 的变化,但我们也希望防止在应用程序的某个位置更改它。例如,当我们有一个被 provide 的响应式对象时,我们不想让它在注入的时候被改变。为此,我们可以基于原始对象创建一个只读的 proxy 对象:
传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的。
import { reactive, readonly } from 'vue'
const original = reactive({ count: 0 })
const copy = readonly(original)
// 通过 original 修改 count,将会触发依赖 copy 的侦听器
original.count++
// 通过 copy 修改 count,将导致失败并出现警告
copy.count++ // 警告: "Set operation on key 'count' failed: target is readonly."
如果参数是一个ref , 则返回它的 value,否则返回参数本身。它是 val = isRef(val) ? val.value : val 的语法糖。
function useFoo(x: number | Ref) {
const unwrapped = unref(x) // unwrapped 一定是 number 类型
}
https://vue-composition-api-rfc.netlify.app/zh/api.html#toref
toRef 可以用来为一个 reactive 对象的属性创建一个 ref。这个 ref 可以被传递并且能够保持响应性。
setup() {
const user = reactive({ age: 1 });
const age = toRef(user, "age");
age.value++;
console.log(user.age); // 2
user.age++;
console.log(age.value); // 3
}
当您要将一个 prop 中的属性作为 ref 传给组合逻辑函数时,toRef 就派上了用场:
export default {
setup(props) {
useSomeFeature(toRef(props, 'foo'))
},
}
当我们想使用大型响应式对象的一些 property 时,可能很想使用 ES6 解构来获取我们想要的 property。遗憾的是,使用解构的 property 的响应性都会丢失。
对于这种情况,我们需要将我们的响应式对象转换为一组 ref。这些 ref 将保留与源对象的响应式关联:
将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的
ref
。
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
/*
Type of stateAsRefs:
{
foo: Ref,
bar: Ref
}
*/
// ref 和 原始property “链接”
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
当想要从一个组合函数中返回响应式对象时,用
toRefs
非常有用。该API 让消费组件在不丢失响应性的情况下对返回的对象进行解构/ 拓展。
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2
})
// 逻辑运行状态
// 返回时转换为ref
return toRefs(state)
}
export default {
setup() {
// 可以在不失去响应性的情况下破坏结构
const { foo, bar } = useFeatureX()
return {
foo,
bar
}
}
}
使用前需要先进行引入,引入后就可以对
data
进行包装。把 data 变成refData,
这样就可以使用扩展运算符的方式了,而且也具有响应式的能力。
这样写之后,你的template
就可以去掉 data. ,而是直接使用变量名和方法。
isRef:检查一个值是否为一个 ref 对象。
isProxy:检查一个对象是否是由 reactive 或者 readonly 方法创建的代理。
isReactive:检查一个对象是否是由 reactive 创建的响应式代理。如果这个代理是由 readonly 创建的,但是又被 reactive 创建的另一个代理包裹了一层,那么同样也会返回 true。
isReadonly:检查一个对象是否是由 readonly 创建的只读代理。
Vue 是组件化编程,从一个组件诞生到消亡,会经历很多过程,这些过程就叫做生命周期。
钩子函数: 伴随着生命周期,给用户使用的函数,操控生命周期,主要是操控钩子函数。
Vue2.x 和 Vue3.x 生命周期对比:
Vue3.x生命周期详解:
- setup() :开始创建组件之前,在
beforeCreate
和created
之前执行。创建的是data
和method
- onBeforeMount() : 组件挂载到节点上之前执行的函数。
- onMounted() : 组件挂载完成后执行的函数。
- onBeforeUpdate(): 组件更新之前执行的函数。
- onUpdated(): 组件更新完成之后执行的函数。
- onBeforeUnmount(): 组件卸载之前执行的函数。
- onUnmounted(): 组件卸载完成后执行的函数
- onActivated(): 被包含在
中的组件,会多出两个生命周期钩子函数。被激活时执行。
- onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行。
- onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数(以后用到再讲,不好展现)。
因为
setup
是围绕beforeCreate
和created
生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在setup
函数中编写。
这些函数接受一个回调函数,当钩子被组件调用时将会被执行:
export default {
setup() {
// mounted
onMounted(() => {
console.log('Component is mounted!')
})
}
}
实际例子如下:
Vue3.x 生命周期在调用前需要先进行引入
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
onMounted(getUserRepositories) // on `mounted` call `getUserRepositories`
return {
repositories,
getUserRepositories
}
},
可以在setup()
函数之后编写 Vue2
的生命周期函数。
Vue2.x的生命周期和Vue3.x的生命周期,都可用,但是不要混用。
beforeCreate() {
console.log("1-组件创建之前-----beforeCreate()");
},
beforeMount() {
console.log("2-组件挂载到页面之前执行-----BeforeMount()");
},
mounted() {
console.log("3-组件挂载到页面之后执行-----Mounted()");
},
beforeUpdate() {
console.log("4-组件更新之前-----BeforeUpdate()");
},
updated() {
console.log("5-组件更新之后-----Updated()");
},
Vue2.x 和 Vue3.x 生命周期对比:
Vue2--------------vue3
beforeCreate -> setup()
created -> setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
activated -> onActivated
deactivated -> onDeactivated
errorCaptured -> onErrorCaptured
通过这样对比,可以很容易的看出 Vue3 的钩子函数基本是在 Vue2 的基础上加了一个on,
但也有两个钩子函数发生了变化。
BeforeDestroy
变成了onBeforeUnmount
destroyed
变成了onUnmounted
除了这些钩子函数外,Vue3.x
还增加了onRenderTracked
和onRenderTriggered
函数
onRenderTracked() 和onRenderTriggered() 钩子函数的使用
Vue3.x新增的生命周期钩子函数,官方说是用来调试使用的
onRenderTracked 状态跟踪
跟踪页面上所有响应式变量和方法的状态,也就是我们用
return
返回去的值,它都会跟踪。只要页面有
update
的情况,它就会跟踪,然后生成一个event
对象,我们通过event
对象来查找程序的问题所在。
import { .... ,onRenderTracked,} from "vue";
onRenderTracked((event) => {
console.log("状态跟踪组件----------->");
console.log(event);
});
onRenderTriggered 状态触发
它不会跟踪每一个值,而是给你变化值的信息,并且新值和旧值都会给你明确的展示出来。
如果把
onRenderTracked
比喻成散弹枪,每个值都进行跟踪,那onRenderTriggered
就是狙击枪,只精确跟踪发生变化的值,进行针对性调试。
import { .... ,onRenderTriggered,} from "vue";
onRenderTriggered((event) => {
console.log("状态触发组件--------------->");
console.log(event);
});
监听器(侦听器)。作用是用来侦测响应式数据的变化
使用 watch 同样需要先进行引入。它接受3个参数:
- 一个响应式引用 ref 或一个返回值的 getter 函数
- 一个回调
- 可选的配置选项
import { ref, watch } from 'vue'
const counter = ref(0)
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
应用到实际例子中:
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs } from 'vue'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
// 在我们组件中
setup (props) {
// 使用 `toRefs` 创建对prop的 `user` property 的响应式引用
const { user } = toRefs(props)
const repositories = ref([])
const getUserRepositories = async () => {
// 更新 `prop.user` 到 `user.value` 访问引用值
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
// 在用户 prop 的响应式引用上设置一个侦听器
watch(user, getUserRepositories)
return {
repositories,
getUserRepositories
}
}
},
在我们的 setup
的顶部使用了 toRefs
。这是为了确保我们的侦听器能够对 user
prop 所做的更改做出反应。
第一个参数以数组形式传入,第二个参数回调返回的结果也是数组
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
Vue3 中监听 reactive 中的值,必须以 getter 函数 的形式,不然会报错。和 Vue2 的区别是不用写 deep 属性,默认就是深度监听了。
watch([result2, () => data.title], (newV, oldV) => {
console.log(newV, oldV) // [20, "111"] [20, "222"]
})
监听 reactive 中的多个值时:
watch([result2, () => [data.title, data.value1]], (newV, oldV) => {
console.log(newV, oldV)
})
与 watchEffect 比较,
watch
允许我们:
- 懒执行副作用;
- 更具体地说明什么状态应该触发侦听器重新运行;
- 访问侦听状态变化前后的值。
立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。
watchEffect - 侦听器
{{data.count}}
computed
计算属性与 ref
和 watch
类似,也可以使用从 Vue 导入的 computed
函数在 Vue 组件外部创建计算属性。
computed
函数传递了第一个参数,它是一个类似 getter 的回调函数,输出的是一个只读 的响应式引用。为了访问新创建的计算变量的 value,我们需要像使用
ref
一样使用.value
property。
import { ref, computed } from 'vue'
const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)
counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2
接受 getter 函数,并返回一个默认不可手动修改的响应式 ref 对象
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 报警告,computed value is readonly
报警告:
如果是这种使用方式,则会报错 ❌,而不是警告 ⚠️
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1
})
console.log(plusOne.value) // 2
console.log(count.value) // 1
plusOne.value = 1 // 报错
使用一个带有
get
和set
函数的对象来创建一个可手动修改的 ref 对象
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: val => {
console.log('触发set', val)
count.value = val - 1
}
})
console.log(plusOne.value) // 2
console.log(count.value) // 1
plusOne.value = 1 // 触发set 1
console.log(plusOne.value) // 1
console.log(count.value) // 0
实际项目中:
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs, computed } from 'vue'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
// 在我们组件中
setup (props) {
// 使用 `toRefs` 创建对prop的 `user` property 的响应式引用
const { user } = toRefs(props)
const repositories = ref([])
const getUserRepositories = async () => {
// 更新 `prop.user` 到 `user.value` 访问引用值
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
// 在用户 prop 的响应式引用上设置一个侦听器
watch(user, getUserRepositories)
const searchQuery = ref('')
const repositoriesMatchingSearchQuery = computed(() => {
return repositories.value.filter(
repository => repository.name.includes(searchQuery.value)
)
})
return {
repositories,
getUserRepositories,
searchQuery,
repositoriesMatchingSearchQuery
}
}
},
Vue3.x版本最大的一个提升,就是有更好的重用机制,你可以把任何你想独立的模块独立出去。
把相同的逻辑关注点提取到一起,封装成一个独立的组合式函数
例如,创建 useUserRepositories
:
// src/composables/useUserRepositories.js
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch } from 'vue'
export default function useUserRepositories(user) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
watch(user, getUserRepositories)
return {
repositories,
getUserRepositories
}
}
然后是搜索功能:
// src/composables/useRepositoryNameSearch.js
import { ref, computed } from 'vue'
export default function useRepositoryNameSearch(repositories) {
const searchQuery = ref('')
const repositoriesMatchingSearchQuery = computed(() => {
return repositories.value.filter(repository => {
return repository.name.includes(searchQuery.value)
})
})
return {
searchQuery,
repositoriesMatchingSearchQuery
}
}
现在在单独的文件中有了这两个功能,我们就可以开始在组件中使用它们了。以下是如何做到这一点:
// src/components/UserRepositories.vue
import { toRefs } from 'vue'
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import useRepositoryFilters from '@/composables/useRepositoryFilters'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup (props) {
const { user } = toRefs(props)
// 获取并返回传入指定的用户的储蓄库列表
const { repositories, getUserRepositories } = useUserRepositories(user)
// 搜索并返回包含某名字的储蓄库列表
const {
searchQuery,
repositoriesMatchingSearchQuery
} = useRepositoryNameSearch(repositories)
// 过滤
const {
filters,
updateFilters,
filteredRepositories
} = useRepositoryFilters(repositoriesMatchingSearchQuery)
return {
// 因为我们并不关心未经过滤的仓库
// 我们可以在 `repositories` 名称下暴露过滤后的结果
repositories: repositoriesMatchingSearchQuery,
getUserRepositories,
searchQuery,
filters,
updateFilters
}
}
}
或者:
组合式api架构
usemouse
position x: {{x}}
position y: {{y}}
useCount
{{count}}
安装: npm install axios --save
Vue2.x中组件的痛点:
01、组件的Dom都是在app节点下的;想改变节点,在Vue2时代是非常困难。
02、组件被包裹在其他组件中,容易被干扰,样式也变得比较混乱
使用 teleport 函数(方法):
这个函数也可以叫独立组件,它可以把你写的组件挂载到任何你想挂载的DOM上,不必嵌套在#app里,这样就不会互相干扰了,所以说是很自由很独立的。
在使用
Vue2
的时候是做不到的。
把你编写的组件用
标签进行包裹,在组件上有一个to
属性,这个就是要写你需要渲染的DOM
ID了。
组件
然后我们在打开/public/index.html,
增加一个modal
节点
背景:
在前端开发中,异步请求组件必不可少。比如读取远程图片,比如调用后台接口,这些都需要异步请求。在Vue2.x时代,判断异步请求的状态是一件必须的事情,但是这些状态都要自己处理,根据请求是否完毕展示不同的界面。尤大神深知民间饥苦,在Vue3.x中给我们提供了
Suspense
组件。
Suspense组件用于在等待某个异步组件解析时显示后备内容。
用法:
注意点:如果你要使用Suspense
的话,要返回一个Promise对象,而不是原来的那种JSON
对象。
新建一个AsyncShow.vue
组件:
{{ result }}
放入父组件中:用Suspense闭合标签包住,包含了两个template插槽。
第一个
default
代表异步请求完成后,显示的模板内容。第二个
fallback
代表在加载中时,显示的模板内容。
Loading...
用async...await
的写法,它是promise
的语法糖
onErrorCaptured
— 处理理异步请求错误在异步请求中必须要作的一件事情,就是要捕获错误,因为我们没办法后端给我们返回的结果,也有可能服务不通,所以一定要进行捕获异常和进行处理。
在vue3.x
的版本中,可以使用 onErrorCaptured
这个钩子函数来捕获异常。在使用这个钩子函数前,需要先进行引入
import { ref , onErrorCaptured } from "vue";
有了onErrorCaptured
就可以直接在setup()
函数中直接使用了。钩子函数要求我们返回一个布尔值,代表错误是否向上传递,我们这里返回了true
。
const app = {
name: "App",
components: { AsyncShow, GirlShow },
setup() {
onErrorCaptured((error) => {
console.log(`error====>`,error)
return true
})
return {};
},
};
写好后,我们故意把请求地址写错,然后打开浏览器的终端,看一下控制台已经捕获到错误了。在实际工作中,你可以根据你的真实需求,处理这些错误。
案例:
{{ errMsg }} div>
template>
正在拼了命的加载…div>
template>
Suspense>
template>