下一代 Web 框架,去萬物糟粕,合精華爲一css
→ https://github.com/Tencent/omihtml
文件名—類名—hash值
,如:CSS Modules,Vue),都是 hack 技術;Shadow DOM Style 是最完美的方案對比一樣開發 TodoApp, Omi 和 React 渲染完的 DOM 結構:react
左(上)邊是Omi,右(下)邊是 React,Omi 使用 Shadow DOM 隔離樣式和語義化結構。webpack
下面這個頁面不須要任何構建工具就能夠執行git
<!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>
$ 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 用戶指南。github
先建立一個自定義元素:web
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> ) } }
使用該元素:npm
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() 的調用:json
{ "presets": ["env", "omi"] }
須要安裝下面兩個 npm 包支持上面的配置:redux
"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 的例子:
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 體系能夠告別 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() } }
須要在 render 的時候從根節點注入 store 才能在全部自定義 Element 裏使用 this.store:
render(<todo-app></todo-app>, 'body', store)
總結一下:
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
。
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,駝峯的方式。
class MyFirstElement extends WeElement { onClick = (evt) => { alert('Hello Omi!') } render() { return ( <h1 onClick={this.onClick}>Hello, wrold!</h1> ) } }
@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
能夠獲取到傳遞的數據。
@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
訪問該元素。
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!
MIT © Tencent