English | 简体中文 |
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 项目。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-weui
- Omi 版本的 WeUI。omi-auto-animate
- Omi 版本的 @formkit/auto-animate。omi-suspense
- 处理异步依赖。omi-transition
- 提供进入和离开动画。omi-ripple
- 用于为用户界面元素添加涟漪(ripple)效果。当用户与元素交互(例如点击按钮)时,涟漪效果会从交互点开始扩散开来。snake-game-2tier
- 基于 Omi Signal
class 两层架构的贪吃蛇游戏。snake-game-3tier
- 基于 Omi 响应是函数三层架构的贪吃蛇游戏。omi-tutorial
- Omi 官方教程源代码。如果你想帮助项目发展,可以先简单地与同行分享!
多谢!
数据驱动编程
在数据驱动编程中,我们将重点放在数据本身和对数据的操作上,而不是数据所在的对象或数据结构。这种编程范式强调的是数据的变化和流动,以及如何响应这些变化。基于响应式函数的 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)
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<{ count: number }> {
static props = {
count: {
type: Number,
default: 0,
changed(newValue, oldValue) {
this.setState({ count: newValue })
}
}
}
state = {
count: 1
}
@bind
sub() {
this.setState({ count: this.state.count - 1 })
this.fire('change', this.state.count)
}
@bind
add() {
this.setState({ count: this.state.count + 1 })
this.fire('change', this.state.count)
}
render() {
return (
<>
<button onClick={this.sub}>-</button>
<span>{this.state.count}</span>
<button onClick={this.add}>+</button>
</>
)
}
}
<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></h1>
<my-counter @change="change" :count="count" />
<p>
【Omi】
</p>
<div class="card">
<button type="button" @click="count++">count is 8</button>
<p>
【Vue】
</p>
</div>
</template>
如果在 omi 组件中使用:
this.fire('count-change', this.state.count)
在 vue 中使用组件监听事件如下:
<my-counter @count-change="change" :count="count" />
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
MIT © Tencent