腾讯发布新版前端组件框架 Omi,全面拥抱 Web Components
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.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.
由于需要使用 Proxy 的原因,放弃IE!
Star & Fork
License
MIT © Tencent
腾讯发布新版前端组件框架 Omi,全面拥抱 Web Components的更多相关文章
- 前端未来趋势之原生API:Web Components
声明:未经允许,不得转载. Web Components 现世很久了,所以你可能听说过,甚至学习过,非常了解了.但是没关系,可以再重温一下,温故知新. 浏览器原生能力越来越强. js 曾经的 JQue ...
- 如何使用前端分页框架bootstrap paginator
前端分页框架bootstrap paginator用于web前端页面快速实现美观大方的翻页功能.在实现交互良好的页面翻页功能时,往往还需要配合使用后端分页框架pagehelper.pagehelper ...
- 跨界!Omi 发布多端统一框架 Omip 打通小程序与 Web 腾讯开源 2月28日
https://mp.weixin.qq.com/s/z5qm-2bHk_BCJAwaodrMIg 跨界!Omi 发布多端统一框架 Omip 打通小程序与 Web 腾讯开源 2月28日
- 跨界 - Omi 发布多端统一框架 Omip 打通小程序与 Web
Omip 今天,Omi 不仅仅可以开发桌面 Web.移动 H5,还可以直接开发小程序!直接开发小程序!直接开发小程序! Github Omi 简介 Omi 框架是微信支付线研发部研发的下一代前端框架, ...
- 前端mv框架下(目前写的是vue),对组件抽象的思考
前沿: 抽象是门大学问.前端mv框架中,以组件化的概念为主.经常会考虑抽象到组件级别,进行复用.合理的抽象,能提高效率,减少业务逻辑视图的耦合程度.不合理的抽象,则会增加代码的复杂程度. 遇到的问题 ...
- 比较实用的前端 js框架 ,组件 汇总
一.js 前端ui框架 Kendo UI 商用收费,组件丰富,界面简洁美观,有jQuery和angular两个js的版本 Webix 商用收费,组件丰富,界面特别美观 JQwidgets 商用收费,丰 ...
- 基于node的前端组件包发布至nexus和npmjs
目录 目录... 3 1. 前言... 1 2. 配置... 1 2.1. 建立组件的导出模块... 1 2.2. 建立组件入口文件... 1 2.3. 配置“ng-package.json”文件.. ...
- 前端Js框架汇总
概述: 有些日子没有正襟危坐写博客了,互联网飞速发展的时代,技术更新迭代的速度也在加快.看着Java.Js.Swift在各领域心花路放,也是煞是羡慕.寻了寻.net的消息,也是振奋人心,.net co ...
- 前端UI框架和JS类库
一.前端框架库: 1.Zepto.js 地址:http://www.css88.com/doc/zeptojs/ 描述:Zepto是一个轻量级的针对现代高级浏览器的JavaScript库, 它与jqu ...
随机推荐
- 测者的性测试手册:SWAP的监控
swap是什么 swap是磁盘上的一块区域,可以使一个磁盘分区,也可以是一个文件,也可能是一个两种的组合.当物理内存资源紧张的时候,操作系统(Linux)会将一些不常访问的数据放到swap里.为其他常 ...
- Orchard详解--第四篇 缓存介绍
Orchard提供了多级缓存支持,它们分别是: 1. 应用程序配置级缓存ICacheManager: 它用来存储应用程序的配置信息并且可以提供一组可扩展的参数来处理缓存过期问题,在Orchard中默认 ...
- Spark WordCount 文档词频计数
一.使用数据 Apache Spark is a fast and general-purpose cluster computing system.It provides high-level AP ...
- SQL Server如何定位自定义标量函数被那个SQL调用次数最多浅析
前阵子遇到一个很是棘手的问题,监控系统DPA发现某个自定义标量函数被调用的次数非常高,高到一个离谱的程度.然后在Troubleshooting这个问题的时候,确实遇到了一些问题让我很是纠结,下文是解决 ...
- STL之迭代器(iterator)
1 头文件 所有容器有含有其各自的迭代器型别(iterator types),所以当你使用一般的容器迭代器时,并不需要含入专门的头文件.不过有几种特别的迭代器,例如逆向迭代器,被定义于<iter ...
- 二、tableau常用难点操作
常用操作: 1.Ctrl+要选的多个字段+“智能显示”选择相应的图形 2.ctrl+m:新建工作表 3.添加行和列时,注意分层结构的利用 3.行的标题颜色的修改: (1)单行:表-右击-阴影-选择相应 ...
- Linux用户抢占和内核抢占详解(概念, 实现和触发时机)--Linux进程的管理与调度(二十)
1 非抢占式和可抢占式内核 为了简化问题,我使用嵌入式实时系统uC/OS作为例子 首先要指出的是,uC/OS只有内核态,没有用户态,这和Linux不一样 多任务系统中, 内核负责管理各个任务, 或者说 ...
- c/c++ 函数模板初探
函数模板初探 1,由来:有时候,函数的逻辑是一样的,只是参数的类型不同,比如下面 int Max(int a, int b){ return a > b ? a : b; } double Ma ...
- python爬虫起步...开发环境搭建,最简单的方式
研究一门编程语言,一般第一步就是配置安装部署相关的编程环境.我认为啊,在学习的初期,大家不是十分了解相关的环境,或者是jar包,python模块等的相关内容,就不需要花费大量的时间去研究如何去安装它. ...
- Linux Rsyslog日志集中管理
Linux Rsyslog日志集中管理 一.Rsyslog简介 ryslog 是一个快速处理收集系统日志的程序,提供了高性能.安全功能和模块化设计.rsyslog 是syslog 的升级版,它将多种来 ...