0

0

从零开始一步一步写一个简单的 Virtual DOM 实现 2 :Props&Event 处理_html/css_WEB-ITnose

php中文网

php中文网

发布时间:2016-06-24 11:14:57

|

1270人浏览过

|

来源于php中文网

原创

props

首先我们要回顾下前文讲的一个有些偏差的小点,假设我们在jsx中只写一个最简单的div:

Babel会自动将该JSX转化为如下的DOM表达式:

{ type: ‘’, props: null, children: [] }

注意,这里的props默认是null,我们在之前的文章中并没有关注到这个属性,而本部分则是要讲解Virtual DOM中Props的用法。一般来说,无论在哪种编程环境下都要尽量避免Null的出现,因此我们首先来改造下 h 函数,使得其能够默认返回一个空的Object,而不是Null:

function h(type, props, …children) { return { type, props: props || {}, children };}

Setting Props:设置Props

接触过React的同学对于Props肯定不会陌生,而设置Props也就跟使用普通的HTML标签属性很类似:

    而最终会转化为如下的表达式:

    立即学习前端免费学习笔记(深入)”;

    {  type: ‘ul’,  props: { className: ‘list’, style: ’list-style: none;’ }  children: []}

    props对象中的每个键即为属性名,而值为属性值,一般来说我们只需要简单的调用一个 setAttribute 方法来讲这个Props中的键值对设置到DOM元素上即可:

    function setProp($target, name, value) { $target.setAttribute(name, value);}

    这个函数用于将单个的Prop值设置到DOM元素上,而对于props对象,我们要做的就是依次遍历:

    function setProps($target, props) { Object.keys(props).forEach(name => {   setProp($target, name, props[name]); });}

    你应该还记得那个用于创建元素的 createElement 方法吧,我们需要将 setProps 方法放置到元素成功创建之后:

    function createElement(node) { if (typeof node === ‘string’) {   return document.createTextNode(node); } const $el = document.createElement(node.type); setProps($el, node.props); node.children   .map(createElement)   .forEach($el.appendChild.bind($el)); return $el;}

    不要急,这还远远不够。React的初学教程中一直强调className与class的区别,在我们的setProps中也需要对于这些JS的保留字做一个替换,譬如:

    另外,还有比较常见的就是对于DOM的布尔属性,譬如checked、disabled等等的处理:

    在真实的DOM节点上,如果是出现了false的情况,我们并不希望checked属性会出现,那么我们的Props函数就要能智能地进行判断:

    function setBooleanProp($target, name, value) { if (value) {   $target.setAttribute(name, value);   $target[name] = true; } else {   $target[name] = false; }}

    最后呢,要做的就是对于自定义的,即非标准的HTML属性进行一个过滤,这些属性只应该出现在JS对象上,而不应该出现在真实的DOM对象上:

    function isCustomProp(name) { return false;}
    function setProp($target, name, value) { if (isCustomProp(name)) {   return; } else if (name === ‘className’) {   $target.setAttribute(‘class’, value); } else if (typeof value === ‘boolean’) {   setBooleanProp($target, name, value); } else {   $target.setAttribute(name, value); }}

    总结一下,本部分完整的JSX代码为:

    /** @jsx h */function h(type, props, ...children) {  return { type, props: props || {}, children };}function setBooleanProp($target, name, value) {  if (value) {    $target.setAttribute(name, value);    $target[name] = true;  } else {    $target[name] = false;  }}function isCustomProp(name) {  return false;}function setProp($target, name, value) {  if (isCustomProp(name)) {    return;  } else if (name === 'className') {    $target.setAttribute('class', value);  } else if (typeof value === 'boolean') {    setBooleanProp($target, name, value);  } else {    $target.setAttribute(name, value);  }}function setProps($target, props) {  Object.keys(props).forEach(name => {    setProp($target, name, props[name]);  });}function createElement(node) {  if (typeof node === 'string') {    return document.createTextNode(node);  }  const $el = document.createElement(node.type);  setProps($el, node.props);  node.children    .map(createElement)    .forEach($el.appendChild.bind($el));  return $el;}//--------------------------------------------------const f = (  
    • item 1
    );const $root = document.getElementById('root');$root.appendChild(createElement(f));

    Diffing Props:Props变化比较

    现在我们已经创建了带有Props属性的元素,下一个需要考虑的就是应该如何应用到我们上文提到的Diff算法中。首先我们要来看下如何从真实的DOM中移除某些Props:

    function removeBooleanProp($target, name) { $target.removeAttribute(name); $target[name] = false;}function removeProp($target, name, value) { if (isCustomProp(name)) {   return; } else if (name === ‘className’) {   $target.removeAttribute(‘class’); } else if (typeof value === ‘boolean’) {   removeBooleanProp($target, name); } else {   $target.removeAttribute(name); }}

    然后我们需要写一个updateProp函数,来根据新旧节点的Props的变化进行恰当的真实DOM节点的修改,共有以下几种情况:

    • 新节点移除了某个旧节点的Prop

    BGremover
    BGremover

    VanceAI推出的图片背景移除工具

    下载
    • 新节点添加了某个旧节点没有的Prop

    • 新旧节点的某个Prop的值发生了变化

    根据以上规则,我们可知更新Prop的函数为:

    function updateProp($target, name, newVal, oldVal) { if (!newVal) {   removeProp($target, name, oldVal); } else if (!oldVal || newVal !== oldVal) {   setProp($target, name, newVal); }}

    可以看出,更新单个Prop的函数还是非常简单的,就是将移除与设置结合起来使用,那么我们扩展到Props,就得到如下的函数:

    function updateProps($target, newProps, oldProps = {}) {  const props = Object.assign({}, newProps, oldProps);  Object.keys(props).forEach(name => {    updateProp($target, name, newProps[name], oldProps[name]);  });}

    同样地,我们需要将该更新函数添加到 updateElement 函数中:

    function updateElement($parent, newNode, oldNode, index = 0) {  ...  } else if (newNode.type) {    updateProps(      $parent.childNodes[index],      newNode.props,      oldNode.props    );     ...  }}

    Events

    用户交互是任何一个应用不可或缺的部分,而在这里我们讨论下如何为Virtual DOM添加事件处理的能力,React大概会这么做:

    可以看出,设置一个事件处理器就是添加一个Prop,只不过名称会以 on 开始,那么我们可以用如下函数来判断某个Prop是否与事件相关:

    function isEventProp(name) { return /^on/.test(name);}

    判断是事件类型之后,我们可以提取出事件名:

    function extractEventName(name) { return name.slice(2).toLowerCase();}

    看到这里,估计你会考虑直接将事件处理也放到setProps与updateProps函数中,不过这边就会存在一个问题,在diffProps的时候,你很难去比较两个function:

    因此我们将所有的事件类型的Props认为是自定义的Props,这样我们上面提到的isCustomProp就起作用了:

    function isCustomProp(name) { return isEventProp(name);}

    而把事件响应函数绑定到真实的DOM节点也很简单:

    function addEventListeners($target, props) { Object.keys(props).forEach(name => {   if (isEventProp(name)) {     $target.addEventListener(       extractEventName(name),       props[name]     );   } });}

    同样的需要将该函数添加到createElement中:

    function createElement(node) { if (typeof node === ‘string’) {   return document.createTextNode(node); } const $el = document.createElement(node.type); setProps($el, node.props); addEventListeners($el, node.props); node.children   .map(createElement)   .forEach($el.appendChild.bind($el)); return $el;}

    Re-Adding Events:重新设置了事件响应

    在这里我们暂时不考虑地很复杂,即不深入地比较那些事件类型的Prop发生变化的情况,作为替代的,我们引入一个forceUpdate属性,即强制整个DOM进行更新:

    function changed(node1, node2) { return typeof node1 !== typeof node2 ||        typeof node1 === ‘string’ && node1 !== node2 ||        node1.type !== node2.type ||        node.props.forceUpdate;}
    function isCustomProp(name) { return isEventProp(name) || name === ‘forceUpdate’;}

    最后,本文完整的JSX为:

    /** @jsx h */function h(type, props, ...children) {  return { type, props: props || {}, children };}function setBooleanProp($target, name, value) {  if (value) {    $target.setAttribute(name, value);    $target[name] = true;  } else {    $target[name] = false;  }}function removeBooleanProp($target, name) {  $target.removeAttribute(name);  $target[name] = false;}function isEventProp(name) {  return /^on/.test(name);}function extractEventName(name) {  return name.slice(2).toLowerCase();}function isCustomProp(name) {  return isEventProp(name) || name === 'forceUpdate';}function setProp($target, name, value) {  if (isCustomProp(name)) {    return;  } else if (name === 'className') {    $target.setAttribute('class', value);  } else if (typeof value === 'boolean') {    setBooleanProp($target, name, value);  } else {    $target.setAttribute(name, value);  }}function removeProp($target, name, value) {  if (isCustomProp(name)) {    return;  } else if (name === 'className') {    $target.removeAttribute('class');  } else if (typeof value === 'boolean') {    removeBooleanProp($target, name);  } else {    $target.removeAttribute(name);  }}function setProps($target, props) {  Object.keys(props).forEach(name => {    setProp($target, name, props[name]);  });}function updateProp($target, name, newVal, oldVal) {  if (!newVal) {    removeProp($target, name, oldVal);  } else if (!oldVal || newVal !== oldVal) {    setProp($target, name, newVal);  }}function updateProps($target, newProps, oldProps = {}) {  const props = Object.assign({}, newProps, oldProps);  Object.keys(props).forEach(name => {    updateProp($target, name, newProps[name], oldProps[name]);  });}function addEventListeners($target, props) {  Object.keys(props).forEach(name => {    if (isEventProp(name)) {      $target.addEventListener(        extractEventName(name),        props[name]      );    }  });}function createElement(node) {  if (typeof node === 'string') {    return document.createTextNode(node);  }  const $el = document.createElement(node.type);  setProps($el, node.props);  addEventListeners($el, node.props);  node.children    .map(createElement)    .forEach($el.appendChild.bind($el));  return $el;}function changed(node1, node2) {  return typeof node1 !== typeof node2 ||         typeof node1 === 'string' && node1 !== node2 ||         node1.type !== node2.type ||         node1.props && node1.props.forceUpdate;}function updateElement($parent, newNode, oldNode, index = 0) {  if (!oldNode) {    $parent.appendChild(      createElement(newNode)    );  } else if (!newNode) {    $parent.removeChild(      $parent.childNodes[index]    );  } else if (changed(newNode, oldNode)) {    $parent.replaceChild(      createElement(newNode),      $parent.childNodes[index]    );  } else if (newNode.type) {    updateProps(      $parent.childNodes[index],      newNode.props,      oldNode.props    );    const newLength = newNode.children.length;    const oldLength = oldNode.children.length;    for (let i = 0; i < newLength || i < oldLength; i++) {      updateElement(        $parent.childNodes[index],        newNode.children[i],        oldNode.children[i],        i      );    }  }}//---------------------------------------------------------function log(e) {  console.log(e.target.value);}const f = (  
    • alert('hi!')}>item 1
    • {/* this node will always be updated */}
    • text
    );const g = (
    • alert('hi!')}>item 1
    • {/* this node will always be updated */}
    • text
    );const $root = document.getElementById('root');const $reload = document.getElementById('reload');updateElement($root, f);$reload.addEventListener('click', () => { updateElement($root, g, f);});

    到这里我们就完成了一个最简单的Virtual DOM算法,不过其与真正能够投入实战的Virtual DOM算法还是有很大距离,进一步阅读推荐:

    • 深度剖析:如何实现一个 Virtual DOM 算法

    • 我的前端故事----React算法又是个什么鬼?!

    • A Virtual DOM and diffing algorithm :一个比较复杂的Virtual DOM算法的实现

    • simple-virtual-dom :一个简单的Virtual DOM的实现

    • how-to-write-your-own-virtual-dom

    • Virtual DOM Benchmark

    HTML速学教程(入门课程)
    HTML速学教程(入门课程)

    HTML怎么学习?HTML怎么入门?HTML在哪学?HTML怎么学才快?不用担心,这里为大家提供了HTML速学教程(入门课程),有需要的小伙伴保存下载就能学习啦!

    下载

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

    热门AI工具

    更多
    DeepSeek
    DeepSeek

    幻方量化公司旗下的开源大模型平台

    豆包大模型
    豆包大模型

    字节跳动自主研发的一系列大型语言模型

    通义千问
    通义千问

    阿里巴巴推出的全能AI助手

    腾讯元宝
    腾讯元宝

    腾讯混元平台推出的AI助手

    文心一言
    文心一言

    文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

    讯飞写作
    讯飞写作

    基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

    即梦AI
    即梦AI

    一站式AI创作平台,免费AI图片和视频生成。

    ChatGPT
    ChatGPT

    最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

    相关专题

    更多
    C++ 设计模式与软件架构
    C++ 设计模式与软件架构

    本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

    14

    2026.01.30

    c++ 字符串格式化
    c++ 字符串格式化

    本专题整合了c++字符串格式化用法、输出技巧、实践等等内容,阅读专题下面的文章了解更多详细内容。

    9

    2026.01.30

    java 字符串格式化
    java 字符串格式化

    本专题整合了java如何进行字符串格式化相关教程、使用解析、方法详解等等内容。阅读专题下面的文章了解更多详细教程。

    12

    2026.01.30

    python 字符串格式化
    python 字符串格式化

    本专题整合了python字符串格式化教程、实践、方法、进阶等等相关内容,阅读专题下面的文章了解更多详细操作。

    4

    2026.01.30

    java入门学习合集
    java入门学习合集

    本专题整合了java入门学习指南、初学者项目实战、入门到精通等等内容,阅读专题下面的文章了解更多详细学习方法。

    20

    2026.01.29

    java配置环境变量教程合集
    java配置环境变量教程合集

    本专题整合了java配置环境变量设置、步骤、安装jdk、避免冲突等等相关内容,阅读专题下面的文章了解更多详细操作。

    18

    2026.01.29

    java成品学习网站推荐大全
    java成品学习网站推荐大全

    本专题整合了java成品网站、在线成品网站源码、源码入口等等相关内容,阅读专题下面的文章了解更多详细推荐内容。

    19

    2026.01.29

    Java字符串处理使用教程合集
    Java字符串处理使用教程合集

    本专题整合了Java字符串截取、处理、使用、实战等等教程内容,阅读专题下面的文章了解详细操作教程。

    3

    2026.01.29

    Java空对象相关教程合集
    Java空对象相关教程合集

    本专题整合了Java空对象相关教程,阅读专题下面的文章了解更多详细内容。

    6

    2026.01.29

    热门下载

    更多
    网站特效
    /
    网站源码
    /
    网站素材
    /
    前端模板

    精品课程

    更多
    相关推荐
    /
    热门推荐
    /
    最新课程
    关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
    php中文网:公益在线php培训,帮助PHP学习者快速成长!
    关注服务号 技术交流群
    PHP中文网订阅号
    每天精选资源文章推送

    Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号