Vue3初体验

安装

独立安装

可以在Vue.js官网直接下载最新版本,并用script标签引入

独立安装

CDN方式安装

直接使用script引入

npm方式安装

npm版本需大于3.0

npm install vue@next

命令行工具:

从之前的版本包名改变了,从vue-cli变为@vue/cli。如果之前已全局安装了vue-cli1.x或vue-cli2.x。首先需要

使用命令npm uninstall vue-cli -g或者yarn global remove vue-cli卸载掉之前的版本,在进行安装

Node版本注意点:

Vue CLI 4.x 需要NodeJs的版本>=8.9

npm install -g @vue/cli

或者

yarn global add @vue/cli

注意:vue-cli 3.x 和 vue-cli 2.x 使用了相同的 vue 命令,如果你之前已经安装了 vue-cli 2.x,它会被替换为 Vue-cli 3.x。安装 @vue/cli-int:

npm i -g @vue/cli-init

创建项目

Vue CLI

使用命令vue create 项目名称来创建项目

然后等待下载对应的模板以及依赖。

运行

cd 项目名

npm run serve

Vite

Vite 是一个 web 开发构建工具,由于其原生 ES 模块导入方式,可以实现闪电般的冷服务器启动。

通过在终端中运行以下命令,可以使用 Vite 快速构建 Vue 项目。

全局安装 create-vite-app:

npm i -g create-vite-app

创建项目:

npm init vite-app <项目名>

运行:

cd 项目名

npm install

npm run dev

Vue3目录结构

命令行工具@vue/cli

目录解析

目录文件 说明
public 公共资源目录
src 这里是我们要开发的目录,基本上要做的事情都在这个目录里
.xxxx文件 这些是一些配置文件,包括语法配置,git配置等
package.json 项目配置文件
README.md 项目的说明文档,markdown 格式

Vue3-基础点

起步

以下所以笔记都是基于@vue/cli方式创建项目进行说明

Composition API

为什么需要Composition API

Composition API是Vue3的最大特点,也可以很明显看出他是受到React Hooks的启发
  • 解决代码的可读性随着组件变大而变差
  • 每一种代码复用的方式,都存在缺陷
  • TS支持有限

setup

setup 是 Vue3.x 新增的一个选项, 它是组件内使用 Composition API的入口
setup执行时机

基于VueJs生命周期的对比,发现setup要早于beforeCreate执行

setup参数

使用setup时,它接受两个参数:

  1. {Data} props
  2. {SetupContext} context

setup 中接受的props是响应式的, 当传入新的 props 时,会及时被更新。由于是响应式的, 所以不可以使用 ES6 解构,解构会消除它的响应式。错误代码示范:

Getting a value from the props in root scope of setup() will cause the value to lose reactivity vue/no-setup-props-destructure

setup类型
interface Data {
  [key: string]: unknown
}

interface SetupContext {
  attrs: Data
  slots: Slots
  emit: (event: string, ...args: unknown[]) => void
}

function setup(props: Data, context: SetupContext): Data

从上面ts定义的两个接口可以看出,setup函数的第二个参数context对象,有三个属性,分别是:

  • attrs:对应vue2.x中的$attr属性
  • slots: 对应vue2.x中的slot插槽
  • emit: 对应vue2.x中的$emit发送事件

这样设计的目的在于,我们在setup函数中不能访问到this。并且这几个属性都是自动同步最新的值,所以我们每次使用拿到的都是最新值。

生命周期

Vue3初体验_第1张图片
改图来源其他博主

可以通过直接导入onX方法来注册生命周期钩子函数。

这些生命周期钩子函数在setup函数里能够同步地使用,因为它们依赖于内部全局状态来定位当前的活动实例(当前正在调用setup的组件实例)。在没有当前活动实例的情况下调用它们将导致错误。

组件实例上下文也在生命周期钩子的同步执行期间设置。在卸载组件时候,在生命周期钩子内同步创建的的观察程序watch和计算属性computed也将自动删除。

  • 对比Options API 和Composition API 生命周期

    • beforeCreate -> use setup()
    • created -> use setup()
    • beforeMount -> onBeforeMount
    • mounted -> onMounted
    • beforeUpdate -> onBeforeUpdate
    • updated -> onUpdated
    • beforeUnmount -> onBeforeUnmount
    • unmounted -> onUnmounted
    • errorCaptured -> onErrorCaptured
    • renderTracked -> onRenderTracked
    • renderTriggered -> onRenderTriggered
    • activated -> onActivated
    • deactivated -> onDeactivated
    Options API Hook inside setup
    beforeCreate Not needed*
    created Not needed*
    beforeMount onBeforeMount
    mounted onMounted
    beforeUpdate onBeforeUpdate
    updated onUpdated
    beforeUnmount onBeforeUnmount
    unmounted onUnmounted
    errorCaptured onErrorCaptured
    renderTracked onRenderTracked
    renderTriggered onRenderTriggered
    activated onActivated
    deactivated onDeactivated

    从上面的对比可以看出:

    1. beforeCreatecreatedsetup替换了
    2. 钩子命名都增加了on
    3. 新增用于调试的钩子函数onRenderTriggeredonRenderTricked
    4. 将 Vue2.x 中的beforeDestroy名称变更成beforeUnmount; destroyed 表更为 unmounted

provide与inject

provide与inject启动了依赖注入项,两者都只能在setup期间使用当前组件实例进行调用
类型
interface InjectionKey extends Symbol {}

function provide(key: InjectionKey | string, value: T): void

// without default value
function inject(key: InjectionKey | string): T | undefined
// with default value
function inject(key: InjectionKey | string, defaultValue: T): T
// with factory
function inject(
  key: InjectionKey | string,
  defaultValue: () => T,
  treatDefaultAsFactory: true
): T

reactive、ref、toRefs

在vue2.x中,数据的定义都是在data函数中,但是在vue3.x中,可以使用reactvieref来定义数据
reactive

返回一个响应式的对象副本。

const obj = reactive({ count: 0 })

特点:响应式的转换是“深度”的。它会影响所有嵌套属性,基于Proxy去实现,返回的proxy对象与原始对象并不相等,建议只与响应式的proxy对象使用避免依赖原始对象。

类型
function reactive(target: T): UnwrapNestedRefs

函数reactive接受一个对象作为参数,并返回一个响应式的对象。这里采用了泛型约束的方式使得reactive函数的参数的类型更加具体。

例子:


结合toRefs使用解构

上面的例子中可以看到,我们在页面上使用user.nameuser.hobby等比较繁琐。那么能否将user对象进行解构,直接得到它的相关属性呢?这是不能的,因为会消除它的响应式。但我们可以借助toRefstoRefs 用于将一个 reactive 对象转化为属性全部为 ref 对象的普通对象


注意点:reactive会“解开”所有深层的refs,同时保持ref是响应式的

在将ref分配给响应属性时,该ref将自动解包

reactive本质
  • 是一个基于proxy实现的响应式函数,返回值是一个proxy的响应式对象
  • 函数的参数是对象类型,对于基本数据类型来说不能用reactive
  • 能够深层次地监听到响应式对象属性
ref

接受一个内部值并返回一个响应式且可变的ref对象。ref对象具有指向内部值的单个属性(.value)

如:


如果将一个对象分配为ref的值,则该对象将通过响应式方法reactive赋予深度响应式

类型
interface Ref {
  value: T
}

function ref(value: T): Ref

定义了一个Ref泛型接口,接口中定义了一个value变量,类型通过泛型进行决定。方法ref返回值是Ref类型

如:

ref总结
  • 方法ref接受一个参数,类型可以是任何类型
  • 方法的返回值是一个接口(对象)类型
  • 要访问或修改值需要通过.value的形式去实现
toRefs

能够将一个响应式对象转换为一个普通对象,其中结果对象的每个属性都指向原始对象相应属性的ref

如:

从组合函数返回响应式对象的时候,toRefs是很有用的,以便使用组件时可以对返回的对象进行解构/扩展而不会失去"响应式"

toRefs仅为源对象中包含的属性生成ref引用。要为特定属性创建ref引用,需使用toRef

toRefs总结
  • 能将一个响应式对象转换为一个普通对象
  • 得到的对象的属性拥有一个隐式的value属性
  • 得到的对象的每个属性可看成是一个个的ref
  • 在对对象进行解构的时候,toRefs很有用

readonly

能将一个对象(响应式或普通对象)或者一个ref,并返回原始的只读proxy代理。只读proxy代理“很深”:访问的任何嵌套属性也将是只读的

如:

与响应式一样,如果任何属性使用了ref,则通过proxy代理访问该属性时,该属性将自动解包。

isProxy

检测对象是否由reactive响应式或readonly只读方式创建的proxy代理。

isReactive

检测对象是否由reactive响应式创建的proxy代理对象。

如:

如果通过readonly创建一个proxy代理对象,也会返回true。但包装由reactive响应式创建的另一个proxy代理对象。

import { reactive, isReactive, readonly } from 'vue'
export default {
  setup() {
    const state = reactive({
      name: 'John'
    })
    // readonly proxy created from plain object
    const plain = readonly({
      name: 'Mary'
    })
    console.log(isReactive(plain)) // -> false

    // readonly proxy created from reactive proxy
    const stateCopy = readonly(state)
    console.log(isReactive(stateCopy)) // -> true
  }
}

isReadonly

检测对象是否是由readonly创建的只读proxy代理。

toRaw

返回reactivereadonly代理的原始对象。只是一个转义口,可用于临时读取而不会产生代理访问/跟踪开销,也可用于写入而不会触发更改。不建议保留对原始对象的持久引用,使用的时候要谨慎。

如:

markRaw

标记一个对象,使其永远不会转换为代理,而是返回对象本身。

注意点:

markRaw以及下面的shallowXXX API使我们可以有选择地选择默认的深度响应式/只读转化,并将原始的,非代理的对象嵌入状态图中,有如下理由:

  • 不应使某些值具有响应式,如负责的第三方类实例或Vue组件实例对象。
  • 渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。

shallowReactive

创建一个响应式代理,该代理跟踪其自身属性的相应性,但不执行嵌套对象的深度响应式转换。类似咱们的浅拷贝。

如:

shallowReadonly

类似上面的shallowReactive,就不多说了。

toRef

用于为源响应式对象上的属性创建ref,然后可以传递ref,保留与其源属性的响应式链接。

如:

当要将属性的ref传递给组合函数时,toRef很有用。

export default {
  setup(props) {
    useSomeFeature(toRef(props, 'foo'))
  }
}

customRef

创建一个自定义ref,并对其依赖项跟踪进行显式控制,并触发更新。它需要一个工厂函数,该函数接收tracktrigger函数作为参数,并返回带有getset的对象。

如:


类型
function customRef(factory: CustomRefFactory): Ref

type CustomRefFactory = (
  track: () => void,
  trigger: () => void
) => {
  get: () => T
  set: (value: T) => void
}

computed与watch

computed

使用getter函数,并为getter返回的值返回一个不可变的响应式ref对象。

如:

另外,它还可以使用带有getset函数的对象来创建可写的ref对象。

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0
类型
// read-only
function computed(getter: () => T): Readonly>>

// writable
function computed(options: { get: () => T; set: (value: T) => void }): Ref

watchEffect

在响应式地跟踪其依赖关系时立即运行一个函数,并在依赖关系发生更改时重新运行这个函数。

如:

类型
function watchEffect(
  effect: (onInvalidate: InvalidateCbRegistrator) => void,
  options?: WatchEffectOptions
): StopHandle

interface WatchEffectOptions {
  flush?: 'pre' | 'post' | 'sync' // default: 'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

interface DebuggerEvent {
  effect: ReactiveEffect
  target: any
  type: OperationTypes
  key: string | symbol | undefined
}

type InvalidateCbRegistrator = (invalidate: () => void) => void

type StopHandle = () => void

watch

watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。默认情况是惰性的,也就是说仅在侦听的源数据变更时才执行回调。

watch(source, callback, [options])

参数说明:

  • source: 可以支持 string,Object,Function,Array; 用于指定要侦听的响应式变量
  • callback: 执行的回调函数
  • options:支持 deep、immediate 和 flush 选项。

接下来我会分别介绍这个三个参数都是如何使用的, 如果你对 watch 的使用不明白的请往下看:

监听reactive定义的数据

如:

监听ref定义的数据
监听多个数据

语法:

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})

如,针对上面监听reactive与ref定义的数据:

监听复杂的嵌套对象

如:

如果不使用第三个参数deep:true, 是无法监听到数据变化的。 前面我们提到,默认情况下,watch 是惰性的, 那什么情况下不是惰性的, 可以立即执行回调函数呢?其实使用也很简单, 给第三个参数中设置immediate: true即可。

stop停止监听

我们在组件中创建的watch监听,会在组件被销毁时自动停止。如果在组件销毁之前我们想要停止掉某个监听, 可以调用watch()函数的返回值,操作如下:

对比watchEffect:

  1. watchEffect 不需要手动传入依赖
  2. watchEffect 会先执行一次用来自动收集依赖
  3. watchEffect 无法获取到变化前的值, 只能获取变化后的值

Vue3-高级

自定义hooks

这里如果有熟悉react的小伙伴的话,那么对hooks是比较熟悉的。在Vue3.x中,提供了自定义hooks,目的在于代码的重用,与vue2.x中mixins的区别在于其性能以及阅读性更好。

如:构建一个平时开发中常见的hooks来进行数据的封装请求处理。

我们将构建两个可组合的hooks。

  • 第一个hook将用于直接与其余API进行交互
  • 第二个hook将依赖于第一个

/hooks/api.ts

import { ref } from 'vue'

export default function useApi(url: RequestInfo, options ?: RequestInit | undefined) {
  const response = ref()
  const request = async () => {
    const res = await fetch(url, options)
    const data = await res.json()
    response.value = data
  }
  return {
    response,
    request
  }
}

/hooks/products.ts

import useApi from './api'
import { ref } from 'vue'

export default async function useProducts() {
  const { response: products, request } = useApi(
    "https://ecomm-products.modus.workers.dev/"
  )
  const loaded = ref(false)
  if(loaded.value === false) {
    await request()
    loaded.value = true
  }
  return {
    products
  }
}

/test.vue


注意,给setup加上async。需要给父组件设置包裹子组件,如parent.vue

再来看一个获取用户信息的例子:

/hooks/user.ts

import useApi from "./api";
import { ref } from "vue";
export interface Location {
  lat: number;
  lng: number;
}
export interface Address {
  street: string;
  suite: string;
  city: string;
  zipcode: number;
  geo: Location;
}
export interface User {
  id: string;
  name: string;
  username: string;
  email: string;
  address: Address;
}
export default async function useUserss() {
  const { response: users, request } = useApi(
    "https://jsonplaceholder.typicode.com/users"
  );
  const loaded = ref(false);
  if (loaded.value === false) {
    await request();
    loaded.value = true;
  }
  return { users };
}

/component/User.vue

Teleport

为什么需要

准许将一个元素从一个地方移到另一个地方。

有了这个认识,我们再来看一下为什么需要用到 Teleport 的特性呢,看一个小例子: 在子组件Header中使用到Dialog组件,我们实际开发中经常会在类似的情形下使用到 Dialog ,此时Dialog就被渲染到一层层子组件内部,处理嵌套组件的定位、z-index和样式都变得困难。 Dialog从用户感知的层面,应该是一个独立的组件,从 dom 结构应该完全剥离 Vue 顶层组件挂载的 DOM;同时还可以使用到 Vue 组件内的状态(data或者props)的值。简单来说就是,即希望继续在组件内部使用Dialog, 又希望渲染的 DOM 结构不嵌套在组件的 DOM 中。 此时就需要 Teleport 上场,我们可以用包裹Dialog, 此时就建立了一个传送门,可以将Dialog渲染的内容传送到任何指定的地方。 接下来就举个小例子,看看 Teleport 的使用方式

使用

我们希望 Dialog 渲染的 dom 和顶层组件是兄弟节点关系, 在index.html文件中定义一个供挂载的元素。

  
    
    

Dialog.vue


你可能感兴趣的:(Vue3初体验)