优秀的编程知识分享平台

网站首页 > 技术文章 正文

带你了解 vue-next(Vue 3.0)之 初入茅庐【实践】

nanyue 2024-10-25 13:13:02 技术文章 6 ℃


作者:大转转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的原因 :

  1. 属性的新加或者删除也无法监听;
  2. 数组元素的增加和删除也无法监听

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【推荐】

  1. clone 项目

$ git clone https://github.com/vuejs/vue-next.git

  1. 编辑文件

$ npm run dev

  1. 拷贝文件,运行上面命令后,就会生成 [项目根路径]/packages/vue/dist/vue.global.js 文件

依赖 @vue/composition-api

  1. 安装 vue-cli
$ npm install -g @vue/cli
# OR
$ yarn global add @vue/cli

2. 创建项目

$ vue create my-project
# OR
$ vue ui

3. 在项目中安装 composition-api 体验 vue-next 新特性

$ npm install @vue/composition-api --save
# OR
$ yarn add @vue/composition-api

4. 在使用任何 @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)这里看细节。

  1. template和之前一样,同样 vue-next也支持手写 render的写法, template和 render同时存在的情况,优先 render。
  2. setup选项是新增的主要变动,顾名思义, setup函数会在组件挂载前( beforeCreate和 created生命周期之间)运行一次,类似组件初始化的作用, setup需要返回一个对象或者函数。返回对象会被赋值给组件实例的 renderContext,在组件的模板作用域可以被访问到,类似 data的返回值。返回函数会被当做是组件的 render。具体可以细看文档。
  3. reactive的作用是将对象包装成响应式对象,通过 Proxy代理后的对象。
  4. 上面的计数器的例子,在组件的 setup函数中,创建了一个响应式对象 state包含一个 age属性。然后创建了一个 increment递增的函数,最后将 state和 increment返回给作用域,这样 template里的 button按钮就能访问到 increment函数绑定到点击的回调, age。我们点击按钮,按钮上的数值就能跟着递增。

推荐React和Vue学习资料文章:

实践Vue 3.0做JSX(TSX)风格的组件开发

一篇文章教你并列比较React.js和Vue.js的语法【实践】

手拉手带你开启Vue3世界的鬼斧神工【实践】

深入浅出通过vue-cli3构建一个SSR应用程序【实践】

怎样为你的 Vue.js 单页应用提速

聊聊昨晚尤雨溪现场针对Vue3.0 Beta版本新特性知识点汇总

【新消息】Vue 3.0 Beta 版本发布,你还学的动么?

Vue真是太好了 壹万多字的Vue知识点 超详细!

Vue + Koa从零打造一个H5页面可视化编辑器——Quark-h5

深入浅出Vue3 跟着尤雨溪学 TypeScript 之 Ref 【实践】

手把手教你深入浅出vue-cli3升级vue-cli4的方法

Vue 3.0 Beta 和React 开发者分别杠上了

手把手教你用vue drag chart 实现一个可以拖动 / 缩放的图表组件

Vue3 尝鲜

总结Vue组件的通信

手把手让你成为更好的Vue.js开发人员的12个技巧和窍门【实践】

Vue 开源项目 TOP45

2020 年,Vue 受欢迎程度是否会超过 React?

尤雨溪:Vue 3.0的设计原则

使用vue实现HTML页面生成图片

实现全栈收银系统(Node+Vue)(上)

实现全栈收银系统(Node+Vue)(下)

vue引入原生高德地图

Vue合理配置WebSocket并实现群聊

多年vue项目实战经验汇总

vue之将echart封装为组件

基于 Vue 的两层吸顶踩坑总结

Vue插件总结【前端开发必备】

Vue 开发必须知道的 36 个技巧【近1W字】

构建大型 Vue.js 项目的10条建议

深入理解vue中的slot与slot-scope

手把手教你Vue解析pdf(base64)转图片【实践】

使用vue+node搭建前端异常监控系统

推荐 8 个漂亮的 vue.js 进度条组件

基于Vue实现拖拽升级(九宫格拖拽)

手摸手,带你用vue撸后台 系列二(登录权限篇)

手摸手,带你用vue撸后台 系列三(实战篇)

前端框架用vue还是react?清晰对比两者差异

Vue组件间通信几种方式,你用哪种?【实践】

浅析 React / Vue 跨端渲染原理与实现

10个Vue开发技巧助力成为更好的工程师

手把手教你Vue之父子组件间通信实践讲解【props、$ref 、$emit】

1W字长文+多图,带你了解vue的双向数据绑定源码实现

深入浅出Vue3 的响应式和以前的区别到底在哪里?【实践】

干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)

基于Vue/VueRouter/Vuex/Axios登录路由和接口级拦截原理与实现

手把手教你D3.js 实现数据可视化极速上手到Vue应用

吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【上】

吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【中】

吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【下】

Vue3.0权限管理实现流程【实践】

后台管理系统,前端Vue根据角色动态设置菜单栏和路由

React

React 函数式组件性能优化知识点指南汇总

13个精选的React JS框架

深入浅出画图讲解React Diff原理【实践】

【React深入】React事件机制

Vue 3.0 Beta 和React 开发者分别杠上了

手把手深入Redux react-redux中间件设计及原理(上)【实践】

手把手深入Redux react-redux中间件设计及原理(下)【实践】

前端框架用vue还是react?清晰对比两者差异

为了学好 React Hooks, 我解析了 Vue Composition API

【React 高级进阶】探索 store 设计、从零实现 react-redux

写React Hooks前必读

深入浅出掌握React 与 React Native这两个框架

可靠React组件设计的7个准则之SRP

React Router v6 新特性及迁移指南

用React Hooks做一个搜索栏

你需要的 React + TypeScript 50 条规范和经验

手把手教你绕开React useEffect的陷阱

浅析 React / Vue 跨端渲染原理与实现

React 开发必须知道的 34 个技巧【近1W字】

三张图详细解说React组件的生命周期

手把手教你深入浅出实现Vue3 & React Hooks新UI Modal弹窗

手把手教你搭建一个React TS 项目模板

全平台(Vue/React/微信小程序)任意角度旋图片裁剪组件

40行代码把Vue3的响应式集成进React做状态管理

手把手教你深入浅出React 迷惑的问题点【完整版】


作者:大转转FE

转发链接:https://mp.weixin.qq.com/s/gZVn9eDruyv7G_UJFTPuGQ

Tags:

最近发表
标签列表