Analysis of Vue.js 3.0 Composition APIs and 3.0 Principles
Introduction to Vue.js 3.0
1. Vue.js source code organization
1. The source code is rewritten in TypeScript
Improve the maintainability of the code. For the development of large projects, it is recommended to use typed languages to check type problems during the coding process.
2. Use Monorepo to manage the project structure
Use one project to manage multiple packages, put the codes of different functions in different packages for management, and each function module can be released separately, tested separately, and used separately.
3. Different build versions
Vue3 no longer builds the UMD modular way, because UMD will make the code more redundant, it needs to support multiple modular ways. In Vue3, the CJS, ESModule, and self-executing functions are packaged into different files. There are different build versions of Vue3 in packages/vue.
cjs (both versions are full versions, including compiler)
- vue.cjs.js
- vue.cjs.prod.js (development version, code is compressed)
global (all four versions can be imported directly through the scripts tag in the browser, and a global Vue object will be added after import)
- vue.global.js (full version, including compiler and runtime)
- vue.global.prod.js (full version, including compiler and runtime, this is the development version, the code is compressed)
- vue.runtime.global.js
- vue.runtime.global.prod.js
browser (Esm is included in all four versions, the native modularity of the browser, the module can be imported
<script type="module" />
- vue.esm-browser.js
- vue.esm-browser.prod.js
- vue.runtime.esm-browser.js
- vue.runtime.esm-browser.prod.js
Bundler (these two versions do not package all the code, only the code used, it needs to be used in conjunction with the packaging tool, which will make Vue smaller)
- vue.esm-bundler.js
- bue.runtime.esm-bundler.js
Two, Composition API
RFC (Request For Coments)
Composition API RFC
1. Design Motivation
Options API
- Contains an object describing component options (data, methods, props, etc.)
- Options API develops complex components, the code of the same functional logic is split into different options
Composition API
- A new set of APIs in Vue.js 3.0
- A set of function-based APIs
- The logic of components can be organized more flexibly
Three, performance improvement
1. Responsive system upgrade
Vue3 uses the Proxy object to rewrite the reactive system.
- The core of the responsive system in Vue.js 2.x,
defineProperty
, recursively traverse all properties during initialization and convert them to getters and setters Rewrite responsive system
Proxy
object in Vue.js 3.0- Can monitor dynamically added properties
- Can monitor deleted attributes
- You can monitor the index and length properties of the array
2. Compiler optimization
Rewrite the DOM to improve rendering performance.
- In Vue.js 2.x, the process of diff is optimized by marking the static root node
In Vue.js 3.0, all static root nodes are marked and promoted. When diffing, only dynamic node content needs to be compared
- Fragments (upgrade vetur plugin)
- Static promotion
- Patch flag
- Cache event handler
3. Optimization of source code volume
Reduce the size of large packages by optimizing the size of the source code and better TreeShaking support
Some uncommon APIs have been removed in Vue.js 3.0
- For example: inline-template, filter, etc.
Tree-shaking
- For example: unused modules in Vue3 will not be packaged, but core modules will be packaged. Keep-Alive, transition, etc. are all introduced on demand.
Four, Vite
A packaging tool for Vue. Vite means "fast" in French
1. ES Module
- Modern browsers support ES Module (not supported by IE)
- Load the module in the following way
<script type="module" src="..."></script>
- Support the default lazy loading of the script of the module
Modules with type="module" are lazily loaded, similar to the script tag setting defer
- After the document is parsed, that is, after the DOM tree is generated, it is executed before the DOMContentLoaded event is triggered
2. Vite as Vue-CLI
- Vite can run directly without packaging in development mode
- In the Vue-CLI development mode, the project must be packaged before it can run
Vite uses Rollup packaging in a production environment
- Package based on ES Module
- Vue-CLI is packaged with Webpack
3. Vite features
- Fast cold start
- Compile on demand
- Module hot update
4. Create project with Vite
Vite create project
npm init vite-app <project-name> cd <project-name> npm install npm run dev
Create project based on template
npm init vite-app --template react npm init vite-app --template preact
Compositon API
1. Use of Composition API
1. Use Vue3.0
First create an empty folder, then enter the folder and execute npm init -y
, and then execute npm install vue@3.0.0-rc.1
install vue3.0
Create index.html, use vue3.0
<body>
<div id="app">
x: {{ position.x }} <br>
y: {{ position.y }} <br>
</div>
<script type="module">
import { createApp } from './node_modules/vue/dist/vue.esm-browser.js'
const app = createApp({
data () {
return {
position: {
x: 0,
y: 0
}
}
}
})
console.log(app)
app.mount('#app')
</script>
</body>
2. Use of setup and reactive
- createAPP: Create a Vue object
- setup: Entry of CompositionAPI
- reactive: create reactive objects
<body>
<div id="app">
x: {{ position.x }} <br>
y: {{ position.y }} <br>
</div>
<script type="module">
import { createApp, reactive } from './node_modules/vue/dist/vue.esm-browser.js'
const app = createApp({
setup () {
// 第一个参数 props,响应式对象,不能被解构
// 第二个参数 context, attrs、emit、slots
const position = reactive({
x: 0,
y: 0
})
return {
position
}
},
mounted () {
this.position.x = 2
}
})
console.log(app)
app.mount('#app')
</script>
</body>
Second, the life cycle hook function in setup
Just capitalize the first letter of the vue hook function and add on before the hook function. Special: destroy in the original life cycle corresponds to onUnmounted.
<body>
<div id="app">
x: {{ position.x }} <br>
y: {{ position.y }} <br>
</div>
<script type="module">
import { createApp, reactive, onMounted, onUnmounted } from './node_modules/vue/dist/vue.esm-browser.js'
function useMousePosition () {
const position = reactive({
x: 0,
y: 0
})
const update = e => {
position.x = e.pageX
position.y = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return position
}
const app = createApp({
setup () {
const position = useMousePosition()
return {
position
}
},
mounted () {
this.position.x = 2
}
})
console.log(app)
app.mount('#app')
</script>
</body>
Three, reactive-toRefs-ref
The reactive data created by reactive is no longer reactive after deconstruction. toRefs can also convert all the properties of the reactive object into reactive, so the object returned by toRefs can be deconstructed, and the data is still reactive after deconstruction.
Reactive is to transform ordinary objects into reactive objects, and ref is to package basic types of data into reactive objects.
Use of ref:
<body>
<div id="app">
<button @click="increase">Button</button>
<span>{{count}}</span>
</div>
<script type="module">
import { createApp, ref } from './node_modules/vue/dist/vue.esm-browser.js'
function useCount () {
const count = ref(0) // 将基本类型数据转化成响应式对象
return {
count,
increase: () => {
count.value++
}
}
}
createApp({
setup () {
return {
...useCount()
}
}
}).mount('#app')
</script>
</body>
Four, Computed
Computed can create a responsive data, this responsive data depends on other responsive data, which is a calculated attribute.
The first usage
- computed(() => count.value + 1)
The second usage
const count = ref(1) const plusOne = computed({ get: () => count.value + 1, set: val => { count.value = val - 1 } })
use:
<body>
<div id="app">
<button @click="push">Button</button>
<span>未完成:{{activeCount}}</span>
</div>
<script type="module">
import { createApp, reactive, computed } from './node_modules/vue/dist/vue.esm-browser.js'
const data = [
{ text: '看书', complated: false },
{ text: '敲代码', complated: false },
{ text: '约会', complated: true },
]
createApp({
setup () {
const todos = reactive(data)
const activeCount = computed(() => {
return todos.filter(item => !item.complated).length
})
return {
activeCount,
push: () => {
todos.push({
text: '开会',
complated: false
})
}
}
}
}).mount('#app')
</script>
</body>
Five, watch
1. Three parameters of watch
- The first parameter: the data to be monitored must be the object returned by reactive or ref
- The second parameter: the function to be executed after monitoring the data change, this function has two parameters which are the new value and the old value
- The third parameter: option object, deep and immediate
2. The return value of watch
- Function to cancel listening
use:
<body>
<div id="app">
请选择一个yes/no的问题:
<input v-model.lazy="question">
<p>{{answer}}</p>
</div>
<script type="module">
import { createApp, ref, watch } from './node_modules/vue/dist/vue.esm-browser.js'
createApp({
setup () {
const question = ref('')
const answer = ref('')
watch(question, async (newValue, oldValue) => {
const response = await fetch('https://www.yesno.wtf/api')
const data = await response.json()
answer.value = data.answer
})
return {
question,
answer
}
}
}).mount('#app')
</script>
</body>
Six, WatchEffect
- Is a simplified version of the watch function, also used to monitor changes in data
- Accept a function as a parameter and monitor the changes of responsive data in the function
<body>
<div id="app">
<button @click="increase">increase</button>
<button @click="stop">stop</button>
<p>{{count}}</p>
</div>
<script type="module">
import { createApp, ref, watchEffect } from './node_modules/vue/dist/vue.esm-browser.js'
createApp({
setup () {
const count = ref(0)
const stop = watchEffect(() => {
console.log(count.value)
})
return {
count,
stop,
increase: () => count.value ++
}
}
}).mount('#app')
</script>
</body>
Six, realize todolist case
1. ToDoList function list
- Add to-do
- Delete to-do
- Edit to-do
- Switch to-do
- Store to-do
2. Project structure
Use vue scaffolding to create a Vue project, first upgrade vue-cli, you can choose the vue version when creating a project with vue-cli of version 4.5.6.
The package name of Vue CLI has been changed vue-cli
@vue/cli
. If you have installed the old version of vue-cli
(1.x or 2.x) globally, you need to uninstall it npm uninstall vue-cli -g
or yarn global remove vue-cli
You can install this new package using any of the following commands:
npm install -g @vue/cli
# OR
yarn global add @vue/cli
First use vue create to create the project, and select 3.0 when creating it.
vue create 04-todolist
Then select Default (Vue 3 Preview) ([Vue 3] babel, eslint)
The project will be created automatically.
3. Add to-do list
Enter text in the input box and press enter to submit the to-do
// 1. 添加待办事项
const useAdd = todos => {
const input = ref('')
const addTodo = () => {
const text = input.value?.trim()
if (text.length === 0) return
todos.value.unshift({
text,
completed: false
})
input.value = ''
}
return {
input,
addTodo
}
}
4. Delete to-do list
Click the cross on the right side of the to-do item to delete the to-do item
// 2. 删除待办事项
const useRemove = todos => {
const remove = todo => {
const index = todos.value.indexOf(todo)
todos.value.splice(index, 1)
}
return {
remove
}
}
5. Edit to-do list
Double-click to enter the editing state, press esc to exit the editing, and press enter to submit the editing. If the text is deleted, this item is deleted.
- Double-click the to-do to show the edit text box
- Press Enter or the edit text box loses focus, modify the data
- Press esc to cancel editing
- Empty the edit text box and press Enter to delete this item
- Get focus when the edit text box is displayed
// 3. 编辑待办事项
const useEdit = (remove) => {
let beforeEditingText = ''
const editingTodo = ref(null)
const editTodo = todo => {
beforeEditingText = todo.text
editingTodo.value = todo
}
const doneEdit = todo => {
if (!editingTodo.value) return
todo.text = todo.text.trim()
if (todo.text === '') remove(todo)
editingTodo.value = null
}
const cancelEdit = todo => {
editingTodo.value = null
todo.text = beforeEditingText
}
return {
editingTodo,
editTodo,
doneEdit,
cancelEdit
}
}
In the template:
<ul class="todo-list">
<li v-for="todo in todos" :key="todo" :class="{editing: todo === editingTodo}">
<div class="view">
<input type="checkbox" class="toggle">
<label @dblclick="editTodo(todo)">{{ todo.text }}</label>
<button class="destroy" @click="remove(todo)"></button>
</div>
<input type="text" class="edit" v-model="todo.text" @keyup.enter="doneEdit(todo)" @blur="doneEdit(todo)" @keyup.esc="cancelEdit(todo)">
</li>
</ul>
6. Edit text box to get focus-vue3.0 custom instruction
Object format:
Vue 2.x
Vue.directive('editingFocus', { bind(el, binding, vnode, prevVnode) {}, inserted() {}, update() {} // remove, componentUpdated() {}, unbind() {} })
Vue 3.0
app.directive('editingFocus', { beforeMount(el, binding, vnode, prevVnode) {}, mounted() {}, breforeUpdate() {}, // new updated() {}, beforeUnmount() {}, // new unmounted() {} })
Transfer function form:
Vue 2.x
Vue.directive('editingFocus', (el, binding) => { binding.value && el.focus() })
Vue 3.0
app.directive('editingFocus', (el, binding) => { binding.value && el.focus() })
The code implements a custom event to get the focus of the text box being edited:
export default {
name: 'App',
// 省略了setup() {},
directives: {
editingFocus: (el, binding) => { // binding可以获取到一些参数
binding.value && el.focus()
}
}
}
use:
<input v-editing-focus="todo === editingTodo" type="text" class="edit" v-model="todo.text" @keyup.enter="doneEdit(todo)" @blur="doneEdit(todo)" @keyup.esc="cancelEdit(todo)">
. Switch to-do list
- Click CheckBox to change the status of all agents
- All/Active/Completed
other
- Show the number of outstanding items
- Remove all completed items
- If there is no to-do item, hide main and footer
// 4. 切换待办项完成状态
const useFilter = todos => {
const allDone = computed({
get: () => {
return !todos.value.filter(item => !item.completed).length
},
set: (value) => {
todos.value.forEach(todo => {
todo.completed = value
})
}
})
const filter = {
all: list => list,
active: list => list.filter(todo => !todo.completed),
completed: list => list.filter(todo => todo.completed)
}
const type = ref('all')
const filteredTodos = computed(() => filter[type.value](todos.value))
const remainingCount = computed(() => filter.active(todos.value).length)
const count = computed(() => todos.value.length)
const onHashChange = () => {
const hash = window.location.hash.replace('#/', '')
if (filter[hash]) {
type.value = hash
} else {
type.value = 'all'
window.location.hash = ''
}
}
onMounted(() => {
window.addEventListener('hashchange', onHashChange)
onHashChange()
})
onUnmounted(() => {
window.removeEventListener('hashchange', onHashChange)
})
return {
allDone,
filteredTodos,
remainingCount,
count
}
}
8. Local storage
import useLocalStorage from './utils/useLocalStorage'
const storage = useLocalStorage()
// 5. 存储待办事项
const useStorage = () => {
const KEY = 'TODOKEYS'
const todos = ref(storage.getItem(KEY) || [])
watchEffect(() => {
storage.setItem(KEY, todos.value)
})
return todos
}
utils/useLocalStorage.js
function parse(str) {
let value
try {
value = JSON.parse(str)
} catch {
value = null
}
return value
}
function stringify(obj) {
let value
try {
value = JSON.stringify(obj)
} catch {
value = null
}
return value
}
export default function useLocalStorage() {
function setItem(key, value) {
value = stringify(value)
window.localStorage.setItem(key, value)
}
function getItem(key) {
let value = window.localStorage.getItem(key)
if(value) {
value = parse(value)
}
return value
}
return {
setItem,
getItem
}
}
9. Complete code
Code address: https://gitee.com/jiailing/lagou-fed/tree/master/fed-e-task-03-05/code/04-todolist
<template>
<section id="app" class="todoapp">
<header class="header">
<h1>todos</h1>
<input type="text" class="new-todo" placeholder="What needs to be done?" autocomplete="off" autofocus v-model="input" @keyup.enter="addTodo">
</header>
<section class="main" v-show="count">
<input id="toggle-all" class="toggle-all" type="checkbox" v-model="allDone">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li v-for="todo in filteredTodos" :key="todo" :class="{editing: todo === editingTodo, completed: todo.completed}">
<div class="view">
<input type="checkbox" class="toggle" v-model="todo.completed">
<label @dblclick="editTodo(todo)">{{ todo.text }}</label>
<button class="destroy" @click="remove(todo)"></button>
</div>
<input v-editing-focus="todo === editingTodo" type="text" class="edit" v-model="todo.text" @keyup.enter="doneEdit(todo)" @blur="doneEdit(todo)" @keyup.esc="cancelEdit(todo)">
</li>
</ul>
</section>
<footer class="footer" v-show="count">
<span class="todo-count">
<strong>{{ remainingCount }}</strong> {{remainingCount > 1 ? 'items' : 'item'}} left
</span>
<ul class="filters">
<li><a href="#/all">All</a></li>
<li><a href="#/active">Active</a></li>
<li><a href="#/completed">Completed</a></li>
</ul>
<button class="clear-completed" @click="removeCompleted" v-show="count > remainingCount">Clear completed</button>
</footer>
</section>
</template>
<script>
import './assets/index.css'
import useLocalStorage from './utils/useLocalStorage'
import {
ref,
computed,
onMounted,
onUnmounted,
watchEffect
} from 'vue'
const storage = useLocalStorage()
// 1. 添加待办事项
const useAdd = todos => {
const input = ref('')
const addTodo = () => {
const text = input.value?.trim()
if (text.length === 0) return
todos.value.unshift({
text,
completed: false
})
input.value = ''
}
return {
input,
addTodo
}
}
// 2. 删除待办事项
const useRemove = todos => {
const remove = todo => {
const index = todos.value.indexOf(todo)
todos.value.splice(index, 1)
}
const removeCompleted = () => {
todos.value = todos.value.filter(todo => !todo.completed)
}
return {
remove,
removeCompleted
}
}
// 3. 编辑待办事项
const useEdit = (remove) => {
let beforeEditingText = ''
const editingTodo = ref(null)
const editTodo = todo => {
beforeEditingText = todo.text
editingTodo.value = todo
}
const doneEdit = todo => {
if (!editingTodo.value) return
todo.text = todo.text.trim()
if (todo.text === '') remove(todo)
editingTodo.value = null
}
const cancelEdit = todo => {
editingTodo.value = null
todo.text = beforeEditingText
}
return {
editingTodo,
editTodo,
doneEdit,
cancelEdit
}
}
// 4. 切换待办项完成状态
const useFilter = todos => {
const allDone = computed({
get: () => {
return !todos.value.filter(item => !item.completed).length
},
set: (value) => {
todos.value.forEach(todo => {
todo.completed = value
})
}
})
const filter = {
all: list => list,
active: list => list.filter(todo => !todo.completed),
completed: list => list.filter(todo => todo.completed)
}
const type = ref('all')
const filteredTodos = computed(() => filter[type.value](todos.value))
const remainingCount = computed(() => filter.active(todos.value).length)
const count = computed(() => todos.value.length)
const onHashChange = () => {
const hash = window.location.hash.replace('#/', '')
if (filter[hash]) {
type.value = hash
} else {
type.value = 'all'
window.location.hash = ''
}
}
onMounted(() => {
window.addEventListener('hashchange', onHashChange)
onHashChange()
})
onUnmounted(() => {
window.removeEventListener('hashchange', onHashChange)
})
return {
allDone,
filteredTodos,
remainingCount,
count
}
}
// 5. 存储待办事项
const useStorage = () => {
const KEY = 'TODOKEYS'
const todos = ref(storage.getItem(KEY) || [])
watchEffect(() => {
storage.setItem(KEY, todos.value)
})
return todos
}
export default {
name: 'App',
setup() {
const todos = useStorage()
const {
remove,
removeCompleted
} = useRemove(todos)
return {
todos,
remove,
removeCompleted,
...useAdd(todos),
...useEdit(remove),
...useFilter(todos)
}
},
directives: {
editingFocus: (el, binding) => { // binding可以获取到一些参数
binding.value && el.focus()
}
},
}
</script>
<style>
</style>
Principles of Vue.js 3.0 Responsive System
1. Introduction
1. Vue.js responsive review
- Proxy object implements property monitoring
- Multi-level attribute nesting, processing the next level of attributes in the process of accessing attributes
- Dynamically added attributes are monitored by default
- The delete operation of the default monitoring attribute
- The default monitoring array index and length property
- Can be used as a separate module
2. Core functions
- eactive/ref/toRefs/computed
- effect
- track
- trigger
Two, Proxy object review
1. In strict mode, the Proxy function must return a Boolean value, otherwise it will report TypeError
Uncaught TypeError: 'set' on proxy: trap returned falsish for property 'foo'
'use strict'
// 问题1: set和deleteProperty中需要返回布尔类型的值
// 严格模式下,如果返回false的话,会出现TypeError的异常
const target = {
foo: 'xxx',
bar: 'yyy'
}
// Reflect.getPrototypeOf()
// Object.getPrototypeOf()
const proxy = new Proxy(target, {
get (target, key, receiver) {
// return target[key]
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
// target[key] = value
return Reflect.set(target, key, value, receiver) // 这里得写return
},
deleteProperty(target, key) {
// delete target[key]
return Reflect.deleteProperty(target, key) // 这里得写return
}
})
proxy.foo = 'zzz'
2. Use receiver in Proxy and Reflect
Receiver in Proxy: Proxy or an object that inherits Proxy
Receiver in React: If the target object has a getter, this in the getter points to the receiver
const obj = {
get foo () {
console.log(this)
return this.bar
}
}
const proxy = new Proxy(obj, {
get(target, key, receiver) {
if (key === 'bar') {
return 'value - bar'
}
return Reflect.get(target, key, receiver) // 执行this.bar的时候,this指向代理对象,也就是获取target.bar
}
})
console.log(proxy.foo) // value - bar
If return Reflect.get(target, key, receiver)
written as return Reflect.get(target, key)
, the this in the responsive attribute foo still points to the original object obj, this.bar is undefined, and after the receiver is passed in, the this in the responsive attribute points to the new responsive object proxy, this .bar returns value - bar
.
Three, reactive
- Accept a parameter, determine whether this parameter is an object
- Create interceptor object handler, set get/set/deleteProperty
- Return the Proxy object
Realize reactive by yourself
function isObject(value) {
return value !== null && typeof value === 'object'
}
function convert(target) {
return isObject(target) ? reactive(target) : target
}
const hasOwnProperty = Object.prototype.hasOwnProperty
function hasOwn(target, key) {
return hasOwnProperty.call(target, key)
}
export function reactive(target) {
if (!isObject(target)) return target
const handler = {
get (target, key, receiver) {
// 收集依赖
// track(target, key) // 稍后解注释
console.log('get', key)
const ret = Reflect.get(target, key, receiver)
return convert(ret)
},
set (target, key, value, receiver) {
const oldValue = Reflect.get(target, key, receiver)
let ret = true
if (oldValue !== value) {
ret = Reflect.set(target, key, value, receiver)
// 触发更新
// trigger(target, key) // 稍后解注释
console.log('set', key, value)
}
return ret
},
deleteProperty (target, key) {
const hasKey = hasOwn(target, key)
const ret = Reflect.deleteProperty(target, key)
if (hasKey && ret) {
// 触发更新
// trigger(target, key) // 稍后解注释
console.log('detele', key)
}
return ret
}
}
return new Proxy(target, handler)
}
use:
<body>
<script type="module">
import { reactive } from './reactivity/index.js'
const obj = reactive({
name: 'zs',
age: 18
})
obj.name = 'lisi'
delete obj.age
console.log(obj)
</script>
</body>
The output is:
set name lisi
index.js:39 detele age
index.html:17 Proxy {name: "lisi"}
Fourth, collect dependencies
Five, 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)
}
Six, trigger
export function trigger(target, key) { // 触发依赖
const depsMap = targetMap.get(target)
if(!depsMap)return
const dept = depsMap.get(key)
if(dept) {
dept.forEach(effect => {
effect()
})
}
}
use:
<body>
<script type="module">
import { reactive, effect } from './reactivity/index.js'
const product = reactive({
name: 'iPhone',
price: 5000,
count: 3
})
let total = 0
effect(() => {
total = product.price * product.count
})
console.log(total) // 15000
product.price = 4000
console.log(total) // 12000
product.count = 1
console.log(total) // 4000
</script>
</body>
Seven, ref
eactive vs ref
- ref can convert basic data type data into responsive objects
- The object returned by ref is also responsive to re-assignment to an object
- Object returned by reactive, reassignment loses reactive
- The object returned by reactive cannot be deconstructed
reactive
const product = reactive({ name: 'iPhone', price: 5000, count: 3 })
ref
const price = ref(5000) const count = ref(3)
Implement 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
}
use:
<body>
<script type="module">
import { reactive, effect, ref } from './reactivity/index.js'
const price = ref(5000)
const count = ref(3)
let total = 0
effect(() => {
total = price.value * count.value
})
console.log(total) // 15000
price.value = 4000
console.log(total) // 12000
count.value = 1
console.log(total) // 4000
</script>
</body>
Eight, toRefs
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
}
use
<body>
<script type="module">
import { reactive, effect, toRefs } from './reactivity/index.js'
function useProduct() {
const product = reactive({
name: 'iPhone',
price: 5000,
count: 3
})
return toRefs(product) // 直接返回解构的product不是响应式对象,所以调用toRefs将reactive对象的每个属性都转化成ref对象
}
const { price, count } = useProduct()
let total = 0
effect(() => {
total = price.value * count.value
})
console.log(total) // 15000
price.value = 4000
console.log(total) // 12000
count.value = 1
console.log(total) // 4000
</script>
</body>
Nine, computed
export function computed(getter) {
const result = ref()
effect(() => (result.value = getter()))
return result
}
use
<body>
<script type="module">
import { reactive, effect, computed } from './reactivity/index.js'
const product = reactive({
name: 'iPhone',
price: 5000,
count: 3
})
let total = computed(() => {
return product.price * product.count
})
console.log(total.value) // 15000
product.price = 4000
console.log(total.value) // 12000
product.count = 1
console.log(total.value) // 4000
</script>
</body>
Remarks: trigger/track/effct is a low-level function, generally not used. Use computed instead of effect
Vite implementation principle
1. Introduction to Vite
1. Vite concept:
- Vite is a lighter and faster web application development tool for modern browsers
- It is based on the ECMAScript standard native module system (ES Modules) implementation
2. Vite project dependencies:
- Vite
- @vue/compiler-sfc
3. Basic use:
vite serve / vite build
When running vite serve
, there is no need to package, and directly start a web server. When the browser requests the server, such as requesting a single-file component, then compile the single-file component on the server side, and then return the compiled result to the browser. Pay attention to this The compilation is on the server side, and the processing of the modules is handled on the server side from the request.
And vue-cli-service serve
:
When running vue-cli-service serve, it will use webpack internally. First, package all the modules. If there are a large number of modules, the packaging speed will be very slow. Store the packaged results in the memory, and then Start the developed web server, the browser requests the web server, and returns the packaged results in memory directly to the browser. A tool like webpack uses all modules to be compiled and packaged into the bundle in advance, that is, regardless of whether the module is When it is executed, whether it is used or not, it must be compiled and packaged into a bundle. As the project becomes larger and larger, the packaged bundle becomes larger and larger, and the packaging speed naturally becomes slower and slower.
Vite uses the modular feature of ESModule natively supported by modern browsers to omit the packaging of modules. For files that need to be compiled, such as single-file components, style modules, etc., Vite uses another mode of instant compilation, which means that only When a file is specifically requested, the file will be compiled on the server side, so the benefits of this kind of just-in-time compilation are mainly reflected in the on-demand compilation, which will be faster.
4. HMR:
Vite HMR
- Immediately mutate the currently modified file
Webpack HMR
- It will automatically rebuild with this file location entry, and all the dependencies involved will also be reloaded once, so the response speed will be slower
5. Build:
Vite build
- Rollup
Dynamic import
- polyfill
6. Pack OR not pack:
Two reasons to use Webpack packaging:
- The browser environment does not support modularity (and now most browsers support ESM modularization)
- Scattered module files will generate a large number of HTTP requests (HTTP2 can be a long connection)
7. Browser support for ESModule:
8. Out of the box:
- TypeScript-built-in support
- less/sass/stylus/postcss-built-in support (need to install separately)
- JSX
- Web Assembly
9. Vite features
- Fast cold start
- Module hot update
- Compile on demand
- Out of the box
Two, static web server
1. Vite core functions
- Static web server
- Compile single-file components: intercept modules that are not recognized by the browser and process them
- HMR
Three, modify the path of the third-party module
Create two middleware. One middleware is to change the path in the import in the third-party module to load @modules/module file name, and the other middleware is to determine whether there is any in the request path after the request comes.
@modules/module name, if any, go to node_modules to load the corresponding module
Fourth, load third-party modules
When the request comes, determine whether the request path @modules
, if so, go to node_modules to load the corresponding module
Five, compile single file components
Send two requests, the first request is to compile the single-file component into an object, the second request is to compile the template of the single-file component, return a render function, and mount the render function to the object's render method.
Final code
#!/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. 加载第三方模块。判断请求路径中是否以`@modules`开头,如果是的话,去node_modules加载对应的模块
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) // 返回一个对象,成员descriptor、errors
let code
if (!ctx.query.type) { // 第一次请求,把单文件组件编译成一个对象
code = descriptor.script.content
// console.log('code', 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/') // 分组匹配,第一个分组中,from原样匹配form,\s+匹配一至多个空格,['"]匹配单引号或双引号。第二个分组中,?!标识不匹配这个分组的结果,也就是排除点开头或者\开头的情况
.replace(/process\.env\.NODE_ENV/g, '"development"')// 替换process对象
}
})
app.listen(4000)
console.log('Server running @ http://localhost:4000')
When using, first link the cli project to the global, npm link
Then execute my-vite-cli
run the project in the vue3 project. The image and style module import code in vue3 is commented out.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。