feeblejs / feeble

A React/Redux Architecture
https://feeble.js.org
73 stars 7 forks source link

是否考虑集成 normalizr ? #4

Closed Darmody closed 7 years ago

Darmody commented 8 years ago

Hi, 详细地看了代码, 写得很棒, 很像我一位前同事的风格, cool !

请问是否考虑集成类似 normalizr 这样的 lib 呢? JSON API 的返回结果处理起来还是比较麻烦的, 就算用上了 normalizr, 也是要在 reducer 里频繁去更新 entities, 也许在 feeble 里集成 normalizr 或类似的功能可以更方便呢, 而且也能给 model 加上 has_many, has_one 的关联关系.

yesmeck commented 8 years ago

说说你的看法,解决什么问题,怎么实现。

Darmody commented 8 years ago

解决的问题:

  1. model 之间通常存在有关联关系,目前 feeble 里的 model 还并不支持关联。假设存在 老师学生 两个 model,老师可以有多个学生。那么如果我在一个老师的页面里需要显示属于这个老师的所有学生,现在的做法应该是要写 select 方法去找出来,比较不优美。如果能用类似 teacher.students()的实例方法直接得到关联的 model 对象,开发者就会会心一笑。
  2. 使用 normalizr 的成本问题。如果在 app 中使用了 normalizr, 可以看到需要自己去维护类似 Entity 这样的 model,而且当我需要更新 老师学生 这些 model 时,都需要去 call Entity.update 这样的 action 来更新数据,代码读起来就不是很舒服。如果能有内建的 normalizr,开发者就不需要维护和关心 Entity 这样的 model (除非他有特殊需求,也还是可以去建立 Entity model 自己维护),而 model 本身提供 Teacher.updateData 这样的静态方法去更新数据也会更语意化。

怎么实现:

以 hackernews example 为例:

  1. feeble 的构造函数中,将 Entity 定义为默认 model https://github.com/feeblejs/feeble/blob/master/src/feeble.js#L34
  2. 给 model 实例定义获取关联 model 的方法
  3. 给 model 定义可以更新 Entity model 中相对应 state 的静态方法
  4. 在定义 Story 的时候可以直接定义 model 的 schema :
import { Schema, arrayOf } from 'normalizr'

const story = new Schema('story')
const user = new Schema('user')

const schema = {
  STORY: story,
  STORY_ARRAY: arrayOf(story),
  USER: user,
}

const model = feeble.model({
  namespace: `story::${type}`,
  state: {
    loading: false,
  },
  schema,
})
  1. 在需要更新 Story 的数据时,直接调用 Story.updateDate(id, story)Story.updateDate(stories) 如: https://github.com/feeblejs/hackernews/blob/master/src/models/story.js#L85-L86
  2. 在查询 Story 相关的用户时,可以调用 story.users() 获取。
  3. 还可以封装一些默认可用的 select https://github.com/feeblejs/hackernews/blob/master/src/models/story.js#L97-L106
Darmody commented 8 years ago

我把 feeble 想象成一个比较完整的框架,所以我觉得类似 normalizr 这样的 lib 是需要的?

yesmeck commented 8 years ago

参考:https://github.com/tommikaikkonen/redux-orm

可以考虑加进来。

yesmeck commented 8 years ago

还有一种做法,其实用 normalizr 定义完的 schema 已经知道 relationship 了,可以实现一个对 state 进行方便查询的工具:

const state = {
    tweets: {
        ids: [2, 1];
    },
    entities: {
        tweets: {
            1: { id: 1, body: 'Hello', author: 1 },
            2: { id: 2, body: '你好', author: 2 }
        },
        users: {
            1: { id: 1, name: 'Meck', active: true },
            2: { id: 2 name: 'Ava', active: false }
        }
    }
}

const tweet = new Schema('tweets');
const user = new Schema('users');

tweet.define({
  author: user
});

const schemas = {
  tweet,
  user
}

const db = createDatabase(schemas, state.entities);

@connect(
    state => ({
        // [
        //   { id: 2, body: '你好', author: { id: 2, name: 'Ava' } },
        //   { id: 1, body: 'Hello', author: { id: 1, name: 'Meck' } }
        // ]
        tweets: db.tweets.find({id: { $in: state.tweets.ids }}).includes('author'),
        // [
        //   { id: 1, name: 'Meck', active: true }
        // ]
        users: db.users.find({active: true}),
        // { id: 1, name: 'Meck', active: true }
        user: db.users.findOne({id: 1}),
    })
)
class Tweets extends Component {
    render() {
        ...
    };
}
Darmody commented 8 years ago

Exactly what I am thinking. feeble-activerecord.

Darmody commented 8 years ago

@yesmeck 说一下我更具体的想法:

新增 Entity model, 并自带如下 actions:

所有自建的 model 继承 Entity model, 并拥有上述的 actions,并且:

我这里没有用到你上面提到的 tweets: db.tweets.find({id: { $in: state.tweets.ids }}).includes('author') 这种方式,是因为我的想法是,这可能增加了使用成本。开发者需要了解一套新的 DSL。如果把这一块查询转换成用 reselect(或是 feeble 提供的 select 方法) 自己实现查找的逻辑,也许成本会更低。

yesmeck commented 8 years ago

我感觉 Entity 还比较难抽象的,不同的项目用法可能不一样的。https://github.com/feeblejs/feeble/blob/master/examples/todo/src/models/entity.js 跟https://github.com/feeblejs/hackernews/blob/master/src/models/entity.js

Darmody commented 8 years ago

你说的不一样,是指在 todo 里,用到了 createupdate actions,在 hackernews 里,用到的是 set 方法吗?

因为我看这两个项目里,stateentities 结构都是一样的,都是按照 normalizr 来的,所以只有 actions 定义的不一样吧。

所以我们保持 normalizrstate 结构,再定义非常通用的 action 做 CRUD。

两个例子中的分别定义的 create update set action,其实就可以通过 set 达到 createupdate 的效果,所以提供越简单的 action 就可以应对更多情况。

而且我想了下,关联更新也是可以实现的。

Darmody commented 8 years ago

如果真的还有比较复杂的需求没有考虑到,就保留一个类似 set 的 action 去直接可以更新整个 entities 的 state 也是可行的吧。

Darmody commented 8 years ago

又认真看了下 redux-orm,感觉太重了。还是 normalizr 做的事情比较明了。