当前位置: 首页 > 编程日记 > 正文

从Preact了解一个类React的框架是怎么实现的(一): 元素创建

首先欢迎大家关注我的掘金账号和Github博客,也算是对我的一点鼓励,毕竟写东西没法获得变现,能坚持下去也是靠的是自己的热情和大家的鼓励。
  之前分享过几篇关于React的文章:

  • React技术内幕: key带来了什么
  • React技术内幕: setState的秘密

其实我在阅读React源码的时候,真的非常痛苦。React的代码及其复杂、庞大,阅读起来挑战非常大,但是这却又挡不住我们的React的原理的好奇。前段时间有人就安利过Preact,千行代码就基本实现了React的绝大部分功能,相比于React动辄几万行的代码,Preact显得别样的简洁,这也就为了我们学习React开辟了另一条路。本系列文章将重点分析类似于React的这类框架是如何实现的,欢迎大家关注和讨论。如有不准确的地方,欢迎大家指正。
  
  关于Preact,官网是这么介绍的:

Fast 3kb React alternative with the same ES6 API. Components & Virtual DOM.

我们用Preact编写代码就雷同于React,比如举个例子:

import { Component , h } from 'preact'
export default class TodoList extends Component {state = { todos: [], text: '' };setText = e => {this.setState({ text: e.target.value });};addTodo = () => {let { todos, text } = this.state;todos = todos.concat({ text });this.setState({ todos, text: '' });};render({ }, { todos, text }) {return (<form onSubmit={this.addTodo} action="javascript:"><input value={text} onInput={this.setText} /><button type="submit">Add</button><ul>{ todos.map( todo => (<li>{todo.text}</li>)) }</ul></form>);}
}复制代码

上面就是用Preact编写TodoList的例子,掌握React的你是不是感觉再熟悉不过了,上面的例子和React不太相同的地方是render函数有参数传入,分别是render(props,state,context),其目的是为了你解构赋值方便,当然你仍然可以render函数中通过this来引用propsstatecontext。语法方面我们不再多做赘述,现在正式开始我们的内容。

本人还是非常推崇React这一套机制的,React这套机制提我们完成了数据和视图的绑定,使得开发人员只需要关注数据和数据流的改变,从而极大的降低的开发的关注度,使得我们能够集中精力于数据本身。而且React引入了虚拟DOM(virtual-dom)的机制,从而提升渲染性能。在开始接触React时,觉得虚拟DOM机制十分的高大上,但经过一段时间的学习,开始对虚拟DOM有了进一步的认识。虚拟DOM从本质上将就是将复杂的DOM转化成轻量级的JavaScript对象,不同的渲染中会生成同的虚拟DOM对象,然后通过高效优化过的Diff算法,比较前后的虚拟DOM对象,以最小的变化去更新真实DOM。

正如上面的图,其实类React的框架的代码都基本可以分为两部分,组件到虚拟DOM的转化、以及虚拟DOM到真实DOM的映射。当然细节性的东西还有非常多,比如生命周期、事件机制(代理)、批量刷新等等。其实Preact精简了React中的很多部分,比如React中采用的是事件代理机制,Preact就没这么做。这篇文章将着重于叙述Preact的JSX与组件相关的部分代码。
  
  最开始学习React的时候,以为JSX是React的所独有的,现在其实明白了JSX语法并不是某个库所独有的,而是一种JavaScript函数调用的语法糖。我们举个例子,假如有下面的代码:

import ReactDOM from 'react-dom'const App = (props)=>(<div>Hello World</div>)
ReactDOM.render(<APP />, document.body);复制代码

请问可以执行吗?事实上是不能只能的,浏览器会告诉你:

Uncaught ReferenceError: React is not defined

如果你不了解JSX你就会感觉奇怪,因为没有地方显式地调用React,但是事实上上面的代码确实用到了React模块,奥秘就在于JSX。JSX其实相当于JavaScript + HTML(也被称为hyperscript,即hyper + script,hyper是HyperText超文本的简写,而script是JavaScript的简写)。JSX并不属于新的语法,其目的也只是为了在JavaScript脚本中更方便的构建UI视图,相比于其他的模板语言更加的易于上手,提升开发效率。上面的实例如果经过Babel转化其实会得到下面结果:

var App = function App(props) {return React.createElement('div',null,'Hello World');
};复制代码

我们可以看到,之前的JSX语法都被转换成函数React.createElement的调用方式。这就是为什么在React中有JSX的地方都需要显式地引入React的原因,也是为什么说JSX只是JavaScript的语法糖。但是按照上面的说法,所有的JSX语法都会被转化成React.createElement,那岂不是JSX只是React所独有的?当然不是,比如下面代码:

/** @jsx h */
let foo = <div id="foo">Hello!</div>;复制代码

我们通过为JSX添加注释@jsx(这也被成为Pragma,即编译注释),可以使得Babel在转化JSX代码时,将其装换成函数h的调用,转化结果成为:

/** @jsx h */
var foo = h("div",{ id: "foo" },"Hello!"
);复制代码

当然在每个JSX上都设置Pragma是没有必要的,我们可以在工程全局进行配置,比如我们可以在Babel6中的.babelrc文件中设置:

{"plugins": [["transform-react-jsx", { "pragma":"h" }]]
}复制代码

这样工程中所有用到JSX的地方都是被Babel转化成使用h函数的调用。
  
  
  说了这么多,我们开始了解一下Preact是怎么构造h函数的(关于为什么Preact将其称为h函数,是因为作为hyperscript的缩写去命名的),Preact对外提供两个接口: hcreateElement,都是指向函数h:

import {VNode} from './vnode';const stack = [];const EMPTY_CHILDREN = [];export function h(nodeName, attributes) {let children = EMPTY_CHILDREN, lastSimple, child, simple, i;for (i = arguments.length; i-- > 2;) {stack.push(arguments[i]);}if (attributes && attributes.children != null) {if (!stack.length) stack.push(attributes.children);delete attributes.children;}while (stack.length) {if ((child = stack.pop()) && child.pop !== undefined) {for (i = child.length; i--;) stack.push(child[i]);}else {if (typeof child === 'boolean') child = null;if ((simple = typeof nodeName !== 'function')) {if (child == null) child = '';else if (typeof child === 'number') child = String(child);else if (typeof child !== 'string') simple = false;}if (simple && lastSimple) {children[children.length - 1] += child;}else if (children === EMPTY_CHILDREN) {children = [child];}else {children.push(child);}lastSimple = simple;}}let p = new VNode();p.nodeName = nodeName;p.children = children;p.attributes = attributes == null ? undefined : attributes;p.key = attributes == null ? undefined : attributes.key;return p;
}复制代码

函数h接受两个参数节点名nodeName,与属性attributes。然后将除了前两个之外的参数都压如栈stack。这种写法挺令人吐槽的,写成h(nodeName, attributes, ...children)不是一目了然吗?因为h的参数是不限的,从第三个参数起的所有参数都是节点的子元素,所以栈存储的是当前元素的子元素。然后会再排除一下第二个参数(其实就是props)中是否含有children属性,有的话也将其压如栈中,并且从attributes中删除。然后循环遍历栈中的每一个子元素:

  • 首先判别该元素是不是数组类型,这里采用的就是鸭子类型(duck type),即看起来来一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子,我们在这里通过是否含有函数pop去判别是否是一个数组,如果子元素是一个数组,就将其全部压入栈中。为什么这么做呢?因为子元素有可能是数组,比如:
    render(){
    return(<ul>{[1,2,3].map((val)=><li>{val}</li>)}</ul>
    )
    }复制代码
  • 因为子元素是不支持布尔类型的,因此将其置为: null。 如果传入的节点不是函数的话,分别判断如果是null,则置为空字符,如果是数字的话,将其转化成字符串类型。变量simple用来记录节点是否是简单类型,比如dom名称或者函数就不属于,如果是字符串或者是数字,就会被认为是简单类型

  • 然后代码

    if (simple && lastSimple) {children[children.length - 1] += child;
    }复制代码

    其实做的就是一个字符串拼接,lastSimple是用来记录上次的节点是否是简单类型。之所以这么做,是因为某些编译器会将下面代码

    let foo = <div id="foo">Hello World!</div>;复制代码

    转化为:

    var foo = h(
    "div",
    { id: "foo" },
    "Hello",
    "World!"
    );复制代码

    这是时候h函数就会将后两个参数拼接成一个字符串。

  • 最后将处理子节点的传入数组children中,现在传入children中的节点有三种类型: 纯字符串、代表dom节点的字符串以及代表组件的函数(或者是类)

函数结束循环遍历之后,创建了一个VNODE,并将nodeNamechildrenattributeskey都赋值到节点中。需要注意的是,VNODE只是一个普通的构造函数:

function VNode() {}复制代码

说了这么多,我们看几个转化之后的例子:

//jsx
let foo = <div id="foo">Hello World!</div>;  //js
var Element = h("div",{ id: "foo" },"Hello World!"
);//转化为的元素节点
{nodeName: "div", children: ["Hello World!"], attributes: {id: "foo"},key: undefined
}复制代码
/* jsx
class App extends Component{
//....
}class Child extends Component{
//....
}
*/let Element = <App><Child>Hello World!</Child></App>//js
var Element = h(App,null,h(Child,null,"Hello World!")
);//转化为的元素节点
{nodeName: ƒ App(argument), children: [{nodeName: ƒ Child(argument),children: ["Hello World!"],attributes: undefined,key: undefined}], attributes: undefined,key: undefined
}复制代码

上面JSX元素转化成的JavaScript对象就是DOM在内存中的表现。在Preact中不同的数据会生成不同的虚拟DOM节点,通过比较前后的虚拟DOM节点,Preact会找出一种最简单的方式去更新真实DOM,以使其匹配当前的虚拟DOM节点,当然这会在后面的系列文章讲到,我们会将源码和概念分割成一块块内容,方便大家理解,这篇文章着重讲述了Preact的元素创建与JSX,之后的文章会继续围绕Preact类似于diff、组件设计等概念展开,欢迎大家关注我的账号获得最新的文章动态。

相关文章:

呵呵,哈哈,嘿嘿,从今天起就开始写博客文了

第一篇嘛&#xff0c;完完全全的水篇&#xff0c;因为确实不知道该写些什么好啦&#xff0c;恩&#xff0c;哈&#xff0c;以后就多写一些的了&#xff0c;嘘&#xff0c;玩别的去了&#xff01;拜拜&#xff01;转载于:https://www.cnblogs.com/thinkgao/archive/2011/04/26/2…

UI设计培训怎么选择就业方向

相信大部分人学习UI设计就是为了找到一份不错的工作&#xff0c;那么UI设计培训怎么选择就业方向呢?UI设计师有哪些就业方向选择呢?来看看下面的详细介绍吧。 UI设计培训怎么选择就业方向? 图形设计&#xff1a;图形设计不仅仅是美术的设计。在UI设计的工作中&#xff0c;图…

用JavaScript获取URL中的参数值

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"> <title>测试JS获取…

LeetCode实战:最长回文子串

题目英文 Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000. Example 1: Input: "babad" Output: "bab" Note: "aba" is also a valid answer.Example 2: Input: &quo…

2017年9月11日 梁勇 java教材 编程练习题 第二章 2.15 键盘 读取两个点的坐标值(小数),控制台输出两点间距离。...

package com.swift;import java.util.Scanner;public class PToP {public static void main(String[] args) {Scanner scannew Scanner(System.in);System.out.println("请输入第一个点的坐标值x1");Double x1Double.parseDouble(scan.nextLine());System.out.printl…

学习java技术有前途吗

java技术在我国的普及已经是非常广泛的了&#xff0c;很多人都知道&#xff0c;java行业的发展前景是非常好的&#xff0c;但竞争压力也是非常大的&#xff0c;到底学习java技术有前途吗?来看看下面的详细介绍。 学习java技术有前途吗?目前按照各类招聘来看&#xff0c;Java的…

充分理解表达式——《狂人C》习题解答2(第二章习题5)

/* 编程求1357911。 */ #include <stdio.h> #include <stdlib.h>int main( void ) {printf ("1357911") ; printf ("%d\n" , 1 3 5 7 9 11 ) ;system("PAUSE"); return 0;}这个题目的主要目的有两个&#xff1a; 1.掌握写整…

LeetCode实战:整数反转

题目英文 Given a 32-bit signed integer, reverse digits of an integer. Example 1: Input: 123 Output: 321Example 2: Input: -123 Output: -321Example 3: Input: 120 Output: 21Note: Assume we are dealing with an environment which could only store integers …

深度洞悉2017企业IT三大关注焦点

本文讲的是深度洞悉2017企业IT三大关注焦点【IT168 云计算】随着经济走向整体放缓&#xff0c;2017有哪些议题会受到企业IT的关注? 一&#xff1a;如何提升员工工作体验 随着80后、90后成为职场主力军&#xff0c;数字化工作场所的推行与建立日渐成为主流&#xff0c;企业将更…

APP测试和传统软件测试有什么区别

APP测试和传统软件测试有什么区别?APP测试和传统测试是有一些区别的&#xff0c;移动APP的特点使得它与传统软件在开发、测试方面都有所不同。比较移动APP测试与传统软件测试的不同&#xff0c;要从以下几个方面进行考虑&#xff1a; (1) 页面布局不同 对于传统软件&#xff0…

LeetCode实战:字符串转换整数 (atoi)

题目英文 Implement atoi which converts a string to an integer. The function first discards as many whitespace characters as necessary until the first non-whitespace character is found. Then, starting from this character, takes an optional initial plus or…

[C#] enum 枚举

默认情况下&#xff0c;枚举第一个值是0&#xff0c; 可显式为枚举赋值。 可以定义枚举的基础类型&#xff0c;如enum E : short {}, sizeof(E) 2&#xff1b;默认情况下是int。 枚举的继承链&#xff1a;ValueType->Enum->enum 枚举类型和基础类型之间的转换都是显式的…

制作Windows Mobile程序安装包

使用Visual Studio 2005制作wm上的cab安装包 打开项目&#xff0c;解决方案中添加新项&#xff0c;添加"智能设置CAB项目"&#xff1b;或者在空VS中新建一个"智能设置CAB项目" 添加新项 左侧的Program Files文件夹&#xff0c;没用可以删除 添加项目主输出…

学Java需要下载什么软件?都有什么作用?

学习java并非大家想象中的那么简单&#xff0c;除了书本和老师面授&#xff0c;软件的使用也有很大的作用&#xff0c;接下来小编为大家分享的就是关于“学Java需要下载什么软件?都有什么作用?”的内容&#xff0c;希望能够给正在学习java知识的同学带来帮助。 学Java需要下载…

一种新的攻击方式:使用Outlook 表单进行横向渗透和常驻

本文讲的是一种新的攻击方式&#xff1a;使用Outlook 表单进行横向渗透和常驻&#xff0c;背景最近我们针对CrowdStrike服务进行例行调查&#xff0c;发现了一种攻击方法&#xff0c;其主要用于横向渗透和系统常驻&#xff0c;而且是以前我们没有看到过的。这种攻击利用Microso…

ACM 1740 A New Stone Game http://acm.pku.cn/JudgeOnline/problem?id=1740

题目大意:有N堆石头,每堆石头数目在1到100之间,最多有10堆.两人分别取走石头.取石头的规则是:每次只能从1堆中取,每次取走至少1个.取过后还可以把这堆的石头任意分配到其它堆上(这些堆必须有石头,废话呵呵),当然也可以不分配.问给定这些石头堆的情况,两人轮流取,谁先取完谁胜利…

LeetCode实战:回文数

题目英文 Determine whether an integer is a palindrome. An integer is a palindrome when it reads the same backward as forward. Example 1: Input: 121 Output: trueExample 2: Input: -121 Output: false Explanation: From left to right, it reads -121. From ri…

安全测试的基本原则有哪些?

软件测试顾名思义就是要进行软件安全方面的测试&#xff0c;对于软件测试人员来说&#xff0c;软件安全是一个广泛而复杂的主题&#xff0c;完全避免软件安全缺陷问题是不切实际的&#xff0c;但通过安全测试可以发现并修复软件大部分安全缺陷。下面介绍一些安全测试的基本原则…

LeetCode实战:盛最多水的容器

题目英文 Given n non-negative integers a1, a2, …, an, where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0). Find two lines, which together with x-axis forms a conta…

微软极品Sysinternals Suite工具包使用指南

微软极品Sysinternals Suite工具包使用指南 按照名称首字母排序&#xff0c;点击每个蓝色标题链接都可以转到微软的对应官方页面&#xff0c;有对这些工具包的直接下载地址和更详尽的用法。因为每个软件几乎都可以长篇大论的介绍&#xff0c;所以&#xff0c;在此就只做简介和罗…

【布局】圣杯布局双飞翼布局

背景 随着前端技术的发展推进&#xff0c;web端的布局方式已基本成熟&#xff0c;那么在网站布局方式中&#xff0c;三列布局最为常用&#xff0c;布局方式也有很多&#xff0c;渐渐的开发者们开始从效率的角度优化自己的代码“如果三排布局能将中间的模块放在dom树前面&#x…

UI设计师面试如何操作才能获得高薪

UI设计师在近几年是非常吃香的&#xff0c;求职招聘网站上对于UI设计师的要求也越来越高&#xff0c;那么在面试的过程中UI设计师面试如何操作才能获得高薪呢?来看看下面的详细解析。 UI设计师面试如何操作才能获得高薪? 1、行为举止得体大方 我们先从仪态体态方面说&#xf…

HDU2673-shǎ崽(水题)

如果不能够直接秒杀的题&#xff0c;就不算水题。又应证了那句话&#xff0c;有时候&#xff0c;如果在水题上卡住&#xff0c;那么此题对于你来说&#xff0c;也就不算是水题了额~~ 刚睡醒&#xff0c;迷迷糊糊。 题目的意思很简单&#xff0c;求一个最大的&#xff0c;再求一…

center os7 安装mysql

安装mariadb MariaDB数据库管理系统是MySQL的一个分支&#xff0c;主要由开源社区在维护&#xff0c;采用GPL授权许可。开发这个分支的原因之一是&#xff1a;甲骨文公司收购了MySQL后&#xff0c;有将MySQL闭源的潜在风险&#xff0c;因此社区采用分支的方式来避开这个风险。M…

LeetCode实战:最长公共前缀

题目英文 Write a function to find the longest common prefix string amongst an array of strings. If there is no common prefix, return an empty string “”. Example 1: Input: ["flower","flow","flight"] Output: "fl"…

软件测试培训需要学习什么技术

软件测试技术相对于IT行业的其他技术&#xff0c;学习起来是比较简单的&#xff0c;大多数零基础学员想要进入到IT行业都会优先选择软件测试&#xff0c;那么具体软件测试培训需要学习什么技术呢?来看看下面的介绍就知道了。 软件测试培训需要学习什么技术? 每个软件在上线之…

检测晃动的三种方法

http://stackoverflow.com/questions/150446/how-do-i-detect-when-someone-shakes-an-iphone 我的实现&#xff08;基于Eran Talmor&#xff09;&#xff1a; 没必要application.applicationSupportsShakeToEdit YES; Set the applicationSupportsShakeToEdit property in th…

android随手记

Linearlayout:   gravity&#xff1a;本元素中所有子元素的重力方向   layout_gravity&#xff1a;本元素对于父元素的重力方向 自定义权限:http://www.cnblogs.com/it-tomorrow/p/4115161.html 注意:1 .在被调用时就算是normal权限也需要在加入,不然会permission Deney,在…

LeetCode实战:最接近的三数之和

题目英文 Given an array nums of n integers and an integer target, find three integers in nums such that the sum is closest to target. Return the sum of the three integers. You may assume that each input would have exactly one solution. Example: Given ar…

零基础学习UI设计有哪些简单有效的方法

UI设计的普及让越来越多的人对UI有了重新的认识&#xff0c;很多企业对UI设计这个岗位都是非常重视的&#xff0c;如今很多零基础学员都想要转行做UI设计&#xff0c;那么针对零基础学习UI设计有哪些简单有效的方法呢?来看看下面的详细介绍吧。 零基础学习UI设计有哪些简单有效…