简易版Vue
实现ref功能
ref功能主要是收集依赖和触发依赖的过程。
javascript
export class Dep { // 创建一个类,使用时使用new Dep
constructor(value) { // 初始化可传入一个值
this._val = value;
this.effects = new Set(); //收集依赖的容器,使用set数据结构
}
get value() { //对value值进行拦截,获取时收集依赖到effects中
this.depend();
return this._val;
}
set value(val) { // 设置的同时触发所有收集到的依赖
this._val = val;
this.notice()
}
depend() {
if (currentEffect) {
this.effects.add(currentEffect); //将依赖放入effects中
}
}
notice() {
this.effects.forEach( fn => { //触发所有收集的依赖
fn();
})
}
}
let currentEffect = null;
export function effectWatch(fn) { //收集依赖的函数,所有函数必须用这个函数包裹
currentEffect = fn;
fn()
currentEffect = null;
}
javascript
// index.js
const a = new Dep(10);
let b = 0;
effectWatch( () => {
b = a.value + 10;
console.log(b)
})
a.value = 20;
实现reactive功能
reactive主要是让对象也可以进行依赖的收集,这就需要为对象的每一个key创建对应的Dep。
javascript
const targetsMap = new Map(); // 用map数据结构来存储,因为它的key可以是对象
export function reactive(raw) { //传入的raw是一个对象
return new Proxy(raw, { //拦截raw上所有的get和set
get(target, key) {
let depMap = targetsMap.get(raw); // 为每一个raw创建对应的map
if (!depMap) {
depMap = new Map();
targetsMap.set(raw, depMap);
}
let dep = depMap.get(key); // 给raw上的每一个值创建Dep
if (!dep) {
dep = new Dep();
depMap.set(key, dep);
}
dep.depend();
return Reflect.get(target, key)
},
set(target, key, value) {
let depMap = targetsMap.get(raw);
if (!depMap) {
depMap = new Map();
targetsMap.set(raw, depMap);
}
let dep = depMap.get(key);
if (!dep) {
dep = new Dep();
depMap.set(key, dep);
}
const result = Reflect.set(target, key, value)
dep.notice();
return result;
}
})
}
javascript
//test
const user = reactive({
age: 10
})
let nextAge = 0;
effectWatch( () => {
nextAge = user.age + 1;
console.log(nextAge);
})
user.age++;
简易版Vue雏形
使用上面的reactive和effectWatch功能可以实现miniVue的雏形
javascript
import { effectWatch, reactive } from './core/index.js';
const App = {
render(context) {
effectWatch(() => {
document.querySelector('#app').textContent = '';
const element = document.createElement('div');
const text = document.createTextNode('nihao');
const text1 = document.createTextNode(context.obj.count);
element.append(text);
element.append(text1);
document.querySelector('#app').append(element)
})
},
setup() {
const obj = reactive({
count: 1
})
window.obj = obj
return{
obj
}
}
}
App.render(App.setup())
//通过在console中输入obj.count的值修改视图
优化
将代码抽离,effectWatch在框架中调用,视图的清空和append也在框架中调用
javascript
export function createApp(rootComponent) {
return {
mount(rootContainer) {
const setupResult = rootComponent.setup();
effectWatch(() => {
rootContainer.textContent = '';
const element = rootComponent.render(setupResult);
rootContainer.append(element);
})
}
}
}
export const App = {
render(context) {
const element = document.createElement('div');
const text = document.createTextNode('nihao');
const text1 = document.createTextNode(context.obj.count);
element.append(text);
element.append(text1);
return element;
},
setup() {
const obj = reactive({
count: 1
})
window.obj = obj
return {
obj
}
}
}
优化并使用虚拟Dom
在上面的代码中,每次都会更新所有节点,需要进行优化,只更新变化的节点
将节点关键信息转化成一个对象。props是一个对象,代表节点上的attrs,children是一个数组,可以有多个
javascript
export function h(tag, props, children) {
return {
tag,
props,
children
}
}
在App中
javascript
import { reactive, h } from './core/index.js';
export const App = {
render(context) {
return h('div', {}, [h('p', {}, 'nihao'), h('p', {}, context.obj.count)])
},
setup() {
const obj = reactive({
count: 1
})
window.obj = obj
return {
obj
}
}
}
此时获取的element只是一个对象,需要将其映射成真实的Dom
映射真实Dom
依次处理tag props 和children,把他们变成真实的节点
javascript
function createElement(tag) {
return document.createElement(tag);
}
function patchProps(el, key, prevValue, nextValue) {
el.setAttribute(key, nextValue);
}
export function mountElement(element, root) {
const { tag, props, children } = element;
const el = createElement(tag);
for (const key in props) {
const val = props[key];
patchProps(el, key, null, val);
}
if (typeof children === 'string') {
const textNode = document.createTextNode(children);
el.append(textNode)
} else if (Array.isArray(children)) {
children.forEach((v) => {
mountElement(v, el)
})
}
root.append(el)
}