React 單文件組件的解決方案 Omil 和 Omi Snippets

Omil 是什麼?

Omil是一個 webpack 的 loader,它容許你以一種名爲單文件組件(SFCs)的格式撰寫 Omi 組件:css

<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 還提供了不少酷炫的特性:html

  • 容許爲 Omi 組件的每一個部分使用其它的 webpack loader,例如在<style>的部分使用 Sass 和在<template>的部分使用 jsx;
  • 容許在一個 .omi 文件中使用自定義塊,並對其運用自定義的 loader 鏈;
  • 使用 webpack loader 將<style><template>中引用的資源看成模塊依賴來處理;
  • 在開發過程當中使用熱重載來保持狀態。

簡而言之,webpack 和 Omi Loader 的結合爲你提供了一個現代、靈活且極其強大的前端工做流,來幫助撰寫 Omi.js 應用。前端

起步

Omi CLI

若是你不想手動設置 webpack,咱們推薦使用 Omi CLI 直接建立一個項目的腳手架。經過 Omi CLI 建立的項目會針對多數常見的開發需求進行預先配置,作到開箱即用。node

若是Omi CLI提供的內建沒有知足你的需求,或者你樂於從零開始建立你本身的 webpack 配置,那麼請繼續閱讀這篇指南。react

手動設置

安裝

首先先安裝好Omilwebpack

npm install -D omil

若是你使用的是 Visual Studio Code 進行開發,強烈建議下載 Omi Snippets 擴展,它會提供給你語法高亮,局部編譯等功能。您能夠在 VSC 擴展界面裏面搜索 omi 這個關鍵詞出現Omi Snippets點擊安裝便可,稍等片刻,當它安裝成功後會提醒你須要從新加載編輯工具,點擊從新加載便可使用。git

圖片描述

每一個Omil包的新版本發佈時,一個相應版本的Omi Snippets也會隨之發佈。github

webpack 配置

Omi Loader 的配置和其它的 loader 基本同樣。web

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

一個更完整的 webpack 配置示例看起來像這樣:npm

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 文件

  • src

    • Hello.omi
    • Hello.js
Hello.omi 開發中你須要編寫的單文件組件
Hello.js 修改或者保存文件Hello.omi後通過插件轉化的js文件

以下圖,左邊的代碼是咱們編寫的 .omi 後綴的單文件組件,右邊是通過 Omi Snippets 生成的 .js 後綴文件。

圖片描述

示例代碼

上圖的示例代碼以下

  • <template> 標籤負責放 JSX 的內容,屬性name="my-test"爲該組件的名字,後面能夠在 JSX 中用<my-text>使用該組件;
  • <script> 標籤負責放入組件的邏輯文件,固定的結構爲 export default class { // 你的代碼 }或者爲export default HOC(class { // 你的代碼 })兩種形式,第一種是定義類組件,第二種用來定義高階組件,你的代碼部分能夠放入生命週期,函數等;
  • <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>

如下代碼就是通過 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 組件,默認生成類組件。

  • name屬性值是組件名要知足 React 框架的組件名字定義規範,首字母必須大寫字母;
  • <template>模板中不能有<script><style>代碼片斷。
<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>來使用該組件

注意:

  • name屬性值是組件名要知足 omi 框架的組件名字定義規範,首字母不能用大寫字母,而且中間必須有-字符;
  • <template>模板中不能有<script><style>代碼片斷。
<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>來使用該組件

注意:

  • name屬性值是組件名要知足 React 框架的組件名字定義規範,首字母必須大寫字母;
  • <template>模板中不能有<script><style>代碼片斷。
<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 { // 你的代碼 }這種寫法,class MyText {} ; export default MyText這種寫法不能夠,由於 Omil 和 Omil Snippets 只識別連續的export default class這段字符串
export default class { // 你的代碼 } 能夠 建議使用
module.exports = class { // 你的代碼 } 能夠 支持
class MyText { // 你的代碼 }<br/>export default MyText 不能夠 不支持
class MyText { // 你的代碼 }<br/>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 { // 你的代碼 }<br/>export default HOC(MyText) 不能夠 不支持
class MyText { // 你的代碼 }<br/>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={{ ...this.props }} /></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={{ ...this.props }} /></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={{ name: 'Eno Yao' }} />

組件內部經過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 元素的很類似,可是有一點語法上的不一樣:

  • Omi 事件的命名採用小駝峯式(camelCase),而不是純小寫。
  • 使用 JSX 語法時你須要傳入一個函數做爲事件處理函數,而不是一個字符串。
<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={{display:this.data.bool?'block':'none'}}>顯示或者隱藏</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 的性能:

  • 提早賦值
  • createRef

提早賦值

<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++
    },
})
  • 經過 static use 聲明依賴的 path
  • store 經過 render 的第三個參數從根節點注入到全部組件。

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>
相關文章
相關標籤/搜索