Omi框架Store體系的前世今生

原文鏈接

寫在前面

先說說Store系統是幹什麼的!爲什麼要造這樣一個東西?能夠給系統架構帶來什麼?

當我們組件之間,擁有共享的數據的時候,經常需要進行組件通訊。在Omi框架裏,父組件傳遞數據給子組件非常方便:

  • 通過在組件上聲明 data-* 或者 :data-* 傳遞給子節點
  • 通過在組件上聲明 data 或者 :data 傳遞給子節點 (支持複雜數據類型的映射)
  • 聲明 group-data 把數組裏的data傳給一堆組件傳遞(支持複雜數據類型的映射)

注:上面帶有冒號的是 傳遞javascript表達式

通過聲明onXxx=”xxxx”可以讓子組件內執行父組件的方法。具體的如下圖所示:


如果還不明白的話,那… 我就直接上代碼了:

Crayon Syntax Highlighter v_2.7.2_beta
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>`;
    }
}
[Format Time: 0.0042 seconds]

上面的例子中,

  • 父組件的render方法裏,通過 data-✽ 傳遞數據給子組件 Pagination
  • 通過onPageChange=”handlePageChange”實現子組件與父組件通訊

詳細代碼可以點擊: 分頁例子地址

當然你也可以使用event emitter / pubsub庫在組件之間通訊,比如這個只有 200b 的超小庫 mitt 。但是需要注意mitt兼容到IE9+,Omi兼容IE8。但是,使用event emitter / pubsub庫會對組件代碼進行入侵,所以非常不建議在基礎非業務組件使用這類代碼庫。

雖然組件通訊非常方便,但是在真實的業務場景中,不僅僅是父子、爺孫、爺爺和堂兄、嫂子和堂弟…
onXxx=”xxxx”就顯得無能爲力,力不從心了,各種數據傳遞、組件實例互操作、 emitter/pubsub或者循環依賴,讓代碼非常難看且難以維護。所以:

Crayon Syntax Highlighter v_2.7.2_beta
Omi.Store是用來管理共享數據以及共享數據的邏輯 。
[Format Time: 0.0003 seconds]

Omi Store使用足夠簡便,對架構入侵性極極極小(3個極代表比極小還要小)。下面一步一步從todo的例子看下Store體系怎麼使用。

定義 Omi.Store

Omi.Store是基類,我們可以繼承Omi.Store來定義自己的Store,比如下面的TodoStore。

Crayon Syntax Highlighter v_2.7.2_beta
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
[Format Time: 0.0036 seconds]

TodoStore定義了數據的基本格式和數據模型的邏輯。
比如 this.data 就是數據的基本格式:

Crayon Syntax Highlighter v_2.7.2_beta
{
    items:[],
    length:0
}
[Format Time: 0.0004 seconds]

add和clear就是共享數據相關的邏輯。

值得注意的是,在add和clear方法裏都有調用this.update();這個是用來更新組件的,this.update並不會更新所有組件。但是他到底會更新哪些組件呢?等講到store的addView方法你就明白了。

創建 Omi.Store

通過 new 關鍵字來使用TodoStore對象的實例。

Crayon Syntax Highlighter v_2.7.2_beta
let store = new TodoStore({ /* 初始化數據 */ ,/* 數據是否準備好 */  })
[Format Time: 0.0005 seconds]

上面可以傳入一些初始化配置信息,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:

Crayon Syntax Highlighter v_2.7.2_beta
Omi.render(new Todo(),'body',{
    store: store
});
[Format Time: 0.0006 seconds]

當然ES2015已經允許你這樣寫了:

Crayon Syntax Highlighter v_2.7.2_beta
Omi.render(new Todo(),'body',{
    store
});
[Format Time: 0.0005 seconds]

兩份代碼同樣的效果。

通過Omi.render注入之後,在組件樹的 所有組件 都可以通過 this.$store 訪問到 store。

利用 beforeRender

爲什麼要說beforeRender這個函數? 因爲通過beforeRender轉換store的data到組件的data,這樣store的數據和組件的數據就解耦開了。

beforeRender是生命週期的一部分。且看下面這張圖:

beforeRender

不管是實例化或者存在期間,在render之前,會去執行beforeRender方法。所以可以利用該方法寫store的data到組件data的轉換邏輯。比如:

Crayon Syntax Highlighter v_2.7.2_beta
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;
[Format Time: 0.0071 seconds]

爲什麼要去寫beforeRender方法?因爲render只會使用this.data去渲染頁面而不會去使用this.$store.data,所以需要把數據轉移到組件的this.data下。這樣組件既能使用自身的data,也能使用全局放this.$store.data了,不會耦合在一起。

注意看上面的:

Crayon Syntax Highlighter v_2.7.2_beta
install(){
        this.$store.addView(this)
    }
[Format Time: 0.0005 seconds]

通過 addView 可以讓 store 和 view(也就是組件的實例) 關聯起來,以後store執行update方法的時候,關聯的view都會自動更新!

再看上面的子組件聲明:

Crayon Syntax Highlighter v_2.7.2_beta
<List name="list" data="$store.data" />
[Format Time: 0.0003 seconds]

這樣相當於把this.$store.data傳遞給了List組件。所以在List內部,就不再需要寫beforeRender方法轉換了。

Crayon Syntax Highlighter v_2.7.2_beta
class List extends Omi.Component {
    constructor(data) {
        super(data)
    }
 
    render () {
        return ` <ul> {{#items}} <li>{{.}}</li> {{/items}}</ul>`
    }
}
[Format Time: 0.0015 seconds] Crayon Syntax Highlighter v_2.7.2_beta
這裏需要特別強調,不需要把所有的數據提取到store裏,只提取共享數據就好了,組件自身的數據還是放在組件自己進行管理。
[Format Time: 0.0006 seconds]

異步數據

通常,在真實的業務需求中,數據並不是馬上能夠拿到。所以這裏模擬的異步拉取的todo數據:

Crayon Syntax Highlighter v_2.7.2_beta
let todoStore = new TodoStore()
setTimeout(()=>{
    todoStore.data.items = ["omi","store"];
    todoStore.data.length = todoStore.data.items.length
    todoStore.beReady();
},2000)
[Format Time: 0.0014 seconds]

上面的beReady就是代碼已經準備就緒,在組件內部可以監聽ready方法:

Crayon Syntax Highlighter v_2.7.2_beta
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)
    }
[Format Time: 0.0030 seconds]

可以看到上面的add方法可以通過this.$store.isReady獲取組件store是否準備就緒。

你可以通過Omi.createStore快捷創建store。如:

Crayon Syntax Highlighter v_2.7.2_beta
export default Omi.createStore({
    data: {
        items: ["omi", "store"]
    },
    methods: {
        add: function (value) {
            this.data.items.push(value)
            this.data.length = this.data.items.length
            this.update()
        },
 
        clear: function () {
            this.data.items.length = 0
            this.data.length = 0
            this.update()
        }
    }
})
[Format Time: 0.0025 seconds]

也支持省略Omi.createStore的形式創建store。如:

Crayon Syntax Highlighter v_2.7.2_beta
export default {
    data: {
        items: ["omi", "store"]
    },
    methods: {
        add: function (value) {
            this.data.items.push(value)
            this.data.length = this.data.items.length
            this.update()
        },
 
        clear: function () {
            this.data.items.length = 0
            this.data.length = 0
            this.update()
        }
    }
}
[Format Time: 0.0024 seconds]

Omi Store update

Omi Store的update方法會更新所有關聯的視圖。
Omi Store體系以前通過addView進行視圖收集,store進行update的時候會調用組件的update。

與此同時,Omi Store體系也新增了addSelfView的API。

  • addView 收集該組件視圖,store進行update的時候會調用組件的update
  • addSelfView 收集該組件本身的視圖,store進行update的時候會調用組件的updateSelf

當然,store內部會對視圖進行合併,比如addView裏面加進去的所有視圖有父子關係的,會把子組件去掉。爺孫關係的會把孫組件去掉。addSelfView收集的組件在addView裏已經收集的也去進行合併去重,等等一系列合併優化。

源碼地址

相關

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章