分类 其它 下的文章

Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)

https://cli.vuejs.org/zh/

使用脚手架

  1. 安装脚手架

    npm install -g @vue/cli
    
  2. 使用脚手架创建一个项目

    vue create vue-demo
    
  3. 进入目录启动服务

    npm run serve
    

脚手架结构

├── node_modules 
├── public
│   ├── favicon.ico: 页签图标
│   └── index.html: 主页面
├── src
│   ├── assets: 存放静态资源
│   │   └── logo.png
│   │── component: 存放组件
│   │   └── HelloWorld.vue
│   │── App.vue: 汇总所有组件
│   │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件 
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
├── vue.config.js:脚手架配置文件
// 默认的main.js
// 整个项目的入口文件

// 引入vue
import Vue from 'vue'
// 引入App组件,所有组件的父组件
import App from './App.vue'

// 关闭vue的生产提示
Vue.config.productionTip = false

// 创建vue实例对象 vm
new Vue({
  // 将App组件放入容器中
  render: h => h(App),
}).$mount('#app') // 挂载app容器

<!-- 默认index文件-->

<!DOCTYPE html>
<html lang="">
<head>
    <meta charset="utf-8">
    <!--      针对ie浏览器的配置,让ie浏览器以最高级别渲染页面-->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!--      开启移动端的理想视口-->
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <!--   配置页签图标   -->
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <!--    配置网页标题-->
    <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- 浏览器不支持js 的时候, noscript中的元素会被渲染-->
<noscript>
    <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
        Please enable it to continue.</strong>
</noscript>
<!-- 容器-->
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

render函数

在main.js文件中,
import Vue from 'vue’
,此处导入的vue不是完整的,缺少模板解析器,所以不能使用template属性去设置组件标签

, vue代码完成之后是要打包的, 其中少不了vue的核心代码,当我们打包好之后, 我们其实是不需要再来解析模板的, 那么, vue的核心代码中的模板解析器根本用不着, 所以,vue为了使代码的体积减少, 就去掉了模板解析器,但是我们开发的时候, 又要使用, 所以就创建了一个 render 方法来解析模板,总之, 它的目的就是让打包后的代码体积尽量小,提升性能

关于不同版本的Vue:

  1. vue.js与vue.runtime.xxx.js的区别:
    (1).vue.js是完整版的Vue,包含:核心功能+模板解析器。
    (2).vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。

  2. 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项

    需要使用render函数接收到的createElement函数去指定具体内容

render
函数主要用于创建虚拟 DOM 节点(VNode)。它是一个 JavaScript 函数,接收一个名为
createElement
(通常缩写为
h
)的函数作为参数。这个
createElement
函数用于构建 VNode

    render(createElement){
        // 创建一个节点  <h1>hello world</h1> 并在页面渲染
        return createElement("h1","hello world") 

    }
// 将App组件传入,将APP中的template模版交给render渲染
// 简写的箭头函数
render: h => h(App),

脚手架默认配置

脚手架默认隐藏了webpack的相关配置,使用下面命令将相关配置输出为一份js文件供查看(仅查看)

vue inspect > output.js

默认情况下,public文件夹下的index、favicon ,src下的App.vue和main.js不允许修改,vue默认是从该路径找对应的文件

可修改的配置
,可以修改脚手架规则,进行个性化定制

ref属性

被用来给元素或子组件注册引用信息(id的替代者)

应用在html标签上获取的是真实DOM元素、组件标签上是组件实例对象(vc)

<template>
  <div id="app">

    <!--   设置标题DOM的ref是title-->
    <h1 ref="title">标题</h1>
    <button @click="showDOm">点击获取标题的DOM元素</button>
    <!--   设置组件ref是studen-->

    <StudentComp ref="student"></StudentComp>
  </div>
</template>

<script>
import StudentComp from "./components/StudentComp.vue";

export default {
  name: 'App',
  components: {
    StudentComp
  },
  data() {
    return {name: "app->vue", address: "Beijing"}
  },
  methods: {
    showDOm() {
      // 这里的this是vc

      //模版标签里ref="title", 在vc上面的结构是 在vc的$refs对象里面有title的key,value就是对应的DOM
      console.log(this.$refs.title)
      // 在组件标签上面使用ref,这里获取到的是对应组件的实例对象
      console.log(this.$refs.student)
    }
  }
}
</script>

props配置

可以让组件接收外部传过来的数据,动态传参

  1. 只接受参数

    <script>
     export default {
      name: "StudentComp",
      data: function () {
        return {name: "vue", address: "Beijing"}
      },
       // 数组形式->使用props选项,配置city和age参数
      props:["city","age",]
    
    }
    
    
    </script>
    
    <template>
      <div class="student">
        <h1>名字:{{ name }}</h1>
        <h1>地址:{{ address }}</h1>
        {{city}}
        <!-- 接受到age之后加1,age默认是字符串,所以不会正常计算,只会字符串拼接 --> 
        {{age + 1}} 
    
      </div>
    </template>
    

     <!-- 在使用组件标签的时候,传入对应的数据 -->
    <StudentComp city="beijing" age="18"></StudentComp>
    <!--  如果要传入的是个表达式 ,用数据绑定语法  :age="xxxxx"-->
    
  2. 接收参数-限制类型

    <script>
    export default {
      name: "StudentComp",
      data: function () {
        return {name: "vue", address: "Beijing"}
      },
    // 对象形式
      props: {
        city: String, // 定义city参数,字符串类型
        age: Number // 定义age参数,Number类型
      }
    }
    
    
    </script>
    
    <template>
      <div class="student">
        <h1>名字:{{ name }}</h1>
        <h1>地址:{{ address }}</h1>
        {{ city }}
        <!--     age限制是Number类型,可以正常计算-->
        {{ age + 1 }}
    
      </div>
    </template>
    
  3. 接收参数-类型限制-必填限制-默认值设置

    // 对象嵌套对象 
    props:{
        city:{
          type:String, // 字符串类型
          required:true // 必填项,如果不传递,默认是false
        },
        age:{
          type:Number,// number类型
          required: false,// 非必填
          default:18 // 默认值,如果不传参,走默认项
        }
      }
    
  4. props是只读的


    props是只读的,模版标签中传递什么就是什么,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据


    // props的渲染优先级比data高
    // 页面使用myCity进行渲染,myCity的初始值是props的city,从模版标签传入
    // 修改myCity即修改页面效果
    data: function () {
        return {name: "vue", address: "Beijing",myCity:this.city}
      }
    
  5. 通过props传递数据

<template>
  <div>

    <Student  :demo="demo"></Student>
  </div>
</template>

<script>


import Student from "@/components/Student.vue";

export default {
  name: 'App',
  components: {
    Student
  },
  methods: {
    demo(name) {
      console.log("子组件data name", name)
    }
  }
}
<script>
 export default {
   // eslint-disable-next-line vue/multi-word-component-names
   name: "Student",
   // 定义props属性
   props:["demo"],
  data: function () {
    return {name: "vue", address: "Beijing"}
  },
  methods: {
    sendStudentName(){
      this.demo(this.name)
    }
  }
}


</script>

mixin混入

可以把多个组件共用的配置提取成一个混入对象

  1. 第一步:定义混合

    // mixin.js文件 
    export const mixin = {
        // 回调
        methods:{
            showName(){
                alert(this.name)
            }
        }
    
    }
    
  2. 第二步:组件配置

    <script>
    
    // 导入mixin
    import {mixin} from "@/mixin";
    
    export default {
      name: "StudentComp",
      data: function () {
        return {name: "vue", address: "Beijing"}
      },
      // 配置混合(局部混入),导入的mixin
      // 如果有多个 ,数组可以配置多个
      mixins:[mixin]
    
    }
    
    
    </script>
    

    Vue.mixin(mixin) // 全局混入
    
  3. 第三部:正常使用

        <button @click="showName">按钮</button>
    
  4. 一个mixin对象可以报警data、methods、computed等各种组件选项

    理论上可以组件配置所有选项都可以使用,template和el可能会存在一定限制和合并问题

    export const mixin = {
        // 回调
        methods: {
            showName() {
                alert(this.name)
            }
        },
        mounted() {
            console.log("test")
        },
        data() {
            // 如果混合和组件内的data使用了同样的属性,以组件内部为主,如果不相同,两个会组合起来
            return {baseUrl: "xxxx"}
        },
        // 计算
        computed:{
            
        },
        
    }
    

插件

  1. 功能:用于增强Vue

  2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据

  1. 定义插件

    export const TestPlugins  = {
      // 如果需要自定义参数,在Vue后传参即可
        install(Vue){
          // 可以自定义一些方法
            // 过滤器
            Vue.filter()
            // 混入
            Vue.mixin()
    
            // 在Vue原型上添加方法(vm和vc都可以用)
            Vue.property.say = ()=>{}
        }
    }
    
  2. 使用插件

    import {TestPlugins} from "@/plugins";
    
    Vue.use(TestPlugins)
    

scoped样式

多个不同的组件编写的样式,选择器最终是汇总在一起的,有可能出去类名、ID名字冲突,scoped可以让样式在局部生效,防止冲突,原理是在对应的元素上加上一个 data-v-xxxxx,唯一标识

<style scoped ></style>

组件化编码流程

  1. 实现静态组件:抽取组件,使用组件实现静态页面效果
    • 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突
  2. 展示动态数据:数据类型、名称、保存在哪个组件等等
    • 考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用
    • 一个组件在用:放在组件自身即可
    • 一些组件在用:放在他们共同的父组件上(状态提升)
  3. 交互:绑定事件监听等等
  4. props适用于:
    • 父组件 ==> 子组件 通信
    • 子组件 ==> 父组件 通信(要求父先给子一个函数)

浏览器本地存储

存储储内容大小一般支持5MB左右(不同浏览器可能还不一样)

  1. LocalStorage


    LocalStorage存储的内容,需要手动清除才会消失,关闭浏览器不会消失


        // 本地存储-> 存储到Local Storage
        // key和value都是字符串,若value不是字符串,会默认调toString方法
        // 如果key已经存在,更新数据
        localStorage.setItem("name","vue")
    
        // 读取数据,如果没有该key,读取的是null
        localStorage.getItem("name")
    
        // 删除数据
        localStorage.removeItem("name")
    
        // 清空数据-所有的数据
        localStorage.clear()
    
  2. sessionStorage


    SessionStorage存储的内容会随着浏览器窗口关闭而消失


        // 本地存储-> 存储到Session Storage
         sessionStorage.setItem("name","vue")
    
        // 读取数据,如果没有该key,读取的是null
        sessionStorage.getItem("name")
    
        // 删除数据
        sessionStorage.removeItem("name")
    
    
        // 清空数据-所有的数据
        sessionStorage.clear()
    

组件的自定义事件

在 Vue 中,组件自定义事件是一种用于组件间通信的重要机制。它允许子组件向父组件传递信息。当子组件发生某些操作或者状态变化时,可以通过触发自定义事件来通知父组件

例如,在一个包含表单组件(子组件)和显示数据组件(父组件)的应用中,当用户在表单组件中提交数据后,表单组件可以通过自定义事件将数据传递给父组件,以便父组件进行后续处理

通过自定义事件传递数据
  1. 定义自定义事件-第一种方式
<template>

  <div>
    <!--     给Student这个组件实例对象Vc绑定了一个事件-->
    <!--    给谁绑定了事件,就找谁触发事件-->
    <!--     v-on:自定义事件名="对应方法 "-->
    <!-- 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法 -->    
    <!--  可以简写为@testDemo="demo" -->
    <Student v-on:testDemo="demo"></Student>
  </div>
</template>

<script>


import Student from "@/components/Student.vue";

export default {
  name: 'App',
  components: {
    Student
  },
  methods: {
    demo(name) {
      console.log("子组件data name", name)
    }
  }
}
</script>
  1. 定义自定义事件-第二种方式
<template>
  <div>
<!-- 给组件设置ref属性 -->
    <Student  ref="student"></Student>
  </div>
</template>

<script>


import Student from "@/components/Student.vue";

export default {
  name: 'App',
  components: {
    Student
  },
  methods: {
    demo(name) {
      console.log("子组件data name", name)
    }
  },
  // 当组件加载完毕之后
  mounted() {
    // student组件实例对象,绑定testDemo事件,触发Demo回调
    // 更加灵活,可以做一些前后置操作再绑定事件
    // 该方式回调要么配置在methods中,要么用箭头函数,写普通函数中的this是要绑定的组件实例对象
    this.$refs.student.$on("testDemo",this.demo)
  }
}
</script>
  1. 触发自定义事件
<script>
 export default {
   name: "Student",
  data: function () {
    return {name: "vue", address: "Beijing"}
  },
  methods: {
    sendStudentName(){
      //  通过$emit方法触发事件,传入自定义事件的名字
      //  触发testDemo事件
      this.$emit("testDemo",this.name)
    }
  }
}


</script>

<template>
   <div class="student">
    <h1>名字:{{ name }}</h1>
    <h1>地址:{{ address }}</h1>
    <button @click="sendStudentName">按钮</button>

  </div>


</template>
解绑自定义事件
// 在要解绑的组件上调用解绑指定组件   
this.$off("testDemo")
// 解绑多个指定事件
this.$off(["testDemo","xxxx"])
// 所有的自定义事件都解绑
this.$off()
native

如果在组件标签上绑定原生的事件,组件标签也会当成自定义事件去寻找自定义的方法

<!-- 使用native修饰符,告诉组件这个是原生事件 -->
<Student  ref="student" @click.native="show"></Student>

全局事件总线

一种组件间通信的方式,用于任意组件间通信,在应用程序中实现事件通信的机制,它允许不同的组件之间进行解耦的通信。可以把它想象成一个消息中心,各个组件或模块可以向这个中心发布(触发)事件,也可以从这个中心订阅(监听)事件。当一个事件被发布时,所有订阅了该事件的组件或模块都会收到通知并可以执行相应的操作

创建全局事件总线

VueComponent.prototype.
__proto__
=== Vue.prototype

$on、$off、$emit都是Vue原型上的方法,组件实例对象(vc)可以访问到Vue原型上的属性、方法

可以通过创建一个新的 Vue 实例来作为全局事件总线。通常在项目的入口文件中进行设置

// 入口文件
new Vue({
    render: h => h(App),
    // 生命周期函数:beforeCreate 组件实例刚被创建时调用
    beforeCreate() {

        // 在vue的原型上添加一个$bus属性,值是当前vue实例 

         Vue.prototype.$bus = this
    }
}).$mount('#app')
使用全局事件总线

接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身

// 子组件A
// 组件挂载后绑定事件
mounted() {
    // 在bus(vue)上绑定一个test事件
    this.$bus.$on("test",(data)=>{
      console.log("收到数据",data)
    })
  },
 // 组件销毁之前解绑
    beforeDestroy(){
      this.$bus.$off("test")
    }
    
触发全局事件总线
// 子组件B
sendData(){
      // 触发bus(vue)的test事件
      this.$bus.$emit("test","data数据")

    }

消息订阅与发布

PubSub 的概念
  • PubSub 是 Publish - Subscribe(发布 - 订阅)的缩写,它本质上是一种消息传递模式。在这种模式下,有发布者(Publishers)和订阅者(Subscribers)两类角色。发布者负责产生消息并将其发送到一个消息中心(也称为消息代理,Message Broker),订阅者则向消息中心表达自己对某些消息类型的兴趣,当消息中心收到发布者发送的匹配订阅者兴趣的消息时,就会将消息转发给订阅者

  • 一种组件间通信的方式,适用于任意组件间通信

  • 有一些专门的 PubSub 库可以帮助实现消息的发布和订阅。例如pubsu-js库

使用pubsub
  1. 安装pubsu-js库

    npm install -g pubsub-js       
    
  2. 引入pubsub

    import pubsub from 'pubsub-js'
    
  3. 订阅消息


    A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身


    // 组件挂载完之后订阅消息
    mounted() {
        // 参数:主题、回调函数(主题名、数据)
        // 订阅了testTopic主题的消息,如果有人给testTopic主题发消息,会接受到
        // pubsub.subscribe会返回一个订阅id
        this.pid = pubsub.subscribe("testTopic",function (msgName,data){
          // 此时this是undefined,因为使用的三方库
          // 可以使用箭头函数,this是当前vc 或者配置回调函数在这里调用
          console.log(data)
    
        })
      },
        // 组件销毁前
      beforeDestroy(){
        // 取消订阅(订阅id)
        pubsub.unsubscribe(this.pid)
      }
    
  4. 发布消息

        // 给订阅testTopic主题的发送消息,发送消息内容是helloworld
        pubsub.publish("testTopic","helloworld")
    

$nextTick

$nextTick
是 Vue.js 提供的一个实例方法,它的主要作用是在下次 DOM 更新循环结束之后执行延迟回调。在 Vue 中,数据的变化到 DOM 的更新是异步的,当数据发生改变时,Vue 会开启一个异步更新队列,将同一事件循环中的所有数据变化引起的 DOM 更新操作合并到一个更新任务中,等本轮事件循环结束后,再一次性执行 DOM 更新。
$nextTick
就是用于在这个 DOM 更新完成后执行一些操作

     edit(todo){
       if(todo.hasOwnProperty('isEdit')){
        todo.isEdit = true
      }else {
        this.$set(todo,'isEdit',true)
      }
       this.$refs.inputTitle.focus()
     }
// 问题示例:我们想修改一个状态,状态是true的时候输入框可以获取获取焦点
// 问题在于,todo.isEdit = true修改后,vue不是立马渲染的,而是等代码都执行完才渲染
// 相当于this.$refs.inputTitle.focus() 执行完 再去渲染,
// 执行的时候页面还没有渲染,是false,从而获取焦点失败
// 当页面渲染完后,再去获取焦点
this.$nextTick(function (){
        this.$refs.inputTitle.focus()
      })
  1. 作用:在下一次 DOM 更新结束后执行其指定的回调。

  2. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行

过渡与动画

在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名

image-20241121135250739

动画默认名称效果

vue会根据规则自动实现动画效果,适用一个动画

<template>
  <div>
    <button @click="isShow = !isShow"> 显示/隐藏</button>
    <!--   让谁有动画效果,就用transition 把谁包起来 -->
    <!--   appear属性用于控制元素在初始渲染时是否应用过渡效果 不加则初始渲染不应用-->
    <!-- 只适用于包裹单个元素 -->
    <transition appear>
      <h1 v-show="isShow">hello world</h1>

    </transition>

  </div>
</template>

<style scoped>
h1 {
  background-color: orange;
}

/* v-enter-active主要用于定义元素进入(插入)时过渡动画的行为 */
.v-enter-active {
  animation: test 1s;
}

/* v-leave-active用于定义元素离开(移除)时过渡动画的行为 */
.v-leave-active {
  animation: test 1s reverse;
}

/* 定义动画关键帧 */
@keyframes test {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0px);
  }

}
</style>
动画自定义名称

设置自定义名称,可以指定多个不同的效果

<!-- 指定name名称-->
<transition appear name="hello">
      <h1 v-show="isShow">hello world</h1>
 
    </transition>
/* 使用指定name */
.hello-enter-active {
  animation: test 1s;
}

/* 使用指定name */
.hello-leave-active {
  animation: test 1s reverse;
}
过渡效果
<template>
  <div>
    <button @click="isShow = !isShow"> 显示/隐藏</button>
    <!--   让谁有动画效果,就用transition 把谁包起来 -->
    <!--   appear属性用于控制元素在初始渲染时是否应用过渡效果 不加则初始渲染不应用-->
    <transition appear>
      <h1 v-show="isShow">hello world</h1>

    </transition>

  </div>
</template>

<style scoped>

h1{
  background-color: orange;
  transition: 0.5s linear;
}
/* 进入的起点 */
.hello-enter {
  transform: translateX(-100%);
}

/* 进入的终点 */
.hello-enter-to {
  transform: translateX(0);

}

/* 离开的起点 */
.hello-leave{
  transform: translateX(0%);

}

/* 离开的终点  */
.hello-leave-to{
  transform: translateX(-100%);

}
</style>

合并写法

h1{
  background-color: orange;
}

/* 进入的起点和离开的终点 */
.hello-enter,.hello-leave-to {
  transform: translateX(-100%);
}

/* 不去修改h1本身的样式,使用进入和离开的时候设置效果 */
.hello-enter-active,.hello-leave-active {
  transition: 0.5s linear;
}

/* 进入的终点和离开的启动 */
.hello-enter-to,.hello-leave {
  transform: translateX(0);

}
多个元素过渡
<!--  transition只能包裹一个元素   -->
<!--   包裹多个元素使用transition-group,且每个元素都有一个key值 -->
    <transition-group appear name="hello">
      <h1 v-show="isShow" key="1">hello world</h1>
      <h1 v-show="isShow" key="2">hello vue</h1>
      <h1 v-show="isShow" key="3">hello html</h1>

    </transition-group>
集成第三方动画

可以使用现成的三方动画库来实现效果

animate动画库文档(墙)

  1. 安装

    npm install animate.css --save
    
  2. 引入

    import "animate.css"
    
    
  3. 配置

        <!-- 配置name-->
        <!--   配置进入的效果  在该库文档上选用对应效果的名字-->
        <!--   配置离开的效果 在该库文档上选用对应效果的名字-->
    
        <transition-group
            appear
            name="animate__animated animate__bounce"
            enter-active-class="animate__bounceOutRight"
            leave-active-class="animate__fadeInDown"
        >
          <h1 v-show="isShow" key="1">hello world</h1>
          <h1 v-show="isShow" key="2">hello vue</h1>
          <h1 v-show="isShow" key="3">hello html</h1>
    
        </transition-group>
    

在鸿蒙开发中,系统提供了PhotoViewPicker选择图片,cameraPicker拍照和录视频,此方式能够达到无需向用户申请权限就能选图片、拍照和录视频。除了用户操作的选图、拍照和录视频,其它时间APP无权获取相册图片,无权调用相机,对于用户来说,使用相册和相机都是很明确的时机,安全性非常高。以下来看看具体的使用方式。

使用PhotoViewPicker选择图片

我们new一个PhotoViewPicker并调用select方法,就可以打开系统图片选择器进行图片选择了,支持的参数非常多,基本满足了日常的使用场景。

  • MIMEType:我们可以通过MIMEType来设置是选择图片或者选择视频,甚至是同时选择图片和视频。
  • isEditSupported:通过isEditSupported参数来设置选择好图片后,是否进入图片编辑模式。
  • isOriginalSupported:通过isOriginalSupported参数来设置选择的图片是否为源图,该参数设置为true,选择页面有一个按钮,可以控制是否使用源图。
  • isPhotoTakingSupported:在图片选择页面,可以通过isPhotoTakingSupported参数设置第一个位置是否为拍照选项,设置后点击第一个位置,可以打开相机拍摄一张图片。
  • isPreviewForSingleSelectionSupported:通过isPreviewForSingleSelectionSupported参数可以在图片选择后,进入大图预览页面,使我们能够查看图片的更多细节。
  • isSearchSupported:当相册的图片太多时,我们可以通过isSearchSupported参数显示搜索框,在搜索框中输入图片相关内容,以便快速查找相关图片
  • maxSelectNumber:通过maxSelectNumber来设置选择的数量,默认选择50张图片,最多选择500张。
  • preselectedUris:当为多选模式时,通过preselectedUris参数可以预选中一些图片,以便避免每次打开图片选择器,都是重新选择,从而导致之前选择的图片丢失了。
    一个完整的示例如下
new photoAccessHelper.PhotoViewPicker().select({
  MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
  maxSelectNumber: 9,
  isEditSupported: false,
  isOriginalSupported: rue,
  preselectedUris: [],
  isPhotoTakingSupported: false,
  isPreviewForSingleSelectionSupported: false,
  isSearchSupported: false
}, (err, data) => {
  console.log(data)
})

使用cameraPicker拍照和录视频

我们可以通过cameraPicker的select方法来进行拍照或录视频,也可以同时设置拍照和录视频,用户在拍照页面自行选择是拍照还是录视频。具体使用参数如下

  • mediaTypes:是一个数组,设置为拍照片,还是录视频,也可以同时设置
  • cameraPosition:设置是使用前置摄像头还是后置摄像头进行拍照或录视频
  • saveUri:设置拍照或录制的视频保存的位置,通常设置成我们的沙箱目录
  • videoDuration:当模式为录视频时,此参数为设置视频的最大时长。
    一个完整的示例如下
cameraPicker.pick(context,[cameraPicker.PickerMediaType.PHOTO, cameraPicker.PickerMediaType.VIDEO],
  { cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,saveUri:'',videoDuration:30 })
  .then((value) => {
    console.log(value.resultUri)
  })

用户在系统拍照录视频页面,可以自己选择很多参数,例如是否打开闪光灯,图片的宽高比,视频的帧率,是否撕开参考线等等,可选择的参数很多,灵活度也很高,基本可以满足日常的使用场景,最主要的是不用向用户申请权限,还是非常方便的。

相关:

NVIDIA机器人仿真项目 —— Isaac Gym - Preview Release


本文说下NVIDIA公司的机器人仿真项目的一些历史发展。

NVIDIA公司的产品最初只有显卡,但是卖着卖着发现有很多人用CPU搞图形化设计,比如OpenGL,于是老黄大受启发,从此NVIDIA的显卡开发计算接口支持图形化计算,慢慢的NVIDIA的显卡被美国、中国等国家的超算机构所采用,于是NVIDIA公司就出现了家用游戏显卡和企业用计算显卡两种硬件产品以及图形化计算平台这个软件产品;随着AI时代的火热,Deep learning下的时代来临,NVIDIA公司在之前的三个产品(家用游戏显卡和企业用计算显卡两种硬件产品以及图形化计算平台)的基础上建立了AI模型库集成AI软件集成平台,然后随着AI平台在监督学习和无监督学习上集成的AI模型初具规模后,NVIDIA公司开始着眼于强化学习以及元宇宙概念,于是就有了NVIDIA公司的机器人仿真平台,这也是本文所要介绍的内容。


NVIDIA公司最初推出的机器人仿真平台是NVIDIA issac gym,这个项目一共出了四个版本,preview 1,preview 2,preview 3,preview 4,可以说这个issac gym是NVIDIA公司在机器人仿真方向上的大始祖,下面给出其中的第一、二、四版本的下载资源:

https://openi.pcl.ac.cn/devilmaycry812839668/issac_gym_store

由于是初代产品,因此这个Isaac gym平台只能够导入3D模型(urdf、mjdf文件),而不能提供3D设计的操作平台,并且将仿真平台和RL算法等AI算法集成在了一起,随着时间发展,这个产品逐渐成熟,于是在preview 4版本时将AI算法的接口和算法库单独拿了出来形成了IsaacGymEnvs项目:

https://github.com/NVIDIA-Omniverse/IsaacGymEnvs


由于NVIDIA公司认为机器人的AI平台是未来走向通用人工智能(AGI)的重要途径,于是不断加码这个领域的投入,于是就出现了多个部门同时在issac gym的基础上进行扩展开发,因此在issac gym项目抽取出issac gym envs项目后又将issac gym项目和issac gym envs项目升级为OmniIsaacGymEnvs项目,此时的重点是加强3D设计的操作界面做准备,也就是加入元宇宙概念,Omniverse:

https://github.com/NVIDIA-Omniverse/OmniIsaacGymEnvs

其实这里的元宇宙概念与之前的issac gym项目和issac gym envs项目的区别就是其内部的C++实现接口进行了升级,这样可以在下一个升级版本中加入3D设计的操作接口,这样就可以在一个项目中进行3D设计从而生成urdf和mjdf文件,而不需要从第三方仿真设计平台导入了,因此虽然说OmniIsaacGymEnvs是issac gym和issac gym envs的升级,但是对于使用者来说其实并没有太实质的变化,因为这个升级主要是为了服务其内部开发人员的。


在nvidia公司将issac gym和issac gym envs升级为OmniIsaacGymEnvs的同时也进行了fork操作,也就是在issac gym和issac gym envs的基础上又开了一个独立分支,这个分支也是为了元宇宙概念服务的,在这个新分支中考虑对更多的输入数据类型进行处理并且加入更多的AI模型的接入接口,于是有了orbit项目:

https://github.com/NVIDIA-Omniverse/orbit


因为NVIDIA公司的对机器人项目的投入是一个点开始,然后从一条线分出多条线独立发展,然后再将多条线合并到一起的发展路子,因此OmniIsaacGymEnvs和orbit都可以认为是issac gym项目基础上结合不同的元宇宙发展需要的后期接口进行fork后独立发展的,在issac gym项目分叉为OmniIsaacGymEnvs和orbit项目后,NVIDIA公司又决定将这几个项目合并成一个项目,这就是issac sim项目,地址:

https://developer.nvidia.cn/isaac/sim

可以说issac sim是NVIDIA公司的元宇宙技术平台的一个阶段性的汇总,在NVIDIA公司将issac gym项目升级为IsaacGymEnvs,然后再升级为OmniIsaacGymEnvs和orbit的同时,NVIDIA公司也在issac gym的最原始版本开出了其他路线的项目,如:Isaac ROS和Isaac Manipulator,其中Isaac ROS可以看做是issac gym和ROS软件平台的集成,而Isaac Manipulator可以看做是issac gym和视觉识别系统和动作规划系统的集成,于是issac gym发展成了今天的NVIDIA的issac家族,具体如下:

1、Isaac ROS:

https://developer.nvidia.cn/isaac/ros

体验参考指南标准:

https://img-bss.csdnimg.cn/bss/NVIDIA/IsaacROS.docx

2、Isaac Manipulator:

https://developer.nvidia.cn/isaac/manipulator

体验参考指南标准:

https://img-bss.csdnimg.cn/bss/NVIDIA/IsaacManipulator.docx

3、NVIDIA Isaac Sim:

https://developer.nvidia.cn/isaac/sim

体验参考指南标准:

https://img-bss.csdnimg.cn/bss/NVIDIA/IsaacSim.docx


issac sim可以看做是在issac gymenvs基础上加入了3D设计的图形化操作界面等支持其他功能接口的一次升级。


在NVIDIA推出NVIDIA issac家族后又进行了升级,推出了一个新产品,那就是issac Lab;issac Lab在issac sim基础上进行的,于是就从issac家族再次升级为issac Lab家族,其中issac Lab家族可以看做是在issac家族基础上加入了输入数据类型扩增的一次升级,因此在windows平台下issac sim平台就代表了issac lab平台,这二者是同义词,但是在Linux平台下二者有着不同的下载地址:

Isaac lab的主页地址:

https://developer.nvidia.com/isaac/lab?ncid=ref-inor-146211

Isaac sim的主页地址:

https://developer.nvidia.com/isaac/sim/


可以说issac lab是在issac sim的基础上增加一些功能后的产品,可以看作是一次升级,因此现在NVIDIA的维护项目为issac sim和issac lab项目,而之前的issac gym/issac gym envs/OmniIsaacGymEnvs/orbit项目都已经不再维护了。

因为我是从issac gym项目就关注NVIDIA公司的产品,因为我是从事RL方向的,于是就发现了NVIDIA公司在同一个产品上不对的升级,而且是多部门从事搞,于是就fork出了多个项目,然后项目开发到一定时候就merge到一起,然后再开发遇到问题,然后再fork出分支,于是就出现了今天的这种要人感觉乱七八糟的感觉,如果是新人入手来学机器人的AI方向,那么之前的版本就可以不用太在意了,直接从issac sim和issac lab开始即可。



扩展:

issac-gym下载地址:

https://developer.nvidia.com/isaac-gym/download

image-20241118120100625


Isaac Sim主页地址:

https://developer.nvidia.com/isaac/sim#isaac-lab

docker安装方式地址:

https://catalog.ngc.nvidia.com/orgs/nvidia/containers/isaac-sim

image-20241118120435405



个人github博客地址:
https://devilmaycry812839668.github.io/

大家好,我是 V 哥,很好奇,在ArkTS中实现异步编程是怎样的,今天的内容来聊聊这个问题,总结了一些学习笔记,分享给大家,在 ArkTS中实现异步编程主要可以通过以下几种方式:

1. 使用
async

await
关键字

async
函数是一种特殊的函数,它能以同步代码的方式编写异步代码。在
async
函数内部,可以使用
await
表达式来等待一个异步操作返回一个Promise对象。这种方式可以让异步代码的编写和阅读更接近同步代码的风格,提高代码的可读性和可维护性。

// 定义一个async函数
async function myAsyncFunction() {
  // 使用await等待异步操作完成
  const result = await someAsyncOperation();
  console.log(result);
}

async函数和await关键字有什么区别?

async

await
是 JavaScript 中用于异步编程的两个关键字,它们共同工作以简化异步代码的编写和理解,但它们各自有不同的用途和含义:

async 关键字

  • async
    是一个用于声明异步函数的关键字。
  • 任何使用
    async
    声明的函数都会自动返回一个
    Promise
    对象。
  • 如果函数正常执行结束,
    Promise
    将被解决(resolve)并返回函数的返回值。
  • 如果函数中抛出错误,
    Promise
    将被拒绝(reject)。
  • async
    函数内部可以包含一个或多个
    await
    表达式。

await 关键字

  • await
    是一个用于等待一个
    Promise
    对象解决(resolve)的关键字。
  • await
    只能在
    async
    函数内部使用。
  • 当执行到
    await
    表达式时,JavaScript 引擎会暂停该
    async
    函数的执行,直到等待的
    Promise
    被解决。
  • 如果
    Promise
    被解决,
    await
    表达式返回
    Promise
    的值。
  • 如果
    Promise
    被拒绝,
    await
    表达式会抛出拒绝的原因,这可以通过
    try...catch
    语句捕获。
  • await
    后面通常跟的是一个异步操作,比如一个返回
    Promise
    的函数调用。

它们的区别

  • async
    是用来声明函数的,而
    await
    是用来在函数内部等待异步操作的。
  • async
    本身不暂停代码执行,它只是让函数返回一个
    Promise
    ;而
    await
    是用来暂停代码执行,直到
    Promise
    被解决。
  • async
    可以不与
    await
    一起使用,这时函数仍然返回一个
    Promise
    ,但不会暂停执行。
  • await
    必须用在
    async
    函数内部,否则会抛出语法错误。

示例

// async 函数声明
async function fetchData() {
  // 等待异步操作完成
  const data = await fetchSomeData(); // 这里使用了 await
  return data;
}

// 单独使用 async
async function justAsync() {
  console.log('This is an async function, but without await.');
}

// 使用 await
async function useAwait() {
  console.log('Before await.');
  await justAsync(); // 等待 justAsync 完成
  console.log('After await.');
}

所以啊, V哥提醒一下,
async

await
是异步编程中两个互补的概念,
async
用于声明异步函数,而
await
用于在异步函数内部等待异步操作的完成,初学者需要注意不要混淆了。

2. 使用Promise对象

上面我们已经提到Promise对象了,Promise是处理异步操作的对象,它提供了一个状态机制来管理异步操作的不同阶段,代表一个异步操作的最终完成(或失败)及其结果值,并提供了一些方法来注册回调函数以处理异步操作的成功或失败的结果。Promise有三种状态:pending(进行中)、fulfilled(已完成)和rejected(已拒绝)。

// 创建一个新的Promise
const myPromise = new Promise((resolve, reject) => {
  // 异步操作
  if (/* 异步操作成功 */) {
    resolve("Success");
  } else {
    reject("Error");
  }
});

// 使用then和catch处理Promise的结果
myPromise.then((value) => {
  console.log(value); // "Success"
}).catch((error) => {
  console.error(error); // "Error"
});

3. 异常处理

在异步函数中,如果出现错误,可以通过
try...catch
语句来捕获异常。

async function myAsyncFunction() {
  try {
    const result = await someAsyncOperation();
    console.log(result);
  } catch (error) {
    console.error(error);
  }
}

4. 并发执行异步任务

可以使用
Promise.all
来并发执行多个异步任务,并等待它们全部完成。

async function concurrentTasks() {
  const [result1, result2] = await Promise.all([
    asyncOperation1(),
    asyncOperation2()
  ]);
  console.log(result1, result2);
}

5. 使用Actor模型

ArkTS语言采用的Actor模型是一种并发编程模型,其中每个Actor都是一个独立的计算单元,拥有自己的状态和行为,并通过消息传递与其他Actor进行通信和协作。

1. Actor模型概述

在Actor模型中,每个Actor都可以接收异步消息,并根据消息内容和当前状态做出响应。Actor之间的消息传递是异步的,发送消息的Actor不需要等待接收消息的Actor的响应,从而实现并发执行。由于每个Actor都是独立的,它们之间不存在共享状态,因此不需要进行锁机制和同步操作,避免了一些常见的并发编程问题,如死锁和竞争条件。

2. 数据传输对象

ArkTS语言支持传输的数据对象可以分为四种:普通对象、可转移对象、可共享对象和Native绑定对象。普通对象的传输是通过结构化克隆算法进行序列化的,支持多种类型,包括基础类型、Date、String、RegExp、Array、Map、Set等。

3. 实现例子

以下是一个使用ArkTS的Actor模型实现的生产者消费者问题的示例:

生产者(Producer)和消费者(Consumer)的Actor模型实现:

import taskpool from '@ohos.taskpool';

// 跨线程并发任务
@Concurrent
async function produce(): Promise<number>{
  // 添加生产相关逻辑
  console.log("test producing...");
  return Math.random();
}

class Consumer {
  public consume(value: number) {
    // 添加消费相关逻辑
    console.log("test consuming value: " + value);
  }
}

@Entry
@Component
struct Index {
  @State message: string = 'Hello I am VG';
  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
        Button() {
          Text("start")
        }.onClick(() => {
          let produceTask: taskpool.Task = new taskpool.Task(produce);
          let consumer: Consumer = new Consumer();
          for (let index: number = 0; index < 10; index++) {
            // 执行生产异步并发任务
            taskpool.execute(produceTask).then((res: number) => {
              consumer.consume(res);
            }).catch((e: Error) => {
              console.error(e.message);
            })
          }
        })
        .width('20%')
        .height('20%')
      }
      .width('100%')
    }
    .height('100%')
  }
}

在这个例子中,
produce
函数是一个异步函数,模拟生产者的生产过程,并返回一个随机数作为产品。
Consumer
类包含
consume
方法,用于消费产品。在UI界面上,当用户点击“start”按钮时,会启动10次生产任务,每次生产任务完成后,都会调用
Consumer

consume
方法来消费产品。

这个例子展示了如何在ArkTS中使用Actor模型来实现并发执行,其中生产者和消费者之间通过异步消息传递来通信,无需等待对方的响应,从而实现高效的并发处理。

最后

ArkTS提供了强大的异步编程能力,使得开发者可以有效地处理异步任务,提高程序的性能和响应能力。对于有 TypeScript 经验的兄弟是不是有种找到真爱的感觉呢。关注威哥爱编程,鸿蒙开天辟地,你我皆是同路人。

前言

watch这个API大家应该都不陌生,在Vue3版本中给watch增加不少有用的功能,比如
deep选项支持传入数字

pause、resume、stop方法

once选项

onCleanup函数
。这些功能大家平时都不怎么用得上,但是在一些特定的场景中,他们能够起大作用,这篇文章欧阳就来带你盘点一下这些功能。

关注公众号:【前端欧阳】,给自己一个进阶vue的机会

deep
支持传入数字

deep
选项大家应该比较熟悉,常见的值为
true
或者
false
,表示是否深度监听
watch
传入的对象。

在Vue3.5版本中对
deep
选项进行了增强,不光支持布尔值,而且还支持传入数字,数字表示需要监听的层数。

比如下面这个例子:

const obj1 = ref({
  a: {
    b: 1,
    c: {
      d: 2,
      e: {
        f: 3,
      },
    },
  },
});

watch(
  obj1,
  () => {
    console.log("监听到obj1变化");
  },
  {
    deep: 3,
  }
);

function changeDeep3Obj() {
  obj1.value.a.c.d = 20;	// 能够触发watch回调
}

function changeDeep4Obj() {
  obj1.value.a.c.e.f = 30;	// 不能触发watch回调
}

在上面的例子
watch

deep
选项值是3,表明监听到对象的第3层。

changeDeep3Obj
函数中就是修改对象的第3层的
d
属性,所以能够触发
watch
的回调。


changeDeep4Obj
函数是修改对象的第4层的
f
属性,所以不能触发
watch
的回调。

他的实现也很简单,我们来看一下deep相关的源码:

function watch(source, cb, options) {
  // ...省略
  if (cb && deep) {
    const depth = deep === true ? Infinity : deep
    getter = () => traverse(baseGetter(), depth)
  }
  // ...省略
}

这里的
depth
就表示watch监听一个对象的深度。

如果
deep
选项的值为true,那么就将
depth
设置为正无穷
Infinity
,说明需要监听到对象的最深处。

如果
deep
选项的值为false,或者没有传入
deep
,那么就表明只需要监听对象的最外层。

如果
deep
选项的值为number类型数字,那么就把这个数字赋给
depth
,表明需要监听到对象的具体某一层。

pause、resume、stop方法

这三个方法也是Vue3.5版本中引入的,通过解构
watch
函数的返回值就可以直接拿到
pause

resume

stop
这三个方法。

我们来看一下源码,其实很简单:

function watch(source, cb, options) {
  // ...省略
  watchHandle.pause = effect.pause.bind(effect)
  watchHandle.resume = effect.resume.bind(effect)
  watchHandle.stop = watchHandle
  return watchHandle
}

watch返回了一个名为
watchHandle
的对象,对象上面有
pause、resume、stop
这三个方法,所以我们可以通过解构
watch
函数的返回值拿到这三个方法。

pause
方法的作用是“暂停”watch回调的触发,也就是说在暂停期间不管watch监听的响应式变量如何改变,他的回调函数都不会触发。

有“暂停”,那么肯定就有“恢复”。

resume
方法的作用是恢复watch回调的触发,此时会主动执行一次watch的回调。后面watch监听的响应式变量改变时,他的回调函数也会触发。

来看个demo,代码如下:

<template>
  <button @click="count++">count++</button>
  <button @click="runner.pause()">暂停</button>
  <button @click="runner.resume()">恢复</button>
  <button @click="runner.stop()">停止</button>
</template>

<script setup lang="ts">
import { watch, ref } from "vue";

const count = ref(0);
const runner = watch(count, () => {
  console.log(count.value);
});
</script>

点击“count++”按钮会导致
watch
回调中的console执行。

但是当我们点击了“暂停”按钮后,此时我们再怎么点击“count++”按钮都不会触发
watch
的回调。

点击
恢复
按钮后会立即触发一次
watch
回调的执行,后面点击“count++”按钮也同样会触发
watch
的回调。

我们来看看
pause

resume
方法的源码,很简单,代码如下:

class ReactiveEffect {
  pause(): void {
    this.flags |= EffectFlags.PAUSED
  }

  resume(): void {
    if (this.flags & EffectFlags.PAUSED) {
      this.flags &= ~EffectFlags.PAUSED
      if (pausedQueueEffects.has(this)) {
        pausedQueueEffects.delete(this)
        this.trigger()
      }
    }
  }

  trigger(): void {
    if (this.flags & EffectFlags.PAUSED) {
      pausedQueueEffects.add(this)
    } else if (this.scheduler) {
      this.scheduler()
    } else {
      this.runIfDirty()
    }
  }
}


pause

resume
方法中通过修改
flags
属性的值,来切换是不是“暂停状态”。

在执行
trigger
方法依赖触发时,就会先去读取
flags
属性判断当前是不是“暂停状态”,如果是那么就不去执行watch的回调。

从上面的代码可以看到这三个方法是在
ReactiveEffect
类上面的,这个
ReactiveEffect
类是Vue的一个底层类,
watch

watchEffect

watchPosEffect

watchSyncEffect
都是基于这个类实现的,所以他们自然也支持
pause

resume

stop
这三个方法。

最后就是
stop
方法了,当你确定后面都不再想要触发watch的回调了,那么就调用这个
stop
方法。代码如下:

const watchHandle: WatchHandle = () => {
  effect.stop()
  if (scope && scope.active) {
    remove(scope.effects, effect)
  }
}

watchHandle.stop = watchHandle

响应式变量
count
收集的订阅者集合中有这个watch回调,所以当
count
的值改变后会触发watch回调。这里的
stop
方法中主要是依靠双向链表将这个watch回调从响应式变量
count
的订阅者集合中给remove掉,所以执行stop方法后无论
count
变量的值如何改变,watch回调也不会再执行了。(PS:如果你看不懂这段话,建议你去看看我的上一篇
Vue3.5双向链表
文章,看完后你就懂了)

once选项

如果你只想让你的watch回调只执行一次,那么可以试试这个
once
选项,这个是在Vue3.4版本中新加的。

看个demo:

<template>
  <button @click="count++">count++</button>
</template>

<script setup lang="ts">
import { watch, ref } from "vue";

const count = ref(0);
watch(
  count,
  () => {
    console.log("once", count.value);
  },
  {
    once: true,
  }
);
</script>

由于使用了
once
选项,所以只有第一次点击“count++”按钮才会触发watch的回调。后面再怎么点击按钮都不会触发watch回调。

我们来看看
once
选项的源码,很简单,代码如下:

function watch(source, cb, options) {
  const watchHandle: WatchHandle = () => {
    effect.stop()
    if (scope && scope.active) {
      remove(scope.effects, effect)
    }
  }

  if (once && cb) {
    const _cb = cb
    cb = (...args) => {
      _cb(...args)
      watchHandle()
    }
  }

  // ...省略
  watchHandle.pause = effect.pause.bind(effect)
  watchHandle.resume = effect.resume.bind(effect)
  watchHandle.stop = watchHandle
  return watchHandle
}

先看中间的代码
if (once && cb)
,这句话的意思是如果
once
选项的值为true,并且也传入了watch回调。那么就封装一层新的
cb
回调函数,在新的回调函数中还是会执行用户传入的watch回调。然后再去执行一个
watchHandle
函数,这个
watchHandle
是不是觉得有点眼熟?

前面讲的
stop
方法其实就是在执行这个
watchHandle
,执行完这个
watchHandle
函数后watch就不再监听
count
变量了,所以后续不管
count
变量怎么修改,watch的回调也不会再触发。

onCleanup函数

有的情况我们需要watch监听一个变量,然后去发起http请求。如果变量改变的很快就会出现第一个请求还没回来,第二个请求就已经发起了。在一些极端情况下还会出现第一个请求的响应比第二个请求的响应还要慢,此时第一个请求的返回值就会覆盖第二个请求的返回值。实际上我们期待最终拿到的是第二个请求的返回值。

这种情况我们就可以使用
onCleanup函数
,他是作为watch回调的第三个参数暴露给我们的。看个例子:

watch(id, async (newId, oldId, onCleanup) => {
  const { response, cancel } = myFetch(newId)
  // 当 `id` 变化时,`cancel` 将被调用,
  // 取消之前的未完成的请求
  onCleanup(cancel)
  data.value = await response
})

watch回调的前两个参数大家都很熟悉:新的id值和旧的id值。第三个参数就是
onCleanup
函数,在watch回调触发之前调用,所以我们可以使用他来cancel掉上一次的请求。

onCleanup
函数的注册也很简单,代码如下:

let boundCleanup

boundCleanup = fn => onWatcherCleanup(fn, false, effect)

function watch(source, cb, options) {
  // ...省略
  const job = (immediateFirstRun?: boolean) => {
    const args = [
      newValue,
      oldValue,
      boundCleanup,
    ]
    cb(...args)
    oldValue = newValue
  }
  // ...省略
}

执行watch回调实际就是在执行这个
job
函数,在
job
函数中执行watch回调时传入了三个参数。分别是
newValue

oldValue

boundCleanup
。前两个参数大家都很熟悉,第三个参数
boundCleanup
是一个函数:
fn => onWatcherCleanup(fn, false, effect)

这个
onWatcherCleanup
大家熟悉不?这也是Vue暴露出来的一个API,注册一个清理函数,在当前侦听器即将重新运行时执行。关于
onWatcherCleanup
之前欧阳写过一篇文章专门讲了如何使用:
使用Vue3.5的onWatcherCleanup封装自动cancel的fetch函数

总结

这篇文章盘点了Vue3 watch新增的一些新功能:
deep选项支持传入数字

pause、resume、stop方法

once选项

onCleanup函数
。这些功能大家平时可能用不上,但是还是要知道有这些功能,因为有的情况下这些功能能够派上大用场。

关注公众号:【前端欧阳】,给自己一个进阶vue的机会

另外欧阳写了一本开源电子书
vue3编译原理揭秘
,看完这本书可以让你对vue编译的认知有质的提升。这本书初、中级前端能看懂,完全免费,只求一个star。