Omi - 合一

下一代 Web 框架,去万物糟粕,合精华为一

→ https://github.com/Tencent/omi

特性

  • 4KB 的代码尺寸,比小更小
  • 顺势而为,顺从浏览器的发展和 API 设计
  • Webcomponents + JSX 相互融合为一个框架 Omi
  • Webcomponents 也可以数据驱动视图, UI = fn(data)
  • JSX 是开发体验最棒(智能提示)、语法噪音最少的 UI 表达式
  • 独创的 Path Updating 机制,基于 Proxy 全自动化的精准更新,功耗低,自由度高,性能卓越,方便集成 requestIdleCallback
  • 使用 store 系统不需要调用 this.udpate,它会自动化按需更新局部视图
  • 看看Facebook React 和 Web Components对比优势,Omi 融合了各自的优点,而且给开发者自由的选择喜爱的方式
  • Shadom DOM 与 Virtual DOM 融合,Omi 既使用了虚拟 DOM,也是使用真实 Shadom DOM,让视图更新更准确更迅速
  • 类似 WeStore 体系,99.9% 的项目不需要什么时间旅行,也不仅仅 redux 能时间旅行,请不要上来就 redux,Omi store 体系可以满足所有项目
  • 局部 CSS 最佳解决方案(Shadow DOM),社区为局部 CSS 折腾了不少框架和库(使用js或json写样式,如:Radium,jsxstyle,react-style;与webpack绑定使用生成独特的className文件名—类名—hash值,如:CSS Modules,Vue),都是 hack 技术;Shadow DOM Style 是最完美的方案

对比同样开发 TodoApp, Omi 和 React 渲染完的 DOM 结构:

左(上)边是Omi,右(下)边是 React,Omi 使用 Shadow DOM 隔离样式和语义化结构。


一个 HTML 完全上手

下面这个页面不需要任何构建工具就可以执行

<!DOCTYPE html>
<html> <head>
<meta charset="UTF-8" />
<title>Add Omi in One Minute</title>
</head> <body>
<script src="https://unpkg.com/omi"></script>
<script>
const { WeElement, h, render, define } = Omi class LikeButton extends WeElement {
install() {
this.data = { liked: false }
} render() {
if (this.data.liked) {
return 'You liked this.'
} return h(
'button',
{
onClick: () => {
this.data.liked = true
this.update()
}
},
'Like'
)
}
} define('like-button', LikeButton) render(h('like-button'), 'body')
</script>
</body> </html>

Getting Started

Install

$ npm i omi-cli -g               # install cli
$ omi init your_project_name # init project, you can also exec 'omi init' in an empty folder
$ cd your_project_name # please ignore this command if you executed 'omi init' in an empty folder
$ npm start # develop
$ npm run build # release

Cli 自动创建的项目脚手架是基于单页的 create-react-app 改造成多页的,有配置方面的问题可以查看 create-react-app 用户指南

Hello Element

先创建一个自定义元素:

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

@tag('hello-element')
class HelloElement extends WeElement { onClick = (evt) => {
//trigger CustomEvent
this.fire('abc', { name : 'dntzhang', age: 12 })
evt.stopPropagation()
} css() {
return `
div{
color: red;
cursor: pointer;
}`
} render(props) {
return (
<div onClick={this.onClick}>
Hello {props.msg} {props.propFromParent}
<div>Click Me!</div>
</div>
)
}
}

使用该元素:

import { tag, WeElement, render } from 'omi'
import './hello-element' @tag('my-app')
class MyApp extends WeElement {
static get data() {
return { abc: '', passToChild: '' }
} //bind CustomEvent
onAbc = (evt) => {
// get evt data by evt.detail
this.data.abc = ' by ' + evt.detail.name
this.update()
} css() {
return `
div{
color: green;
}`
} render(props, data) {
return (
<div>
Hello {props.name} {data.abc}
<hello-element onAbc={this.onAbc} prop-from-parent={data.passToChild} msg="WeElement"></hello-element>
</div>
)
}
} render(<my-app name='Omi v4.0'></my-app>, 'body')

告诉 Babel 把 JSX 转化成 Omi.h() 的调用:

{
"presets": ["env", "omi"]
}

需要安装下面两个 npm 包支持上面的配置:

"babel-preset-env": "^1.6.0",
"babel-preset-omi": "^0.1.1",

如果不想把 css 写在 js 里,你可以使用 to-string-loader, 比如下面配置:

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

如果你的 css 文件以 _ 开头, css 会使用 to-string-loader. 如:

import { tag, WeElement render } from 'omi'
//typeof cssStr is string
import cssStr from './_index.css' @tag('my-app')
class MyApp extends WeElement { css() {
return cssStr
}
...
...
...

TodoApp

下面列举一个相对完整的 TodoApp 的例子:

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

@tag('todo-list')
class TodoList extends WeElement {
render(props) {
return (
<ul>
{props.items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);
}
} @tag('todo-app')
class TodoApp extends WeElement {
static get data() {
return { items: [], text: '' }
} 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></todo-app>, 'body')

Store

使用 Store 体系可以告别 update 方法,基于 Proxy 的全自动属性追踪和更新机制。强大的 Store 体系是高性能的原因,除了靠 props 决定组件状态的组件,其余组件所有 data 都挂载在 store 上,

export default {
data: {
items: [],
text: '',
firstName: 'dnt',
lastName: 'zhang',
fullName: function () {
return this.firstName + this.lastName
},
globalPropTest: 'abc', //更改我会刷新所有页面,不需要再组件和页面声明data依赖
ccc: { ddd: 1 } //更改我会刷新所有页面,不需要再组件和页面声明data依赖
},
globalData: ['globalPropTest', 'ccc.ddd'],
add: function () {
if (!this.data.text.trim().length) {
return;
}
this.data.items.push({
text: this.data.text,
id: Date.now()
})
this.data.text = ''
}
//默认 false,为 true 会无脑更新所有实例
//updateAll: true
}

自定义 Element 需要声明依赖的 data,这样 Omi store 根据自定义组件上声明的 data 计算依赖 path 并会按需局部更新。如:

class TodoApp extends WeElement {
static get data() {
//如果你用了 store,这个只是用来声明依赖,按需 Path Updating
return { items: [], text: '' }
}
...
...
...
handleChange = (e) => {
this.store.data.text = e.target.value
} handleSubmit = (e) => {
e.preventDefault()
this.store.add()
}
}
  • 数据的逻辑都封装在了 store 定义的方法里 (如 store.add)
  • 视图只负责传递数据给 store (如上面调用 store.add 或设置 store.data.text)

需要在 render 的时候从根节点注入 store 才能在所有自定义 Element 里使用 this.store:

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

→ Store 完整的代码

总结一下:

  • store.data 用来列出所有属性和默认值(除去 props 决定的视图的组件)
  • 组件和页面的 data 用来列出依赖的 store.data 的属性 (omi会记录path),按需更新
  • 如果页面简单组件很少,可以 updateAll 设置成 true,并且组件和页面不需要声明 data,也就不会按需更新
  • globalData 里声明的 path,只要修改了对应 path 的值,就会刷新所有页面和组件,globalData 可以用来列出所有页面或大部分公共的属性 Path

文档

My First Element

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

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

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

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

Props

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

@tag('my-first-element')
class MyFirstElement extends WeElement {
render(props) {
return (
<h1>Hello, {props.name}!</h1>
)
}
} render(<my-first-element name="world"></my-first-element>, 'body')

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

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

@tag('my-first-element')
class MyFirstElement 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,驼峰的方式。

Event

class MyFirstElement extends WeElement {
onClick = (evt) => {
alert('Hello Omi!')
} render() {
return (
<h1 onClick={this.onClick}>Hello, wrold!</h1>
)
}
}

Custom Event

@tag('my-first-element')
class MyFirstElement extends WeElement {
onClick = (evt) => {
this.fire('myevent', { name: 'abc' })
} render(props) {
return (
<h1 onClick={this.onClick}>Hello, world!</h1>
)
}
} render(<my-first-element onMyEvent={(evt) => { alert(evt.detail.name) }}></my-first-element>, 'body')

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

Ref

@tag('my-first-element')
class MyFirstElement 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 访问该元素。

Store System

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

@tag('my-first-element')
class MyFirstElement extends WeElement {
//You must declare data here for view updating
static get data() {
return { name: null }
} onClick = () => {
//auto update the view
this.store.data.name = 'abc'
} render(props, data) {
//data === this.store.data when using store stystem
return (
<h1 onClick={this.onClick}>Hello, {data.name}!</h1>
)
}
} const store = {
data: { name: 'Omi' }
}
render(<my-first-element name="world"></my-first-element>, 'body', store)

当使用 store 体系是,static get data 就仅仅被用来声明依赖,举个例子:

static get data() {
return {
a: null,
b: null,
c: { d: [] },
e: []
}
}

会被转换成:

{
a: true,
b: true,
'c.d':true,
e: true
}

举例说明 Path 命中规则:

diffResult updatePath 是否更新
abc abc 更新
abc[1] abc 更新
abc.a abc 更新
abc abc.a 不更新
abc abc[1] 不更新
abc abc[1].c 不更新
abc.b abc.b 更新

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

总结就是只要等于 updatePath 或者在 updatePath 子节点下都进行更新!

看可以看到 store 体系是中心化的体系?那么怎么做到部分组件去中心化?使用 tag 的第二个参数:

@tag('my-first-element', true)

纯元素!不会注入 store!

生命周期

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 render()
afterUpdate after render()

生态

在里面查找你想要的组件,直接使用,或者花几分钟就能转换成 Omi Element(把模板拷贝到 render 方法,style拷贝到 css 方法)。

浏览器兼容

Omi 4.0+ works in the latest two versions of all major browsers: Safari 10+, IE 11+, and the evergreen Chrome, Firefox, and Edge.

→ polyfills

由于需要使用 Proxy 的原因,放弃IE!

Star & Fork

License

MIT © Tencent

腾讯发布新版前端组件框架 Omi,全面拥抱 Web Components的更多相关文章

  1. 前端未来趋势之原生API:Web Components

    声明:未经允许,不得转载. Web Components 现世很久了,所以你可能听说过,甚至学习过,非常了解了.但是没关系,可以再重温一下,温故知新. 浏览器原生能力越来越强. js 曾经的 JQue ...

  2. 如何使用前端分页框架bootstrap paginator

    前端分页框架bootstrap paginator用于web前端页面快速实现美观大方的翻页功能.在实现交互良好的页面翻页功能时,往往还需要配合使用后端分页框架pagehelper.pagehelper ...

  3. 跨界!Omi 发布多端统一框架 Omip 打通小程序与 Web 腾讯开源 2月28日

    https://mp.weixin.qq.com/s/z5qm-2bHk_BCJAwaodrMIg 跨界!Omi 发布多端统一框架 Omip 打通小程序与 Web 腾讯开源 2月28日

  4. 跨界 - Omi 发布多端统一框架 Omip 打通小程序与 Web

    Omip 今天,Omi 不仅仅可以开发桌面 Web.移动 H5,还可以直接开发小程序!直接开发小程序!直接开发小程序! Github Omi 简介 Omi 框架是微信支付线研发部研发的下一代前端框架, ...

  5. 前端mv框架下(目前写的是vue),对组件抽象的思考

    前沿: 抽象是门大学问.前端mv框架中,以组件化的概念为主.经常会考虑抽象到组件级别,进行复用.合理的抽象,能提高效率,减少业务逻辑视图的耦合程度.不合理的抽象,则会增加代码的复杂程度. 遇到的问题 ...

  6. 比较实用的前端 js框架 ,组件 汇总

    一.js 前端ui框架 Kendo UI 商用收费,组件丰富,界面简洁美观,有jQuery和angular两个js的版本 Webix 商用收费,组件丰富,界面特别美观 JQwidgets 商用收费,丰 ...

  7. 基于node的前端组件包发布至nexus和npmjs

    目录 目录... 3 1. 前言... 1 2. 配置... 1 2.1. 建立组件的导出模块... 1 2.2. 建立组件入口文件... 1 2.3. 配置“ng-package.json”文件.. ...

  8. 前端Js框架汇总

    概述: 有些日子没有正襟危坐写博客了,互联网飞速发展的时代,技术更新迭代的速度也在加快.看着Java.Js.Swift在各领域心花路放,也是煞是羡慕.寻了寻.net的消息,也是振奋人心,.net co ...

  9. 前端UI框架和JS类库

    一.前端框架库: 1.Zepto.js 地址:http://www.css88.com/doc/zeptojs/ 描述:Zepto是一个轻量级的针对现代高级浏览器的JavaScript库, 它与jqu ...

随机推荐

  1. CSS布局---居中方法

    在web页面布局中居中是我们常遇到的情况,而居中分为水平居中与垂直居中 文本的居中 CSS中对文本的居中做的非常友好,我们是需要text-align, line-height 两个属性就可以控制文本的 ...

  2. (转载)SPARKR,对RDD操作的介绍

    原以为,用sparkR不能做map操作, 搜了搜发现可以. lapply等同于map, 但是不能操作spark RDD. spark2.0以后, sparkR增加了 dapply, dapplycol ...

  3. 实现wc部分功能 java

    GitHub地址:https://github.com/carlylewen/ruangong 相关要求 基本功能 wc.exe -c file.c     //返回文件 file.c 的字符数(实现 ...

  4. Python中类的定义及使用

    类是一些有共同特征和行为事务事物的抽象概念的总和. 从中可以看出,方法只能使用实例直接调用(无需传self参数),而使用类调用必须传入实例对象: 属性可以使用实例调用,也可以使用类直接调用,因此可以看 ...

  5. 前后端分离djangorestframework——restful规范

    restful现在非常流行,所以很有必要提一下 web服务交互 在浏览器中能看到的每个网站,都是一个web服务.那么我们在提供每个web服务的时候,都需要前后端交互,前后端交互就一定有一些实现方案,我 ...

  6. Vue2 学习笔记1

    什么是Vue.js Vue.js 是目前最火的一个前端框架,React是最流行的一个前端框架(React除了开发网站,还可以开发手机App,Vue语法也是可以用于进行手机App开发的,需要借助于Wee ...

  7. 如何解决远程连接mysql出现Can’t connect to MySQL server on (111 “Connection refused”)的问题

    如何解决远程连接mysql出现Can’t connect to MySQL server on (111 “Connection refused”)的问题 开放Mysql的远程连接 在服务器上登录my ...

  8. Java入门(一):Hello World !

    前言 从今天开始,准备写Java Web开发的系列文章,毕竟自己主攻的还是Java方向,Python只是业余学习的兴趣使然,在第二技能还没有培养成熟前,做好第一技能的巩固和提高是很有必要的.从正式入行 ...

  9. css点滴2—六种方式实现元素水平居中

    本文参考文章<六种方式实现元素水平居中> 元素水平居中的方法,最常见的莫过于给元素一个显式的宽度,然后加上margin的左右值为auto.这种方式给固定宽度的元素设置居中是最方便不过的.但 ...

  10. JDK动态代理和cglib代理详解

    JDK动态代理 先做一下简单的描述,通过代理之后返回的对象已并非原类所new出来的对象,而是代理对象.JDK的动态代理是基于接口的,也就是说,被代理类必须实现一个或多个接口.主要原因是JDK的代理原理 ...