vue key重复_【第2112期】 import { reactive } from #39;vue#39;

前言

今日早读文章由@Anthony Fu授权分享。

@Anthony Fu,是 Vue 的 Core Team 的一员,在 Vue主要负责 @vue/composition-api 这个项目的维护。这是一个面向 Vue 2 的插件,它在 Vue 2 中增加了 Vue 3 的 Composition API 的支持。最近也加入了 Vite 负责一些 Code Review 的工作。GitHub:@antfu

正文从这开始~~

介绍

我这次分享的主要会和大家简单介绍一下响应式与组合式 API,然后通过一个例子的形式介绍组合式 API 所带来的优势。再来,我会以一个工具库作者的角度跟大家聊一聊如何做到 Vue 2 与 Vue 3 双版本同时兼容的同构。最后,我会去再介绍一下响应式 API 的一些延伸应用。

庆祝 Vue 3.0 One Piece 在上个礼拜正式发布!

大家知道,在 Vue 3.0 中我们使用 TypeScript 进行了一次从零的重写。利用这次重写的机会,我们对整个 Repo 的结构进行了一些解构,把 Vue 拆分成了这几个独立的库。在这一次的分享中我会主要会面向比较底层的响应式(@vue/reactivity)和组合式(@vue/runtime-core)这两个模块进行讨论。

响应式 Reactivity API

那么什么是响应式呢?提到这个就得祭出这张非常经典的 GIF。在一个 Excel 表格里面,我们会以公示的形式去定义一个一个单元格应该去做怎么样的一个运算。那么大家可以看到,在我设置好了 A3 这个格子的公式之后,我去更新 A1 的数值时, A3 就会自动更新,而我不需要再去做任何的操作。这就是响应是能够给我们带来的一个非常好的帮助,依赖的自动收集跟更新。

在 Vue 3 里面,我们对整个响应式系统做了一个重新的设计,同时暴露出了这几个新的API,ref reactive computed effect。我们把原本 Vue 2 Object.defineProperty 的实现改成了使用 Proxy 的实现方式。而 Proxy 可以给我们提供对属性更新监控的更大的灵活性。

const reactive = (target) => new Proxy(target, {

get(target, prop, receiver) {

track(target, prop)

return Reflect.get(...arguments) // get original data

},

set(target, key, value, receiver) {

trigger(target, key)

return Reflect.set(...arguments)

}

})

const obj = reactive({

hello: 'world'

})

console.log(obj.hello) // `track()` get called

obj.hello = 'vue' // `trigger()` get called

我们可以通过 get 和 set 这两个 handler 去追踪每一个属性的访问和修改,在这个例子中我们在 get 里注入了 track 这个函数,在 set 里注入了trigger 这个函数。那么在对 reactive 这个对象的 hello 属性进行访问的时候 track 就会被执行,在对 obj.hello 进行赋值的时候,trigger 就会被执行。通过 track 和 trigger 我们就可以进行一些响应式的追踪。

Effect

effect 是在 Vue 3 里面新引入的一个API,它的作用就是去结合 track 和 trigger 这两个功能,track 的作用是追踪调用他的函数,trigger 是去触发绑定的依赖更新。

const targetMap = new WeakMap()

export const track = (target, key) => {

if (tacking && activeEffect)

targetMap.get(target).key(key).push(activeEffect)

}

export const trigger = (target, key) => {

targetMap.get(target).key(key).forEach(effect => effect())

}

export const effect = (fn) => {

let effect = function() { fn() }

enableTracking()

activeEffect = effect

fn()

resetTracking()

activeEffect = undefined

}

在 effect 里面我们会接受一个函数作为参数,在执行这个函数之前的我们会开启 tracking,然后把当前的函数设置在一个全局变量 activeEffect,然后再去执行这个函数。那么在这个函数的调用时间里面我们有任何的 reactive 的调用就会触发 track 这个函数。track 的主要功能就是说我们把当前的 activeEffect 绑定到所触发它的这个属性调用上。然后在数据更新的时候,我们再去找到这个依赖上面所绑定的所有 effect 把他们一一调用。这样就完成了一个最基本的响应式的功能。

computed & watch

在 Vue 3.0 里面,computed 和 watch 都是基于 effect 的包装,我们这边可以看到一个简单的 computed 的实现

const computed = (getter) => {

let value

let dirty = true

const runner = effect(getter, {

lazy: true,

scheduler() {

dirty = true // deps changed

}

})

return {

get value() {

if (dirty) {

value = runner() // re-evaluate

dirty = false

}

return value

}

}

}

computed 接受一个 getter 函数,这个函数我们把它直接传给 effect,effect会在先执行一次进行依赖收集,在收集完了之后,如果里面其中的依赖发生了变动,他就会触发这个 scheduler 将 dirty 设置为 true。在最后我们在对 computed 进行求值的时候,如果 dirty 为 true,我们就会重新进行一次运算得到新的 value 后再把 value 传出去。在第二次调用时,如果里面的依赖没有更新,我们就可以直接用上一次计算的结果,这件可以避免掉多余重复的计算。这里有一些 延伸阅读,大家如果有兴趣去了解一些比较深入的原理的话也可以去看一看。

组合式 Composition API

那么聊完了响应式,我们再来看看什么是组合式。

组合式其实是基于响应式延伸出来的一套和 Vue 生命周期绑定的一套工具。它提供了 Vue 生命周期的钩子像是 onMounted onUpdate 和 onUnmounted 等等。还有个非常重要的功能就是说在 Vue 的 setup() 里面,所建立的类似 computed 或者 watch 的 effect 会在组件销毁的时候自动跟随这个组件一并销毁。那么组合是最重要的作用就是它可以提供可复用的逻辑,我们可以把很多的逻辑拆分出来,做成一个一个的工具。然后可以跨组件的进行复用或甚至是把它做成一个第三方库,跨应用地进行复用。这个我们会在之后进行详细的介绍。

响应式是跟组合式的区别,就是他们是有两个不同的包提供的,在整个 Vue 应用的角度来看的话 ,这些 API 都会从 vue 这个包里面统一导出的。但是如果我们会我们想要使用其中的一部分的话,那么可以看到 ref reactive computed effect 是在 @vue/reactivity 这个包里导出的,然后像是 watch setup 和一些生命周期是在 @vue/runtime-core 这个包里导出的。可以注意到一点也是非常有趣的一点,就是 @vue/reactivity 这个包其实是可以作为一个独立的包使用的,也就是说我可以不依赖于 Vue,我可以基于这个自己做一个框架,甚至我可以在 Node.js,在没有 UI 的环境下去进行使用。这个也会在我们后面的PPT里面去做一个比较详细的介绍。

Case Study

那我们来看一个简单的使用场景的一个例子,这里有一个需求,我们现在想给我们的网页实现一个 Dark Mode 这个功能。我希望整个页面在默认的情况下会随着我系统的系统的偏好改变。然后我可能希望一个用户有一个手动可以修改的功能,比如说我有一个按钮一个直接改变 Dark Mode。

然后又希望这个这个功能是一个可持久化的,我可以保存下用户的偏好,在网页刷新后还可以还可以继续存留用户的上一次的修改。最后可能会希望说在两个模式切换的时候去执行一些代码,比如说通知用户或者是通知组件进行一些操作之类的。

基础实现

那我们看一下我们怎么去实现这样一个功能。我们假设说 Dark Mode 已经在CSS层面上都做好了,也就是说我把 dark class 加上的时候,整个页面就会变成黑暗模式。那么我再提供一个按钮去给用户做切换。这个就是我们提供的模板的部分

:class='{dark}'>

@click='toggleDark'>Toggle

我们再来看代码的部分要怎么实现

那么在 Options API 里面,非常的简单,我们可以这样实现:

export default {

data() {

return {

dark: false

}

},

methods: {

toggleDark() {

this.dark = !this.dark

}

}

}

那在 Composition API 里面,我们可以把 dark 变成 ref。这个 dark 会直接从setup() 里面传出去,那我们同时可以在 return 里面传一个叫做 toggleDark 的函数,然后我们也是一样对 dark 进行取反。这样我们就实现了一个简单的开关的功能。

系统偏好

再来的话,我们希望去增加用户系统偏好的更新。我们可以通过一个浏览器提供的 API window.matchMedia。然后再利用一个 CSS 的 Query (prefers-color-scheme: dark),我们就可以知道是用户的系统的颜色偏好。然后我们会我们可以对这个 matchMedia 调用 addEvenetListener 进行监听,那么在用户系统改变的时候,我们可以随之一起改变。

那么为了实现这样一个功能的话,在 Options API 里面我们需要在需要将 media 暴露在 Vue 实例上,然后在 created 中进行事件的绑定,同时在 destroyed 的时候再把这个事件监听注销。

// Options API

export default {

data() {

return {

dark: false,

media: window.matchMedia('(prefers-color-scheme: dark)')

}

},

methods: {

toggleDark() {

this.dark = !this.dark

},

update() {

this.dark = this.media.matches

}

},

created() {

this.media.addEventListener('change', this.update)

this.update()

},

destroyed() {

this.media.removeEventListener('change', this.update)

}

}

那么再来看看 Composition API 要怎么实现。我们直接定义这个 media。

然后因为在 Composition API 中,setup() 相当于 Options API 的 created,我们直接可以把 addEventListener 的直接写在 setup() 里面,对应的我们再通过一个生命周期的钩子 OnUnmounted 注销事件监听。

// Composition API

import { onUnmounted, ref } from 'vue'

export default {

setup() {

const media = window.matchMedia('(prefers-color-scheme: dark)')

const dark = ref(media.matches)

const update = () => dark.value = media.matches

media.addEventListener('change', update)

onUnmounted(() => {

media.removeEventListener('change', update)

})

return {

dark,

toggleDark() {

dark.value = !dark.value

}

}

}

}

用户设置持久化

再来我们需要让用户的设置可以持久化,我们就需要把用户的设置存在 localStorage 里。设置修改的时候存入 localStorage,每次页面加载的时候再读出来。边代码大家看一看就可以了,主要想让大家看到的一点就是在 Options API 里面,我们给现有的一个组件增加功能的时候,我们会在不同的地方插入代码。比如说在 data 里面声明状态,在 methods 加几个函数。我们插入非常零碎的几个片段去实现一个功能,当这个组件的代码变得非常的长的时候我们很容易去丢失掉单一功能的上下文。

那么在 Composition API 里,我们可以我们可以很好的把代码给组织在一起。像是这样的一个功能,就只需要在一个 Block 里面加入这些代码,我们可以很清楚的上有上下文,也可以有 TypeScript 进行检查。 以我们刚刚实现的 Dark Mode 为例,其实相对并不是一个非常复杂的功能,而我们已经写了这么多行的代码。如果在再这个组件继续的扩展的时候,会导致代码的整个结构变得非常的复杂,其实就是一个不是非常好的 Smell。这也是我们希望避免的一件事情。

那么我们会可能会希望我们可以把逻辑拿出来复用,或者是我们希望 Dark Mode 的这个功能,可以在另外的一个组件去做调用,或者是我就希望让整个代码看起来比较的干净。在 Options API 里面,我们是可以做到这一点,但是现有的几个方案都并不是非常的理想 (Mixin, Renderless Component, Vuex, etc.)

Mixin 问题是会有命名空间的冲突。像是我们刚刚的例子,我们会有一个 updated 的函数,那么如果我们在 Mixin 中使用 updated 这个函数,然后用户端在使用的时候如果没有注意到,他也自己写了一个 updated 函数,这就会导致函数覆盖,会出现一些不希望的情况,但是又很难去 debug。

Renderless Component 可以一定程度上解决命名空间的问题,但是他只能在模板里面使用,组合性也有很多的局限。

Vuex 的话要做到这些就会变得更加复杂,你需要去定义 Mutations 也需要去定义 Actions。然后再绑定一些浏览器的事件。

但是 Composition API 的话就变得非常的简单粗暴,我只需要把 setup() 的代码复制粘贴出去,然后用一个函数把它包装起来。那么在这里,我就只需要去调一个 use 就可以了。而且我们可以继续在这里面写更多的逻辑,同时也不会导致找不到对应的上下文。

进一步复用

我们甚至可以进行进一步的复用。以刚刚的代码为例,我们可以把这个 useDark 里面的这个 matchMedia 和用户设置的部分把他单独拉出来,变成两个独立的独立的函数。那么这些函数它就可以单独去专注在解决他单一问题上。以 useDark 的层面就只需要去在意,我在什么时候需要使用系统的设置和什么时候需要使用用户的设置。这里还有一个有趣的点,就是在这些组合工具里面他都可以使用生命周期的钩子,它就可以做到自动更新和自动注销。或者是说在数据改变的时候自动进行保存。

那么做到这一点的情况下,在使用的时候就可以没有什么负担。我只需要去在意他每一个 ref 对应什么样的功能,更新了之后它就可以帮我做到它应该做到的事情。这样对一个非常庞大的项目来说,可以更好的提高代码的复用度也可以提高代码的可读性跟可维护性。

export function useDark() {

const system = usePreferDark()

const setting = useLocalStorage('setting-dark', 'auto')

const dark = computed({

get() {

return setting.value === 'auto'

? system.value

: setting.value === 'dark'

},

set(v) {

if (v === system.value)

setting.value = 'auto'

else

setting.value = v ? 'dark' : 'light'

},

})

return dark

}

export function usePreferDark() {

const media = window.matchMedia('(prefers-color-scheme: dark)')

const dark = ref(media.matches)

const update = () => dark.value = media.matches

media.addEventListener('change', update)

onUnmounted(() => {

media.removeEventListener('change', update)

})

return dark

}

export function useLocalStorage(key, defaultValue) {

const data = ref(localStorage.getItem(key) ?? defaultValue)

watch(data, () => localStorage.setItem(key, data.value))

return data

}

逻辑的组件

所以我觉得对于这些可以被复用的这些函数来说,它更像是一个逻辑的组件。我们平常讲组件的时候,一般来说都是指UI组件。UI 组件我们可以把它抽象成这样一个情况,就是说 UI 组件接受一个 Props,也就是从他的父组件传进来的一些参数,然后会根据它的 State 去更新对应的UI,再以通过事件的形式去通知父组件。

那么换到逻辑组件来说,其实就是一个函数,函数可以接受一些参数。这些参数可以是普通参数,也可以是响应式的。然后在这些在这些函数里面,我们可以进行一些生命周期的绑定,可以去做一些对监听事件的销毁。最后我再回传出一些响应式的数据,这些数据可以是 ref 也可以是 reactive。同时这些响应的数据会根据其中内部的状态进行一些更新,可以达到类似事件通知的效果。其实右边这张图是给 UI 组件的一张图,但是我觉得他也同样适用于逻辑组件。

也就是说,我可以复用底层的 useLocalStorage useQuery 去实现一个更高层的逻辑组件。让每一层组件都专注于在做自己的事情上就好了。

现有逻辑组件库

现有的 Vue 3 已经可以使用的有两个主要的逻辑的组件库,VueUse 和 vue-composable。有点像 React 中的 react-use 或者 ahooks 这一类的工具。VueUse 提供了更加细粒度的 Web API 以及工具分装。vue-composable 是由另外一个 Core Team Member @pikax 做的,它提供了更多常用的逻辑封装。例如 useI18n, useValidation 等等。这些功能直接实现在了这个工具里面,而不需要再去安装另外依赖于别的库的。

组合式 API 生态

然后和大家简单讲一下组合式 API 的生态支持。在 DevTools 6.0.0-beta.2 的更新了之后,加入了 Vue 3 的支持,同时加入一个新的功能是 Timeline 这个自定义的事件的打点,他可以去监听整个应用里面发生的各种各样的事件,然后把它做成一个个的点,让你可以去以时间的维度知道发生了什么。

然后在 vue-composable 里面提供了一个非常有趣的 API 叫做 useDevtoolsInspector,你可以传一些响应式的数据,当这些数据更新的时候去打点在 Timeline。你就可以更好的知道你的这些响应式的数据什么时候被什么时候被更新了以及更新成了什么。

import { useDevtoolsInspector } from 'vue-composable'

const counter = ref(0)

useDevtoolsInspector({ counter })

然后再来一个就是 SFC 的单文件组件的一些更新。我们给 script 标签加了一个 setup 的 flag。那么通过

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/534186.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

matlab系统稳定性分析,控制系统稳定性分析的MATLAB实现

收稿日期 :200706220 基金项目 :周口师范学院青年基金资助项目(No. ZKNUQN200621) 作者简介 :刘  伟(1976 - ) ,女 ,河南太康人 ,助教 ,硕士 ,主要从事电力系统及其自动化仿真研究. 第 25 卷 第 2 期 周口师范学院学报 2008 年 3 月 Vol. 25 No. 2 Journal of Zhoukou Normal …

路由器下一跳地址怎么判断_网络基本功三:细说路由器

介绍以太网交换机工作在第二层即数据链路层,用于在同一网络内部转发以太网帧。但是,当源和目的IP地址位于不同网络时,以太网帧必须发送给路由器。路由器负责在不同网络间传输报文,通过路由表来决定最佳转发路径。当主机将报文发送…

HTML多选mysql,html多选下拉框 | 学步园

一个jquery ui,实现html的多选下拉框,在下拉里面加checkbox,不改变页面的提交特性,只是动态的改变select选中的多选数据。jsp页面例子:pageEncoding"UTF-8" import"java.util.*,java.text.*"%>String path…

利用逆矩阵解线性方程组_经典Jacobi方法用于求解矩阵特征值

1、引言求解线性方程组在许多领域中都有重要应用,写成矩阵的形式: 。求解 可以写成: ,这里需要求解矩阵 的逆。《线性代数》中给出的方法主要有两类:1、设置增广矩阵,利用高斯消元法,通过初等行…

filename: core/loader.php,使用第三方包后出现的这个错误,你们都遇到过吗?

使用了一些第三方包,经常会发现,引入某些第三方包后(比如在laravel5.6中引入viacreative/sudo-su),使用命令行工具会遇到这样的错误提示,卸载了第三方包后重新安装vendor目录问题立马解决。真是把人头发都愁白了:PHP F…

python函数的作用域_python学习第五篇 函数 变量作用域

原博文 2019-07-18 23:40 − 函数 函数是组合好的,可以重复使用的,用来实现单一或相关联功能的代码片段作用 能提高应用的模块性和代码的重复利用率函数的创建 第一函数的规则 1.函数代码块一def关键字开头,后接函数标识符名称和圆括号‘&…

js post中文乱码 php,AJAX之POST数据中文乱码如何解决

本文主要和大家分享AJAX之POST数据中文乱码如何解决,前端使用encodeURI进行编码,希望能帮助到大家。var param encodeURI(param);$.ajax({url: url,methodtype: "POST",async: false,timeout: 60000,contentType: "application/json&quo…

python递归 数字全排列_利用递归实现全排列(python)

利用递归实现全排列(python) """ 利用递归实现全排列 第一个位置可能有n种可能,第二个位置可能 有n-1种可能...... 代码思路就是第一个位置可以和n个元素交换, 第二个元素可以和n-1个元素进行交换,到最 后一个输出这次排列&am…

python pip使用_Python——pip的安装与使用

pip 是 Python 包管理工具,该工具提供了对Python 包的查找、下载、安装、卸载的功能。目前如果你在 python.org 下载最新版本的安装包,则是已经自带了该工具。Python 2.7.9 或 Python 3.4 以上版本都自带 pip 工具。pip 官网:https://pypi.o…

php文章列表样式,PHPCMS V9 文章列表循环样式自定义方法

在此,再次分享Whidy的文章"phpcms文章列表循环不同样式制作方法",下面CMSYOU来与大家具体分享,原地址为http://whidy.net/phpcms-list-with-different-style.html,在这里感谢。大家在用PHPCMS系统做网站的时候,有时候在…

角速度求积分能得到欧拉角吗_一个有趣的反常积分问题

今天物理考试,老师提到了一个有趣的积分问题。听说是拉普拉斯变换的一个应用之一(生成函数?),但是我没听过那个东西所以硬上了:D1)试求积分 2) 试说明积分 的收敛性1)对于第一问可以…

php计算1-100奇数的和,学习脚本1:计算100以内奇数和和偶数和 (笔记)

let I$[$I1]let I1let I 注意此处只有是原先数值加1才可用此方法上述三者运算是相同的- 减等 两边的变量前边的减去后边的变量之后把值再放到原来的变量上 加等 两的的变量前边的加上后边的变量之后把值再放到原来的变量上* 乘等 两边的变量前边的乘上后边的变量之后把值再放到…

查看ie保存的表单_解决浏览器保存密码自动填充问题

解决浏览器保存密码自动填充问题问题描述话说有一天,我如往常一样打开我的开发网站进行登录操作。浏览器很平常的在我们进行登录操作之后询问我是否需要记住密码,懒惰如我点击了记住密码。一切都很正常的进行着,没有什么异常发生。然而&#…

java满江红1apk,满江红满V版游戏下载_满江红满V版安卓版游戏下载v1.0_3DM手游

喜欢玩精彩的传奇游戏吗?那就来《满江红满V版》这款佳作中吧!这款手游操作方式极其的简单,且玩法自由度也很高,咱们将会置身于一座很精美热血的魔幻大陆中,各种大伙熟悉的人物职业可供收集培养,极致精彩的P…

go get 的不再src目录中_GO语言基础进阶教程:包的使用

Go语言使用包(package)这种语法元素来组织源码,所有语法可见性均定义在package这个级别,与Java 、python等语言相比,这算不上什么创新,但与C传统的include相比,则是显得“先进”了许多。myblog …

python mysql 正则表达式,MySQL之正则表达式(REGEXP)

MySQL中正则表达式通常被用来检索或替换符合某个模式的文本内容,根据指定的匹配模式匹配文中符合要求的特殊字符串。例如,从一个文件中提取电话号码,查找一篇文章中重复的单词或替换用户输入的敏感语汇等,这些地方都可以使用正则表…

pyecharts anaconda_Pyecharts安装使用和绘图案例

一次偶然的机会,接触了pyecharts,发现做图交互效果非常棒,便深究、摸索、入坑。这篇文章主要讲述自己在安装和使用中遇到的问题,解决方法,最后还会有pyecharts中自己比较喜欢的绘图功能。pyecharts是一款将python与ech…

控制附件的大小 php,wordpress如何修改默认上传附件限制大小

关于上传文件大小的限制,有很多有几种情况,一是服务器上的限制(php.ini)php虚拟主机空间提供商为了保障服务器稳定、都会限制大容量附件上传,在php.ini文件中做了限制,二是网站程序本身都会有限制大小,wp媒体文件大小默…

如何把密度函数化为标准正态二维分布_概率微课:第三章(22) 二维随机变量及分布函数定义...

主要内容二维随机变量及分布函数定义更多系列视频概率微课:第二章(1) 随机变量的定义概率微课:第二章(2) 离散型随机变量概率微课:第二章(3) 两点分布及伯努利试验概率微课:第二章(4) 二项分布1概率微课:第二章(5) 二…

php中的缓,php中的缓存机制解释

php缓存的理解,先列出ob系列函数的作用:ob_start(func) 开启php缓存,回调函数是对缓存内数据的处理函数ob_gzhandler 作为 ob_start 的回调函数,对数据进行gz压缩ob_implicit_flush(true/false) 打开或关闭apache缓存&#xff0c…