目录
一.配置环境、创建项目
二.响应式对象
2.1 ref()、computed()、setup()
2.2 reactive()、toRefs()、区分 ref 和 reactive
三.生命周期(Vue2 vs Vue3)
四.侦测函数 - watch
五.模块化开发 - hooks
5.1 useMouseTracker 鼠标追踪器
5.2 useURLLoader 抽离加载函数
5.3 【拓展】为加载函数添加泛型
六.瞬间移动【模态框】- Teleport
6.1 Modal 组件 编写
6.2 Modal 组件 使用
七.异步请求 - Suspense
八.全局 API 修改
一.配置环境、创建项目
1.配置环境
- 安装要注意的问题(指我自己的电脑):使用 vue3 时,需要保证 vue cli 版本在 4.5.0 以上
卸载 1.0、2.0 旧版本脚手架:
- npm uninstall vue-cli -g
- yarn global remove vue-cli
- 安装/卸载 3.0、4.0 新版脚手架:
- npm install/uninstall -g @vue/cli
- yarn global add/remove @vue/cli
- 安装最新发布的脚手架(需要执行两个卸载命令,卸载"旧版"、"新版",安装最新版):
- npm install -g @vue/[email protected]
- yarn global add @vue/[email protected]
注意:因为是 alpha 版本(未发布),所以一定要注意 命令不同;- 此时 VueCLI 官网还未更新相关命令,但 github 上已经更新相关内容,如下所示:
- 相关网址:
- https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md
- https://cli.vuejs.org/guide/installation.html
2.创建项目
- Please pick a preset (创建 vue2/3 或者手动决定) - 选择 Manually select features
- Check the features needed for your project:创建的项目需要搭配的内容,比如路由、babel、ts、vuex、css预处理器、lint语法规范等
- Choose a version of Vue.js that you want to start the project with - 选择 3.x (Preview)
- Use class-style component syntax - 输入 n,回车
- Use Babel alongside TypeScript - 输入n,回车
- Pick a linter / formatter config - 直接回车
- Pick additional lint features - 直接回车
- Where do you prefer placing config for Babel, ESLint, etc.? - 直接回车
- Save this as a preset for future projects? - 输入n,回车
- 关于 Eslint 插件:
- 如果 Eslint 不生效,可以在根目录创建 .vscode 文件夹,在里面创建 settings.json 然后输入:
{ "eslint.validate": [ "typescript" ] }
二.响应式对象
2.1 ref()、computed()、setup()
- ref 函数,接受一个参数,返回一个 响应式对象
- const count = ref(0):将 参数0 传入 ref()函数,使之变成响应式对象
const double = computed(() => {
return count.value * 2
})- computed() 计算属性,接受一个 回调函数
- return count.value * 2:count 在上面被声明为了响应式对象,需要通过 .value 获取或改变 响应式对象实际值
setup() 位于: beforeCreate() 和 Created() 两个钩子函数之间执行- 也就是说,在 setup() 执行的时候,Created() 还没有执行,因此 setup() 不存在 data 和 methods,这些内容可以在 setup() 之外进行书写
- setup() 中的 this 被设为 undefined,因此 setup() 不能使用 this
- 在 setup() 中定义的变量和方法,需要通过 return 返回出去,供模板使用
{{count}}
{{double}}
import { computed, ref } from "vue" setup() { // 将 参数0 传入 ref()函数,未来可以检测到改变,并作出响应 const count = ref(0) // 计算属性 computed() 接受一个回调函数 () => {} const double = computed(() => { // 获取响应式对象值的方法:.value return count.value * 2 }) // setup() 中,不存在 data、method,因此直接通过 回调函数 () => {}声明方法 const increase = () => { count.value++ } // setup() 中的变量和方法需要 return 出去,才能被模板使用 return { count, increase, double } }2.2 reactive()、toRefs()、区分 ref 和 reactive
- reactive({}) 内部,接受普通数据,toRefs() 让 reactive({}) 对象变成响应式的
- 定义复杂变量,需要在 setup() 之前,声明 interface 规范其 内部各个参数的 数据类型
- 在 reactive({}) 中使用计算属性,比如下面的例子,直接 data1.xxxx 就行,无需添加 .value,因为 使用 toRefs() 之前, reactive({}) 中的数据并非是响应式的
import { ref, computed, reactive, toRefs } from 'vue' // 为复杂变量 定义复杂参数类型 interface DataProps { count: number; double: number; increase: () => void; } setup() { const data1: DataProps = reactive({ count: 0, // 注意,这里直接改变 data 中变量的值,不需要.value,因为 data1 此时并非响应式 double: computed(() => data1.count * 2) increase: () => { data1.count++}, }) // toRefs() 为普通数据注入响应式能力 const refData = toRefs(data1) return { ...refData // 也可以直接写成 ...toRefs(data1) } }
- 使用 ref() 还是 reactive() 的场景:
- ref() 适合简单变量,取值改值需要通过 .value
- reactive() 适合复杂变量(对象、数组等),取值改值无需 .value,而是直接改(state.xxx = xx),要记得使用 toRefs() 保证 reactive 对象属性保持响应性
- 一个文件中,reactive 和 ref 选一种,toRef 和 toRefs 选一种,最好二选一,尽量不要混着用(混着用也没啥问题)
- 个人推荐 reactive(),把数据挂载到 state 变量里,统一管理;return 用 toRefs() 解构出去,写法简洁;
- 其实 React 或 小程序 都是有 state数据管理 这样的概念的
setup() { const state = reactive({ firstIcon: require("@/assets/images/icons.png").default, cardData: [{ id: 1, name: '啦啦啦', }]; cardActive: '啦啦啦' }); return { ...toRefs(state), } }
三.生命周期(Vue2 vs Vue3)
- 在 setup() 中使用的 hook 名称 和 原生命周期 的关系:
- beforeCreate -> 不需要
- created -> 不需要
- 上面这俩其实被替换成 setup() 了,setup() 在他俩之间进行
- beforeMount -> onBeforeMount
- mounted -> onMounted(挂载)
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated(更新)
- beforeUnmount -> onBeforeUnmount
- unmounted -> onUnmounted(卸载)
- errorCaptured -> onErrorCaptured(捕获错误)
- renderTracked -> onRenderTracked(?)
- renderTriggered -> onRenderTriggered(?)
四.侦测函数 - watch
- watch(),接受两个参数:要监听的对象、接受新旧值的回调函数
- watch(data, (newValue, oldValue) => { })
- 此处 data 是 响应式对象(props.data、reactive、ref 等等)
- 注意:此处的 data 是完整的响应式对象,就是说不能只是 reactive 里面的某一项
- 要监听的对象 可以是 数组,里面包含多个要监听的值
- watch([greetings, data], (newValue, oldValue) => {})
- 使用 getter 写法:() => data.count,可以侦测 reactive 中的某一项
- watch([greetings, () => data.count], (newValue, oldValue) => {})
- 此处 data.count,是 reactive 中的某一项,这么做是保证 reactive 被 toRefs() 后,再获取需要的部分,这样获取的就是响应式对象了
// watch 简单应用 // 此处 data 是完整的响应式对象(比如整个reactive对象,整个ref) watch(data, () => { document.title = 'updated ' + data.count }) // watch 的两个参数,代表新的值和旧的值 watch(refData.count, (newValue, oldValue) => { console.log('old', oldValue) console.log('new', newValue) document.title = 'updated ' + data.count }) // watch 多个值,返回的也是多个值的数组 watch([greetings, data], (newValue, oldValue) => { document.title = 'updated' + greetings.value + data.count }) // 使用 getter 的写法 watch reactive 对象中的一项 watch([greetings, () => data.count], (newValue, oldValue) => { document.title = 'updated' + greetings.value + data.count })
五.模块化开发 - hooks
5.1 useMouseTracker 鼠标追踪器
- 先尝试一下,在组件内添加对应的逻辑
- 声明两个响应式对象:
- const x = ref(0) / const y = ref(0)
- 声明一个鼠标事件,为之前的响应式对象进行赋值:
- const updateMouse = (e: MouseEvent) => {
x.value = e.pageX
y.value = e.pageY
}- 挂载的时候,为整个文档添加点击事件监听,调用鼠标事件:
- onMounted(() => {
document.addEventListener('click', updateMouse)
})- 卸载的时候,为整个文档移除点击事件监听:
- onUnmounted(() => {
document.removeEventListener('click', updateMouse)
})
- 这段逻辑代码 跟 组件本身 其实没有任何关系,可以把它抽成一个方法
- 该方法会被写入 xxx.ts 文件中,放在 hooks 目录下
- hooks目录 推荐放一些公共逻辑代码,导出方法供给组件使用
- xxx.ts 文件中,是可以导入 vue 各种方法 / 生命周期 的
function useMouseTracker() { const x = ref(0) const y = ref(0) const updatePosition = (event: MouseEvent) => { x.value = event.clientX y.value = event.clientY } onMounted(() => { document.addEventListener('click', updatePosition) }) onUnmounted(() => { document.removeEventListener('click', updatePosition) }) return { x, y } } export default useMouseTracker
- vue3 这种实现方式的优点
- 可以清楚的知道 x、y 的来源、作用:他们来自 useMouseTracker 的返回,用来追踪鼠标位置
- 不同文件引入,避免了命名冲突
- 这段逻辑可以脱离组件存在,因为它和组件的实现没有关系
- 建议:存放公共方法的文件最好以 usexxx.ts 这种格式命名
5.2 useURLLoader 抽离加载函数
- 安装 axios:npm install axios --save
import { ref } from 'vue' import axios from 'axios' // 添加参数url,用于接受地址 const useURLLoader = (url: string) => { const result = ref(null) // 真实结果 const loading = ref(true) // 正在加载 const loaded = ref(false) // 加载完成 const error = ref(null) // 错误信息 // 发送异步请求,获取结果 axios.get(url).then((rawData) => { loading.value = false // 获取到结果之后,更改 正在加载 的值为 false loaded.value = true // 更改 加载完成 的值为 true result.value = rawData.data // 更改 结果 的值为 rawData.data }).catch((e) => { error.value = e // 更改 错误 的值为 e }) return { result, loading, error, loaded } } export default useURLLoader // 导出方法
- 在组件中使用 useURLLoader:
- 导入公共方法,传入参数,解构内容,把内容 return 给模板使用
Loading...
import useURLLoader from './hooks/useURLLoader' const { result, loading, loaded } = useURLLoader('xxxurl') return { result, loading, loaded, }
5.3 【拓展】为加载函数添加泛型
- 由于请求数据存在延迟,请求到数据前,结果为空,请求到数据后,结果不为空
- 因此最终返回的结果 result 存在两种情况,需要 泛型 为它指定类型
- const result = ref
(null) - 解释:<> 内包含了两种情况(类型T | 空null),默认值为 null
function useURLLoader
(url: string) { // 还没请求到的时候结果为 null 类型,请求到了结果为 T 类型 const result = ref (null) ======= 上面是 hooks 目录下的公共方法 ======= =========================================== ======= 下面是组件内调用公共方法 ======= // 定义泛型具体类型 interface CatResult { id: string; url: string; width: number; } // 使用方法 并 传入泛型 const { result, loading, loaded } = useURLLoader ('xxxurl')
六.瞬间移动【模态框】- Teleport
- vue2 存在的问题:模态框大多嵌套在很多层级之下,更改了会导致全局混乱
- vue3 新添加了一个默认组件 Teleport
- 它有一个 to 属性,代表要把这个组件渲染到哪个 dom元素中
- 该组件最终的渲染,不再嵌套在 根元素之下,而是独立于根元素,和根元素并排
6.1 Modal 组件 编写
- 通过 to="#modal" 把 Modal 组件 绑定在 #modal 这个 dom元素 上
- 和 将会是同一层级
- 通过 v-if="isOpen" 控制 Modal 是否显示
- Modal 中,定义插槽,供用户自定义 Modal 内容
- Modal 中,定义按钮,绑定 @click="buttonClick",Modal 组件通过 context.emit('close-modal') 发送事件,Modal 组件 监听 父组件 是否 触发该方法
- 注意:setup() 中, context 参数可以获取到 emit,context.emit()
// 独立于根元素的 modal元素
// 这里定义了插槽// 点击后 将事件提交给 调用 Modal 的组件(父组件)
this is a modal 6.2 Modal 组件 使用
- 父组件触发 Modal组件 发送/监听 的方法 @close-modal="XXX"
- 父组件可以自定义 Modal 组件展示的内容
My Modal ! import Modal from './components/Modal.vue' const modalIsOpen = ref(false) const openModal = () => { modalIsOpen.value = true } const onModalClose = () => { modalIsOpen.value = false } return { modalIsOpen, openModal, onModalClose, }
七.异步请求 - Suspense
- vue3 新添加了一个默认组件 Suspense,用于优雅的处理 异步请求
异步请求结果 loading 内容
- 当异步请求还未成功时,#fallback 中的内容会显示,用来放 loading 内容
- 当异步请求成功时,#default 中的内容会显示,用来放 异步请求结果
- Suspense 中可以添加 多个异步组件
// 这里显示异步组件结果 // 这里显示加载动画 Loading ...
八.全局 API 修改
- Vue2 的全局配置:
import Vue from 'vue' import App from './App.vue' Vue.config.ignoredElements = [/^app-/] Vue.use(/* ... */) Vue.mixin(/* ... */) Vue.component(/* ... */) Vue.directive(/* ... */) Vue.prototype.customProperty = () => {} new Vue({ render: h => h(App) }).$mount('#app')
- Vue2 这样写在一定程度上,修改了 Vue对象 的 全局状态
- 在单元测试中,全局配置易污染全局环境,用户需要在每次 case 之间,保存和恢复配置。有一些 api (vue use vue mixin)甚至没有方法恢复配置
- 不同的 APP 中,想共享一份有不同配置的 Vue对象,非常困难
- Vue3 的全局配置:
import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) // 此时 app 就是 App 的实例之一 // 现在设置任何配置,都是在不同的 app实例上的,就不会冲突 app.config.isCustomElement = tag => tag.startsWith('app-') app.use(/* ... */) app.mixin(/* ... */) app.component(/* ... */) app.directive(/* ... */) app.config.globalProperties.customProperty = () => {} // 配置结束以后,再用 mount() 方法,把 App 挂载到 固定的 DOM节点 app.mount(App, '#app')