Vinson

Vinson

Vue3

发表于 2023-09-12
Vinson
阅读量 25

Vue3 脚手架分析

/* createApp 创建对应的应用,产生应用的实例 */
import { createApp } from 'vue'
import App from './App.vue'
/* 创建 APP 返回实例对象,mount 方法挂载到 #app */
createApp(App).mount('#app')
js

注意
vscode 设置搜索 Validate vue-html in <template> using eslint-plugin-vue 关闭,否则会提示 eslint 报错

APP.vue

  1. template 不需要有一个根节点
  2. <script lang=“ts”> 代表编写ts文件
  3. import { defineComponent } from ‘vue’; // 引入函数,定义一个组件,传入配置对象
export default defineComponent({
 name: 'App',
 components: {
  HelloWorld
 }
});
js

Vue Ref、reactive、toRefs、toRef、customRef 创建响应式数据

Ref

import { ref } from 'vue';
setup(){
  const a = ref(0); // 一般用于定义基本类型响应式, 返回 Ref 对象
  function add(){
    a.value ++;   // js 中操作数据: xxx.value,模板中则不需要
  }
  return{
    a,add
  }
}
js

reactive

let num = ref(0);
let obj = reactive({})  // 用于创建引用类型响应式
let obj = reactive({num}) // ref 会被解包 obj.num === num.value
obj.num = num       // ref 赋值给 reactive 属性也会被解包,obj.num === num.value
js

响应式原理

let obj = {
 name: 'name',
 arr: []
}
js
  1. target 被代理的对象
  2. handler 处理器对象,监视数据操作
let pro = new Proxy(obj,{
 get(target,prop){
  console.log('get方法');
  return Reflect.get(target,prop); // 读取对象的值
 },

 set(target,prop,value){
  console.log('set方法调用');
  return Reflect.set(target,prop,value); // 设置对象的值
 },

 deleteProperty(target,prop){
  console.log('delete方法调用了');
  return Reflect.deleteProperty(target,prop ); // 设置对象的值
 }
})
js

toRefs

const reactiveObj = reactive({
 num: 1,
})
// 将 reactive 对象转成 ref 对象,可用于解构
const toRefObj = toRefs(reactiveObj);
setInterval(() => {
 // reactiveObj.num ++ ;
 toRefObj.num.value++
},1000)

return {
 reactiveObj,
 ...toRefObj,
}
js

toRef

const user = reactive({
 age: 18,
 money: 20,
});

// toRef age 与 user 互通
const age = toRef(user, "age");
// ref money 与 user 不互通
const money = ref(user.money);
js

组件中的应用

<!-- 这里传入的是 age.value -->
<child :age='age'/>

<!-- child组件 -->
<template>
  <div>{{ age }}</div>
  <div>{{ length }}</div>
</template>
html
// 别人写的 hook 函数
const getLength = (age: Ref) => computed(() => age.value.toString().length);
export default defineComponent({
 name: "child",
 props: {
  age: {
   type: number,
   required: true,
  },
 },
 setup(props) {
  // 传入 props 把 age 变为 ref 对象并且同步响应
 const length = getLength(toRef(props, "age"));
  return { length };
 },
});
js

customRef

创建自定义的响应式

<input v-model="text" />
<div>{{text}}</div>
html
import {customRef} from 'vue'
const useDebouncedRef = (value, delay = 200) => {
  let timeout
  return customRef((track, trigger) => {
    get() {
      track() // 跟踪 return 出去的 value
      return value
    },
    set(newValue) {
      // 延迟后设置新的值
      clearTimeout(timeout)
      timeout = setTimeout(() => {
        value = newValue
        trigger() // 通知 vue 出发模板更新
      }, delay)
    }
  })
}

export default {
  setup() {
    return {
      text: useDebouncedRef('hello')
    }
  }
}
js

Vue setup 组合式 API

组合式 API

import {ref,onMounted,watch,toRefs,computed} from 'vue'
setup(props) {

 console.log(props); // 父传子接收的参数

 let { user } = toRefs(props); // 创建对 user 的响应式引用
 let arr = ref([]); // 创建响应式引
 let getArr = async () => {  // 异步调用 api
  arr.value = await Promise.resolve([user.value.name,Math.random() + '','asdf','qwesax','ervhf','dfdaw']);
 }

 onMounted(getArr); // 在mounted时期调用此方
 watch(user,getArr); // 当user变化调用方法
 let search = ref(''); // 创建响应式引用
 let arrSearch = computed(() =>// 计算属性 过滤
  arr.value.filter(item => item.includes(search.value))
 ); 

 return { // 返回的对象可以在模板当中直接使用
  arr:arrSearch, // 只需要搜索过滤的结果
  getArr,
  search,
 }
}
js

setup 细节

  • 执行的时机
    • 在 beforeCreate 之前执行(一次)此时组件对象还没有创建
    • this 是 undefined,不能通过 this 来访问 data/computed/methods / props
    • 所有的 composition API 相关回调函数中也都不可以
  • 返回值
    • 返回一个对象:属性会与 data 合并,方法会与 methods 合并
    • 如果有重名,setup 优先
  • 注意:
    • methods 中可以访问 setup 提供的属性和方法,但在 setup 方法中不能访问 data 和 methods
    • setup 不能是一个 async 函数:因为返回值不再是 return 的对象,而是 promise
  • 参数
    • setup(props, {attrs, slots, emit})
    • props:接收的传值
    • attrs:相当于 this.$attrs,包含 props没有接的参数
    • slots:所有传入的插槽内容的对象,相当于 this.$slots
    • emit:用来分发自定义事件的函数,相当于 this.$emit

vue computed 计算属性与 watch 监听

computed

  • 只有 get,使用回调
const name = computed(() => obj.name + obj.age)
js
  • get,set方法 name1 执行 set 时调用
const name1 = computed({
 get: () => obj.name + obj.age,
 set: val => {obj.name = val}
})
js

watch 监听对象

watch(obj,newVal => {
 console.log(newVal);
}, {
 immediate: true, // 是否在初始化的时候就执行一次,默认 false
 deep: true,    // 深监听,默认 false
})
const one = ref('')
/* 监听一个数据 */
watch(one, value => {
 console.log(value)
})
js
  • watch多个数据:
    使用数组来指定

ref 对象, 直接指定

reactive 对象必须通过函数返回

watch([() => obj.name, () => obj.age, one], (values) => {
 console.log('监视多个数据', values)
})
js

回调中使用到的数据会自动监视

默认初始化执行一次,不需要设置 immediate

watchEffect(() => {
 let a = obj.name + obj.age;
})
js

Vue3 生命周期

  • vue3 中只有 beforeDestroy 和 destroyed 名字变了
  • vue2 中生命周期使用的是对象上 option,vue3 使用的是 api
  • setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,这两个周期里面的代码都可以在 setup 中编写
  • vue2 的声明周期可以在3中写
    • beforeCreate -> 使用 setup()
    • created -> 使用 setup()
    • beforeMount -> onBeforeMount
    • mounted -> onMounted
    • beforeUpdate -> onBeforeUpdate
    • updated -> onUpdated
    • beforeDestroy -> onBeforeUnmount
    • destroyed -> onUnmounted
    • errorCaptured -> onErrorCaptured
    • activated -> onActivated
    • deactivated -> onDeactivated
  • 新增
    • renderTracked -> onRenderTracked
    • renderTriggered -> onRenderTriggered
  • 父子生命周期加载顺序
    • parent-beforeCreate
    • parent-created
    • parent-onBeforeMount
    • parent-beforeMount
    • child-beforeCreate
    • child-created
    • child-onBeforeMount
    • child-beforeMount
    • child-onMounted
    • child-mounted
    • parent-onMounted
    • parent-mounted
  • 更新
    • child-onBeforeUpdate
    • child-beforeUpdate
    • child-onUpdated
    • child-updated

Vue Provide/Inject 祖孙传值

setup 中使用,需要 import 引入。

  • 祖先组件
const location = ref('North Pole')
const geolocation = reactive({
 longitude: 90,
 latitude: 135
})
js
  • 使用 ref,reactive 是为了响应性
provide('location', location)
provide('geolocation', geolocation)
/* readonly 可以保证不被孙组件修改属性 */
provide('location', readonly(location))
/* 孙组件引入 inject */
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')
js

Vue+ts ref 获取 DOM 元素

  • template
<input type="text" ref="inputRef" />
<!-- 加冒号传入 divs 方法 -->
<div v-for="i of 3" :key="i" :ref="divs"></div>
html
  • setup
const inputRef = ref<HTMLElement | null>(null);
const arr = ref([]);
const divs = (el: HTMLElement) => {
 // 断言为 HTMLElement 类型的数组
 (arr.value as Array<HTMLElement>).push(el);

 // 这样写编译器会抛出错误 --> Argument of type 'HTMLElement' is not assignable to parameter of type 'never'.
 // arr.value.push(el);

};

onMounted(() => {
 // 加载完成获取 input 焦点
 inputRef.value && inputRef.value.focus();

 // 打印多个ref DOM
 console.log(arr);
});

return {
 inputRef,
 divs,
};
js

注意
dom 标签上 :ref 传入的是方法名,如果 :ref='变量 + ‘字符串拼接’'则不能获取指定的 dom 元素了。官网也没有说明如何获取,解决办法还是在 methods 方法中使用 vue2 语法 this.$refs[‘xxx’] 来获取

vue 更改响应式方法、只读属性、标记响应式数据

shallowReactive & shallowRef

// shallowReactive 页面渲染只响应外层属性
// shallowRef 只响应 value 级别属性
/* 
 m1.name += '==';
 m1.a.name += '==';
 同时更改页面会响应,因为 m1.name 是外层的
*/
const m1 = shallowReactive({name:'1',a:{name:'4'}})
const m2 = shallowRef({name:'1',a:{name:'2'}})
js

readonly & shallowReadonly

// 深度只读 所有都不能更改
const m1 = readonly({name:'1',a:{name:'2'}})
// 浅只读, 深层次属性可以更改
const m2 = shallowReadonly({name:'1',a:{name:'4'}})
js

toRaw & markRaw

interface UserInfo {
 name: string;
 age: number;
 likes?: number[];
}

// 页面显示 user
const user = reactive<UserInfo>({
 name: "小明",
 age: 18,
});


const arr = [1, 2];
user.likes = markRaw(arr) // 标记并之后再也无法响应
const upData = () => {
  const raw = toRaw(user); // 将一个响应式数据变为普通对象
 raw.name += "=="; // 界面不会更新
 user.likes && (user.likes[0] += 1) // 不会更新响应
}
js

vue3 单文件 style 特性

scope

只对当前组件的样式生效,这在 vue2 中经常使用。

<style scoped>
.example {
  color: red;
}
</style>

<template>
  <div class="example">hi</div>
</template>
html

深度选择器

选择子组件的类名

<style scoped>
.a :deep(.b) {
  /* ... */
}

/* 或 */
.a /deep/ .b{

}

/* 或 */
.a >>> .b{

}
</style>
html

插槽选择器

子组件的插槽的内容属于父组件,子组件要选择需要用 :slotted

父组件

<template>
  <HelloWorld>
    <div>122</div>
  </HelloWorld>
</template>
html

子组件

<template>
  <div class="children">
    <slot></slot>
  </div>
</template>
<style scoped>
.children :slotted(div){
  color: red;
}
</style>
html

全局选择器

选择全局的 .red class

<style scoped>
:global(.red) {
  color: red;
}
</style>
html

moudle

module 属性会将css类放在 $style 对象上

<template>
  <p :class="$style.red"> 文本 </p>
</template>

<style module>
.red {
  color: red;
}
</style>
html

自定义名称

<template>
  <p :class="classes.red">red</p>
</template>

<style module="classes">
.red {
  color: red;
}
</style>
html

API 中获取 style module

// 默认, 返回 <style module> 中的类
useCssModule()

// 命名, 返回 <style module="classes"> 中的类
useCssModule('classes')
js

v-model 动态 css

v-bind(color) 绑定即可

<template>
  <div class="text">hello</div>
</template>

<script>
export default {
  data() {
    return {
      color: 'red'
    }
  }
}
</script>

<style>
.text {
  color: v-bind(color);
}
</style>
html

<script setup>中一样

<script setup>
  const theme = {
    color: 'red'
  }
</script>

<template>
  <p>hello</p>
</template>

<style scoped>
p {
  color: v-bind('theme.color');
}
</style>
html

vue3 新组件

Fragment(片段)

  • vue2 中组件必须要一个根表情
  • vue3 不需要
  • vue3 中没有根标签,内部会将其包裹在Fragment虚拟元素中(在新版vue.js devtools 审查元素能够看到)
<template>
  <h2>aaaa</h2>
  <h2>aaaa</h2>
</template>
html

Teleport(瞬移)

  • to:表示teleport包裹的元素会插入body标签下。
  • 即使在不同的元素下渲染,但组件的父子关系不会变。
<template>
  <teleport to="body">
    <div class="modal">
      这是一个弹窗
    </div>
  </teleport>
</template>
html

也可以使用选择器

<teleport to="#modals">
  <div>A</div>
</teleport>
<teleport to="#modals">
  <div>B</div>
</teleport>

<!-- 输出 -->
<div id="modals">
  <div>A</div>
  <div>B</div>
</div>
html

Suspense(不确定)

  • 在子组件异步加载之前,可以先显示后背内容

父组件

<template>
  <Suspense>
    <template #default>
      <son />
    </template>
    
    <template #fallback>
      <h1>加载中...</h1>
    </template>
  </Suspense>
</template>
html

son 子组件

<template>
  <h2>
    {{data}}
  </h2>
</template>

<script>
export default {
  setup () {
    // 注意此处 setup 返回的是 Promise 对象
    return new Promise(res => {
      setTimeout(() => {
        res({data: '加载完成'})
      }, 2000);
    })
  }
}
</script>
html
评论
来发一针见血的评论吧!
表情

快来发表评论吧~

推荐文章
  • 测试文章

    20点赞10评论

  • webpack5(一)

    20点赞10评论