2019. 10.5 released a Vue3.0
preview version of the source code, but it is expected to wait until the first quarter of 2020, the first official version 3.0 release possible.
You can see directly github source.
The new Vue 3.0
major infrastructure improvements and new features planned and realized:
-
Compiler (Compiler)
- Use modular architecture
- Optimization "Block tree"
- More radical static tree hoisting function (detection of static grammar, a lift)
- Support Source map
- Built-identifier prefix (also known as "stripWith")
- Built-in neat print (pretty-printing) function
- After removing the prefix and the identifier Source map function, use Brotli compressed version of the browser to streamline the approximately 10KB
-
Runtime (Runtime)
- Speed significantly improved
- Composition API supports and Options API, and typings
- Proxy-based data change detection implemented
- Fragments supported (allows a plurality of root component has from)
- Support Portals (rendered in other locations to allow the DOM)
- 支持 Suspense w/ async setup()
Not currently supported
IE11
1. analyze Vue Composition API
You can see the official address
- Vue 3 use
ts
to achieve a type inference, the new api in all normal function, when writing code can enjoy the full type inference (avoid using decorator) - Among multi-component logic to solve the reuse problem (to solve: the high-order components, mixin, scope slots)
- Composition API easy to use
First look at the effect of early adopters Vue3.0
<script src="vue.global.js"></script> <div id="container"></div> <script> function usePosition(){ // 实时获取鼠标位置 let state = Vue.reactive({x:0,y:0}); function update(e) { state.x= e.pageX state.y = e.pageY } Vue.onMounted(() => { window.addEventListener('mousemove', update) }) Vue.onUnmounted(() => { window.removeEventListener('mousemove', update) }) return Vue.toRefs(state); } const App = { setup(){ // Composition API 使用的入口 const state = Vue.reactive({name:'youxuan'}); // 定义响应数据 const {x,y} = usePosition(); // 使用公共逻辑 Vue.onMounted(()=>{ console.log('当组挂载完成') }); Vue.onUpdated(()=>{ console.log('数据发生更新') }); Vue.onUnmounted(()=>{ console.log('组件将要卸载') }) function changeName(){ state.name = 'webyouxuan'; } return { // 返回上下文,可以在模板中使用 state, changeName, x, y } }, template:`<button @click="changeName">{{state.name}} 鼠标x: {{x}} 鼠标: {{y}}</button>` } Vue.createApp().mount(App,container); </script>
Here you will find a responsive is the
Vue
soul
2. The analysis of the source directory
packages directory contains Vue3.0
all the features
├── packages
│ ├── compiler-core # 所有平台的编译器
│ ├── compiler-dom # 针对浏览器而写的编译器
│ ├── reactivity # 数据响应式系统
│ ├── runtime-core # 虚拟 DOM 渲染器 ,Vue 组件和 Vue 的各种API
│ ├── runtime-dom # 针对浏览器的 runtime。其功能包括处理原生 DOM API、DOM 事件和 DOM 属性等。 │ ├── runtime-test # 专门为测试写的runtime │ ├── server-renderer # 用于SSR │ ├── shared # 帮助方法 │ ├── template-explorer │ └── vue # 构建vue runtime + compiler
compiler compiler-core
main function is to compile the exposure API
and baseCompile
a method compiler-dom
based on compiler-core
the package for the browser compiler
(browser process tag)
runtime runtime-core
virtual DOM renderer, and the various components of the API Vue Vue of runtime-test
the DOM
structure into an object format, to facilitate testing runtime-dom
based runtime-core
browser is written runtime
(increasing change search node additions and deletions, style, etc.) to return render
, createApp
method
reactivity
separate data responsive system, the core method reactive
, effect
, ref
,computed
vue
integration compiler
+ runtime
We resolved this
Vue3.0
directory structure, the entire project as a whole is still very clear
Taste fresh again:
We can look at how to use the official test casesVue3.0
const app = {
template:`<div>{{count}}</div>`,
data(){
return {count:100} }, } let proxy = Vue.createApp().mount(app,container); setTimeout(()=>{ proxy.count = 200; },2000)
Now let's compare the difference in principle responsive and Vue 2 Vue 3
3.Vue2.0 responsive principle mechanism - defineProperty
This principle is nothing new, that is 拦截对象
, to increase the object's properties set
and get
methods, because the core is defineProperty
so needed for an array of methods to intercept
3.1 pairs of objects intercept
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方法中触发更新 } } }) } let obj = {name:'youxuan'} observer(obj); obj.name = 'webyouxuan';
3.2 array method hijacking
let oldProtoMehtods = Array.prototype;
let proto = Object.create(oldProtoMehtods);
['push','pop','shift','unshift'].forEach(method=>{ Object.defineProperty(proto,method,{ get(){ update(); oldProtoMehtods[method].call(this,...arguments) } }) }) 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]) } }
test
let obj = {hobby:[{name:'youxuan'},'喝']} observer(obj) obj.hobby[0].name = 'webyouxuan'; // 更改数组中的对象也会触发试图更新 console.log(obj)
Here the process is not dependent on the collection described in detail, we focus on
Vue3.0
the
-
Object.defineProperty shortcomings
- Unable to monitor changes in the array
- We need deep traversal, a waste of memory
4.Vue3.0 data response mechanism - Proxy
Before learning Vue3.0, you must first master the ES6 in the Proxy , Reflect and ES6 as we provide the Map , the Set two data structures
Principle applied first to say:
let p = Vue.reactive({name:'youxuan'});
Vue.effect(()=>{ // effect方法会立即被触发 console.log(p.name); }) p.name = 'webyouxuan';; // 修改属性后会再次触发effect方法
Source code is the use of
ts
writing, in order to facilitate better understanding of the principles, here we use js to write from zero, then look at the source code it is very easy!
4.1 reactive way to achieve
Custom acquisition by proxy, add, delete and other acts
function reactive(target){
// 创建响应式对象 return createReactiveObject(target); } function isObject(target){ return typeof target === 'object' && target!== null; } function createReactiveObject(target){ // 判断target是不是对象,不是对象不必继续 if(!isObject(target)){ return target; } const handlers = { get(target,key,receiver){ // 取值 console.log('获取') let res = Reflect.get(target,key,receiver); return res; }, set(target,key,value,receiver){ // 更改 、 新增属性 console.log('设置') let result = Reflect.set(target,key,value,receiver); return result; }, deleteProperty(target,key){ // 删除属性 console.log('删除') const result = Reflect.deleteProperty(target,key); return result; } } // 开始代理 observed = new Proxy(target,handlers); return observed; } let p = reactive({name:'youxuan'}); console.log(p.name); // 获取 p.name = 'webyouxuan'; // 设置 delete p.name; // 删除
How do we continue to consider a multi-layer proxy object implementation
let p = reactive({ name: "youxuan", age: { num: 10 } }); p.age.num = 11
Since we only agents of the first layer of the object, so theage
object will not trigger the change is set methods, but it triggeredget
method, which is due top.age
causeget
operation
get(target, key, receiver) {
// 取值
console.log("获取");
let res = Reflect.get(target, key, receiver); return isObject(res) // 懒代理,只有当取值时再次做代理,vue2.0中一上来就会全部递归增加getter,setter ? reactive(res) : res; }
Here we willp.age
take to the proxy object again, so to change the value to triggerset
method
We continue to consider an array of issues
we can find Proxy
the default array can support, including changes in the length of the array as well as changes in index values
let p = reactive([1,2,3,4]); p.push(5);
But this will trigger twoset
methods, the first update of the array is the first4
item, the second is an array of updatedlength
Let masked trigger several times, update
set(target, key, value, receiver) {
// 更改、新增属性
let oldValue = target[key]; // 获取上次的值
let hadKey = hasOwn(target,key); // 看这个属性是否存在 let result = Reflect.set(target, key, value, receiver); if(!hadKey){ // 新增属性 console.log('更新 添加') }else if(oldValue !== value){ // 修改存在的属性 console.log('更新 修改') } // 当调用push 方法第一次修改时数组长度已经发生变化 // 如果这次的值和上次的值一样则不触发更新 return result; }
Solving repeated use of reactive cases
// 情况1.多次代理同一个对象
let arr = [1,2,3,4]; let p = reactive(arr); reactive(arr); // 情况2.将代理后的结果继续代理 let p = reactive([1,2,3,4]); reactive(p);
By
hash表
way to solve the case of duplicate proxy
const toProxy = new WeakMap(); // 存放被代理过的对象
const toRaw = new WeakMap(); // 存放已经代理过的对象 function reactive(target) { // 创建响应式对象 return createReactiveObject(target); } function isObject(target) { return typeof target === "object" && target !== null; } function hasOwn(target,key){ return target.hasOwnProperty(key); } function createReactiveObject(target) { if (!isObject(target)) { return target; } let observed = toProxy.get(target); if(observed){ // 判断是否被代理过 return observed; } if(toRaw.has(target)){ // 判断是否要重复代理 return target; } const handlers = { get(target, key, receiver) { // 取值 console.log("获取"); let res = Reflect.get(target, key, receiver); return isObject(res) ? reactive(res) : res; }, set(target, key, value, receiver) { let oldValue = target[key]; let hadKey = hasOwn(target,key); let result = Reflect.set(target, key, value, receiver); if(!hadKey){ console.log('更新 添加') }else if(oldValue !== value){ console.log('更新 修改') } return result; }, deleteProperty(target, key) { console.log("删除"); const result = Reflect.deleteProperty(target, key); return result; } }; // 开始代理 observed = new Proxy(target, handlers); toProxy.set(target,observed); toRaw.set(observed,target); // 做映射表 return observed; }
To this
reactive
method basically completed, the next step is to Vue2 logic implementations rely collect and update as trigger
get(target, key, receiver) {
let res = Reflect.get(target, key, receiver);
+ track(target,'get',key); // 依赖收集
return isObject(res)
?reactive(res):res;
},
set(target, key, value, receiver) {
let oldValue = target[key];
let hadKey = hasOwn(target,key);
let result = Reflect.set(target, key, value, receiver);
if(!hadKey){
+ trigger(target,'add',key); // 触发添加
}else if(oldValue !== value){
+ trigger(target,'set',key); // 触发修改
}
return result;
}
the role of track is dependent on the collection, compilation of theeffect
, let's implementeffect
the principle, after the perfectiontrack
andtrigger
methods
4.2 effect to achieve
effect means that the side effects, this method will first perform a default. If the data changes trigger this callback again.
let school = {name:'youxuan'}
let p = reactive(school);
effect(()=>{ console.log(p.name); // youxuan })
We do this effect
method, we need to be effect
packaged into a responsive method effect
.
function effect(fn) {
const effect = createReactiveEffect(fn); // 创建响应式的effect effect(); // 先执行一次 return effect; } const activeReactiveEffectStack = []; // 存放响应式effect function createReactiveEffect(fn) { const effect = function() { // 响应式的effect return run(effect, fn); }; return effect; } function run(effect, fn) { try { activeReactiveEffectStack.push(effect); return fn(); // 先让fn执行,执行时会触发get方法,可以将effect存入对应的key属性 } finally { activeReactiveEffectStack.pop(effect); } }
When you call fn()
you may trigger get
method, this time will triggertrack
const targetMap = new WeakMap();
function track(target,type,key){ // 查看是否有effect const effect = activeReactiveEffectStack[activeReactiveEffectStack.length-1]; if(effect){ let depsMap = targetMap.get(target); if(!depsMap){ // 不存在map targetMap.set(target,depsMap = new Map()); } let dep = depsMap.get(target); if(!dep){ // 不存在set depsMap.set(key,(dep = new Set())); } if(!dep.has(effect)){ dep.add(effect); // 将effect添加到依赖中 } } }
When the update property will trigger trigger
execution, to find out the corresponding set of stored effect
sequentially executed
function trigger(target,type,key){
const depsMap = targetMap.get(target); if(!depsMap){ return } let effects = depsMap.get(key); if(effects){ effects.forEach(effect=>{ effect(); }) } }
We have found the following problems
let school = [1,2,3];
let p = reactive(school); effect(()=>{ console.log(p.length); }) p.push(100);
Added value,effect
the method does not re-run, becausepush
the modificationlength
we have been masked the triggertrigger
method, when the new item should manually triggerlength
property in the dependency.
function trigger(target, type, key) {
const depsMap = targetMap.get(target); if (!depsMap) { return; } let effects = depsMap.get(key); if (effects) { effects.forEach(effect => { effect(); }); } // 处理如果当前类型是增加属性,如果用到数组的length的effect应该也会被执行 if (type === "add") { let effects = depsMap.get("length"); if (effects) { effects.forEach(effect => { effect(); }); } } }
4.3 ref achieve
ref primitive data types can also be converted into a responsive data, need to .value
get the value attribute
function convert(val) {
return isObject(val) ? reactive(val) : val; } function ref(raw) { raw = convert(raw); const v = { _isRef:true, // 标识是ref类型 get value() { track(v, "get", ""); return raw; }, set value(newVal) { raw = newVal; trigger(v,'set',''); } }; return v; }
The problem again we have to write cases
let r = ref(1);
let c = reactive({
a:r
});
console.log(c.a.value);
To do so would mean that every time more than one
.value
, so too hard to use
In get
determining if the acquisition process is ref
of value, this value is value
returned directly to
let res = Reflect.get(target, key, receiver);
if(res._isRef){
return res.value
}
4.4 computed achieve
computed
Implementations are based effect
to achieve, is characterized by computed
the function will not be executed immediately, there is many times the value of caching mechanism
First look at usage:
let a = reactive({name:'youxuan'});
let c = computed(()=>{ console.log('执行次数') return a.name +'webyouxuan'; }) // 不取不执行,取n次只执行一次 console.log(c.value); console.log(c.value);
function computed(getter){
let dirty = true; const runner = effect(getter,{ // 标识这个effect是懒执行 lazy:true, // 懒执行 scheduler:()=>{ // 当依赖的属性变化了,调用此方法,而不是重新执行effect dirty = true; } }); let value; return { _isRef:true, get value(){ if(dirty){ value = runner(); // 执行runner会继续收集依赖 dirty = false; } return value; } } }
Modification effect
method
function effect(fn,options) {
let effect = createReactiveEffect(fn,options); if(!options.lazy){ // 如果是lazy 则不立即执行 effect(); } return effect; } function createReactiveEffect(fn,options) { const effect = function() { return run(effect, fn); }; effect.scheduler = options.scheduler; return effect; }
In trigger
judging
deps.forEach(effect => {
if(effect.scheduler){ // 如果有scheduler 说明不需要执行effect
effect.scheduler(); // 将dirty设置为true,下次获取值时重新执行runner方法 }else{ effect(); // 否则就是effect 正常执行即可 } });
let a = reactive({name:'youxuan'});
let c = computed(()=>{ console.log('执行次数') return a.name +'webyouxuan'; }) // 不取不执行,取n次只执行一次 console.log(c.value); a.name = 'zf10'; // 更改值 不会触发重新计算,但是会将dirty变成true console.log(c.value); // 重新调用计算方法
This will beVue3.0
coreComposition Api
to explain finished! Whether interview or late applications are also no longer have to worry about it! ~