本文会先解释一下JSX的工作原理,再介绍一下如何用“不寻常”的方式来使用JSX。如果你已经了解了JSX的工作原理,可以跳过第一部分。如果你只想学一些实用的东西,那可以跳过第二部分。

上周,我发了一条这样的动态:

JSX这么6?_java

可以看出大家都很喜欢,他们评论区里纷纷留言:“呕”,“这都做了些什么啊”,“天!XML 又回来了”,“又是黑暗的一天” 。我写这篇文章就是为了回报他们的爱。(当然我也和Lorenzo Palmes 打赌,如果这篇tweet收到100个赞我就会写这篇文章,同时感谢 Ken Wheeler的转发).


JSX

只要你用过React,就应该大概了解过 JSX,一种用于创建 React 元素的类 XML 语法:

function getGreeting(name) {  return (    <div>      <h1>Hello!</h1>      <h2>Good to see you {name}</h2>    </div>  );}

因为浏览器还不支持 JSX,所以在运行前,需要将代码转换为普通的 JavaScript。从对开发者友好的代码转换成对浏览器友好的代码这项工作,通常都是用 Babel 这样的工具来做。Babel 编译后,getGreeting函数就会被编译成下面这样:

function getGreeting(name) {  return React.createElement(    "div",    { className: "greeting" },    React.createElement("h1", { foo: "bar" }, "Hello!"),    React.createElement("h2", null, "Good to see you ", name)  );}

在这个不同的语法中,所有的标签名、属性名、属性值以及文本内容都没有改变。那么React.createElement是什么?

React.createElement是 React 用来创建元素的方法。因为JSX通常与React同时使用,所以 Babel 默认注入这个方法,但实际情况并非如此。事实上,JSX 是从React中脱离出来的,JSX 是用于在 JS 中使用类似 XML 的语法定义树结构的规范。这个树结构可以是一个 React 组件渲染的元素,也可以是完全不同的其他的东西。

为了不只是在 React 中使用 JSX,我们需要告诉 Babel 使用其他的函数来代替React.createElement。只要在文件中添加/* @jsx 另一个函数名 /这样的注释就行了。例如:

/** @jsx foo */

var bar = <x>Hi</x>;

var bax = <Y>Hi</Y>;


// becomes:

var bar = foo("x", null, "Hi");

var bax = foo(Y, null, "Hi");

最后要说的一点是,Babel会根据元素名称的大小写使用不同的方式处理JSX。小写字母名称会以字符串参数的形式进行传递,首字母大写的名称则会作为函数进行传递,就像在代码段中一样。

数学中的JSX

免责声明:从这里开始,你可能什么都学不到,我将会在一些JSX不该出现的地方使用它。

我们可以使用Math.sqrt(a * a + b * b)来算a和b的弦等于多少,但这一点都不好玩。我们可以用JSX来计算这个:

const Sum = (...args) => args.reduce((a, b) => a + b, 0);

const Pow = ({ exponent }, base) => Math.pow(base, exponent);

const Sqrt = x => Math.sqrt(x);


const Hypotenuse = ({ a, b }) => (

  <Sqrt>

    <Sum>

      <Pow exponent={2}>{a}</Pow>

      <Pow exponent={2}>{b}</Pow>

    </Sum>

  </Sqrt>

);


/** @jsx calc */

function calc(operation, props, ...args) {

  let params = props ? [props] : [];

  params = params.concat(...args);

  return operation(...params);

}


console.log(<Hypotenuse a={3} b={4} />);

这里还有另一个版本的hypotenuse可以接收两个以上的参数:

const Hypotenuse = ({ values }) => (

  <Sqrt>

    <Sum>{values.map(=> <Pow exponent={2}>{v}</Pow>)}</Sum>

  </Sqrt>

);


console.log(<Hypotenuse values={[3, 4, 5]} />);

无所不能的JSX

接下来尝试做一些更神奇的事情吧,我们来试试归并排序。

我们先通过RamdaJS来给组件添加一些原始功能。下面是ramda中的FizzBuzz

import R from "ramda";var divisibleBy = R.curry(R.pipe(R.flip(R.modulo), R.equals(0)));var fizzbuzz = R.map(  R.cond([    [R.both(divisibleBy(3), divisibleBy(5)), R.always("FizzBuzz")],    [divisibleBy(3), R.always("Fizz")],    [divisibleBy(5), R.always("Buzz")],    [R.T, R.identity]  ]));console.log(fizzbuzz(R.range(1, 16)));

我们可以使用 JSX 写出相同的代码,只需要编写与JSX元素名称匹配的函数去调用ramda函数就好了:

/** @jsx run */

function run(f, props, ...args) {

  return R[f](...args);

}


var divisibleBy = (

  <curry>

    <pipe>

      <flip>

        <modulo />

      </flip>

      <equals>{0}</equals>

    </pipe>

  </curry>

);


var fizzbuzz = (

  <map>

    <cond>

      {[

        [

          <both>

            {divisibleBy(3)}

            {divisibleBy(5)}

          </both>,

          <always>"FizzBuzz"</always>

        ],

        [divisibleBy(3), <always>"Fizz"</always>],

        [divisibleBy(5), <always>"Buzz"</always>],

        [R.T, <identity />]

      ]}

    </cond>

  </map>

);


console.log(fizzbuzz(R.range(1, 16)));

好看了?……并没有。

运行的函数还可以再智能一点,这样可以增加 JSX 代码的 “纯净度”:

var FizzBuzz = (

  <map>

    <cond concat>

      <pair>

        <both>

          <DivisibleBy value={3} />

          <DivisibleBy value={5} />

        </both>

        <always value="FizzBuzz" />

      </pair>

      <pair>

        <DivisibleBy value={3} />

        <always value="Fizz" />

      </pair>

      <pair>

        <DivisibleBy value={5} />

        <always value="Buzz" />

      </pair>

      <pair>

        </>

        <identity />

      </pair>

    </cond>

  </map>

);


console.log(

  <FizzBuzz>

    <range>

      {1}

      {16}

    </range>

  </FizzBuzz>

);

现在,你可以用ramda做的所有事情都可以用JSX来做了。你可以使用它来做任何事情,包括归并排序:

JSX这么6?_java_02


感谢阅读。