3月31日去颐和园转了一圈, 拍的比较满意的几张照片前言本文主要参考了preact的源码准备工作我们首先搭建开发的环境, 我们选择webpack4。值得注意的是, 因为我们需要解析JSX的语法, 我们需要使用@babel/plugin-transform-react-jsx插件。@babel/plugin-transform-react-jsx插件会将JSX语法做出以下格式的转换。@babel/plugin-transform-react-jsx默认使用React.createElement, 我们可以通过设置插件的pragma配置项, 修改默认的函数名// beforevar profile = <div> <img src=“avatar.png” className=“profile” /> <h3>{[user.firstName, user.lastName].join(’ ‘)}</h3></div>;// aftervar profile = React.createElement(“div”, null, React.createElement(“img”, { src: “avatar.png”, className: “profile” }), React.createElement(“h3”, null, [user.firstName, user.lastName].join(" “)));const webpack = require(‘webpack’)const HtmlWebpackPlugin = require(‘html-webpack-plugin’)const path = require(‘path’)const HappyPack = require(‘happypack’)module.exports = { devtool: ‘#cheap-module-eval-source-map’, mode: ‘development’, target: ‘web’, entry: { main: path.resolve(__dirname, ‘./example/index.js’) }, devServer: { host: ‘0.0.0.0’, port: 8080, hot: true }, resolve: { extensions: [’.js’] }, module: { rules: [ { test: /.js$/, exclude: /node_modules/, use: ‘happypack/loader?id=js’ }, { test: /.css$/, use: [ { loader: ‘style-loader’ }, { loader: ‘css-loader’ } ] } ] }, plugins: [ new webpack.HotModuleReplacementPlugin(), new HappyPack({ id: ‘js’, threads: 4, use: [ { loader: ‘babel-loader’, options: { presets: [’@babel/preset-env’], plugins: [ ‘@babel/plugin-syntax-dynamic-import’, [ “@babel/plugin-transform-react-jsx”, { pragma: ‘h’ } ] ] } } ] }), new HtmlWebpackPlugin({ template: path.resolve(__dirname, ‘./public/index.html’) }) ]}上面是完整的打包配置(如果严格来说, 类库应该单独打包的)。同时我们将@babel/plugin-transform-react-jsx插件, pragma参数设置为"h”。我们在使用的时候, 只需要在文件中引入h函数即可。创建VNode我们在这里将会实现h方法, h方法的作用是创建一个VNode。根据编译结果可知, h函数的参数如下。/** * type为VNode的类型 * props为VNode的属性 * childrens为VNode的子节点, 可能用多组子节点, 我们使用es6的rest参数 */h(type, props, …childrens)VNode本质就是Javascript中对象, 因此h函数只需要返回对应的对象即可。export function createElement (type, props, …children) { if (!props) props = {} props.children = […children] let key = props.key if (key) { delete props.key } return createVNode(type, props, null, key)}export function createVNode (type, props, text, key) { const VNode = { type, props, text, key, _dom: null, _children: null, _component: null } return VNode}我们来使用一下,看一下h函数返回的结果, h函数返回的结果即是虚拟DOMimport { h } from ‘yy-react’console.log( <div> <h1>Hello</h1> <h1>World</h1> </div>)实现render我们可以参考React的render函数的实现, render函数接受两个参数, React元素(VNode)以及container(挂载的DOM)。我们将要把VNode渲染成了真实的DOM节点。下面是render函数的实现, 我们在本期还没有来得及实现Diff方法, 读者可以不用关注于这些。整体代码的实现,参考(抄)了preact的源码的实现????。(我还给preact的项目提交了pr????,不过还没有merge????)???? 文章的最后是具体实现, 但是一大坨对阅读不是很友好,不想看的可以略过,直接看解说。我们首先将视角转向render, render函数里调用里diff函数, 将返回的dom挂载到document中。_prevVNode等属性我们会在以后用到,目前可以忽略。export function render (vnode, root) { let oldVNode = root._prevVNode let newVNode = root._prevVNode = vnode let dom = oldVNode ? oldVNode._dom : null let mounts = [] let newDom = diff(dom, root, newVNode, oldVNode, mounts) if (newDom) { root.appendChild(newDom) }}在diff中,我们将对节点类型做出判断, VNode类型可以是普通的节点也可以是组件类型的节点, 我们这里先对普通类型的节点做出处理。function diff ( dom, root, newVNode, oldVNode, mounts, force) { let newType = newVNode.type if (typeof newType === ‘function’) { // render component } else { dom = diffElementNodes( dom, newVNode, oldVNode, mounts ) } newVNode._dom = dom return dom}我们接着将目光转向diffElementNodes函数, 在diffElementNodes函数中我们会根据具体节点类型创建对应的真实的DOM节点。例如文本类型的节点我们使用createTextNode, 而普通类型的我们使用createElement因为整个VNode呈现的一种树状结构, 面对树状结构免不了使用递归去遍历每一颗节点。我们这里将创建后dom,作为父节点传入diffChildren函数中(新创建的节点会append到这个父节点中)。递归的转换的每一个子节点以及子节点的子节点。由此我们也可知道,整个VNode树的渲染的顺序是由外向里的。但是设置VNode的props的顺序则是由里向外的。function diffElementNodes (dom, newVNode, oldVNode, mounts) { if (!dom) { dom = newVNode.type === null ? document.createTextNode(newVNode.text) : document.createElement(newVNode.type) } newVNode._dom = dom if (newVNode.type) { if (newVNode !== oldVNode) { let newProps = newVNode.props let oldProps = oldVNode.props if (!oldProps) { oldProps = {} } diffChildren(dom, newVNode, oldVNode, mounts) diffProps(dom, newProps, oldProps) } } return dom}在diffChildren中, 我们将VNode的子VNode挂载到_children属性上, 遍历每一个子节点, 将子节点带入到diff中, 完成创建的过程function diffChildren ( root, newParentVNode, oldParentVNode, mounts) { let oldVNode, newVNode, newDom, i, j, index, p, oldChildrenLength let newChildren = newParentVNode._children || toChildVNodeArray(newParentVNode.props.children, newParentVNode._children = []) for (i = 0; i < newChildren.length; i++) { newVNode = newChildren[i] oldVNode = index = null newDom = diff( oldVNode ? oldVNode._dom : null, root, newVNode, oldVNode, mounts, null ) if (newVNode && newDom) { root.appendChild(newDom) } }}我们在遍历递归完子节点后, 就可以使用diffProps来设置我们的root节点了。我们遍历newProps中的每一个key, 并使用setProperty将props设置到dom上, setProperty中对一些dom属性做了特殊的处理。比如处理了驼峰的css的key, 和数字的value自动添加px等。function diffProps (dom, newProps, oldProps) { for (let key in newProps) { if ( key !==‘children’ && key!==‘key’ && ( !oldProps || ((key === ‘value’ || key === ‘checked’) ? dom : oldProps)[key] !== newProps[key] ) ) { setProperty(dom, key, newProps[key], oldProps[key]) } }}function setProperty (dom, name, value, oldValue) { if (name === ‘style’) { let s = dom.style if (typeof value === ‘string’) { s.cssText = value } else { if (typeof oldValue === ‘string’) { s.cssText = ’’ } else { for (let i in oldValue) { if (value==null || !(i in value)) { s.setProperty(i.replace(CAMEL_REG, ‘-’), ‘’) } } } for (let i in value) { v = value[i]; if (oldValue==null || v!==oldValue[i]) { s.setProperty(i.replace(CAMEL_REG, ‘-’), typeof v===‘number’ && IS_NON_DIMENSIONAL.test(i)===false ? (v + ‘px’) : v) } } } } else if (value == null) { dom.removeAttribute(name) } else if (typeof value !== ‘function’) { dom.setAttribute(name, value) }}最后我们再次回到render函数,render函数最后的会将创建好的dom, append到挂载的dom中完成渲染。root.appendChild(newDom)完整示例github的仓库地址将在完成后放出// create-element.jsexport function render (vnode, root) { let oldVNode = root._prevVNode let newVNode = root._prevVNode = vnode let dom = oldVNode ? oldVNode._dom : null let mounts = [] let newDom = diff(dom, root, newVNode, oldVNode, mounts) if (newDom) { root.appendChild(newDom) } runDidMount(mounts, vnode)}// diff.jsfunction diff ( dom, root, newVNode, oldVNode, mounts, force) { if (oldVNode == null || newVNode == null || newVNode.type !== oldVNode.type) { if (!newVNode) return null dom = null oldVNode = {} } let newType = newVNode.type if (typeof newType === ‘function’) { // render component } else { dom = diffElementNodes( dom, newVNode, oldVNode, mounts ) } newVNode._dom = dom return dom}function diffElementNodes (dom, newVNode, oldVNode, mounts) { if (!dom) { dom = newVNode.type === null ? document.createTextNode(newVNode.text) : document.createElement(newVNode.type) } newVNode._dom = dom if (newVNode.type) { if (newVNode !== oldVNode) { let newProps = newVNode.props let oldProps = oldVNode.props if (!oldProps) { oldProps = {} } diffChildren(dom, newVNode, oldVNode, mounts) diffProps(dom, newProps, oldProps) } } return dom}// diff-children.jsfunction diffChildren ( root, newParentVNode, oldParentVNode, mounts) { let oldVNode, newVNode, newDom, i, j, index, p, oldChildrenLength let newChildren = newParentVNode._children || toChildVNodeArray(newParentVNode.props.children, newParentVNode._children = []) for (i = 0; i < newChildren.length; i++) { newVNode = newChildren[i] oldVNode = index = null newDom = diff( oldVNode ? oldVNode._dom : null, root, newVNode, oldVNode, mounts, null ) if (newVNode && newDom) { root.appendChild(newDom) } }}// diffProps.jsfunction diffProps (dom, newProps, oldProps) { for (let key in newProps) { if ( key !==‘children’ && key!==‘key’ && ( !oldProps || ((key === ‘value’ || key === ‘checked’) ? dom : oldProps)[key] !== newProps[key] ) ) { setProperty(dom, key, newProps[key], oldProps[key]) } } for (let key in oldProps) { }}// diff-propsfunction diffProps (dom, newProps, oldProps) { for (let key in newProps) { if ( key !==‘children’ && key!==‘key’ && ( !oldProps || ((key === ‘value’ || key === ‘checked’) ? dom : oldProps)[key] !== newProps[key] ) ) { setProperty(dom, key, newProps[key], oldProps[key]) } } for (let key in oldProps) { }}function setProperty (dom, name, value, oldValue) { if (name === ‘style’) { let s = dom.style if (typeof value === ‘string’) { s.cssText = value } else { if (typeof oldValue === ‘string’) { s.cssText = ’’ } else { for (let i in oldValue) { if (value==null || !(i in value)) { s.setProperty(i.replace(CAMEL_REG, ‘-’), ‘’) } } } for (let i in value) { v = value[i]; if (oldValue==null || v!==oldValue[i]) { s.setProperty(i.replace(CAMEL_REG, ‘-’), typeof v===‘number’ && IS_NON_DIMENSIONAL.test(i)===false ? (v + ‘px’) : v) } } } } else if (value == null) { dom.removeAttribute(name) } else if (typeof value !== ‘function’) { dom.setAttribute(name, value) }}其他preact源码分析(一)preact源码分析(二)preact源码分析(三)preact源码分析(四)preact源码分析(五)