Vue VDOM 渲染 diff 简易版

从零开始创建一项目骨架

项目目录
在这里插入图片描述

npm init -y

npm install webpack webpack-cli html-webpack-plugin css-loader style-loader less-loader less file-loader clean-webpack-plugin extract-text-webpack-plugin webpack-merge webpack-dev-server -D

安装好依赖项,我这里简单做了下webpack dev prod合并

webpack.base.config.js

// 生产和开发环境公用部分
const path = require('path')
const {CleanWebpackPlugin} = require('clean-webpack-plugin') // 新版
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')


module.exports = {
  entry:{
    index:'./src/index.js'
  },
  module: {
    rules:[
      {
        test:/\.css$/,
        use:ExtractTextWebpackPlugin.extract({
          fallback:'style-loader',
          use:'css-loader'
        })
      },
      {
        test:/\.less$/,
        use:ExtractTextWebpackPlugin.extract({
          fallback:'style-loader',
          use:['css-loader','less-loader']
        })
      },
      {
        test:/\.(jpg|png|svg)$/,
        loader:['file-loader']
      },
    ]
  },
  plugins:[
    new HtmlWebpackPlugin({
      template:'./src/index.html',
    }),
    new ExtractTextWebpackPlugin("style.css"),
    new CleanWebpackPlugin(),
  ]
}

webpack.dev.config.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
const webpackMerge = require('webpack-merge')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const baseConfig = require('./webpack.base.config')

const dist_dir = 'dev_dist'

module.exports = webpackMerge(baseConfig,{
  mode:'development',
  output: {
    path:path.resolve(__dirname,dist_dir)
  },
  devServer: { // 开发环境运行 浏览器端口9000
    contentBase: path.join(__dirname, dist_dir),
    compress: true,
    port: 9000
  },
  plugins:[
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template:'./src/index.html',
    }),
    new ExtractTextWebpackPlugin("style.css"),
  ]
})

webpack…prod.config.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
const webpackMerge = require('webpack-merge')
const baseConfig = require('./webpack.base.config')

const dist_dir = 'prod_dist'

module.exports = webpackMerge(baseConfig,{
  mode:'production',
  output: {
    path:path.resolve(__dirname,dist_dir)
  },
  plugins:[
    new HtmlWebpackPlugin({
      template:'./src/index.html',
      title:"diff dev"
    }),
    new ExtractTextWebpackPlugin("style.css"),
  ]
})

package.json

{
  "name": "webpack-diff",
  "version": "1.0.0",
  "description": "",
  "main": "webpack.dev.config.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "serve": "webpack-dev-server --open --config webpack.dev.config.js",
    "build": "webpack --config webpack.prod.config.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^3.4.0",
    "extract-text-webpack-plugin": "^4.0.0-beta.0", // 注意版本
    "file-loader": "^5.0.2",
    "html-webpack-plugin": "^3.2.0",
    "less": "^3.10.3",
    "less-loader": "^5.0.0",
    "style-loader": "^1.1.2",
    "webpack": "^4.41.4",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.10.1",
    "webpack-merge": "^4.2.2"
  }
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    hello world
  </div>
</body>
</html>

index.css

#app{
  color:red
}

#app span{
  font-size: 16px;
}

index.js

import './index.css'

function main() {
  console.log('测试运行')
}
main()

npm run serve

在这里插入图片描述

至此一个简单的webpack工程项目建立起来了。现在我们开始正题。

先简述模板转换成视图的过程

在这里插入图片描述

虚拟DOM

Virtual DOM 其实就是一棵以 JavaScript 对象( VNode 节点)作为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM 的抽象。最终可以通过一系列操作使这棵树映射到真实环境上。

类似于以下结构,

{
	type:"" //标签
	key:"",
	props:{},
	{},
	{}, // 作为子节点
}

vdom/index.js

import createElement from './h.js'
import { render} from './patch'
export {
  createElement,
  render
}

vnode/h.js

import { vnode } from './vnode'
export default function createElement(type, props = {}, ...children) {
  let key;
  if (props.key) {
    key = props.key;
    delete props.key;
  }
  // 将不是虚拟节点的子节点
  children =children.map(child=>{
    if(typeof child==='string'){
      return vnode(undefined,undefined,undefined,undefined,child)
    }else {
      return child
    }
  })
  return vnode(type, key, props, children)
}


vnode/vnode.js

export function vnode(type, key, props, children, text){
  return {
    type,
    key,
    props,
    children,
    text
  }
}

vnode/patch.js

/**
 * @param {} vnode // 用户写的虚拟节点
 * @param {} container // 渲染到某个容器
 */

export function render(vnode, container) {
  let ele = createDomElementFromVnode(vnode);
  container.appendChild(ele)
}


// 通过虚拟DOM创建真实的DOM
function createDomElementFromVnode(vnode) {
  let {
    type,
    key,
    props,
    children,
    text
  } = vnode;
  if (type) { // 说明是一个标签
    vnode.domElement = document.createElement(type);
    updateProperties(vnode) //根据当前的虚拟节点的属性,更新真实的DOM
    // 遍历子节点
    children.forEach(childVnode => render(childVnode, vnode.domElement))
  } else {
    vnode.domElement = document.createTextNode(text)
  }
  return vnode.domElement
}


// 后续比对的时候,会根据老的属性 和 新的属性 重新更新节点
function updateProperties(newVnode, oldProps = {}) {
  let domElement = newVnode.domElement; //真实的dom元素
  let newProps = newVnode.props; // 当前虚拟节点的属性

  // 属性如果老的里面有,新的没有 将移除
  for (let oldPropName in oldProps) {
    if (!newProps[oldPropName]) {
      delete domElement[oldPropName]
    }
  }

  // 同一 style属性,旧得移除 新的覆盖
  let newStyleObj = newProps.style || {}
  let oldStyleObj = newProps.style || {}

  for (let propName in oldStyleObj) {
    if (!newStyleObj[propName]) {
      domElement.style[propName] = ''
    }
  }



  // 如果老的没有,新的里面有 将覆盖老节点
  for (let newPropName in newProps) {
    if (newPropName === 'style') { // 还有事件 
      let styleObj = newProps.style;
      for (let s in styleObj) {
        domElement.style[s] = styleObj[s]
      }
    } else {
      domElement[newPropName] = newProps[newPropName]
    }

  }
}
测试

src/index.js

import { createElement, render } from './vdom'
let vnode = createElement('div',{id:'wrap1',key:'4'},createElement('div',{id:'wrap2',key:'3'},'测试二'),'测试一')

render(vnode,app) // app会自动识别#app

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值