手撸类React框架(1)渲染DOM元素

手撸类React框架(1)渲染DOM元素

标签&分类
Javascript
干货
发表时间
Mar 8, 2023 08:48 AM
描述
通过模仿,来搞清楚内部原理
更新时间
Last updated March 13, 2023

渲染DOM元素

DOM API

常用的DOM API:
// Get an element by id const domRoot = document.getElementById("root"); // Create a new element given a tag name const domInput = document.createElement("input"); // Set properties domInput["type"] = "text"; domInput["value"] = "Hi world"; domInput["className"] = "my-class"; // Listen to events domInput.addEventListener("change", e => alert(e.target.value)); // Create a text node const domText = document.createTextNode(""); // Set text node content domText["nodeValue"] = "Foo"; // Append an element domRoot.appendChild(domInput); // Append a text node (same as previous) domRoot.appendChild(domText);

虚拟DOM 元素对象Element

首先我们需要用对象来描述HTML
<div id="container"> <input value="foo" type="text"> <a href="/bar"></a> <span>Foo</span> </div>
const element = { type: "div", props: { id: "container", children: [ { type: "input", props: { value: "foo", type: "text" } }, { type: "a", props: { href: "/bar" } }, { type: "span", props: { onClick: e => alert("Hi"), children: [ { type: "TEXT ELEMENT", // 1 props: { nodeValue: "Foo" } // 2 } ] } } ] } };

渲染虚拟DOM元素

写一个函数来把这个element对象,渲染成为真实的Dom
const render = function(element, parentElement) { const {type, props} = element; //单独对文字进行处理 const isTextElement = type === 'TEXT ELEMENT'; const dom = isTextElement ? document.createTextNode('') : document.createElement(type); // 处理onClick 等事件绑定 约定点击事件格式为onEventName(click,change)等... const isListener = name => name.startsWith('on'); Object.keys(props).filter(isListener).forEach(name => { const evenType = name.toLowerCase().substring(2); dom.addEventListener(evenType, props[name]); }) // 处理属性 const isAttribute = name => !isListener(name) && name != 'children'; Object.keys(props).filter(isAttribute).forEach(name => { dom[name] = props[name]; }) // 处理子级元素 const childElements = props.children || []; childElements.forEach(childElement => { return render(childElement, dom) }); if(!parentElement.lastChild) { parentElement.appendChild(dom); } else { parentElement.replaceChild(dom, parentElement.lastChild) } }
好的,现在我们已经迈出了重要的一步了~!
但是这个虚拟DOM对于我们来说还是太麻烦了,直接用JSX语法糖多好,对吧
所以,我们需要用到babel框架,来把JSX生成我们需要的结构:
const element = createElement( "div", { id: "container" }, createElement("input", { value: "foo", type: "text" }), createElement( "a", { href: "/bar" }, "bar" ), createElement( "span", { onClick: e => alert("Hi") }, "click me" ) );
所以我们需要一个函数createElement需要创建一个props对象,将其分配给第二个参数中的所有值,将该children属性设置为第二个参数后面的所有参数,然后返回一个对象{}- 带有 {type, props }
const createElement = (type, config, ...args) => { const props = {...config}; const childrens = !!args.length ? [].concat(...args) : []; props.children = childrens.filter(c => c!= null && c != false).map(c=> c instanceof Object ? c : createTextElement(c)); return {type , props} } // 单独处理文本元素 const createTextElement = (value) => { return createElement(TEXT_ELEMENT, { nodeValue: value }); }

开发环境搭建

需要用到Webpack,Babel
npx webpack init ? Which of the following JS solutions do you want to use? ES6 ? Do you want to use webpack-dev-server? Yes ? Do you want to simplify the creation of HTML files for your bundle? Yes ? Do you want to add PWA support? No ? Which of the following CSS solutions do you want to use? CSS only ? Will you be using PostCSS in your project? No ? Do you want to extract CSS for every file? No ? Do you like to install prettier to format generated configuration? No ? Pick a package manager: npm
然后需要引入JSX语法支持
npm install --save-dev @babel/plugin-transform-react-jsx
修改webpack配置文件, 修改pragma 可以自定义输出的函数名称
test: /\.(js|jsx)$/i, loader: 'babel-loader', options: { presets: ['@babel/preset-env'], // 引入插件, 修改pragma plugins: [['@babel/plugin-transform-react-jsx', {pragma: 'createElement'}]], },
运行npm run serve 就能在浏览器看到了