Loading

VUE3小实验 - 渲染 + 响应式数据

小实验 - 渲染 + 响应式数据

渲染


  • 这里写一个编译时的小编译器。

前面说了HTML是树型结构的

<body>
	<main></main>
	<footer></footer>
</body>

而JS恰好拥有各类元素的创建结构(DOM为JS提供的控制文档树的结构)

document.createElement(); // div input a ...

所以我们可以使用一种树型的数据结构,并根据这个树型的数据结构在内存中把文档树生成出来,随后绑定在真实文档树上显示出来。

  • 树型数据结构
  • 几番考虑之下,这里选择如下的数据结构
type HTMLNode = {
    tag: string,
    id?: string,
    children?: HTMLNode[], // 子
    innerText?: string,
    props?: [
        {
            propname: string,
            value: string
        }
    ],
    methods?: [
        {
            funcName: string,
            funcBody: (...args: any[]) => any
        }
    ]
}
  • 渲染过程也很好理解
    1. 先根据tag创建一个HTMLElement
    2. 随后查阅当前的各种属性是否存在,如果存在则主动添加到这个HTMLElement上
    3. 最终把这个HTMLElement返回回来
function render(node: HTMLNode): HTMLElement {
    // 1. TAG to Element
    let curElement = document.createElement(node.tag);
    if (node.id) {
        curElement.id = node.id;
    }    
    if (node.innerText) {
        console.log("节点的内部是", node.innerText);
        curElement.innerText = node.innerText as string;
    }

    if (node.methods) {
        for (let methods of node.methods) {
            // @ts-ignore
            curElement.addEventListener(methods.funcName, methods.funcBody);
        }
    }

    if (node.props) {
        for (let prop of node.props) {
            curElement.setAttribute(prop.propname, prop.value); // 属性赋值 
        }
    }

    if (node.children) {
        for (let child of node.children) {
            curElement.appendChild(render(child));
        }
    }
    return curElement;
}
  • 如此我们就已经可以利用这个小编译器画一些东西出来

Untitled

  • 代码贴在最后

响应

  • 创建代理的过程就好像是把数据的使用权交给某个新的数据

Untitled

  • 当代理人遇到修改数据的请求时,可以先自己斟酌,随后传递给数据的所有者。

Untitled

  • 具体代码
const 大聪明 = {
	name: "大聪明",
	age: 9999,
	des: "身高八尺,力大无穷~",
	告诉(消息) {
		// 告诉大聪明
	}
}

const 代理小伙 = new Proxy(大聪明, {
	// 代理信息
	见(大聪明, 请求) {
		let 思考结果 = 深思熟虑(请求); // 好
		大聪明.告诉(思考结果)
	}
})
  • 那么回到响应设计中来,当数据发生变化的时候,我们希望可以追踪这些数据的变化,并且及时反馈到屏幕上,具体表现为数据的响应式更新。
  • 我们固然可以写一个setInterval(), 每当发现数据不一致就更新,但是性能不高。
  • 代理就是我们的新选择。
  • 我们把访问过这些数据的所有函数存在一个盒子里面(直接与页面展示有关的)
  • 当数据发生更新的时候就通过js操作DOM修改页面中的数据。

Untitled

为了使得所有函数都能装载进去,我们给传入的渲染函数重命名一下

let affectFunc: AnyFunc | undefined = undefined; // 未知或者是一个函数
const effect = (callFunc: AnyFunc) => {
    affectFunc = callFunc; // 当前副作用函数赋值
    console.log("当前affectFunc", affectFunc);
    return callFunc();  // 内部可以包裹参数
}

这就是我们要的渲染函数盒子

随后写一下代理人工厂函数:

  1. get事件,获取这个数据中某个属性时触发,那么我们可以在此处添加当前的渲染函数到盒子(WeakMapBucket)
  2. set事件,当出现重新赋值的时候,说明数据发生了改变,那么应当对绑定在当前数据中的所有数据进行重新渲染。
function reactive<T extends object>(data: T) {
        let curVariableFuncMap: VarBucket;
        const dataProxy = new Proxy(data, {
            // 对数据采用代理
            
            get(target, key) {
                console.log("Get 事件");
                if (affectFunc) {
                    if (!WeakMapBucket.get(target)) {
                        WeakMapBucket.set(target, (curVariableFuncMap = new Map())); // 创建一个函数存储
                    }
                    let propertyFuncMap: Set<AnyFunc> | undefined = curVariableFuncMap.get(key);
                    if (!propertyFuncMap) {
                        // 当前键下未发现集合, 创建集合 
                        curVariableFuncMap.set(key, (propertyFuncMap = new Set<AnyFunc>()));
                    }
                    propertyFuncMap.add(affectFunc);
                }

                // @ts-ignore
                return target[key];
            },
            set(target, key, newVal) {
                // 修改时
                // @ts-ignore
                target[key] = newVal;
                let curVariableFuncMap = WeakMapBucket.get(target);
                    
                if (curVariableFuncMap) {
                    curVariableFuncMap.get(key)?.forEach(func => {
                        console.log(func);
                        func();
                    });
                }
                return true;
            }
        })
        return dataProxy;
    }

最后为了使得所有响应式数据都存在一起,我们使用闭包把这些数据封装在一起。

function cluster(): {
    effect: AnyFunc,
    reactive: AnyFunc
} {
    let affectFunc: AnyFunc | undefined = undefined; // 未知或者是一个函数
    const effect = (callFunc: AnyFunc) => {
        affectFunc = callFunc; // 当前副作用函数赋值
        console.log("当前affectFunc", affectFunc);
        return callFunc();  // 内部可以包裹参数
    }
    const WeakMapBucket: FuncBucket = new WeakMap<FuncBucket>(); // 创造一个weakmap
    function reactive<T extends object>(data: T) {
        let curVariableFuncMap: VarBucket;
        const dataProxy = new Proxy(data, {
            // 对数据采用代理
            
            get(target, key) {
                console.log("Get 事件");
                if (affectFunc) {
                    if (!WeakMapBucket.get(target)) {
                        WeakMapBucket.set(target, (curVariableFuncMap = new Map())); // 创建一个函数存储
                    }
                    let propertyFuncMap: Set<AnyFunc> | undefined = curVariableFuncMap.get(key);
                    if (!propertyFuncMap) {
                        // 当前键下未发现集合, 创建集合 
                        curVariableFuncMap.set(key, (propertyFuncMap = new Set<AnyFunc>()));
                    }
                    propertyFuncMap.add(affectFunc);
                }

                // @ts-ignore
                return target[key];
            },
            set(target, key, newVal) {
                // 修改时
                // @ts-ignore
                target[key] = newVal;
                let curVariableFuncMap = WeakMapBucket.get(target);
                    
                if (curVariableFuncMap) {
                    curVariableFuncMap.get(key)?.forEach(func => {
                        console.log(func);
                        func();
                    });
                }
                return true;
            }
        })
        return dataProxy;
    }

    return {
        effect,
        reactive
    };
}
  • 最终导出effect 效果盒子, 以及 reactive 响应式数据构造函数

至此我们以及写好了一个简单的渲染 + 响应式的小引擎了。

小实验 做一个简单的小页面

import { HTMLNode } from "./types.js"; // 类型
import {render, effect, reactive, getElementProperty} from './utils/utils.js'; // 导入

const data = reactive({
    username: "",
    age: 0
}); // 创造响应式数数据

const header: HTMLNode = {
    tag: 'p',
    innerText: "记录",
    props: [
        {
            propname: 'style',
            value: "font-family: FangSong; font-size: 24px; color: #578689"
        }
    ]
}

const inputName: HTMLNode = {
    tag: 'input',
    id: "username",
    props: [
        {
            propname: "type",
            value: "text"
        }
    ],
    methods: [
        {
            funcName: "input",
            funcBody: () => {
                data.username = getElementProperty('username', 'value'); // 拿到value数值绑定
            }
        }
    ]
}

const ageArea: HTMLNode = {
    tag: 'input',
    id: 'age',
    props: [
        {
            propname: "type",
            value: "number"
        }
    ],
    methods: [
        {
            funcName: "input",
            funcBody: () => {
                data.age = getElementProperty('age', 'value'); // 拿到value数值绑定
            }
        }
    ]
}

const showArea: HTMLNode = {
    tag: 'div',
    id: 'show',
}

const divArea: HTMLNode = {
    tag: 'div',
    props: [
        {
            propname: "style",
            value: "background-color: #f3f3f3; display: flex; flex-direction: column; text-align: center"
        }
    ],
    children: [
        header,
        inputName,
        ageArea,
        showArea
    ]
}

document.body.appendChild(render(divArea));

effect(() => {
    // 第一个效果
    document.querySelector('#show')!.innerHTML = "我叫" + data.username + "今年" + data.age.toString() + "岁"; 
})

Untitled

QQ录屏20220904180909.mp4

  • 所有types
export type HTMLNode = {
    id?: string,
    tag: string,
    children?: HTMLNode[], // 子
    innerText?: string | ((...args: any[]) => string),
    props?: [
        {
            propname: string,
            value: string
        }
    ],
    methods?: [
        {
            funcName: string,
            funcBody: (...args: any[]) => any
        }
    ]
}

export type AnyFunc = (...args: any[]) => any

export type FuncBucket = WeakMap<object, Map<string | symbol, Set<AnyFunc>>>
export type VarBucket = Map<string | symbol, Set<AnyFunc>>;
posted @ 2022-09-04 18:16  MushRain  阅读(72)  评论(0编辑  收藏  举报