omi/docs/main-concepts.cn.md

15 KiB
Raw Blame History

English | 简体中文 | 한국어

Omi 文档

My First Element

import { WeElement, define, render } from 'omi'

define('my-first-element', class extends WeElement {
  render() {
    return (
      <h1>Hello, world!</h1>
    )
  }
})

render(<my-first-element></my-first-element>, 'body')

在 HTML 开发者工具里看看渲染得到的结构:

fe

除了渲染到 body你可以在其他任意自定义元素中使用 my-first-element

Props

import { WeElement, define, render } from 'omi'

define('my-first-element', class extends WeElement {
  render(props) {
    return (
      <h1>Hello, {props.name}!</h1>
    )
  }
})

render(<my-first-element name="world"></my-first-element>, 'body')

你也可以传任意类型的数据给 props:

import { WeElement, define, render } from 'omi'

define('my-first-element', class extends WeElement {
  render(props) {
    return (
      <h1>Hello, {props.myObj.name}!</h1>
    )
  }
})

render(<my-first-element my-obj={{ name: 'world' }}></my-first-element>, 'body')

my-obj 将映射到 myObj驼峰的方式。你可以通过静态属性 props 来设置默认值:

import { WeElement, define, render } from 'omi'

define('my-first-element', class extends WeElement {
  static defaultProps = {
		name: 'Omi',
		myAge: 18
	}

  render(props) {
    return (
      <h1>Hello, {props.name}! Age {props.myAge}</h1>
    )
  }
})

render(<my-first-element name="world"></my-first-element>, 'body')

通过 props ,你可以透传 style 或者 class 给根节点,比如 → 这里 透传 style:

<el-button onClick={this.onClick} style="color:red;">默认按钮1</el-button>
<el-button type="primary" style={{color:'red'}}>主要按钮</el-button>

Event

define('my-first-element', class extends WeElement {
  onClick = (evt) => {
    alert('Hello Omi!')
  }

  render() {
    return (
      <h1 onClick={this.onClick}>Hello, world!</h1>
    )
  }
})

Custom Event

define('my-first-element', class extends WeElement {
  onClick = (evt) => {
    this.fire('myevent', { name: 'abc' })
  }

  render(props) {
    return (
      <h1 onClick={this.onClick}>Hello, world!</h1>
    )
  }
})

然后在你的自定义元素上绑定事件:

<my-first-element onMyEvent={(evt) => { alert(evt.detail.name) }}></my-first-element>

通过 this.fire 触发自定义事件fire 第一个参数是事件名称,第二个参数是传递的数据。通过 evt.detail 可以获取到传递的数据。

CSS

define('my-first-element', class extends WeElement {
  static css = `h1 { color: red; }`

  render(props) {
    return (
      <h1>Hello, world!</h1>
    )
  }
})

render(<my-first-element onMyEvent={(evt) => { alert(evt.detail.name) }}></my-first-element>, 'body')

你也可以另起一个文件用来写 CSS但是需要配置一下 webpack to-string-loader

{
  test: /[\\|\/]_[\S]*\.scss$/,
  use: [
      'to-string-loader',
      'css-loader',
      'sass-loader'
  ]
}

然后:

import { define, WeElement } from 'omi'
import css from '../style/_button.scss'

define('el-button', class extends WeElement {
    static css = css
    ...
    ...

或者:

static css = require('../style/_button.scss')

Lifecycle

Lifecycle method When it gets called
install before the component gets mounted to the DOM
installed after the component gets mounted to the DOM
uninstall prior to removal from the DOM
beforeUpdate before update
updated after update
beforeRender before render()
receiveProps parent element re-render will trigger it

For example:

import { render, WeElement, define } from 'omi'

define('my-timer', class extends WeElement {
  static observe = true

  data = {
    seconds: 0
  }

  tick() {
    this.data.seconds++
  }

  install() {
    this.interval = setInterval(() => this.tick(), 1000)
  }

  uninstall() {
    clearInterval(this.interval)
  }

  render() {
    return <div>Seconds: {this.data.seconds}</div>
  }
})

render(<my-timer />, 'body')

Ref

define('my-first-element', class extends WeElement {
  onClick = (evt) => {
    console.log(this.h1)
  }

  render(props) {
    return (
      <div>
        <h1 ref={e => { this.h1 = e }} onClick={this.onClick}>Hello, world!</h1>
      </div>
    )
  }
})

render(<my-first-element></my-first-element>, 'body')

在元素上添加 ref={e => { this.anyNameYouWant = e }} ,然后你就可以 JS 代码里使用 this.anyNameYouWant 访问该元素。

你也可以使用 createRef 来得到更高的性能:

import { define, WeElement, createRef } from 'omi'

define('my-first-element', class extends WeElement {
  onClick = (evt) => {
    console.log(this.myRef.current)  //h1
  }

  myRef = createRef()

  render(props) {
    return (
      <div>
        <h1 ref={this.myRef} onClick={this.onClick}>Hello, world!</h1>
      </div>
    )
  }
})

render(<my-first-element></my-first-element>, 'body')

extractClass

import { classNames, extractClass } from 'omi'

define('my-element', class extends WeElement {
  render(props) {
    //extractClass 会取出 props 的  class 或 className 属性并与其他 classNames 合并在一起
    const cls = extractClass(props, 'o-my-class', {
      'other-class': true,
      'other-class-b': this.xxx === 1
    })

    return (
      <div {...cls} {...props}>
        Test
      </div>
    )
  }
})
  

上面的 classNames 和 npm 上的 classNames 是一样的。

Store

Omi 的 Store 体系: 从根组件注入,在所有子组件可以共享。使用起来非常简单:

import { define, render, WeElement } from 'omi'

define('my-hello', class extends WeElement {
  render() {
    //任意子组件的任意方法都可以使用 this.store 访问注入的 store
    return <div>{this.store.name}</div>
  }
})

define('my-app', class extends WeElement {
  handleClick = () => {
     //任意子组件的任意方法都可以使用 this.store 访问注入的 store
    this.store.reverse()
    this.update()
  }

  render() {
    return (
      <div>
        <my-hello />
        <button onclick={this.handleClick}>reverse</button>
      </div>
    )
  }
})

const store = {
  name: 'abc',
  reverse: function() {
    this.name = this.name.split("").reverse().join("")
  }
}
//通过第三个参数注入
render(<my-app />, document.body, store)

与全局变量不同的是, 当有多个根节点的时候就可以注入多个 store而全局变量只有一个。

Slot

HTML<slot>元素Web组件技术套件的一部分是Web组件内部的占位符您可以用自己的标记填充该占位符该标记允许您创建单独的DOM树并将它们一起呈现。

define('hello-element', class extends WeElement {
  render() {
    return (
      <div onClick={this.onClick}>
        <p><slot name="my-text">My default text</slot></p>
      </div>
    )
  }
})

define('my-app', class extends WeElement {
  render() {
    return (
      <div >
        <hello-element>
          <span slot="my-text">Let's have some different text!</span>
        </hello-element>
      </div>
    )
  }
})

render(<my-app></my-app>, 'body')

→ Slot MDN

noSlot

对于写一些 omi 插件noSlot 非常有用,它不会把 children 插入到 DOM 中,并且你可以在插件中通过 props.children 拿到虚拟 DOM。

import { define, render, WeElement } from 'omi'

define('fancy-tabs', class extends WeElement {
  static noSlot = true

  render() {
    return [
      <div id="tabs">
        <slot id="tabsSlot" name="title" />
      </div>,
      <div id="panels">
        <slot id="panelsSlot" />
      </div>,
      <div>Show me only when noSlot is true!</div>
    ]
  }
})

define('my-app', class extends WeElement {
  render() {
    return (
      <div>
        <fancy-tabs>
          <button slot="title">Title</button>
          <button slot="title" selected>
            Title 2
          </button>
          <button slot="title">Title 3</button>
          <section>content panel 1</section>
          <section>content panel 2</section>
          <section>content panel 3</section>
        </fancy-tabs>
      </div>
    )
  }
})

render(<my-app />, 'body')

Observe

Omi Observe

你可以为那些不需要 store 的自定义元素使用 observe 创建响应式视图,比如:

define("my-app", class extends WeElement {
  static observe = true

  install() {
    this.data.name = "omi"
  }

  onClick = () => {
    this.data.name = "Omi V4.0"
  }

  render(props, data) {
    return (
      <div onClick={this.onClick}>
        <h1>Welcome to {data.name}</h1>
      </div>
    )
  }
})

MergeUpdate

如果使用了observe 和 mergeUpdate数据更改之后视图不是立即变更

define('todo-list', class extends WeElement {
  static observe = true

  static mergeUpdate = true

  ....
})

如果你想获取真实变更后的dom你可以使用tick或者nextTick。

import { render, WeElement, define, tick, nextTick } from 'omi'

define('todo-list', class extends WeElement {
  render(props) {
    return (
      <ul>
        {props.items.map(item => (
          <li key={item.id}>{item.text}</li>
        ))}
      </ul>
    )
  }
})

define('todo-app', class extends WeElement {
  static observe = true

  static get data() {
    return { items: [], text: '' }
  }
  install() {
    tick(() => {
      console.log('tick')
    })

    tick(() => {
      console.log('tick2')
    })
  }

  beforeRender() {
    nextTick(() => {
      console.log('nextTick')
    })

    // don't using tick in beforeRender or beforeUpdate or render or afterUpdate
    // tick(() => {
    //   console.log(Math.random())
    // })
  }

  installed() {
    console.log('installed')
  }

  render() {
    console.log('render')
    return (
      <div>
        <h3>TODO</h3>
        <todo-list items={this.data.items} />
        <form onSubmit={this.handleSubmit}>
          <input
            id="new-todo"
            onChange={this.handleChange}
            value={this.data.text}
          />
          <button>Add #{this.data.items.length + 1}</button>
        </form>
      </div>
    )
  }

  handleChange = e => {
    this.data.text = e.target.value
  }

  handleSubmit = e => {
    e.preventDefault()
    if (!this.data.text.trim().length) {
      return
    }
    this.data.items.push({
      text: this.data.text,
      id: Date.now()
    })
    this.data.text = ''
  }
})

render(<todo-app />, 'body')

你也可以手动执行 this.update ,然后在它后面获取真实的 dom 。

SSR

renderToString(<todo-app />, {
  //是否包含局部样式的开关
  scopedCSS: true
})

Snap