网站首页 > 技术文章 正文
作者:大转转FE
转发链接:https://mp.weixin.qq.com/s/gZVn9eDruyv7G_UJFTPuGQ
前言
这几天,陆续学习了解了关于vue-next(Vue 3.0)(https://github.com/vuejs/vue-next)的一些新特性,尤其是新的 CompositionAPI的用法。这套新的API中最重要、最核心的部分,恐怕就是实现响应式功能的这一块了。而且,这套响应式API不仅可以在 vue-next环境下使用,也可以独立使用。
笔者在阅读源码看到, vue-next已全部由 TypeScript构建,看来 ts 必学技能。接下来带你了解vue-next。
vue-next计划并已实现的主要架构改进和新功能:
- 使用模块化架构
- 优化 "Block tree"
- 更激进的 static tree hoisting 功能
- 支持 Source map
- 内置标识符前缀(又名 "stripWith")
- 内置整齐打印(pretty-printing)功能
- 移除 source map 和标识符前缀功能后,使用 Brotli 压缩的浏览器版本精简了大约 10KB
运行时(Runtime)的更新主要体现在以下几个方面:
- 速度显著提升
- 同时支持 Composition API 和 Options API,以及 typings
- 基于 Proxy 实现的数据变更检测
- 支持 Fragments
- 支持 Portals
- 支持 Suspense w/ async setup()
最后,还有一些 2.x 的功能尚未移植过来,如下:
- SFC compiler
- Server-side rendering (服务端渲染SSR)
==目前不支持IE11==
vue-next(Vue 3.0) 的源码虽然发布了,但是预计最早也需要等到 2020 年第一季度才有可能发布 3.0 正式版。
目录剖析
代码仓库中有个 packages 目录,里面主要是 vue-next 的相关源码功能实现,具体内容如下所示。
- compiler-core:平台无关的编译器,它既包含可扩展的基础功能,也包含所有平台无关的插件。暴露了 AST 和 baseCompile 相关的 API,它能把一个字符串变成一棵 AST
- compiler-dom:基于compiler-core封装针对浏览器的compiler
- runtime-core:与平台无关的运行时环境。支持实现的功能有虚拟 DOM 渲染器、Vue 组件和 Vue 的各种API, 可以用来自定义 renderer ,vue2中也有
- runtime-dom:针对浏览器的 runtime。其功能包括处理原生 DOM API、DOM 事件和 DOM 属性等, 暴露了重要的render和createApp方法
const { render, createApp } = createRenderer<Node, Element>({
  patchProp,
  ...nodeOps
})
export { render, createApp }- runtime-test:一个专门为了测试而写的轻量级 runtime。比如对外暴露了renderToString方法,在此感慨和react越来越像了
- server-renderer:用于 SSR,尚未实现。
- shared:没有暴露任何 API,主要包含了一些平台无关的内部帮助方法。
- vue:「完整」版本,引用了上面提到的 runtime 和 compiler目录。入口文件代码如下
'use strict'
if (process.env.NODE_ENV === 'production') {
  module.exports = require('./dist/vue.cjs.prod.js')
} else {
  module.exports = require('./dist/vue.cjs.js')
}- 所以想阅读源码,还是要看构建流程,这个和vue2也是一致的
回顾 Vue2.0 响应式原理机制 - defineProperty
这个原理老生常谈了,就是拦截对象,给对象的属性增加 set 和 get方法,因为核心是 defineProperty所以还需要对数组的方法进行拦截
对对象进行拦截
function observer(target){
  // 如果不是对象数据类型直接返回即可
  if(typeof target !=='object'){
  	return target
	}
  
// 重新定义key  
for(let key in target){
    defineReactive(target,key,target[key])
  }
}
function update(){
  console.log('update view')
}
function defineReactive(obj,key,value){
  observer(value);
 // 有可能对象类型是多层,递归劫持
  Object.defineProperty(obj,key,{
    get(){
      // 在get 方法中收集依赖
      return value
    },
    set(newVal){
      if(newVal !== value){
        observer(value);
        update();
 // 在set方法中触发更新
      }
    }
  })
}
const obj ={name:'zhuanzhuan'}
observer(obj);
obj.name ='new-name';输出:update view数组方法劫持
const oldProtoMehtods =
 Array
prototype
const
 proto 
=
 
Object
.
create
(
oldProtoMehtods
)
function
 update
(){
  console
.
log
(
'update view'
)
}
function
 defineReactive
(
obj
,
key
,
value
){
  observer
(
value
)
 
// 有可能对象类型是多层,递归劫持
  
Object
.
defineProperty
(
obj
,
key
,{
    
get
(){
      
// 在get 方法中收集依赖
      
return
 value
    
},
    
set
(
newVal
){
      
if
(
newVal 
!==
 value
){
        observer
(
value
)
        update
()
 
// 在set方法中触发更新
      
}
    
}
  
})
}
[
'push'
,
'pop'
,
'shift'
,
'unshift'
].
forEach
(
method
=>{
  
Object
.
defineProperty
(
proto
,
 method
,{
    
get
(){
      update
()
      
return
 oldProtoMehtods
[
method
]
    
}
  
})
})
function
 observer
(
target
){
  
if
(
typeof
 target 
!==
 
'object'
){
    
return
 target
  
}
  
// 如果不是对象数据类型直接返回即可
  
if
(
Array
.
isArray
(
target
)){
    
Object
.
setPrototypeOf
(
target
,
 proto
)
    
// 给数组中的每一项进行observr
    
for
(
let i 
=
 
0
 
;
 i 
<
 target
.
length
;
 i
++){
      observer
(
target
[
i
])
    
}
    
return
  
}
  
// 重新定义key
  
for
(
let key in target
){
    defineReactive
(
target
,
 key
,
 target
[
key
])
  
}
}
let obj 
=
 
{
hobby
:[{
name
:
'zhuanzhuan'
}]}
observer
(
obj
)
// 使用['push','pop','shift','unshift'] 方法,更改数组会触发视图更新
obj
.
hobby
.
push
(
'转转'
)
// 更改数组中的对象也会触发视图更新
obj
.
hobby
[
0
].
name 
=
 
'new-name'
console
.
log
(
obj
.
hobby
)输出:
update view
update view
[ { name: [Getter/Setter] }, '转转' ]Object.defineProperty缺点:
- 无法监听数组的变化
- 需要深度遍历,浪费内存
vue-next 预备知识
无论是阅读这篇文章,还是阅读 vue-next 响应式模块的源码,首先有两个知识点是必备的:
- Proxy(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy):对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。ES6 中新的代理内建工具类。
- Reflect(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect):是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的。ES6 中新的反射工具类
Proxy
let data=[1,2,3]
let p=new Proxy(data,{get(target,key)
{
console.log('获取值:',key)
return target[key]
},set(target,key,value)
{
console.log('修改值:',key,value)
target[key]=value
return true
}
})
p.push(4)输出:
获取值: push
获取值:length
修改值:3 4
修改值: length 4比 defineproperty优秀的 就是数组和对象都可以直接触发 getter和 setter, 但是数组会触发两次,因为获取 push和修改 length的时候也会触发
Proxy 取代 deineProperty 除了性能更高以外,还有以下缺陷,也是为啥会有$set,$delete的原因 :
- 属性的新加或者删除也无法监听;
- 数组元素的增加和删除也无法监听
Reflect
let data = [1,2,3]
let p = new Proxy(data,{get(target,key)
{
    console.log('获取值:',key)
    return Reflect.get(target,key)
},
    set(target,key,value)
{
    console.log('修改值:',key,value)
    return Reflect.set(target,key,value)
}})
p.push(4)
输出:
获取值: push
获取值: length
修改值: 3 4
修改值: length 4多次触发和深层嵌套问题
let data={name:{title:'zhuanzhuan'}}
let p= new Proxy(data,{get(target,key){
    console.log('获取值:',key)
    return Reflect.get(target,key)},
    set(target,key,value){
    console.log('修改值:',key,value)
    return Reflect.set(target,key,value)}
})
p.name.title = 'xx'
输出:获取值: name之后会带你看下 vue-next是怎么解决的。
初始化项目
依赖 项目 vue.global.js【推荐】
- clone 项目
$ git clone https://github.com/vuejs/vue-next.git
- 编辑文件
$ npm run dev
- 拷贝文件,运行上面命令后,就会生成 [项目根路径]/packages/vue/dist/vue.global.js 文件
依赖 @vue/composition-api
- 安装 vue-cli
$ npm install -g @vue/cli
# OR
$ yarn global add @vue/cli2. 创建项目
$ vue create my-project
# OR
$ vue ui3. 在项目中安装 composition-api 体验 vue-next 新特性
$ npm install @vue/composition-api --save
# OR
$ yarn add @vue/composition-api4. 在使用任何 @vue/composition-api 提供的能力前,必须先通过 Vue.use() 进行安装
import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'
Vue.use(VueCompositionApi)安装插件后,您就可以使用新的 Composition API 来开发组件了。
vue-next 尝鲜
直接拷贝下面代码,去运行看效果吧。推荐使用高版本的chrome浏览器,记得打开F12调试工具哦!
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://s1.zhuanstatic.com/common/js/vue-next-3.0.0-alpha.0.js"></script>
</head>
<body>
<div id='app'></div>
</body>
<script>
  const { createApp,reactive, computed, effect } = Vue;
  const RootComponent = { template: `<button @click="increment">{{state.name }}今年{{state.age}}岁了,乘以2是{{state.double}}</button>`,
        setup()
        {
          const state = reactive({ name: '转转',age: 3, double: computed
                  (() => state.age * 2) })
          effect
          (() => {
            console.log(`effect 触发了!-${state.name}今年${state.age}岁了,乘以=2是${state.double}`)
          })
          function increment()
          {
            state.age++
          }
          return { state,increment }
        }
      }
  createApp().mount
  (
    RootComponent, '#app'
  )
</script>
</html>
这个reactive和react-hooks越来越像了, 大家可以去Composition API RFC(https://vue-composition-api-rfc.netlify.com/#api-introduction)这里看细节。
- template和之前一样,同样 vue-next也支持手写 render的写法, template和 render同时存在的情况,优先 render。
- setup选项是新增的主要变动,顾名思义, setup函数会在组件挂载前( beforeCreate和 created生命周期之间)运行一次,类似组件初始化的作用, setup需要返回一个对象或者函数。返回对象会被赋值给组件实例的 renderContext,在组件的模板作用域可以被访问到,类似 data的返回值。返回函数会被当做是组件的 render。具体可以细看文档。
- reactive的作用是将对象包装成响应式对象,通过 Proxy代理后的对象。
- 上面的计数器的例子,在组件的 setup函数中,创建了一个响应式对象 state包含一个 age属性。然后创建了一个 increment递增的函数,最后将 state和 increment返回给作用域,这样 template里的 button按钮就能访问到 increment函数绑定到点击的回调, age。我们点击按钮,按钮上的数值就能跟着递增。
推荐React和Vue学习资料文章:
《一篇文章教你并列比较React.js和Vue.js的语法【实践】》
《深入浅出通过vue-cli3构建一个SSR应用程序【实践】》
《聊聊昨晚尤雨溪现场针对Vue3.0 Beta版本新特性知识点汇总》
《【新消息】Vue 3.0 Beta 版本发布,你还学的动么?》
《Vue + Koa从零打造一个H5页面可视化编辑器——Quark-h5》
《深入浅出Vue3 跟着尤雨溪学 TypeScript 之 Ref 【实践】》
《手把手教你深入浅出vue-cli3升级vue-cli4的方法》
《Vue 3.0 Beta 和React 开发者分别杠上了》
《手把手教你用vue drag chart 实现一个可以拖动 / 缩放的图表组件》
《Vue3 尝鲜》
《手把手让你成为更好的Vue.js开发人员的12个技巧和窍门【实践】》
《2020 年,Vue 受欢迎程度是否会超过 React?》
《手把手教你Vue解析pdf(base64)转图片【实践】》
《手把手教你Vue之父子组件间通信实践讲解【props、$ref 、$emit】》
《深入浅出Vue3 的响应式和以前的区别到底在哪里?【实践】》
《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》
《基于Vue/VueRouter/Vuex/Axios登录路由和接口级拦截原理与实现》
《手把手教你D3.js 实现数据可视化极速上手到Vue应用》
《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【上】》
《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【中】》
《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【下】》
React
《Vue 3.0 Beta 和React 开发者分别杠上了》
《手把手深入Redux react-redux中间件设计及原理(上)【实践】》
《手把手深入Redux react-redux中间件设计及原理(下)【实践】》
《为了学好 React Hooks, 我解析了 Vue Composition API》
《【React 高级进阶】探索 store 设计、从零实现 react-redux》
《深入浅出掌握React 与 React Native这两个框架》
《你需要的 React + TypeScript 50 条规范和经验》
《手把手教你深入浅出实现Vue3 & React Hooks新UI Modal弹窗》
《全平台(Vue/React/微信小程序)任意角度旋图片裁剪组件》
作者:大转转FE
转发链接:https://mp.weixin.qq.com/s/gZVn9eDruyv7G_UJFTPuGQ
猜你喜欢
- 2024-10-25 文档在线预览新版(四)使用js前端组件实现文档在线预览
- 2024-10-25 细聊Vue 3 系列之 JSX 语法(vue中jsx语法)
- 2024-10-25 一篇文章教你并列比较React.js和Vue.js的语法【实践】
- 2024-10-25 10个Vue开发技巧「实践」(vue开发视频教程)
- 2024-10-25 Python开发打印服务(2)(python打印代码)
- 2024-10-25 Vue.js 项目实践——创建记忆卡片游戏
- 2024-10-25 让Jenkins自动部署你的Vue项目「实践」
- 2024-10-25 Java实战系列-前端VUE代码开发及接口流程设计(1)
- 2024-10-25 Vue高性能渲染大数据Tree组件「实践」
- 2024-10-25 轻量级Vue图片上传插件——Vue-core-image-Upload
- 最近发表
- 
- 聊一下 gRPC 的 C++ 异步编程_grpc 异步流模式
- [原创首发]安全日志管理中心实战(3)——开源NIDS之suricata部署
- 超详细手把手搭建在ubuntu系统的FFmpeg环境
- Nginx运维之路(Docker多段构建新版本并增加第三方模
- 92.1K小星星,一款开源免费的远程桌面,让你告别付费远程控制!
- Go 人脸识别教程_piwigo人脸识别
- 安卓手机安装Termux——搭建移动服务器
- ubuntu 安装开发环境(c/c++ 15)_ubuntu安装c++编译器
- Rust开发环境搭建指南:从安装到镜像配置的零坑实践
- Windows系统安装VirtualBox构造本地Linux开发环境
 
- 标签列表
- 
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- apt-getinstall-y (100)
- node_modules怎么生成 (87)
- chromepost (71)
- flexdirection (73)
- c++int转char (80)
- mysqlany_value (79)
- static函数和普通函数 (84)
- el-date-picker开始日期早于结束日期 (76)
- js判断是否是json字符串 (75)
- c语言min函数头文件 (77)
- asynccallback (87)
- localstorage.removeitem (77)
- vector线程安全吗 (73)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 无效的列索引 (74)
 
