下一代 Web 框架,去萬物糟粕,合精華爲一javascript
文件名—類名—hash值
,如:CSS Modules,Vue),都是 hack 技術;Shadow DOM Style 是最完美的方案對比一樣開發 TodoApp, Omi 和 React 渲染完的 DOM 結構:css
左(上)邊是Omi,右(下)邊是 React,Omi 使用 Shadow DOM 隔離樣式和語義化結構。html
下面這個頁面不須要任何構建工具就能夠執行java
<!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 用戶指南。react
先建立一個自定義元素:webpack
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>
)
}
}
複製代碼
使用該元素:git
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() 的調用:github
{
"presets": ["env", "omi"]
}
複製代碼
須要安裝下面兩個 npm 包支持上面的配置:web
"babel-preset-env": "^1.6.0",
"babel-preset-omi": "^0.1.1",
複製代碼
若是不想把 css 寫在 js 裏,你可使用 to-string-loader, 好比下面配置:npm
{
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 命中規則:
Proxy Path | 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!(因爲有些項目IE11是底線,因此即將支持IE11) 已支持IE11 → https://github.com/Tencent/omi/tree/master/packages/omi-ie11
MIT © Tencent