标签

NornJ提供了一种语法同React类似、并且可扩展的特殊组件语法,称为标签。这种语法最常见的使用场景就是流程控制语句:

const Test = props => (
  <If condition={props.isTest}>
    <i>success</i>
    <Else>
      <i>fail</i>
    </Else>
  </If>
);

上例中的<If><Else>等都是标签语法。

与React组件的区别

简单地说,NornJ标签可以实现以下几种普通React组件无法实现的功能:

下面我们用实例分别解释下这些特性。

延迟渲染子节点

从上面<if>的例子我们不难想到,其实用React的组件语法不是也是可以实现么?确实可以实现,比如react-if

import { If, Then, Else } from 'react-if';

const Foo = ({ data }) => (
  <div>
    <If condition={false}>
      <Then>{() =>
        renderData(data)
      }</Then>
      <Else>
        Nothing to see here
      </Else>
    </If>
  </div>
);

但是可以看出,react-if需要一个额外的<Then>标签;而且文档中也注明了,如果不在<Then><Else>中写一个返回子节点的函数是会存在性能消耗的。因为在并不确定condition的值之前,所有的分支节点都没必要进行提前渲染。

然而NornJ<If>标签则不存在上述问题,因为它的本质并不是React组件而是一个模板函数,由配套的babel插件进行了转换:

const test = props => (
  nj.renderH(`
    <if condition={_njParam0}>
      {#_njParam1}
      <else>
        {#_njParam2}
      </else>
    </if>
  `, {
    _njParam0: props.isTest,
    _njParam1: () => <i>success</i>,
    _njParam2: () => <i>fail</i>
  });
);

从上面可以看出,<i>success</i>等其实是包在函数内并没立即执行的。

另外,这并不是NornJ标签最终的运行时代码,NornJ配套的babel插件还会做进一步的模板预编译以提高性能。

生成子节点可用的新变量

例如<Each>循环:

const Test = props => (
  <div>
    <Each of={[1, 2, 3]}>
      <i>{item}</i>
    </Each>
  </div>
);

上例中使用NornJeach标签实现了循环数组[1, 2, 3],然后在子节点中使用新生成的item变量渲染循环中每一项的值。而使用常规JSX写法的组件则必须使用函数才能实现,比如react-loops

import { For } from 'react-loops';

const Test = props => (
  <div>
    <For of={[1, 2, 3]}>
      {item =>
        <li>{item}</li>
      }
    </For>
  </div>
);

上述虽然是JSX的常规写法,但是标签子节点中插入的额外花括号函数体等,可能或多或少还是增加了少量代码,以及影响了一点点标签嵌套的可读性。


下面是NornJ已有内置的标签:

If

示例:

const Test = props => (
  <div>
    This is a if tag demo.
    <If condition={props.type}>
      test if tag
      <span>test1</span>
    </If>
  </div>
);

如上,如果if标签的condition参数计算结果为true,则会渲染if标签内的子节点;如为false则不会渲染if标签内的任何东西。

  • if标签的参数列表:
参数名称 类型 作用
condition Boolean if标签子节点的渲染条件

if标签还包含elseelseif等子标签。

Else

示例:

const Test = props => (
  <div>
    This is a if tag demo.
    <If condition={props.type}>
      test if tag
      <span>test1</span>
      <Else>
        <span>test2</span>
      </Else>
    </If>
  </div>
);

上例中如果if标签的condition参数值为false,则会渲染else标签内的子节点;否则会渲染if标签内除了else标签外的其他内容:

ReactDOM.render(<Test type={false} />, document.body);

/* 以上渲染内容为:
<div>
  This is a if tag demo.
  <span>test2</span>
</div>
*/

Elseif

elseif标签可以实现多分支流程:

const Test = props => (
  <div>
    <If condition={props.num > 100}>
      100
      <Elseif condition={props.num > 50}>
        50
      </Elseif>
      <Elseif condition={props.num > 20}>
        20
      </Elseif>
      <Else>
        0
      </Else>
    </If>
  <div>
);

ReactDOM.render(<Test num={30} />, document.body);

/* 以上渲染内容为:
<div>20</div>
*/
  • elseif标签的参数列表:
参数名称 类型 作用
condition Boolean elseif标签子节点的渲染条件

Each

each标签可以实现循环:

<Each of={numbers}>   //要循环的数组
  <i>num: {item}</i>  //item表示使用数组项
  <i>no: {index}</i>  //index表示使用数组项索引值
</Each>

在循环中内嵌if标签:

<Each of={numbers}>
  <If condition={first}>show first<br/></If>  //first表示数组第一项
  <If condition={last}>show last</If>         //last表示数组最后一项
</Each>

如要循环的数组为空,则可以渲染empty标签的内容:

<Each of={numbers}>
  <span>test {item.no}</span>
  <empty>
    <span>no data</span>
  </empty>
</Each>
  • each标签的参数列表:
参数名称 类型 作用
of 数组 要循环的数组
item String 循环中生成的每项变量名,可以改变
index String 循环中生成的每项索引值变量名,可以改变
first String 循环中生成的第一项变量名,可以改变
last String 循环中生成的最后一项变量名,可以改变

For

for标签是each标签的别名,用法是完全一样的:

<For of={numbers}>
  <span>test {item.no}</span>
  <empty>
    <span>no data</span>
  </empty>
</For>

Switch

switch标签也可以实现多分支流程:

const Test = props => (
  <div>
    <Switch value={props.num}>
      <Case value={50}>
        50
      </Case>
      <Case value={30}>
        30
      </Case>
      <Default>
        0
      </Default>
    </Switch>
  <div>
);

ReactDOM.render(<Test num={30} />, document.body);

/* 以上渲染内容为:
<div>30</div>
*/
  • switchcase标签的参数列表:
参数名称 类型 作用
value Any 在switch标签的value参数传入要判断值;
然后其会和case标签中的value值进行===判断;
所有case都不匹配时则渲染default标签的子节点

With

with标签主要用于在JSX中创建新的变量:

<Each of={[1, 2, 3]}>
  <With num={item} i={index}>
    <span>test-{num}-{i}</span>
  </With>
</Each>

MobxObserver

mobxObserver标签实际上就是mobx-react-lite库的observer组件,只不过它在编写时无需在子节点写函数:

import { Observer, useObservable } from "mobx-react-lite"

function ObservePerson(props) {
  const person = useObservable({ name: "John" })
  return (
    <div>
      {person.name}

      {/* 原生写法 */}
      <Observer>{
        () => <div>{person.name}</div>
      }</Observer>

      {/* MobxObserver标签 */}
      <MobxObserver>
        <div>{person.name}</div>
      </MobxObserver>

      <button onClick={() => (person.name = "Mike")}>No! I am Mike</button>
    </div>
  )
}

开发新的标签

NornJ的标签都是支持可扩展的,也就是说与React组件一样可以自行封装各种新功能。

开发一个最简单的标签

例如实现一个Unless标签,功能即为与If标签相反,它的condition属性为false时才渲染子节点:

<Unless condition={false}>
  <div>Test unless</div>
</Unless>

上面的Unless标签实际上是一个扩展函数,使用nj.registerExtension方法注册:

import nj from 'nornj';

nj.registerExtension(
  'unless',     //注意:标签名称需要使用小写开头的camel命名方式
  options => {
    const { props, children } = options;
    if (!props.condition) {
      return children();  //输出标签的子节点:Test unless
    }
  }
);

然后还需要配置一下.babelrc,因为这样配套的babel插件才知道需要对Unless标签进行转换:

{
  ...
  "plugins": [
    [
      "nornj-in-jsx",
      {
        "extensionConfig": {
          "unless": true
        }
      }
    ]
  ]
}

这样我们就成功开发了一个Unless标签,与普通React组件不同的是,它可以获得NornJ标签的延迟渲染子节点特性。

标签扩展函数的options参数

参数名称 类型 作用
children Function 执行后返回需要渲染的标签子节点,与React的props.children不同,它是函数
props Object 当前标签的属性值(即<tag a=1 b=2>中的ab,这里与React组件一致)

更复杂的标签

上面的例子我们介绍了如何开发一个最简单的标签Unless,它只需在扩展函数内按照一定条件判断是否返回子节点就可以了;也无需配置更多的extensionConfig配置参数,填写true即可。

接下来我们实现一个循环标签SimpleFor,用法如下:

<SimpleFor of={[1, 2, 3]}>
  <If condition={!loopFirst}>
    <div key={loopIndex}>Test for: {loopItem}</div>
  </If>
</SimpleFor>

我们还注意到上面循环体中的loopIndexloopItem是该标签生成出来的新变量,也就是获得了NornJ标签的生成子节点可用的新变量特性。这就需要配置extensionConfignewContext参数:

{
  ...
  "plugins": [
    [
      "nornj-in-jsx",
      {
        "extensionConfig": {
          "simpleFor": {
            "newContext": {
              "data": {
                "index": "loopIndex",
                "item": "loopItem",
                "first": "loopFirst"
              }
            }
          }
        }
      }
    ]
  ]
}

然后使用nj.registerExtension方法注册标签扩展函数:

import nj from 'nornj';

nj.registerExtension(
  'simpleFor',
  options => {
    const { props, children } = options;

    return props.of.map((item, index) => children({
      data: [{  //注意:data参数需要传入一个每项为对象的数组;对象个数不限
        loopIndex: index,
        loopItem: item,
        loopFirst: index == 0
      }]
    }))
  }
);

如需要改变循环体中的loopItem等参数名,在SimpleFor标签的属性上修改在extensionConfig.simpleFor.newContext.data中设置的对象名即可,如itemindex等:

<SimpleFor of={[1, 2, 3]} item="itemNum" index="itemIndex" first="itemFirst">
  <If condition={!itemFirst}>
    <div key={itemIndex}>Test for: {itemNum}</div>
  </If>
</SimpleFor>

子标签

NornJ标签还可以在子节点中附带子标签,比如<If>标签内的<Else><Elseif><Each>标签内的<Empty>等。

配套babel插件在运作时会将子标签连同其主标签一起进行转换。目前NornJ的babel插件暂时只支持转换1级子标签,这在大多数情况下足够用了。

比如我们需要给上面开发的Unless标签增加一个名为Otherwise子标签,功能为在condition条件为true时渲染它的子节点。用法如下:

<Unless condition={false}>
  <div>Test unless</div>
  <Otherwise>
    <div>Test otherwise</div>
  </Otherwise>
</Unless>

编写Otherwise标签扩展函数:

import nj from 'nornj';

nj.registerExtension(
  'otherwise',  //注意:标签名称需要使用小写开头的camel命名方式
  options => {
    const {
      props,
      children,
      tagProps  //tagProps是主标签(Unless)的props对象
    } = options;

    //把Otherwise的子节点函数添加在Unless标签的props对象上面。注意,不必在这里执行它
    tagProps.otherwise = children;
  }
);

接着修改Unless标签的扩展函数:

import nj from 'nornj';

nj.registerExtension(
  'unless',
  options => {
    const { props, children } = options;
    if (!props.condition) {
      return children();  //输出Unless标签的子节点:Test unless
    }
    else if(props.otherwise != null) {
      return props.otherwise();  //输出Otherwise标签的子节点:Test otherwise
    }
  }
);

最后在.babelrc配置一下Otherwise的标签信息,需要设置isSubTag参数为true

{
  ...
  "plugins": [
    [
      "nornj-in-jsx",
      {
        "extensionConfig": {
          "unless": true,
          "otherwise": {
            "isSubTag": true
          }
        }
      }
    ]
  ]
}

results matching ""

    No results matching ""