vue3的provide和inject

浅析

provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。

定义说明:这对选项是一起使用的。以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

通俗的说就是:组件得引入层次过多,我们的子孙组件想要获取祖先组件得资源,那么怎么办呢,总不能一直取父级往上吧,而且这样代码结构容易混乱。这个就是这对选项要干的事情。

provide:是一个对象,或者是一个返回对象的函数。里面呢就包含要给子孙后代的东西,也就是属性和属性值。

inject:一个字符串数组,或者是一个对象。属性值可以是一个对象,包含from和default默认值。

一句话介绍:provide可以向所有子孙组件提供数据以及提供修改数据的方法,子孙组件用inject使用数据。

在组合式API中的用法

基本用法

似乎,组合式API的provide没有提供批量方法, 只能每个变量写一句。

顶级组件:

子组件:

孙组件:

官方说:

inject() can only be used inside setup() or functional components.

这个意思是说,inject()只能放在setup()生命周期里运行,不能放在别的周期里运行,也不能放在事件周期里运行。所以你可以先在setup()周期取值。

例一:这是允许的,因为console.log是setup()生命周期里的同步代码

例三:让例一的xx函数作为鼠标事件回调,也是禁止的,原因也一样。

例四:放在Promise.then()也是禁止的,比如:

一级组件修改数据,孙子组件监听变更

provide的变量必须是响应式变量,孙子组件监听的变量也必须是响应式变量。

一级组件:

孙子组件:

孙子组件修改数据,一级组件监听变更

一级组件除了提供数据,还要提供一个修改数据的方法,孙子组件要接收并使用这个方法,这样修改的就是一级组件的数据,修改之后又会影响孙子组件的数据。

一级组件:

孙子组件:

禁止孙子组件修改一级组件的数据

禁止的话,一级组件传递的变量必须是只读的,可以是readonly,也可以是shallowRef。这样孙子组件修改数据的话,一级组件不会有反应。

一级组件:

在配置项式API中的用法

跟Vue 2差不多,简单说一下。

provide

provide语法比在组合式API里要灵活,毕竟可以用对象。比如:

如果要把data里的数据provide,说白了就是打算用this,provide要写成函数:

如果要提供计算属性,也是一样的道理,不过,如果不打算用配置项式计算属性,而是想用vue的computed方法

可以写成:

inject

inject的用法更简单,比如inject: ['user', 'updateUser']

双向修改而且响应 想要双向修改而且响应,就必须借助reactive或者ref家族方法和computed方法。provide的数据必须是computed返回的数据,才能保证数据有变化时刷新孙子组件的数据。孙子组件要用computed + unref来解包装且计算属性,才能得到真正需要的数据。

可以看到,由于孙子组件的injectcomputed的属性不能重名,所以我造出一个abcabc变量名,这就比较麻烦了。我们再看组合式API,就没有abcabc这种中间变量,原因是ref: abc = inject("abc");让接收和加响应式一气呵成,这也是组合式API的优势之一。

一级组件:

孙子组件:

参考链接


vue3的provide和inject

vue3 -- @vue/compiler-sfc 单文件转换工具

@vue/compiler-sfc

"version": "3.2.37"

SFC是vue中重要的一环,也是vue3中对于静态节点进行缓存提升的重要位置

SFC -- single file Component 单文件组件,以 .vue 进行结尾,这个文件浏览器也不会识别,最终也是要被转换成js代码

SFC中包含三块,template、script、style等三块代码,分别表示模版、脚本、样式三块

@vue/compiler-sfc的作用就是把单文件组件编译成为js代码

parse

下面就看一看具体的使用

新建一个Foo.vue组件

组件同时包括templatescriptstyle三块

新建一个nodejs脚本

用fs读取到文件的内容后,使用parse解析, 最终会返回一个对象

setup脚本改造foo.vue

setup语法编译后的结果

唯一的不同就是编译后的结果从原来的script上迁移到scriptSetup

compileTemplate

拿到之前parse后的结果后,需要对template进行进一步的转换,把template结果进一步编译成对应的js vnode函数

其中code的值就是最终模版编译的结果

我们把结果单独拿出来看下

最终返回了一个render函数,这里也就符合预期,vue组件中如果使用js的方式写,可以写一个render函数去渲染组件

compilerScript

根据parsed的结果来解析脚本部分,compileScript接收2个参数,第一个就是之前parse的结果, 然后再传入相应的option

编译后的结果,content就是最终编译出的代码

content格式化之后的结果, 所以说setup只是语法糖,最终还是以defineComponent去包裹一个对象进行返回的形式

compileStyle

compileStyle 即解析SFC style模块的入口函数

由于sfc中style块是可以写多个的,所以parse最终的结果styles其实是个数组

由变量签名也可以看出

这里我们取第一个块打印出来看一下,实际情况下应该是去循环的

编译结果

其中code即是最终的css结果

参考链接


vue3 -- @vue/compiler-sfc 单文件转换工具 -- 学习笔记

从Vue 2到Vue 3组织代码的思维转变

到了Vue 3,当我们看完组合式 API相关文档,蠢蠢欲动立马上手把业务代码挪到setup内时,竟发现无法在setup()内部通过 this 获取当前组件实例了(this 是 undefined)!

其实想要变相获取“this”可以用 getCurrentInstance ,比如像这样访问全局属性:

真正属于组件的内容是ctxproxyproxy就是对ctx包装了一层 Proxy。目前看来属实没用,就不展开来说了。

getCurrentInstance 只能setup生命周期钩子中调用。不仅使用起来麻烦,且只适用于开发环境。官方都告知我们日常开发中不要用:

貌似到头来this的问题还是无解。Vue 3 Composition API 的设计初衷也是为了减少对组件实例的依赖,避免this指向的困扰。包括一些全局方法也可以提取到 composables 组合函数中,无需再通过当前组件的原型链获得。我们动不动就通过this获取组件实例的思想该转变了

怎么个直接使用法?我们先看一眼简易版 选项式 API组合式 API 的对比。再用例子来捋:

API 对比

1. 读写数据

Vue 2的做法,或者说Option API更准确 (后面就简写成Vue 2)

data中定义,this.xxx获取
<template>模版中不需要用this

Vue 3 的 Composition API (后面就简写成 Vue 3)

ref reactive定义。
通过ref方法定义的属性在 setup 函数内需要通过.value去访问它的值 (template 模版内不用), reactive则不用。我们可以简单地把 ref(obj) 理解为 reactive({ value: obj })
详细请看 ➡️ 【Vue 3 之:弄清 ref reactive toRef toRefs】

如何选择 refreactive?建议:

  1. 基础类型值(StringNumberBoolean等) 或单值对象(类似{ count: 3 }这样只有一个属性值的对象) 使用 ref
  2. 引用类型值(ObjectArray)使用 reactive
  3. 对于 ref 对象可以使用 unref 语法糖来免去.value访问的困扰

2. 定义/调用方法

Vue 2:methods

继续上面的例子,我们把请求列表数据的操作提取成一个方法。(略去重复的代码)

Vue 3

3. 获取模版DOM元素/或组件实例的引用

Vue 2

直接整上 element-plus Form 表单 的的例子,<el-form>是我们写的DemoForm组件的一个子组件,通过为它添加一个refattribute,就能使用使用this.$refs[ref值]直接访问子组件以及它的所有属性/方法了。
这边只放相对实际的场景用例,给 DOM 元素添加 ref 引用的例子看这里:【模板引用】

Vue 3

我们来修改成 Composition API 版本:
别忘了 ref 创建的变量要用.value获取值
详细文档请阅:【在组合式 API 中使用 template refs】

4. 父组件向子组件传值

文档:Props
使用 DOM 模板时,camelCase (驼峰命名法) 的 props 需要使用等价的 kebab-case (短横线分隔命名) 命名。

说明:第 4-8 点 都会结合同一个 Tabs 标签页的例子,循序渐进梳理这几个属性或方法。依然用了element-plus 的组件(TabsTabPane)。

Vue 2

父组件:📃src/views/design/indexV2.vue

子组件 DesignTabs:📃src/views/design/components/DesignTabsV2.vue

Vue 3

转化成 Composition API 后的子组件:(一贯省略重复代码)
📃src/views/design/components/DesignTabsV3.vue

5. 计算属性computed

比如我们现在希望在点击 tab 标签的时候能获得 tab 的序号(index):

Vue 2

组件 DesignTabs:📃src/views/design/components/DesignTabsV2.vue

Vue 3

这里就比较不一样了,props 的值是不允许在子组件直接修改的。子组件的activeName状态初始值为传入的activeTab,如果activeTabtabOptions一样通过 toRefstoRef 包装,那么activeName的修改是和activeTab深度响应的,这样会报错;
如果要同步修改父组件的值,可以通过emit事件(后面会说)

组件 DesignTabs:📃src/views/design/components/DesignTabsV3.vue

这里想额外提一嘴computed可以包在reactive内使用,在组件数据比较简单的情况下甚至可以直接用reactive包起组件全部数据,就好像 Option Api 的 data 选项那样。这样做的目的是在setup()访问不用再带上.value
但这同时会带来一个问题,在模版需要使用state.xx去渲染,而且如果我们 return 的时候把state解构,包含的状态会失去响应性,那就得不偿失了。需要用toRefs包裹再传递,即可维持其响应性。

6. 侦听器watch

获取这个tabIndex有什么用呢,目的是在当前标签变化时根据index刷新对应TabPane的子组件的数据。但是如果在标签点击事件触发数据刷新也不合适,因为我们不希望重复点击相同tab时也去刷新。那么这个时候watch或者watchEffect就登场了。

Vue 2

组件 DesignTabs:📃src/views/design/components/DesignTabsV2.vue

Vue 3

这个写法可能不是很直观,可以看下 ➡️ 组合式 API 模板引用在 v-for 中的用法
组件 DesignTabs:📃src/views/design/components/DesignTabsV3.vue

watchwatchEffect 的功能是等效的,都是侦听其依赖,并在依赖值变更时重新运行定义的函数。两者区别:

watch

  • 必须在第一个参数明确指定跟踪的依赖
    侦听器数据源只能是getter/effect函数、refreactive对象,或者包含这些类型(的数据)的数组
    换句话说,只要侦听数据不是refreactive对象,就必须传入一个箭头函数
    打个比方,若要侦听reactive对象的某个属性(例:const state = reactive({ count: 0 })count),便不能像侦听单个ref或整个reactive对象那样直接传一个变量,而是必须在第一个参数传入一个回调函数,如() => state.count
  • 第二个参数是依赖值变更时执行的回调,函数内能访问被侦听状态的当前值和前一个值;
  • 组件初始化时不会执行回调。如果需要可在第三个参数(Object)中设置immediate: true
  • 如果要对多层嵌套状态深度侦听,在第三个参数中设置deep: true

watchEffect

  • 无需手动传入依赖项;
  • 只有一个参数,即侦听数据变更的回调函数,会自动跟踪所有函数中用到的变量;
  • 组件初始化时即会执行一次。

7. 子组件向父组件通信(触发父组件方法)

Vue 2

父组件:📃src/views/design/indexV2.vue

子组件 DesignTabs:📃src/views/design/components/DesignTabsV2.vue

Vue 3

子组件 DesignTabs:📃src/views/design/components/DesignTabsV3.vue

8. 使用 Vue Router

Vue 2

📃src/views/design/components/DesignTabsV2.vue

Vue 3

📃src/views/design/components/DesignTabsV3.vue

9. 获取 Vuex 对象

Vue 2

Vue 3

参考链接


从 Vue 2 到 Vue 3 组织代码的思维转变(一)- 获取组件实例

ubuntu 16.04/macOS Big Sur(11.4)编译Quasar

注意,目前(2020/07/05)发现,在macOS Big Sur(11.4)版本上,dev 分支没办法编译通过。

目前(2020/07/20)发现,在macOS Big Sur(11.4)系统,quasar 2.0.3/1.15.23 版本已经可以编译通过,开发分支经常编译不通过,这部分经常发生较大变动。

参考链接


macOS Big Sur(11.4)安装指定版本Node

参考链接


macOS Big Sur(11.4)编译vuelidate 2.x适配Vue 3.x

目前,vuelidate 2.x暂时还没发布正式版本,而如果需要适配Vue 3.x,则需要自己编译,编译过程如下:

移动端 JS 引擎哪家强

在一般的移动端开发场景中,每次更新应用功能都是通过 Native 语言开发并通过应用市场版本分发来实现的。但是市场瞬息万变,Native 语言在开发效率上存在一定不足,并且从APP版本更新应用市场审核发布再到用户下载更新,总会存在一定的时间差,这样就导致新的功能无法及时覆盖全量用户。

为了解决这个问题,开发者们一般会在项目里引入一门脚本语言,提速APP的研发流程。在移动端应用比较广泛的脚本语言有 LuaJavaScript,前者在游戏领域用的比较多,后者在应用领域用的比较多。本篇文章主要是想探讨一下移动双端(iOS & Android)的JavaScript引擎选型。由于个人水平有限,文章总会有遗漏和不足的地方,还请各位大佬多多指教。

JS 引擎选型要点

JavaScript作为世界上最热门的脚本语言,有着非常多的引擎实现:有Apple御用的 JavaScriptCore,有性能最强劲的V8,还有最近热度很高的QuickJS......如何从这些JS引擎里选出最适合的?我个人认为要有几个考量:

  • 性能:这个没话说,肯定是越快越好
  • 体积JS引擎会增加一定的包体积
  • 内存占用:内存占用越少越好
  • JavaScript 语法支持程度:支持的新语法越多越好
  • 调试的便捷性:是否直接支持debug?还是需要自己编译实现调试工具链
  • 应用市场平台规范:主要是iOS平台,平台禁止应用集成带JIT功能的虚拟机

比较麻烦的是,上面的几个点都不是互相独立的,比如说开启JITV8引擎,性能肯定是最好的,但它引擎体积就很大,内存占用也很高;在包体积上很占优势的QuickJS,由于没有JIT加持,和有JIT的引擎比起来平均会有5-10倍的性能差距。

下面我会综合刚刚提到的几个点,并选择了JavaScriptCoreV8HermesQuickJS4JSVM,说说它们的优点和特点,再谈谈他们的不足。

继续阅读移动端 JS 引擎哪家强

Qussar QScrollArea动态适配View高度

我们在使用Qussar QScrollArea等控件的时候,很多情况下,必须给出一个固定的高度,否则控件的高度会变成 0,导致无法显示出来。如何动态适配View高度呢? 参考如下:

或者类似下面的情况:

参考链接


Thymeleaf公共css,js提取及自有css,js导入

之前参考网上的各种方法,均为达到期望的效果,于是到Thymeleaf 官网逛了下,找到官网的例子来实现了: 

直接上栗子:

一般页面调用公有CSS,并使用自己的CSS:

公有js的提取方式是一样的:

公有JS的使用包一层DIV即可:

测试结果可用,具体解释请参考Thymeleaf官方文档

注意,上述的配置在引入的是,会多出来一个DIV标签,如果想去掉这个标签,可以使用如下方式声明以及引用

公有JS的使用包一层th:block即可:

另外如果使用的SpringBoot版本是1.5.4,默认的thymeleaf不是3.0版本,上面的测试需要thymeleaf 3.0版本才可以,需要修改下pom.xml文件,添加以下配置即可:

另外如果使用的SpringBoot版本是2.5.0,则不需要任何修改,直接可以生效。

参考链接