29 Star 135 Fork 0

腾讯开源 / omi

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

English | 简体中文

omi

Omi - Web Components 框架

  • 📶 信号 Signal 驱动的响应式编程,reactive-signal强力驱动
  • 微小的尺寸,极速的性能
  • 💗 目标 100+ 模板 & OMI 模板源码
  • 🐲 OMI Form & OMI Form 游乐场 & Lucide Omi 图标
  • 🌐 你要的一切都有: Web Components, JSX, Function Components, Router, Suspense, Directive, Tailwindcss...
  • 💯 面向对象编程(OOP) 和 数据驱动编程(DOP) 两种范式都支持
  • 💒 使用 Constructable Stylesheets 轻松管理和共享样式
import { render, signal, tag, Component, h } from 'omi'

const count = signal(0)

function add() {
  count.value++
}

function sub() {
  count.value--
}

@tag('counter-demo')
export class CounterDemo extends Component {
  static css = 'span { color: red; }'

  render() {
    return (
      <>
        <button onClick={sub}>-</button>
        <span>{count.value}</span>
        <button onClick={add}>+</button>
      </>
    )
  }
}

使用该组件:

import { h } from 'omi'
import './counter-demo'

render(<counter-demo />, document.body)

// 或者
import { CounterDemo, Other } from './counter-demo'
// 当需要导入其他东西的时候,防止被 tree shaking
render(<CounterDemo />, document.body)

// 或者
document.body.appendChild(document.createElement('counter-demo'))

安装

npm i omi

快速创建 Omi + Vite + TS/JS 项目:

$ npx omi-cli init my-app    # 或者创建js项目: npx omi-cli init-js my-app
$ cd my-app           
$ npm start           # develop
$ npm run build       # release

快速创建 Omi + Router + Signal + Suspense + Tailwindcss + Vite + TS 项目:

$ npx omi-cli init-spa my-app  
$ cd my-app           
$ npm start           # develop
$ npm run build       # release

  • 核心包
    • omi - Omi 框架的实现代码。
    • omi-form - 强大易用且跨框架的表单解决方案。
    • lucide-omi - Lucide 的 Omi 图标集合。
    • omiu - 希望打造最好的 web 组件。
    • omi-router - 创建单页应用。
    • omi-cli - 快速创建 Omi + Vite + TS/JS 项目。
  • 入门套件 (未发布到 npm)
    • omi-elements - 官方推出 Tailwind Omi 套件。
    • omi-starter-spa - 快速创建单页应用(SPA),内置了 Omi + OmiRouter + Tailwindcss + TypeScript + Vite + Prettier。
    • omi-starter-ts - 基于 Vite + Omi + TypeScript 的模板。
    • omi-starter-tailwind - 基于 Vite + Omi + TypeScript + Tailwindcss 的模板。
    • omi-starter-js - 基于 Vite + Omi + JavaScript 的模板。
    • omi-vue - Vue SFC + Vite + OMI + OMI-WeUI.
  • 组件
  • 指令
    • omi-transition - 提供进入和离开动画。
    • omi-ripple - 用于为用户界面元素添加涟漪(ripple)效果。当用户与元素交互(例如点击按钮)时,涟漪效果会从交互点开始扩散开来。
  • 综合性例子 (未发布到 npm)

如果你想帮助项目发展,可以先简单地与同行分享!

多谢!

使用

TodoApp 使用响应式函数

数据驱动编程

在数据驱动编程中,我们将重点放在数据本身和对数据的操作上,而不是数据所在的对象或数据结构。这种编程范式强调的是数据的变化和流动,以及如何响应这些变化。基于响应式函数的 TodoApp 就是一个很好的例子,它使用了响应式编程的概念,当数据(即待办事项列表)发生变化时,UI 会自动更新以反映这些变化。

import { render, signal, computed, tag, Component, h } from 'omi'

const todos = signal([
  { text: 'Learn OMI', completed: true },
  { text: 'Learn Web Components', completed: false },
  { text: 'Learn JSX', completed: false },
  { text: 'Learn Signal', completed: false }
])

const completedCount = computed(() => {
  return todos.value.filter(todo => todo.completed).length
})

const newItem = signal('')

function addTodo() {
  // api a,不会重新创建数组
  todos.value.push({ text: newItem.value, completed: false })
  todos.update() // 非值类型的数据更新需要手动调用 update 方法

  // api b, 和上面的 api a 效果一样,但是会创建新的数组
  // todos.value = [...todos.value, { text: newItem.value, completed: false }]

  newItem.value = '' // 值类型的数据更新需会自动 update
}

function removeTodo(index: number) {
  todos.value.splice(index, 1)
  todos.update() // 非值类型的数据更新需要手动调用 update 方法
}

@tag('todo-list')
class TodoList extends Component {
  onInput = (event: Event) => {
    const target = event.target as HTMLInputElement
    newItem.value = target.value
  }

  render() {
    return (
      <>
        <input type="text" value={newItem.value} onInput={this.onInput} />
        <button onClick={addTodo}>Add</button>
        <ul>
          {todos.value.map((todo, index) => {
            return (
              <li>
                <label>
                  <input
                    type="checkbox"
                    checked={todo.completed}
                    onInput={() => {
                      todo.completed = !todo.completed
                      todos.update()
                    }}
                  />
                  {todo.completed ? <s>{todo.text}</s> : todo.text}
                </label>
                {' '}
                <button onClick={() => removeTodo(index)}></button>
              </li>
            )
          })}
        </ul>
        <p>Completed count: {completedCount.value}</p>
      </>
    )
  }
}

render(<todo-list />, document.body)

TodoApp 使用信号类 Signal

面向对象编程

在面向对象编程中,我们将重点放在对象上,对象包含了数据和操作数据的方法。这种编程范式强调的是对象之间的交互和协作,以及如何通过对象的封装、继承和多态性来组织和管理代码。基于响应式函数的 TodoApp 也可以使用面向对象的方式来实现,例如,我们可以创建一个 TodoList 类,这个类包含了待办事项列表的数据和操作这些数据的方法,以及一个 update 方法来更新 UI。

import { render, Signal, tag, Component, h, computed } from 'omi'

type Todo = { text: string, completed: boolean }

class TodoApp extends Signal<{ todos: Todo[], filter: string, newItem: string }> {
  completedCount: ReturnType<typeof computed>

  constructor(todos: Todo[] = []) {
    super({ todos, filter: 'all', newItem: '' })
    this.completedCount = computed(() => this.value.todos.filter(todo => todo.completed).length)
  }

  addTodo = () => {
    // api a
    this.value.todos.push({ text: this.value.newItem, completed: false })
    this.value.newItem = ''
    this.update()

    // api b, same as api a
    // this.update((value) => {
    //   value.todos.push({ text: value.newItem, completed: false })
    //   value.newItem = ''
    // })
  }

  toggleTodo = (index: number) => {
    const todo = this.value.todos[index]
    todo.completed = !todo.completed
    this.update()
  }

  removeTodo = (index: number) => {
    this.value.todos.splice(index, 1)
    this.update()
  }
}

const todoApp = new TodoApp([
  { text: 'Learn OMI', completed: true },
  { text: 'Learn Web Components', completed: false },
  { text: 'Learn JSX', completed: false },
  { text: 'Learn Signal', completed: false }
])

@tag('todo-list')
class TodoList extends Component {
  onInput = (event: Event) => {
    const target = event.target as HTMLInputElement
    todoApp.value.newItem = target.value
  }

  render() {
    const { todos } = todoApp.value
    const { completedCount, toggleTodo, addTodo, removeTodo } = todoApp
    return (
      <>
        <input type="text" value={todoApp.value.newItem} onInput={this.onInput} />
        <button onClick={addTodo}>Add</button>
        <ul>
          {todos.map((todo, index) => {
            return (
              <li>
                <label>
                  <input
                    type="checkbox"
                    checked={todo.completed}
                    onInput={() => toggleTodo(index)}
                  />
                  {todo.completed ? <s>{todo.text}</s> : todo.text}
                </label>
                {' '}
                <button onClick={() => removeTodo(index)}></button>
              </li>
            )
          })}
        </ul>
        <p>Completed count: {completedCount.value}</p>
      </>
    )
  }
}

render(<todo-list />, document.body)

这里不讨论哪种方式(DOP,OOP)的好坏,使用 omi 两种方式都可以任意选择。

自动导入 h

vite.config.js:

import { defineConfig } from 'vite'

export default defineConfig({
  esbuild: {
    jsxInject: "import { h } from 'omi'",
    jsxFactory: "h",
    jsxFragment: "h.f"
  }
})

你可以在构建时候注入代码,这样就不用手动导出 h

定义跨框架组件

在 Vue 中使用 Omi component 例子如下:

my-counter.tsx:

import { tag, Component, h, bind } from 'omi'

@tag('my-counter')
class MyCounter extends Component {
  static props = {
    count: {
      type: Number,
      default: 0,
      changed(newValue, oldValue) {
        this.state.count = newValue
        this.update()
      }
    }
  }

  state = {
    count: null
  }

  install() {
    this.state.count = this.props.count
  }

  @bind
  sub() {
    this.state.count--
    this.update()
    this.fire('change', this.state.count)
  }

  @bind
  add() {
    this.state.count++
    this.update()
    this.fire('change', this.state.count)
  }

  render() {
    return (
      <>
        <button onClick={this.sub}>-</button>
        <span>{this.state.count}</span>
        <button onClick={this.add}>+</button>
      </>
    )
  }
}

在 Vue3 中使用

<script setup>
import { ref } from 'vue'
// 导入 omi 组件
import './my-counter'

defineProps({
  msg: String,
})

const count = ref(0)

const change = (e) => {
  count.value = e.detail
}

</script>

<template>
  <h1>{{ msg }}</h1>

  <my-counter @change="change" :count="count" />
  <p>
    【Omi】 
  </p>

  <div class="card">
    <button type="button" @click="count++">count is {{ count }}</button>
    <p>
     【Vue】 
    </p>
  </div>

</template>

如果在 omi 组件中使用:

this.fire('count-change', this.state.count)

在 vue 中使用组件监听事件如下:

<my-counter @count-change="change" :count="count" />

在 React 中使用

import { useState, useRef, useEffect } from 'react'
import useEventListener from '@use-it/event-listener'
import './my-counter'

function App() {
  const [count, setCount] = useState(100)
  const myCounterRef = useRef(null)

  useEffect(() => {
    const counter = myCounterRef.current
    if (counter) {
      const handleChange = (evt) => {
        setCount(evt.detail)
      }
      counter.addEventListener('change', handleChange)
      return () => {
        counter.removeEventListener('change', handleChange)
      }
    }
  }, [])

  return (
    <>
      <h1>Omi + React</h1>
      <my-counter count={count} ref={myCounterRef}></my-counter>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
      </div>
    </>
  )
}

export default App

贡献者

License

MIT © Tencent

空文件

简介

Omi - 前端跨框架跨平台框架 基于 Web Components 并支持 IE8+(omio),小程序(omi-kbone) 和 任意前端框架集成 展开 收起
JavaScript 等 5 种语言
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
JavaScript
1
https://gitee.com/Tencent/omi.git
git@gitee.com:Tencent/omi.git
Tencent
omi
omi
master

搜索帮助