资讯专栏INFORMATION COLUMN

重拾JSX

adam1q84 / 2464人阅读

摘要:语法糖是一种的语法拓展,可以使用它来进行的展示我们一般会在组件的方法里使用进行布局和事件绑定的核心机制之一就是可以创建虚拟的元素,利用虚拟来减少对实际的操作从而提升性能,正是为了虚拟而存在的语法糖我们在平时的组件编写中,通常都这么写然而代码

React.createElement语法糖

JSX是一种JavaScript的语法拓展,可以使用它来进行UI的展示:

const element = 

Hello, world!

;

我们一般会在组件的render方法里使用JSX进行布局和事件绑定:

class Home extends Component {
  render() {
    return (
      
console.log("hello")}>

Hello, world!

); } }

React的核心机制之一就是可以创建虚拟的DOM元素,利用虚拟DOM来减少对实际DOM的操作从而提升性能,JSX正是为了虚拟DOM而存在的语法糖

我们在平时的组件编写中,通常都这么写:

import React, { Component } from "react";

class Demo extends Component {
  render() {
    return (
      

Hello, world!

) } }

然而代码里面并没有用到React,为什么要引入这个变量呢?

因为JSX是React.createElement这个方法的语法糖:

const element = 

Hello

; // 等价于 const element = React.createElement("h1", { id: "container", className: "home" }, "Hello");

推荐大家在babeljs.io上看下JSX编译后的实际效果

React.createElement有三个参数:

React.createElement(
  type, // dom类型,比如div,h1
  [props], // dom属性,比如id,class,事件
  [...children] // 子节点,字符串或者React.createElement生成的一个对象
)

JSX用一种类似HTML的语法替代了比较繁琐的React.createElement纯JS方法,而@babel/preset-react插件就起到了最关键的一步:负责在webpack编译时,把所有的JSX都改成React.createElement:

class Home extends Component {
  render() {
    return (
      
console.log("hello")}>

Hello, world!

); } }

编译后:

class Home extends Component {
  render() {
    return React.createElement("div", {
      onClick: () => console.log("hello")
    }, React.createElement("h1", null, "Hello, world!"), React.createElement(Blog, {
      title: "deepred"
    }));
  }
}

在开发中,有了JSX后我们基本不怎么需要用到createElement方法,但如果我们需要实现这样一个组件:

// 根据传入的type属性,渲染成相应的html元素
 console.log("hello")}>this is a h1
this is a p

我们不太可能根据type的属性,一个个if else去判断对应的标签:

function Tag(props) {
  const { type, ...other } = props;

  if (type === "h1") {
    return 

{props.children}

} if (type === "p") { return

{props.children}

} }

这时,就需要用到底层的api了:

function Tag(props) {
  const { type, ...other } = props;

  return React.createElement(type, other, props.children);
}
自己实现一个JSX渲染器

虚拟dom本质就是一个js对象:

const vnode = {
  tag: "div",
  attrs: {
    className: "container"
  },
  children: [
    {
        tag: "img",
        attrs: {
          src: "1.png"
        },
        children: []
    },
    {
        tag: "h3",
        attrs: {},
        children: ["hello"]
    }
  ]
}

可以通过在每个文件的上方添加/** @jsx h */来告诉@babel/preset-reacth方法名代替JSX(默认方法是React.createElement)

/** @jsx h */

const element = 

Hello

;
/** @jsx h */
const element = h("h1", {
  id: "container",
  className: "home"
}, "Hello");

现在让我们开始创建自己的h函数吧!

function h(nodeName, attributes, ...args) {
  // 使用concat是为了扁平化args,因为args数组里面的元素可能也是数组
  // h("div", {}, [1, 2, 3])  h("d", {}, 1, 2, 3) 都是合法的调用
  const children = args.length ? [].concat(...args) : null;

  return { nodeName, attributes, children };
}
const vnode = h("div", {
  id: "urusai"
}, "Hello!");

// 返回
// {
//  "nodeName": "div",
//  "attributes": {
//   "id": "urusai"
//  },
//  "children": [
//   "Hello!"
//  ]
// }

h的作用就是返回一个vnode,有了vnode,我们还需要把vnode转成真实的dom:

function render(vnode) {
  if (typeof vnode === "string") {
    // 生成文本节点
    return document.createTextNode(vnode);
  }

  // 生成元素节点并设置属性
  const node = document.createElement(vnode.nodeName);
  const attributes = vnode.attributes || {};
  Object.keys(attributes).forEach(key => node.setAttribute(key, attributes[key]));

  if (vnode.children) {
    // 递归调用render生成子节点
    vnode.children.forEach(child => node.appendChild(render(child)));
  }

  return node;
}

现在让我们使用这两个方法吧:

/** @jsx h */
const vnode = 
Hello!
; const node = render(vnode); document.body.appendChild(node);

编译转码后:

/** @jsx h */
const vnode = h("div", {
  id: "urusai"
}, "Hello!");
const node = render(vnode);
document.body.appendChild(node);

我们还可以遍历数组:

/** @jsx h */
const items = ["baga", "hentai", "urusai"];
const vnode = 
    {items.map((item, index) =>
  • {item}
  • )}
; const list = render(vnode); document.body.appendChild(list);

编译转码后:

/** @jsx h */
const items = ["baga", "hentai", "urusai"];
const vnode = h("ul", null, items.map((item, index) => h("li", {
  key: index
}, item)));
const list = render(vnode);
document.body.appendChild(list);

通过h render两个函数,我们就实现了一个很简单的JSX渲染器!!!

参考

WTF is JSX

JSX In Depth

React Without JSX

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102716.html

相关文章

  • 重拾React: React 16.0

    摘要:然而之前的相当于从最顶层的组件开始,自顶向下递归调用,不会被中断,这样就会持续占用浏览器主线程。众所周知,是单线程运行,长时间占用主线程会阻塞其他类似于样式计算布局绘制等运算,从而出现掉帧的情况。 前言   首先欢迎大家关注我的Github博客,也算是对我的一点鼓励,毕竟写东西没法获得变现,能坚持下去也是靠的是自己的热情和大家的鼓励,希望大家多多关注呀!从今年年初离开React开发岗,...

    henry14 评论0 收藏0
  • 重拾css(1)——写在前边的话

    摘要:本系列文章重拾主要参考王福朋知多少,结合自己的理解和学习需要,修改或添加了一些内容,难免有失偏颇,仅供自我学习参考之用。 工作中或多或少的写一些css,但总感觉掌握的不够扎实,时而需要查阅一下知识点。我想,一方面跟缺少科班出身式的系统学习有关,另一方面也苦于一直未寻觅到一套合我胃口教程。直到我读到了王福朋css知多少系列文章,使我有了重新系统学习css的想法。 本系列文章(重拾css)...

    li21 评论0 收藏0
  • CSS魔法堂:重拾Border之——更广阔的遐想

    摘要:也就是说我们操作的几何公式中的未知变量,而具体的画图操作则由渲染引擎处理,而不是我们苦苦哀求设计师帮忙。 前言  当CSS3推出border-radius属性时我们是那么欣喜若狂啊,一想到终于不用再添加额外元素来模拟圆角了,但发现border-radius还分水平半径和垂直半径,然后又发现border-top-left/right-radius的水平半径之和大于元素宽度时,实际值会按比...

    lily_wang 评论0 收藏0
  • CSS魔法堂:重拾Border之——解构Border

    摘要:本系列将稍微深入探讨一下那个貌似没什么好玩的魔法堂重拾之解构魔法堂重拾之图片作边框魔法堂重拾之不仅仅是圆角魔法堂重拾之更广阔的遐想解构说起我们自然会想起,而由条紧紧包裹着的边组成,所以的最小操作单元是。 前言  当CSS3推出border-radius属性时我们是那么欣喜若狂啊,一想到终于不用再添加额外元素来模拟圆角了,但发现border-radius还分水平半径和垂直半径,然后又发现...

    lingdududu 评论0 收藏0
  • 2017-08-08 前端日报

    摘要:前端日报精选一行代码的逆向工程译只需四个步骤使用实现页面过渡动画如何实现一个基于的模板引擎解剖组件的多种写法与演进深入理解笔记扩展对象的功能性中文基础系列一之实现抽奖刮刮卡橡皮擦掘金小游戏个人文章和最常用的特征众成翻译常用语法总 2017-08-08 前端日报 精选 一行 JavaScript 代码的逆向工程【译】只需四个步骤:使用 React 实现页面过渡动画如何实现一个基于 DOM...

    alin 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<