React专题:JSX

本文是『horseshoe·React专题』系列文章之一,后续会有更多专题推出
来个人 GitHub repo 阅读完整的专题文章
来个人 我的博客 得到无与伦比的阅读体验

话说PHP是世界上最好的语言(笑)。javascript

由于它的入门门槛极低。php

<?php
$str = '<ul>';
foreach ($fruits as $fruit) {
    $str += '<li>' . $fruit . '</li>';
}
$str += '</ul>';
?>

不少年前,这种字符串拼接开发网页的方式很是流行。html

可是这种写法有两个问题:前端

  • 容易形成XSS注入,有极大的安全风险。
  • 拼接的写法很繁琐。

因而facebook的工程师开始动歪脑筋了。vue

XHP

他们的解决方案也很新颖,就是在代码里直接写标签,而不是将标签视为字符串。java

前面说到,字符串拼接很容易形成XSS注入。那么什么是XSS注入呢?react

好比恶意用户输入这么一段内容:<script>code</script>,就可能被程序识别为一段脚本,他能够在脚本里面干任何事情。git

因而人们想到的办法是对全部输入转义,转义的做用就是让全部标签没法被识别为标签,而只是标签写法的字符串。用户的输入就会原本来本的展现在页面上。github

可是输入转义也有问题,就是容易把字符串拼接的标签也给转义了。你们应该看过页面上大段大段的标签写法的文本吧。数组

咱们来看看XHP的写法。

<?hh
$post =
    <div class="post">
        <h2>{$post}</h2>
        <p><span>Hey there.</span></p>
        <a href={$like_link}>Like</a>
    </div>;

诶,是否是有点眼熟?

XHP把标签与字符串区别开来了,变成脚本语法的一部分。

这正好解决了前面提到的两个问题:

  • 标签就是标签,字符串就是字符串,再也别想浑水摸鱼。
  • 像写脚本同样写标签,是否是爽多了?

JSX

其实facebook一直在前端组件化方面作各类尝试,但都不是特别成功。

直到2013年,工程师Jordan Walke提出一个大胆的想法:把XHP的写法迁移到JavaScript中来。即使有XHP的案例在前,你们仍是以为这个想法很疯狂。

不过,facebook极为优秀的工程师文化最终促成了这种尝试。这一尝试不得了,开了天眼。

自此以后就开启了React的开挂之路。

const element = <h1>Hello React!</h1>;

这不就是facebook一直在苦苦求索的前端组件化方案吗?

刀耕火种时期的前端,入口是HTML,脚本和样式被引入到HTML页面上。这是一种分离化的思想,以语言为最小颗粒。

然而通过大量痛苦的实践,人们发现之内容为最小颗粒才是正解。以组件为单位,页面结构、样式和功能都被集成在组件内部,对开发者来讲组件就是一个黑匣子,只能经过暴露出来的接口使用组件。这是一种封装的思想,目的固然是为了复用。

固然,目前React还没法实现真正意义上的CSS封装,不过以当下前端的关注度,CSS被完全招安也指日可待。

语法

标签的写法和HTML同样,只不过融入到了JavaScript中。

组件,其实就是自定义标签,首字母必须大写,为了与原生标签区别开来。

若是标签或组件没有包含内容,能够采用自闭合标签写法。

const element = <App />;

JSX会自动忽略falsenullundefined

标签的class属性和for属性要用className属性和htmlFor属性代替。

组件返回多个标签或多个组件必需要用一个标签或组件包裹,也就是说只能有一个顶层元素。

可是,React16以上的版本支持用空标签包裹或者直接返回数组。这样的好处就是没必要添加不少无用的标签使页面变得更加臃肿。

import React, { Fragment } from 'react';

const App = () => {
    return (
        <Fragment>
            <div>React</div>
            <div>Vue</div>
            <div>Angular</div>
        </Fragment>
    );
}

export default App;
import React from 'react';

const App = () => {
    return (
        <>
            <div>React</div>
            <div>Vue</div>
            <div>Angular</div>
        </>
    );
}

export default App;
import React from 'react';

const App = () => {
    return [
        <div key="1">React</div>,
        <div key="2">Vue</div>,
        <div key="3">Angular</div>,
    ];
}

export default App;

表达式

标签里确定要写一些变量,要否则页面就是死的。

怎么写变量呢?用花括号包围。

const name = 'React';
const element = <h1>Hello {name}!</h1>;

若是我想插入一个对象字面量怎么办?

很简单,再包裹一层花括号。

const obj = { name: 'React' };
const element = <h1 style={{ color: '#f66' }}>Hello {name}!</h1>;

实际上花括号语法支持全部的表达式。

那么问题来了,什么是表达式?

简单来说,表达式的主要做用是计算和声明,老是有返回值。与之相对,语句的主要做用是逻辑和动做,没有返回值。

如下表达式JSX都支持。

const a = <button onClick={() => console.log('react')}>click</button>;

const b = <button onClick={function (){ console.log('react') }}>click</button>;

const c = <div>{popular ? 'react' : 'vue'}</div>;

const d = <div>{popular && 'react'}</div>;

const e = <div>{renderSomething()}</div>;

像赋值语句、判断语句和循环语句JSX都不支持。

那开发者要渲染一个列表怎么办?

for循环语句确定是不行的,好在咱们有map函数。由于从上例咱们知道,JSX是支持函数执行表达式的。

forEach函数行不行呢?不行,由于它没有返回值。也就是说,filter、find、reduce等有返回值的遍历函数都是能够的。

import React, { Component } from 'react';

const list = ['react', 'vue', 'angular'];

class App extends Component {
    render() {
        return (
            <div>{list.map(value => <div key={value}>{value}</div>)}</div>
        );
    }
}

export default App;

编译

不知道大家有没有这样的疑问:

  • 为何返回多个标签或组件必需要用一个标签或组件包裹?
  • 为何在根本没有使用React这个变量的状况下还要import React

这里就要讲到JSX的编译。

由于JSX不是正确的JavaScript语法,它要通过编译才能被浏览器识别。

目前JSX的编译工做是由babel来完成的。

咱们来看看编译都作了哪些工做。

下面的例子,后者是前者编译后的结果。

const app = (
    <div className="form">
        <input type="text" />
        <button>click</button>
    </div>
);
const app = React.createElement(
    "div",
    { className: "form" },
    React.createElement("input", { type: "text" }),
    React.createElement(
        "button",
        null,
        "click",
    ),
);

能够看到,标签最后变成了一个函数执行表达式,第一个参数是标签名,第二个参数是属性集合,以后的参数都是子标签。

看到这里,相信也不用我解释了,前面提出的两个问题恍然大悟。

整个UI其实是经过层层嵌套的React.createElement方法返回的,因此咱们要在文件开头import React,不然编译后就会发现createElement没有定义。

React.createElement执行的结果是一个对象,对象的属性描述了标签或组件的性状,对象再嵌套子对象。若是顶层返回多个标签,就没法表达为一个对象了。

因为React16引入了Fiber机制,使得返回多标签成为可能(并不清楚缘由)。

同时也回答了为何标签的class属性和for属性要用className属性和htmlFor属性代替。在标签里属性怎么写都无所谓,可是classfor是JavaScript中的关键字,因此要换一种写法。

React里面传递props有一种写法,若是传递的是一个对象,能够用扩展运算符很方便的传递。

下面的例子,value先是被扩展运算符将属性分解,而后又被一个对象包裹。这里只是作了一个浅拷贝,并无其余的含义。因此最终传递给组件的仍然是一个对象。

因此疑问就来了,一般给组件传递属性都是键值对的形式,直接传递一个对象也能够吗?

其实全部的属性最后都会放到一个对象里面,因此两种写法异曲同工。React只不过给了一种快捷方式。

了解编译的过程,不少写法都很好理解了。

const value = { a: 1, b: 2 };
const element = <App a={value.a} b={value.b} />;
const value = { a: 1, b: 2 };
const element = <App {...value} />;

React专题一览

什么是UI
JSX
可变状态
不可变属性
生命周期
组件
事件
操做DOM
抽象UI

相关文章
相关标签/搜索