重拾JSX

40次阅读

共计 3644 个字符,预计需要花费 10 分钟才能阅读完成。

React.createElement 语法糖
JSX 是一种 JavaScript 的语法拓展,可以使用它来进行 UI 的展示:
const element = <h1>Hello, world!</h1>;
我们一般会在组件的 render 方法里使用 JSX 进行布局和事件绑定:
class Home extends Component {
render() {
return (
<div onClick={() => console.log(‘hello’)}>
<h1>Hello, world!</h1>
<Blog title=”deepred” />
</div>
);
}
}
React 的核心机制之一就是可以创建虚拟的 DOM 元素,利用虚拟 DOM 来减少对实际 DOM 的操作从而提升性能,JSX 正是为了虚拟 DOM 而存在的语法糖
我们在平时的组件编写中,通常都这么写:
import React, {Component} from ‘react’;

class Demo extends Component {
render() {
return (
<h1>Hello, world!</h1>
)
}
}
然而代码里面并没有用到 React,为什么要引入这个变量呢?
因为 JSX 是 React.createElement 这个方法的语法糖:
const element = <h1 id=”container” className=”home”>Hello</h1>;

// 等价于
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 (
<div onClick={() => console.log(‘hello’)}>
<h1>Hello, world!</h1>
<Blog title=”deepred” />
</div>
);
}
}
编译后:
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 元素
<Tag type=”h1″ id=”hello” onClick={() => console.log(‘hello’)}>this is a h1</Tag>
<Tag type=”p”>this is a p</Tag>
我们不太可能根据 type 的属性,一个个 if else 去判断对应的标签:
function Tag(props) {
const {type, …other} = props;

if (type === ‘h1’) {
return <h1 {…other}>{props.children}</h1>
}

if (type === ‘p’) {
return <p {…other}>{props.children}</p>
}
}
这时,就需要用到底层的 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-react 用 h 方法名代替 JSX(默认方法是 React.createElement)
/** @jsx h */

const element = <h1 id=”container” className=”home”>Hello</h1>;
/** @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 = <div id=”urusai”>Hello!</div>;
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 = <ul>{items.map((item, index) => <li key={index}>{item}</li>)}</ul>;
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

正文完
 0