Omi框架Store体系的前世今生

写在前面

先说说Store系统是干什么的!为什么要造这样一个东西?能够给系统架构带来什么?

当我们组件之间,拥有共享的数据的时候,经常需要进行组件通讯。在Omi框架里,父组件传递数据给子组件非常方便:

  • 通过在组件上声明 data-* 或者 :data-* 传递给子节点
  • 通过在组件上声明 data 或者 :data 传递给子节点 (支持复杂数据类型的映射)
  • 声明 group-data 把数组里的data传给一堆组件传递(支持复杂数据类型的映射)

注:上面带有冒号的是 传递javascript表达式

通过声明onXxx=”xxxx”可以让子组件内执行父组件的方法。具体的如下图所示:


如果还不明白的话,那… 我就直接上代码了:

class Mainextends Omi.Component {
 
    handlePageChange(index){
        this.content.goto(index+1)
        this.update()
    }
 
    render () {
        return `<div>
                    <h1>PaginationExample</h1>
                    <Contentname="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

importOmifrom 'omi'
 
class TodoStoreextends 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()
    }
}
 
exportdefault TodoStore

TodoStore定义了数据的基本格式和数据模型的逻辑。

比如 this.data 就是数据的基本格式:

{
    items:[],
    length:0
}

add和clear就是共享数据相关的逻辑。

值得注意的是,在add和clear方法里都有调用this.update();这个是用来更新组件的,this.update并不会更新所有组件。但是他到底会更新哪些组件呢?等讲到store的addView方法你就明白了。

创建 Omi.Store

通过 new 关键字来使用TodoStore对象的实例。

letstore = 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的转换逻辑。比如:

importOmifrom '../../src/index.js';
importListfrom './list.js';
 
Omi.makeHTML('List', List);
 
class Todoextends Omi.Component {
    constructor(data) {
        super(data)
    }
 
    install(){
        this.$store.addView(this)
    }
 
    beforeRender(){
        this.data.length = this.$store.data.items.length
    }
 
    add (evt) {
        evt.preventDefault()
        letvalue = 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οnclick="clear">Clear</button>
                    <Listname="list" data="$store.data" />
                    <formοnsubmit="add" >
                        <inputtype="text" οnchange="handleChange"  value="{{text}}"  />
                        <button>Add #{{length}}</button>
                    </form>
 
                </div>`;
    }
}
 
exportdefault 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都会自动更新!

再看上面的子组件声明:

<Listname="list" data="$store.data" />

这样相当于把this.$store.data传递给了List组件。所以在List内部,就不再需要写beforeRender方法转换了。

class Listextends Omi.Component {
    constructor(data) {
        super(data)
    }
 
    render () {
        return ` <ul> {{#items}} <li>{{.}}</li> {{/items}}</ul>`
    }
} 

这里需要特别强调,不需要把所有的数据提取到store里,只提取共享数据就好了,组件自身的数据还是放在组件自己进行管理。

异步数据

通常,在真实的业务需求中,数据并不是马上能够拿到。所以这里模拟的异步拉取的todo数据:

lettodoStore = new TodoStore()
setTimeout(()=>{
    todoStore.data.items = ["omi","store"];
    todoStore.data.length = todoStore.data.items.length
    todoStore.beReady();
},2000)

上面的beReady就是代码已经准备就绪,在组件内部可以监听ready方法:

class Todoextends 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
        }
        letvalue = this.data.text
        this.data.text = ''
        this.$store.add(value)
    }

可以看到上面的add方法可以通过this.$store.isReady获取组件store是否准备就绪。

你可以通过Omi.createStore快捷创建store。如:

exportdefault 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()
        }
    }
})

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

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()
        }
    }
}

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里已经收集的也去进行合并去重,等等一系列合并优化。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值