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