Vue 3.0 的变化
- 源码组织方式的变化
- 采用 Typescript重写
- 采用Monoorpo管理项目结构
- composition API
- Options API包含一个描述组件的选项(data、methods、props等)的对象
- Options API 开发复杂组件,同一个功能逻辑代码会被拆分到不同选项
- Vue 3.0 新增一组基于函数的 API,可以更灵活的组织组件的逻辑
- 性能提升
- 响应式系统升级
- Vue.js 2.0 x 中响应式系统的核心 defineProperty
- Vue.js 3.0 x 中使用 Proxy 对象重写响应式系统
- 编译优化
- Vue.js 2.0 x 中通过标记静态节点,优化 diff 的过程
- Vue.js 3.0 x 中标记和提升所有的静态根节点,diff 的时候只需要对比动态内容
- Fragments(升级 Vuter 插件)
- 静态提升
- Patch flag
- 缓存事件处理函数
- 源码体积优化
- Vue.js 3.0 x 中移除了一些不常用的 API
- 例如: inline-template filter 等
- Tree-shaking
- Vue.js 3.0 x 中移除了一些不常用的 API
- 响应式系统升级
Vite
现代浏览器都支持 ES Module (IE不支持)
通过下面的方式加载模块
<script type="module" src="..."></script>
支持模块的script默认延时加载
Vite 在生产环境使用 Rollup 打包
- 基于 ES Module 的方式打包
-
Vite 创建项目
Vite创建项目
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
基于模板创建项目
npm init vite-app --template react
npm init vite-app --template preact
Composition API
生命周期钩子函数
setup()是在beforeCreate和created之间执行的,不需要这两个生命周期函数
reactive/toRefs/ref
- reactive 将一个对象变成响应式对象
- toRefs 将 reactive 返回的响应式对象所有属性添加为响应式
-
computed
需要传入一个函数 ```javascript import { createApp, reactive, computed } from ‘./node_modules/vue/dist/vue.esm-browser.js’ const data = [ { text: ‘看书’, completed: false }, { text: ‘敲代码’, completed: false }, { text: ‘约会’, completed: true } ]
createApp({ setup () { const todos = reactive(data)
const activeCount = computed(() => {
return todos.filter(item => !item.completed).length
})
return {
activeCount,
push: () => {
todos.push({
text: '开会',
completed: false
})
}
}
} }).mount(‘#app’)
<a name="1XGxi"></a>
### Watch
- Watch 的三个参数
- 第一个参数:要监听的数据(ref/reactive 的返回值)
- 第二个参数:监听到数据变化的执行函数,这个函数有两个参数分别是新值和旧值
- 第三个参数:选项对象,deep 和 immediate
- Watch 的返回值
- 取消监听函数
<a name="rjFCt"></a>
### WatchEffect
- 是 Watch 函数的简化版本,也用来监听数据的变化
- 接收一个函数作为参数,监听函数内响应式数据的变化
<a name="6Upsw"></a>
## 响应式原理
<a name="EnlV9"></a>
### reactive
- 接收一个参数,判断这个参数是否为对象
- 创建拦截器对象 handler,设置 get、set、deleteProperty
- 返回 Proxy 对象
```javascript
const isObject = val => val !== null && typeof val === 'object'
const convert = target => isObject(target) ? reactive(target) : target
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target, key) => hasOwnProperty.call(target, key)
export function reactive (target) {
if (!isObject(target)) return target
const handler = {
get (target, key, receiver) {
// 收集依赖
track(target, key)
const result = Reflect.get(target, key, receiver)
return convert(result)
},
set (target, key, value, receiver) {
const oldValue = Reflect.get(target, key, receiver)
let result = true
if (oldValue !== value) {
result = Reflect.set(target, key, value, receiver)
// 触发更新
trigger(target, key)
}
return result
},
deleteProperty (target, key) {
const hadKey = hasOwn(target, key)
const result = Reflect.deleteProperty(target, key)
if (hadKey && result) {
// 触发更新
trigger(target, key)
}
return result
}
}
return new Proxy(target, handler)
}
收集依赖
effect && track
let activeEffect = null
export function effect (callback) {
activeEffect = callback
callback() // 访问响应式对象属性,去收集依赖
activeEffect = null
}
let targetMap = new WeakMap()
export function track (target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
dep.add(activeEffect)
}
trigger
export function trigger (target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => {
effect()
})
}
}
ref
export function ref (raw) {
// 判断 raw 是否是ref 创建的对象,如果是的话直接返回
if (isObject(raw) && raw.__v_isRef) {
return
}
let value = convert(raw)
const r = {
__v_isRef: true,
get value () {
track(r, 'value')
return value
},
set value (newValue) {
if (newValue !== value) {
raw = newValue
value = convert(raw)
trigger(r, 'value')
}
}
}
return r
}
reactive vs ref
- ref 可以将基本数据类型转换为响应式对象
- ref 返回对象,重新赋值成对象也是响应式的
- reactive 返回对象,重新赋值丢失响应式
-
toRefs
```javascript export function toRefs (proxy) { const ret = proxy instanceof Array ? new Array(proxy.length) : {}
for (const key in proxy) { ret[key] = toProxyRef(proxy, key) }
return ret }
function toProxyRef (proxy, key) { const r = { __v_isRef: true, get value () { return proxy[key] }, set value (newValue) { proxy[key] = newValue } } return r }
<a name="Z4Aos"></a>
### computed
```javascript
export function computed (getter) {
const result = ref()
effect(() => (result.value = getter()))
return result
}
Vite
概念
- Vite是一个面向现代化浏览器的一个更轻、更快的Web应用开发工具
它基于ECMAScript标准原生模块系统(ES Modules)实现
项目依赖
Vite
- @vue/compiler-scf
Vite实现原理
静态Web服务器(Koa) ```javascript!/usr/bin/env node
const path = require(‘path’) const { Readable } = require(‘stream’) const Koa = require(‘koa’) const send = require(‘koa-send’) const compilerSFC = require(‘@vue/compiler-sfc’)
const app = new Koa()
const streamToString = stream => new Promise((resolve, reject) => { const chunks = [] stream.on(‘data’, chunk => chunks.push(chunk)) stream.on(‘end’, () => resolve(Buffer.concat(chunks).toString(‘utf-8’))) stream.on(‘error’, reject) })
const stringToStream = text => { const stream = new Readable() stream.push(text) stream.push(null) return stream }
// 3. 加载第三方模块 app.use(async (ctx, next) => { // ctx.path —> /@modules/vue if (ctx.path.startsWith(‘/@modules/‘)) { const moduleName = ctx.path.substr(10) const pkgPath = path.join(process.cwd(), ‘node_modules’, moduleName, ‘package.json’) const pkg = require(pkgPath) ctx.path = path.join(‘/node_modules’, moduleName, pkg.module) } await next() })
// 1. 静态文件服务器 app.use(async (ctx, next) => { await send(ctx, ctx.path, { root: process.cwd(), index: ‘index.html’ }) await next() })
// 4. 处理单文件组件 app.use(async (ctx, next) => { if (ctx.path.endsWith(‘.vue’)) { const contents = await streamToString(ctx.body) const { descriptor } = compilerSFC.parse(contents) let code if (!ctx.query.type) { code = descriptor.script.content // console.log(code) code = code.replace(/export\s+default\s+/g, ‘const script = ‘) code += ` import { render as render } from “${ctx.path}?type=template” script.render = render export default __script ` } else if (ctx.query.type === ‘template’) { const templateRender = compilerSFC.compileTemplate({ source: descriptor.template.content }) code = templateRender.code } ctx.type = ‘application/javascript’ ctx.body = stringToStream(code) } await next() })
// 2. 修改第三方模块的路径 app.use(async (ctx, next) => { if (ctx.type === ‘application/javascript’) { const contents = await streamToString(ctx.body) // import vue from ‘vue’ // import App from ‘./App.vue’ ctx.body = contents .replace(/(from\s+[‘“])(?![.\/])/g, ‘$1/@modules/‘) .replace(/process.env.NODE_ENV/g, ‘“development”‘) } })
app.listen(3000) console.log(‘Server running @ http://localhost:3000‘)
```