原文鏈接-https://github.com/AlloyTeam/omi
Store 體系
先說說Store系統是干什么的!為什么要造這樣一個東西?能夠系統架構帶來什么?
當我們組件之間,擁有共享的數據的時候,經常需要進行組件通訊。在Omi框架里,父組件傳遞數據給子組件非常方便:
- 通過在組件上聲明 data-* 或者 :data-* 傳遞給子節點
- 通過在組件上聲明 data 或者 :data 傳遞給子節點 (支持復雜數據類型的映射)
- 聲明 group-data 把數組里的data傳給一堆組件傳遞(支持復雜數據類型的映射)
注:上面帶有冒號的是傳遞javascript表達式
通過聲明onXxx="xxxx"可以讓子組件內執行父組件的方法。具體的如下圖所示:
如果還不明白的話,那... 我就直接上代碼了:
class Main extends Omi.Component {
handlePageChange(index){
this.content.goto(index+1)
this.update()
}
render () {
return `<div>
<h1>Pagination Example</h1>
<Content name="content" />
<Pagination
name="pagination"
:data-total="100"
:data-page-size="10"
:data-num-edge="1"
:data-num-display="4"
onPageChange="handlePageChange" />
</div>`;
}
}
上面的例子中,
- 父組件的render方法里,通過 data-✽ 傳遞數據給子組件 Pagination
- 通過onPageChange="handlePageChange"實現子組件與父組件通訊
詳細代碼可以點擊: 分頁例子地址
當然你也可以使用event emitter / pubsub庫在組件之間通訊,比如這個只有 200b 的超小庫mitt 。但是需要注意mitt兼容到IE9+,Omi兼容IE8。但是,使用event emitter / pubsub庫會對組件代碼進行入侵,所以非常不建議在基礎非業務組件使用這類代碼庫。
雖然組件通訊非常方便,但是在真實的業務場景中,不僅僅是父子、爺孫、爺爺和堂兄、嫂子和堂弟...
onXxx="xxxx"就顯得無能為力,力不從心了,各種數據傳遞、組件實例互操作、 emitter/pubsub或者循環依賴,讓代碼非常難看且難以維護。所以:
Omi.Store是用來管理共享數據以及共享數據的邏輯 。
Omi Store使用足夠簡便,對架構入侵性極極極小(3個極代表比極小還要小)。下面一步一步從todo的例子看下Store體系怎么使用。
定義 Omi.Store
Omi.Store是基類,我們可以繼承Omi.Store來定義自己的Store,比如下面的TodoStore。
import Omi from 'omi'
class TodoStore extends Omi.Store {
constructor(data , isReady) {
super(isReady)
this.data = Object.assign({
items:[],
length:0
},data)
this.data.length = this.data.items.length
}
add(value){
this.data.items.push(value)
this.data.length = this.data.items.length
this.update()
}
clear(){
this.data.items.length = 0
this.data.length = 0
this.update()
}
}
export default TodoStore
TodoStore定義了數據的基本格式和數據模型的邏輯。
比如 this.data 就是數據的基本格式:
{
items:[],
length:0
}
add和clear就是共享數據相關的邏輯。
值得注意的是,在add和clear方法里都有調用this.update();這個是用來更新組件的,this.update並不會更新所有組件。但是他到底會更新哪些組件呢?等講到store的addView方法你就明白了。
創建 Omi.Store
通過 new 關鍵字來使用TodoStore對象的實例。
let store = new TodoStore({ /* 初始化數據 */ ,/* 數據是否准備好 */ })
上面可以傳入一些初始化配置信息,store里面便包含了整個應用程序共享的狀態數據以及貢獻數據邏輯方法(add,clear)。
當然,這些初始化配置信息可能是異步拉取的。所以,有兩種方法解決異步拉取store配置的問題:
- 拉取數據,然后new TodoStore(),再Omi.render
- 先let store = new TodoStore(),再Omi.render,組件內部監聽store.ready,拉取數據更改store的data信息,然后執行store.beReady()
根組件注入 store
為了讓組件樹能夠使用到 store,可以通過Omi.render的第三個參數給根組件注入 store:
Omi.render(new Todo(),'body',{
store: store
});
當然ES2015已經允許你這樣寫了:
Omi.render(new Todo(),'body',{
store
});
兩份代碼同樣的效果。
通過Omi.render注入之后,在組件樹的所有組件都可以通過 this.$store 訪問到 store。
利用 beforeRender
為什么要說beforeRender這個函數? 因為通過beforeRender轉換store的data到組件的data,這樣store的數據和組件的數據就解耦開了。
beforeRender是生命周期的一部分。且看下面這張圖:
不管是實例化或者存在期間,在render之前,會去執行beforeRender方法。所以可以利用該方法寫store的data到組件data的轉換邏輯。比如:
import Omi from '../../src/index.js';
import List from './list.js';
Omi.makeHTML('List', List);
class Todo extends Omi.Component {
constructor(data) {
super(data)
}
install(){
this.$store.addView(this)
}
beforeRender(){
this.data.length = this.$store.data.items.length
}
add (evt) {
evt.preventDefault()
let value = this.data.text
this.data.text = ''
this.$store.add(value)
}
style () {
return `
h3 { color:red; }
button{ color:green;}
`;
}
clear(){
this.data.text = ''
this.$store.clear()
}
handleChange(evt){
this.data.text = evt.target.value
}
render () {
return `<div>
<h3>TODO</h3>
<button onclick="clear">Clear</button>
<List name="list" data="$store.data" />
<form onsubmit="add" >
<input type="text" onchange="handleChange" value="{{text}}" />
<button>Add #{{length}}</button>
</form>
</div>`;
}
}
export default Todo;
為什么要去寫beforeRender方法?因為render只會使用this.data去渲染頁面而不會去使用this.$store.data,所以需要把數據轉移到組件的this.data下。這樣組件既能使用自身的data,也能使用全局放this.$store.data了,不會耦合在一起。
注意看上面的:
install(){
this.$store.addView(this)
}
通過 addView 可以讓 store 和 view(也就是組件的實例) 關聯起來,以后store執行update方法的時候,關聯的view都會自動更新!
再看上面的子組件聲明:
<List name="list" data="$store.data" />
這樣相當於把this.$store.data傳遞給了List組件。所以在List內部,就不再需要寫beforeRender方法轉換了。
class List extends Omi.Component {
constructor(data) {
super(data)
}
render () {
return ` <ul> {{#items}} <li>{{.}}</li> {{/items}}</ul>`
}
}
這里需要特別強調,不需要把所有的數據提取到store里,只提取共享數據就好了,組件自身的數據還是放在組件自己進行管理。
異步數據
通常,在真實的業務需求中,數據並不是馬上能夠拿到。所以這里模擬的異步拉取的todo數據:
let todoStore = new TodoStore()
setTimeout(()=>{
todoStore.data.items = ["omi","store"];
todoStore.data.length = todoStore.data.items.length
todoStore.beReady();
},2000)
上面的beReady就是代碼已經准備就緒,在組件內部可以監聽ready方法:
class Todo extends Omi.Component {
constructor(data) {
super(data)
}
install(){
this.$store.addView(this)
}
installed(){
this.$store.ready(()=>this.$store.update())
}
add (evt) {
evt.preventDefault()
if(!this.$store.isReady){
return
}
let value = this.data.text
this.data.text = ''
this.$store.add(value)
}
可以看到上面的add方法可以通過this.$store.isReady獲取組件store是否准備就緒。
源碼地址
相關
- Omi官網omijs.org
- Omi的Github地址https://github.com/AlloyTeam/omi
- 如果想體驗一下Omi框架,可以訪問 Omi Playground
- 如果想使用Omi框架或者開發完善Omi框架,可以訪問 Omi使用文檔
- 如果你想獲得更佳的閱讀體驗,可以訪問 Docs Website
- 如果你懶得搭建項目腳手架,可以試試 omi-cli
- 如果你有Omi相關的問題可以 New issue
- 如果想更加方便的交流關於Omi的一切可以加入QQ的Omi交流群(256426170)