Javascript 如何在Redux中处理关系数据?

Javascript 如何在Redux中处理关系数据?,javascript,reactjs,redux,react-redux,reselect,Javascript,Reactjs,Redux,React Redux,Reselect,我正在创建的应用程序有很多实体和关系(数据库是关系型的)。为了得到一个想法,有25个以上的实体,它们之间有任何类型的关系(一对多,多对多) 该应用程序基于React+Redux。为了从存储中获取数据,我们使用了库 我面临的问题是,当我试图从商店获取一个实体及其关系时。 为了更好地解释这个问题,我创建了一个具有类似架构的简单演示应用程序。我将重点介绍最重要的代码库。最后,我将包括一个片段(小提琴),以便使用它 演示应用程序 业务逻辑 我们有书和作家。一本书有一个作者。一位作家有许多书。尽可能简单

我正在创建的应用程序有很多实体和关系(数据库是关系型的)。为了得到一个想法,有25个以上的实体,它们之间有任何类型的关系(一对多,多对多)

该应用程序基于React+Redux。为了从存储中获取数据,我们使用了库

我面临的问题是,当我试图从商店获取一个实体及其关系时。

为了更好地解释这个问题,我创建了一个具有类似架构的简单演示应用程序。我将重点介绍最重要的代码库。最后,我将包括一个片段(小提琴),以便使用它

演示应用程序 业务逻辑 我们有书和作家。一本书有一个作者。一位作家有许多书。尽可能简单

const authors = [{
  id: 1,
  name: 'Jordan Enev',
  books: [1]
}];

const books = [{
  id: 1,
  name: 'Book 1',
  category: 'Programming',
  authorId: 1
}];
Redux商店 商店以扁平结构组织,符合Redux最佳实践-

以下是图书和作者存储的初始状态:

const initialState = {
  // Keep entities, by id:
  // { 1: { name: '' } }
  byIds: {},
  // Keep entities ids
  allIds:[]
};
组件 组件被组织为容器和演示文稿

库,它看起来很有前途,但它的API仍然不稳定,我不确定它是否已准备好生产

const{Component}=React
const{combineReducers,createStore}=Redux
const{connect,Provider}=ReactRedux
const{createSelector}=重新选择
/**
*书籍和作者存储的初始状态
*/
常量初始状态={
byid:{},
allIds:[]
}
/**
*书籍动作创建者和还原者
*/
const addBooks=payload=>({
键入:“添加书籍”,
有效载荷
})
常量=(状态=初始状态,操作)=>{
开关(动作类型){
案例“添加书籍”:
让byIds={}
让allIds=[]
action.payload.map(实体=>{
byIds[entity.id]=实体
allid.push(entity.id)
})
返回{byIds,allIds}
违约:
返回状态
}
}
/**
*作者操作创建者和还原器
*/
const addAuthors=payload=>({
键入:“添加作者”,
有效载荷
})
常量authorsReducer=(状态=初始状态,操作)=>{
开关(动作类型){
案例“添加作者”:
让byIds={}
让allIds=[]
action.payload.map(实体=>{
byIds[entity.id]=实体
allid.push(entity.id)
})
返回{byIds,allIds}
违约:
返回状态
}
}
/**
*表象成分
*/
常量Book=({Book})=>{`Name:${Book.Name}`}
常量Author=({Author})=>{`Name:${Author.Name}`}
/**
*容器组件
*/
类视图扩展了组件{
组件将安装(){
这个是addBooks()
这是addAuthors()
}
/**
*向商店添加虚拟书籍
*/
addBooks(){
常数书=[{
id:1,
名称:'编程书',
类别:"节目",,
作者:1
}, {
id:2,
名称:'健康书',
类别:"健康",,
作者:2
}]
此.props.addBooks(书籍)
}
/**
*将虚拟作者添加到存储
*/
addAuthors(){
常量作者=[{
id:1,
姓名:“Jordan Enev”,
书籍:[1]
}, {
id:2,
名称:“Nadezhda Serafimova”,
书籍:[2]
}]
this.props.addAuthors(authors)
}
renderBooks(){
const{books}=this.props
归还图书。地图(图书=>
{`Name:${book.Name}`}
)
}
渲染器(){
const{authors}=this.props
返回authors.map(author=>)
}
renderHealthAuthors(){
const{healthAuthors}=this.props
返回healthAuthors.map(author=>)
}
RenderHauthouthorSwithBooks(){
const{healthAuthorsWithBooks}=this.props
返回healthAuthorsWithBooks.map(author=>
书:
{author.books.map(book=>)}
)
}
渲染(){
返回
书籍:{this.renderBooks()}

作者:{this.renderAuthors()}
健康作者:{this.renderHealthAuthors()}
已加载书籍的运行状况作者:{this.renderHearthouthorSwithBooks()} } }; 常量mapStateToProps=状态=>({ 书籍:getBooksSelector(州), 作者:getAuthorsSelector(州), healthAuthors:getHealthAuthorsSelector(状态), healthAuthorsWithBooks:getHealthAuthorsWithBooksSelector(状态) }) const mapDispatchToProps={ addBooks,addAuthors } const App=connect(mapStateToProps、mapDispatchToProps)(视图) /** *图书选择器 */ /** *获取书店实体 */ const getBooks=({books})=>books /** *拿到所有的书 */ const getbookselector=createSelector(getBooks, books=>books.allIds.map(id=>books.byIds[id])) /** *作者选择器 */ /** *获取作者存储实体 */ const getAuthors=({authors})=>authors /** *获取所有作者 */ 常量getAuthorsSelector=createSelector(getAuthors, authors=>authors.allIds.map(id=>authors.byIds[id])) /** *获取作者ID的数组, *哪些书属于“健康”类 */ const getHealthAuthorsIdsSelector=createSelector([getAuthors,getBooks], (作者、书籍)=>( authors.allIds.filter(id=>{ const author=authors.byIds[id] const filteredBooks=author.books.filter(id=>( books.byIds[id].category=='Health' )) 返回filteredBooks.length }) )) /** *获取作者数组, *哪些书属于“健康”类 */ 常量getHealthAuthorsSelector=createSelector([getHealthAuthorsIdsSelector,getAuthors], (filteredIds,作者)=>( filteredIds.map(id=>authors.byIds[id]) )) /** *获取一系列作者及其著作, *哪些书属于“健康”类 */ const getHealthAuthorsWithBooksSelector=createSelector([getHealthAuthorsIdsSelector,getAuthors,getBooks], (过滤器、作者、书籍)=>( filteredIds.map(id=>({ …authors.byIds[id], books:authors.byIds[id].books.map(id=>books.byIds[id]) })) )) //组合减速器 常数减速机=组合减速机({ 书籍:书籍, 作者:authorsReducer }) //贮藏 常量存储=创建存储(还原器) 常量渲染=()=>{ ReactDOM.render( ,博士
const mapStateToProps = state => ({
  books: getBooksSelector(state),
  authors: getAuthorsSelector(state),
  healthAuthors: getHealthAuthorsSelector(state),
  healthAuthorsWithBooks: getHealthAuthorsWithBooksSelector(state)
});

const mapDispatchToProps = {
  addBooks, addAuthors
}

const App = connect(mapStateToProps, mapDispatchToProps)(View);
/**
 * Get Books Store entity
 */
const getBooks = ({books}) => books;

/**
 * Get all Books
 */
const getBooksSelector = createSelector(getBooks,
    (books => books.allIds.map(id => books.byIds[id]) ));


/**
 * Get Authors Store entity
 */
const getAuthors = ({authors}) => authors;

/**
 * Get all Authors
 */
const getAuthorsSelector = createSelector(getAuthors,
    (authors => authors.allIds.map(id => authors.byIds[id]) ));
/**
 * Get array of Authors ids,
 * which have books in 'Health' category
 */  
const getHealthAuthorsIdsSelector = createSelector([getAuthors, getBooks],
    (authors, books) => (
    authors.allIds.filter(id => {
      const author = authors.byIds[id];
      const filteredBooks = author.books.filter(id => (
        books.byIds[id].category === 'Health'
      ));

      return filteredBooks.length;
    })
)); 

/**
 * Get array of Authors,
 * which have books in 'Health' category
 */   
const getHealthAuthorsSelector = createSelector([getHealthAuthorsIdsSelector, getAuthors],
    (filteredIds, authors) => (
    filteredIds.map(id => authors.byIds[id])
)); 

/**
 * Get array of Authors, together with their Books,
 * which have books in 'Health' category
 */    
const getHealthAuthorsWithBooksSelector = createSelector([getHealthAuthorsIdsSelector, getAuthors, getBooks],
    (filteredIds, authors, books) => (
    filteredIds.map(id => ({
        ...authors.byIds[id],
      books: authors.byIds[id].books.map(id => books.byIds[id])
    }))
));
...
books: [1]
...
...
authorId: 1
...
const authors = [{
  id: 1,
  name: 'Jordan Enev',
  books: [1]
}];
const authors = [{
  id: 1,
  name: 'Jordan Enev',
  books: [{
      id: 1,
      name: 'Book 1',
      category: 'Programming',
      authorId: 1
  }]
}];
const mapStateToProps = state => ({
  books: getBooksSelector(state),
  authors: getAuthorsSelector(state),
  healthAuthors: getHealthAuthorsSelector(state),
  healthAuthorsWithBooks: getHealthAuthorsWithBooksSelector(state)
});
const mapStateToProps = state => ({
  books: getBooksSelector(state),
  authors: getAuthors(state),
});
const { books, authors } = this.props;

const healthBooksByAuthor = books.reduce((indexedBooks, book) => {
   if (book.category === 'Health') {
      if (!(book.authorId in indexedBooks)) {
         indexedBooks[book.authorId] = [];
      }
      indexedBooks[book.authorId].push(book);
   }
   return indexedBooks;
}, {});
const healthyAuthorIds = Object.keys(healthBooksByAuthor);

...
healthyAuthorIds.map(authorId => {
    const author = authors.byIds[authorId];

    return (<li>{ author.name }
       <ul>
         { healthBooksByAuthor[authorId].map(book => <li>{ book.name }</li> }
       </ul>
    </li>);
})
...
const indexList = fieldsBy => list => {
 // so we don't have to create property keys inside the loop
  const indexedBase = fieldsBy.reduce((obj, field) => {
    obj[field] = {};
    return obj;
  }, {});

  return list.reduce(
    (indexedData, item) => {
      fieldsBy.forEach((field) => {
        const value = item[field];

        if (!(value in indexedData[field])) {
          indexedData[field][value] = [];
        }

        indexedData[field][value].push(item);
      });

      return indexedData;
    },
    indexedBase,
  );
};
const getBooksIndexed = createSelector([getBooksSelector], indexList(['category', 'authorId']));
const getBooksIndexedInCategory = category => createSelector([getBooksIndexed],
    booksIndexedBy => {
        return indexList(['authorId'])(booksIndexedBy.category[category])
    });
    // you can actually abstract this even more!

...
later that day
...

const mapStateToProps = state => ({
  booksIndexedBy: getBooksIndexedInCategory('Health')(state),
  authors: getAuthors(state),
});

...
const { booksIndexedBy, authors } = this.props;
const healthyAuthorIds = Object.keys(booksIndexedBy.authorId);

healthyAuthorIds.map(authorId => {
    const author = authors.byIds[authorId];

    return (<li>{ author.name }
       <ul>
         { healthBooksByAuthor[authorId].map(book => <li>{ book.name }</li> }
       </ul>
    </li>);
})
...
// Handing many-to-many case.
const getBooks = createSelector({ Book } => {
  return Books.all().toModelArray()
   .map( book => ({ 
     book: book.ref,
     authors: book.authors.toRefArray()
   })
})

// Handling Deep filtration.
// Keep in mind here you can pass parameters, instead of hardcoding the filtration criteria.
const getFilteredBooks = createSelector({ Book } => {
  return Books.all().toModelArray()
   .filter( book => {
     const authors = book.authors.toModelArray()
     const hasAuthorInCountry = authors.filter(a => a.country.name === 'Bulgaria').length

     return book.category.type === 'Health' && hasAuthorInCountry
   })
   .map( book => ({ 
     book: book.ref,
     authors: book.authors.toRefArray()
   })
})