omil

English 简体中文

Omil 是什么?

omil是一个 webpack 的 loader,它允许你以一种名为单文件组件(SFCs)的格式撰写 Omi 组件:

<template lang="html" name="component-name">
  <header onClick="${this.test}">${this.data.title}</header>
</template>
<script>
export default class {
  test(){ console.log('Hello Eno!') }
  install() {
    this.data = { title: 'Omi' }
  }
}
</script>
<style>
header { color: #58bc58; }
</style>

Omil 还提供了很多酷炫的特性:

简而言之,webpack 和 Omi Loader 的结合为你提供了一个现代、灵活且极其强大的前端工作流,来帮助撰写 Omi.js 应用。

起步

Omi CLI

如果你不想手动设置 webpack,我们推荐使用 Omi CLI 直接创建一个项目的脚手架。通过 Omi CLI 创建的项目会针对多数常见的开发需求进行预先配置,做到开箱即用。

如果Omi CLI提供的内建没有满足你的需求,或者你乐于从零开始创建你自己的 webpack 配置,那么请继续阅读这篇指南。

手动设置

安装

首先先安装好Omil

npm install -D omil

如果你使用的是 Visual Studio Code 进行开发,强烈建议下载 Omi Snippets 扩展,它会提供给你语法高亮,局部编译等功能。您可以在 VSC 扩展界面里面搜索 omi 这个关键词出现Omi Snippets点击安装即可,稍等片刻,当它安装成功后会提醒你需要重新加载编辑工具,点击重新加载即可使用。

每个Omil包的新版本发布时,一个相应版本的Omi Snippets也会随之发布。

webpack 配置

Omi Loader 的配置和其它的 loader 基本一样。

// webpack.config.js
module.exports = {
  module: {
    rules: [
      // ... 其它规则
      {
        test: /\.omi|eno$/,
        loader: 'omil'
      }
    ]
  }
}

一个更完整的 webpack 配置示例看起来像这样:

module.exports = {
  mode: 'development',
  module: {
    rules: [{
      test: /\.omi|eno$/,
      use: [{
        loader: require.resolve('omil'),
        options: {
          // Use in development, You should remove in production
          sourceMaps: 'both',
          // Config babel plugins for async, await and other many features
          plugins: [
            [
              "@babel/plugin-transform-runtime",
              {
                "absoluteRuntime": false,
                "corejs": false,
                "helpers": true,
                "regenerator": true,
                "useESModules": false
              }
            ]
          ]
        }
      }],
      // Or you can use eno-loader or omil directly
      // use: ['eno-loader']
      // use: ['omil']
    }]
  }
}

Omi Snippets

在配置完 Omil 之后,我们可以在 VS Code 上同时安装好 Omi Snippets 扩展,这个插件可以方便的让你把 .omi 和 .eno 后缀文件在未经过 webpack 处理前转化为 .js 文件,让你可以直观了解到单文件组件经过 omil 转化后的 JS 文件内容,这相当于局部编译减轻 webpack 处理单文件时候的不必要消耗。

目录结构

例如你在 webpack 的入口文件夹中有一个 .omi 的后缀文件,当你新建并经过编辑保存之后,Omi Snippets扩展会在同级目录下新建一份同名但不同后缀的 .js 文件

Hello.omi 开发中你需要编写的单文件组件
Hello.js 修改或者保存文件Hello.omi后经过插件转化的js文件

如下图,左边的代码是我们编写的 .omi 后缀的单文件组件,右边是经过 Omi Snippets 生成的 .js 后缀文件。

示例代码

上图的示例代码如下

<template name="my-test">
  <div class="example">
    { this.data.msg }
  </div>
</template>

<script>
export default class {
  install () {
    this.data = {
      msg: 'Hello world!'
    }
  }
}
</script>

<style>
.example {
  color: red;
}
</style>

以下代码就是经过 Omi Snippets 生成的 .js 后缀文件,可以用于在你没有 omil 模块下,主逻辑文件或者其他组件引入调用。

import { WeElement, define, h } from "omi";
class MyTest extends WeElement {
  render() {
    return h(
      "div",
      {
        class: "example"
      },
      this.data.msg
    );
  }
  install() {
    this.data = {
      msg: "Hello world!"
    };
  }
}
MyTest.css = `
.example {
  color: red;
}
`;
define("my-test", MyTest);

配合 React 开发

安装 React 脚手架和一些必要模块。

npm install create-react-app
# 初始化项目
create-react-app my-project
# 进入项目文件夹目录
cd my-project
# 安装项目依赖
npm install
# 安装 styled-components 这个务必得安装 用于处理 React 单文件组件局部样式
npm install styled-components --save
# 安装 omil 处理React单文件组件,把 .omi 或者 .eno 后缀文件处理为 JS
npm install omil --save-dev

在配置完 Omil 之后,我们可以在 VS Code 上同时安装好 Omi Snippets 扩展,这个插件可以方便的让你把 .omi 和 .eno 后缀文件在未经过 webpack 处理前转化为 .js 文件,让你可以直观了解到单文件组件经过 omil 转化后的 JS 文件内容,这相当于局部编译减轻 webpack 处理单文件时候的不必要消耗。

编写第一个组件

现在你可以使用单文件组件来编写 React 组件,默认生成类组件。

<template name="Component-name">
    <div>
        <p>{this.state.title}</p>
    </div>
</template>
<script>
export default class {
    constructor(props) {
        super(props)
        this.state = {
            title: "react"
        }
    }
    componentDidMount(){
        console.log('生命周期')
    }
}

</script>
<style>
p {color: #58bc58};
</style>

以上文件经过 Omil 处理后将会转化为以下代码:

import { Component as WeElement, createElement as h } from "react";
import styled from "styled-components";
const StyledComponents = styled.div`
  /* CSS */
  p {
    color: #58bc58;
  }
`;

class ComponentName extends WeElement {
  render() {
    return h(
      StyledComponents,
      null,
      h("div", null, h("p", null, this.state.title))
    );
  }

  constructor(props) {
    super(props);
    this.state = {
      title: "react"
    };
  }

  componentDidMount() {
    console.log("生命周期");
  }
}

ComponentName.css = `
/* CSS */
p {color: #58bc58};
`;
export default ComponentName;

语言块规范

简介

.omi 文件是一个自定义的文件类型,用类 HTML 语法描述一个 Omi 组件。每个 .omi 文件包含三种类型的顶级语言块 <template><script><style>:

<template name="my-test">
  <div class="example">
    { this.data.msg }
  </div>
</template>

<script>
export default class {
  install () {
    this.data = {
      msg: 'Hello world!'
    }
  }
}
</script>

<style>
.example {
  color: red;
}
</style>

Omil 会解析文件,提取每个语言块,如有必要会通过其它 loader 处理,最后将他们组装成一个 ES Module,它的默认导出是一个 Omi.js 组件定义好的自定义标签对象。

Omil 支持使用非默认语言,比如 CSS 预处理器,预编译的 HTML 模版语言,通过设置语言块的 lang 属性。例如,你可以像下面这样使用 Sass 语法编写样式:

<style lang="sass">
  /* write Sass! */
</style>

语言块

<template>模板

每个 .omi 文件最多包含一个 <template> 块。

内容将被提取,如果是 JSX 会编译为函数片段,如果为 html 会编译为字符串,并最终注入到从<script>导出的组件 render 函数中。

属性name = "xxx-xxx"(Omi组件)

定义name="xxx-xxx"可以给组件定义一个名字,这个名字会自动调用 omi 框架的 define('xxx-xxx', xxxXxx) 方法来注册组件,你就可以在页面中用这个属性名<xxx-xxx></xxx-xxx>来使用该组件

注意:

<template name="my-test">
  <div class="example">
    { this.data.msg }
  </div>
</template>

在页面容器中如此使用

<my-test/>
<my-test></my-test>

属性name = "XxxXxx"(React组件)

定义name="XxxXxx"可以给组件定义一个名字,这个名字会自动调用 React 框架的 React.Component 方法来定义类组件,你就可以在页面中用这个属性名<XxxXxx></XxxXxx>来使用该组件

注意:

<template name="MyTest">
  <div class="example">
    { this.data.msg }
  </div>
</template>

在页面容器中如此使用

<MyTest/>
<MyTest></MyTest>

属性lang = "html"(仅支持Omi)

默认情况下,我们的<template>模板是使用 JSX 语法,如果我们增加属性lang = "html",就可以支持编写html格式的字符串模板,你可以使用 ES6 的语法来编写 html 模板<div>${ this.data.msg }<div>,Omil 和 Omi-Snippets 会自动帮你引入Omi.html()方法帮你在客户端进行处理,会有一定的性能损耗,一般情况下不建议使用。

<template name="my-test" lang="html">
  <div class="example">
    ${ this.data.msg }
  </div>
</template>

<script>脚本

每个 .omi 文件最多包含一个 <script> 块。

类组件

如果我们使用过 react 我们会了解到组件通常有两种定义方式,一种是函数组件,一种是类组件,Omil 默认是帮你创建类组件,我们在export default class { // 你的代码 }或者module.exports = class { // 你的代码 }片段中写入你的组件逻辑代码,

注意:

export default class { // 你的代码 } 可以 建议使用
module.exports = class { // 你的代码 } 可以 支持
class MyText { // 你的代码 }
export default MyText
不可以 不支持
class MyText { // 你的代码 }
module.export = MyText
不可以 不支持
<script>
export default class {
  install () {
    this.data = {
      msg: 'Hello world!'
    }
  }
}
</script>

高阶组件(仅支持React)

有时候我们可以使用高阶组件拓展组件本身的一些功能,高阶组件跟类组件一样,只支持下面规定的写法。

export default HOC(class { // 你的代码 }) 可以 建议使用
module.exports = HOC(class { // 你的代码 }) 可以 支持
class MyText { // 你的代码 }
export default HOC(MyText)
不可以 不支持
class MyText { // 你的代码 }
module.export = HOC(MyText)
不可以 不支持
<script>
export default HOC(class {
  install () {
    this.data = {
      msg: 'Hello world!'
    }
  }
})
</script>

下面是一个高阶组件的详细参考例子

<template name="MyTest">
    <div><p>{this.state.title}</p></div>
</template>
<script>
// 高阶函数
const HOC = (props) => {
    return (WraooedComponent) => {
        return class HOC extends WeElement {
            render() {
                return (<div><WraooedComponent name= /></div>)
            }
        }
    }
}
export default HOC({
    age: 18
})(class {
    install () {
        this.data = {
            msg: 'Hello world!'
        }
    }
})
</script>
<style lang="scss">
p { color: #58bc58; }
</style>

或者你可以这样写

<template name="MyTest">
    {HOC(<div><p>{this.state.title}</p></div>)}
</template>
<script>
// 高阶函数
const HOC = (props) => {
    return (WraooedComponent) => {
        return class HOC extends WeElement {
            render() {
                return (<div><WraooedComponent name= /></div>)
            }
        }
    }
}
export default class {
    install () {
        this.data = {
            msg: 'Hello world!'
        }
    }
}
</script>
<style lang="scss">
p { color: #58bc58; }
</style>

属性type="text/babel"

通常情况下,你可以在代码中使用ES6的语法,甚至一些新特性,例如:static,某些情况下我们需要转化为ES5做兼容,我们可以添加属性type="text/babel"

<script>
export default class {
  static name = 'Eno Yao'
  install () {
    this.data = {
      msg: 'Hello world!'
    }
  }
}
</script>

<style>样式

一个 .omi 文件可以包含一个<style>标签。

<style>标签的样式本身具有局部样式的特性,这取决于 Omi 的设计是 Web Components,这有点类似于 Vue 的 scoped 属性。

<style>
.example {
  color: red;
}
</style>

属性lang = "scss"

我们还可以使用lang = "scss"来书写 scss 样式,它会自动帮我们编译为 css 格式内容

<style lang = "scss">
$color: red;
.example {
  color: $color;
}
</style>

语法高亮

建议使用 VS Code 配合 Omi Snippets (该扩展支持语法高亮)扩展开发 Omi 项目,当然你可以把 .omi 文件当作 HTML 对待。

注释

在语言块中使用该语言块对应的注释语法 (HTML、CSS、JavaScript 等)。

JSX 注释语法 {/* comment contents here */}
HTML 注释语法 <!-- comment contents here -->

JSX 简介

观察下面这段代码模板:

<template name="component-name">
  <header onClick={this.test}>{this.data.title}</header>
</template>

这个有趣的标签语法既不是字符串也不是 HTML。

它被称为 JSX,是一个 JavaScript 的语法扩展。我们建议在 Omi 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模版语言,但它具有 JavaScript 的全部功能。

上面的代码事实上会自动编译为下面这份 js 代码

import { WeElement, define, h } from "omi";
class ComponentName extends WeElement {
  render() {
    return h(
      "div",
      {
        onClick: this.testClick
      },
      this.data.title
    );
  }
}
define("component-name", ComponentName);

为什么使用 JSX?

Omi 和 React 不强制要求使用 JSX,但是大多数人发现,在 JavaScript 代码中将 JSX 和 UI 放在一起时,会在视觉上有辅助作用。

Omi 和 React 在使用 Omil 和 Omi Snippets 的区别

Omil和Omi Snippets都支持编译Omi和React,编译的区别取决于<template>name属性值,React的组件名必须首字母大写,Omi的组件首字母不能大写,并且名字中间必须有-符号连接。

React Omi
<template name="ComponentName"> <template name="component-name">
组件名必须首字母大写 组件首字母不能大写,并且名字中间必须有-符号连接

在 JSX 中嵌入表达式

在下面的例子中,我们声明了一个名为 title 的变量,然后在 JSX 中使用它,并将它包裹在大括号中:

<template name="component-name">
    <div>
        {this.data.title}
    </div>
</template>
<script>
    export default class {
        install() {
            this.data = {
                title: "Eno Yao !"
            }
        }
    }
</script>

在 JSX 语法中,你可以在大括号内放置任何有效的 JavaScript 表达式。例如,2 + 2,user.firstName 或 formatName(user) 都是有效的 JavaScript 表达式。

<template name="component-name">
    <div>
        <p>Name: {this.formatName(user)}</p>
        <p>Age: {9+9}</p>
    </div>
</template>
<script>
    const user = {
        firstName: 'Eno',
        lastName: 'Yao'
    };
    export default class {
        formatName(user) {
            return user.firstName + ' ' + user.lastName;
        }
    }
</script>

二元和三元表达式

<template name="component-name">
    <div>
        { !0 ? '真' : <p></p> }
        <h1>{ user.age > 18 && <div>成年</div> }<h1></h1>
    </div>
</template>

数组渲染成列表

<template name="component-name">
    <ul>
        {
            ['a','b','c'].map((item,index) => {
                return <li key={index}>{item}</li>
            })
        }
    </ul>
</template>

JSX 也是一个表达式

在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。

也就是说,你可以在 if 语句和 for 循环的代码块中使用 JSX,将 JSX 赋值给变量,把 JSX 当作参数传入,以及从函数中返回 JSX:

<template name="component-name">
    <div>
        <p>{this.getGreeting(user)}</p>
        <p>{this.getGreeting()}</p>
    </div>
</template>
<script>
    const user = {
        firstName: 'Eno',
        lastName: 'Yao'
    };
    export default class {
        formatName(user) {
            return user.firstName + ' ' + user.lastName;
        }
        getGreeting(user) {
            if (user) {
                return <h1>Hello, {this.formatName(user)}!</h1>;
            }
            return <h1>Hello, Stranger.</h1>;
        }
    }
</script>

JSX 特定属性

你可以通过使用引号,来将属性值指定为字符串字面量

<template name="component-name">
    <div tabIndex="0"></div>
</template>

也可以使用大括号,来在属性值中插入一个 JavaScript 表达式:

<template name="component-name">
    <div tabIndex="0">
        <img src={this.data.avatarUrl} />
    </div>
</template>
<script>
    export default class {
        install() {
            this.data = {
                avatarUrl: 'https://avatars1.githubusercontent.com/u/17243165?s=460&v=4'
            }
        }
    }
</script>

HTML 和 JSX 的一些区别

HTML JSX
<div class> <div className>
<label for> <label htmlFor>
<div tabindex> <div tabIndex>

在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号。你应该仅使用引号(对于字符串值)或大括号(对于表达式)中的一个,对于同一属性不能同时使用这两种符号。

警告:

因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。

例如,JSX 里的 class 变成了 className,而 tabindex 则变为 tabIndex。

使用 JSX 指定子元素

假如一个标签里面没有内容,你可以使用 /> 来闭合标签,就像 XML 语法一样:

<img src={this.data.avatarUrl} />
<input onChange={this.getInputValue.bind(this)} />

JSX 标签里能够包含很多子元素:

<template name="component-name">
    <div>{this.data.element}</div>
</template>
<script>
    export default class {
        install() {
            this.data = {
                element: (
                    <div>
                        <h1>Hello!</h1>
                        <h2>Good to see you here.</h2>
                    </div>
                )
            }
        }
    }
</script>

JSX 表示对象

Babel 会把 JSX 转译成一个名为 h() 函数调用。

以下两种示例代码完全等效:

const element = <div>
    <h1 className="greeting">
        Hello, world!
    </h1>
</div>
const element = h(
  "div",
  null,
  h(
    "h1",
    {
      className: "greeting"
    },
    "Hello, world!"
  )
);

h() 会预先执行一些检查,以帮助你编写无错代码,但实际上它创建了一个这样的对象:

// 注意:这是简化过的结构
const element = {
  children: [{
    attributes: {className: "greeting"},
    children: ["Hello, world!"],
    nodeName: "h1",
  }],
  nodeName: "div"
}

这些对象它们描述了你希望在屏幕上看到的内容。Omi 通过读取这些对象,然后使用它们来构建 DOM 以及保持随时更新。

Props

我们可以在组件的属性上传入属性值,通过传入属性值让组件接受外部的数据而更改自身的状态。

<component-name myObj= />

组件内部通过props接受即可:

<template name="component-name">
    <p>{props.myObj.name}</p>
</template>

我们还可以通过static defaultProps设置默认的props值和通过static propTypes设置默认的props类型。

<template name="component-name">
    <div>
        <p>{props.name}</p>
        <p>{props.age}</p>
    </div>
</template>
<script>
    export default class {
        static defaultProps = {
            name: 'Omi',
            age: 18
        }

        static propTypes = {
            name: String,
            age: Number
        }
    }
</script>

事件处理

Omi 元素的事件处理和 React 一样和 DOM 元素的很相似,但是有一点语法上的不同:

<template name="component-name">
    <div>
        <button onClick={this.onClick}>Hello Omi!</button>
        <button onClick={(evt)=> {alert('Hello Omi!')}}>Hello Omi!</button>
        <button onClick={onClick}>Hello Omi!</button>
    </div>
</template>
<script>
    const onClick = (evt) => {
        alert('Hello Omi!')
    }
    export default class {
        onClick(evt) {
            alert('Hello Omi!')
        }
    }
</script>

事件中的this

你必须谨慎对待 JSX 回调函数中的 this,在 JavaScript 中,class 的方法默认不会绑定 this。如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined。

这并不是 React 特有的行为;这其实与 JavaScript 函数工作原理有关。通常情况下,如果你没有在方法后面添加 (),例如 onClick={this.handleClick},你应该为这个方法绑定 this。

<template name="component-name">
    <div>
        <button onClick={this.onClick.bind(this)}>{this.data.title}</button>
    </div>
</template>
<script>
    export default class {
        install() {
            this.data = { title: 'Hello Omi!' }
        }
        onClick() {
            this.data.title = 'Hi Eno!'
            this.update()
        }
    }
</script>

向事件处理程序传递参数

在循环中,通常我们会为事件处理函数传递额外的参数。例如,若 id 是你要删除那一行的 ID,以下两种方式都可以向事件处理函数传递参数:

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

上述两种方式是等价的,分别通过箭头函数和 Function.prototype.bind 来实现。

在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。

生命周期

以下表格是 Omi 的生命周期:

生命周期钩子 描述
install 组件挂载到 DOM 前
installed 组件挂载到 DOM 后
uninstall 组件从 DOM 中移除前
beforeUpdate update 更新前
updated update 更新后
beforeRender render() 之前
receiveProps 父元素重新渲染触发

举个例子:

<template name="component-name">
    <div>Seconds: {this.data.seconds}</div>
</template>
<script>
    export default class {
        data = {
            seconds: 0
        }
        tick() {
            this.data.seconds++
            this.update()
        }
        install() {
            this.interval = setInterval(() => this.tick(), 1000)
        }
        uninstall() {
            clearInterval(this.interval)
        }
    }
</script>

Update

update 方法是内置的重要核心方法,用于更新组件自身。比如:

this.update()

也可以传递参数,决定是否在 html 模式下忽略 attributes,强行更新:

this.update(true)

当我们组件的 data 值发生变化,我们可以使用this.update()更新视图

<template name="component-name">
    <div>
        <button onClick={this.toggle.bind(this)}>Update</button>
        <p style=>显示或者隐藏</p>
    </div>
</template>
<script>
    export default class {
        data = {
            bool: !0
        }
        toggle() {
            this.data.bool = !this.data.bool
            this.update()
        }
    }
</script>

Ref

<template name="component-name">
    <div>
        <h1 ref={e=> { this.h1 = e }} onClick={this.onClick}>Hello, world!</h1>
    </div>
</template>
<script>
    export default class {
        onClick = (evt) => {
            console.log(this.h1)
        }
    }
</script>

在元素上添加 ref={e => { this.anyNameYouWant = e }} ,然后你就可以 JS 代码里使用 this.anyNameYouWant 访问该元素。你可以使用两种方式来提高 update 的性能:

提前赋值

<template name="component-name">
    <div>
        <h1 ref={e=> { this.myRef = e }} onClick={this.onClick}>Hello, world!</h1>
    </div>
</template>
<script>
    export default class {
        myRef = e => { this.h1 = e }
        onClick = (evt) => {
            console.log(this.h1)
        }
    }
</script>

createRef

你也可以使用 createRef 来得到更高的性能,使用前需要引用 import { createRef } from "omi":

<template name="component-name">
    <div>
        <h1 ref={this.myRef} onClick={this.onClick}>Hello, world!</h1>
    </div>
</template>
<script>
    import { createRef } from "omi";
    export default class {
        myRef = createRef()
        onClick = (evt) => {
            console.log(this.myRef.current)
        }
    }
</script>

Store 是什么?

Store 是 Omi 内置的中心化数据仓库,他解决和提供了下面问题和能力:

组件树数据共享 数据变更按需更新依赖的组件

两份代码完全上手 Store

path/elements/app/index.omi下的根组件

<template name="my-app">
    <div>
        <p>
            Clicked: {this.use.count} times
            {' '}
            <button onClick={this.add}>+</button>
            {' '}
            <button onClick={this.sub}>-</button>
            {' '}
            <button onClick={this.addIfOdd}>
                Add if odd
            </button>
            {' '}
            <button onClick={this.addAsync}>
                Add async
            </button>
        </p>
    </div>
</template>
<script>
    export default class {
        static use = [
            { count: 'count' }
        ]

        add = () => this.store.add()
        sub = () => this.store.sub()

        addIfOdd = () => {
            if (this.use.count % 2 !== 0) {
                this.store.add()
            }
        }

        addAsync = () => {
            setTimeout(() => this.store.add(), 1000)
        }
    }
</script>
<style lang="scss">
    /* CSS */
    p {
        color: #58bc58
    };
</style>

path/src/index.js全局的入口文件代码

import { render } from 'omi'
import './elements/app'

render(<my-app />, '#root', {
    data: {
        count: 0
    },
    sub() {
        this.data.count--
    },
    add() {
        this.data.count++
    },
})

Store 里的 data:

{
  count: 0,
  arr: ['china', 'tencent'],
  motto: 'I love omi.',
  userInfo: {
    firstName: 'dnt',
    lastName: 'zhang',
    age: 18
  }
}

下面举一个复杂的 use 例子:

static use = [
  'count', //直接字符串,JSX 里可通过 this.use[0] 访问
  'arr[0]', //也支持 path,JSX 里可通过 this.use[1] 访问
  //支持 json
  {
    //alias,JSX 里可通过 this.use.reverseMotto 访问
    reverseMotto: [
      'motto', //path
      target => target.split('').reverse().join('')  //computed
    ]
  },
  { name: 'arr[1]' }, //{ alias: path },JSX 里可通过 this.use.name 访问
  {
    //alias,JSX 里可通过 this.use.fullName 访问
    fullName: [
      ['userInfo.firstName', 'userInfo.lastName'], //path array
      (firstName, lastName) => firstName + lastName //computed
    ]
  },
]

下面看看 JSX 中使用:

...
...
<template>
    <div>
      <button onClick={this.sub}>-</button>
      <span>{this.use[0]}</span>
      <button onClick={this.add}>+</button>
      <div>
        <span>{this.use[1]}</span>
        <button onClick={this.rename}>rename</button>
      </div>
      <div>{this.use.reverseMotto}</div><button onClick={this.changeMotto}>change motto</button>
      <div>{this.use.name}</div>
      <div>{this.use[3]}</div>
      <div>
        {this.use.fullName}
        <button onClick={this.changeFirstName}>change first name</button>
      </div>
    </div>
</template>
...
...

如果不带有 alias ,你也可以直接通过 this.store.data.xxx 访问。

store.data 发生变化,依赖变更数据的组件会进行更新,举例说明 Path 命中规则:

Proxy Path(由数据更改产生) static use 中的 path 是否更新
abc abc 更新
abc[1] abc 更新
abc.a abc 更新
abc abc.a 不更新
abc abc[1] 不更新
abc abc[1].c 不更新
abc.b abc.b 更新

以上只要命中一个条件就可以进行更新!

总结: 只要注入组件的 path 等于 use 里声明 或者在 use 里声明的其中 path 子节点下就会进行更新!

CSS

这里说的是 props 的 css,而不是 static css,它提供了修改 shadow dom 内部 scoped style 的能力。

<template name="component-name">
    <div>
        <h1>Look at my color!</h1>
    </div>
</template>
<script>
    export default class {
        static css = `h1{
            color: red;
        }`
    }
</script>

上面的 my-element 的 h1 标签颜色是红色。有什么办法修改吗?

<template name="component-name">
    <div onClick={this.onClick}>
        <my-element css={this.myCSS} />
    </div>
</template>
<script>
    export default class {
        myCSS = `
            h1{
                color: green;
            }
        `
        onClick = () => {
            //动态修改
            this.myCSS = `
                h1{
                    color: blue;
                }
            `
            this.update()
        }
    }
</script>

而且还可以通过下面的方式保证一定能够修改:

color: blue!important;

高阶组件

如果您用过 React,相信对高阶组件肯定不陌生,高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

具体而言,高阶组件是参数为组件,返回值为新组件的函数。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。

HOC 在 React 的第三方库中很常见,例如 Redux 的 connect。

下面这个例子是是在组件中使用 Redux 高阶组件

<template name="Component-name">
    <div><p>{this.state.title}</p></div>
</template>
<script>
    import { connect } from 'react-redux';
    export default connect((state) => {
        return state
    })(class {
        constructor(props) {
            super(props)
            this.state = {
                title: "react"
            }
        }
    })
</script>
<style>
    p {color: #58bc58;}
</style>