Redux.js 和数据有关系
Redux.js and data with relations
假设您开发了一些 react-redux 应用程序(具有全局不可变树状态)。并且一些数据在不同的树分支中具有一些规则关系,例如表之间的 SQL 关系。
即如果您正在处理某个公司的待办事项列表,则每个待办事项都与具体用户有关系(多对一)。如果你添加一些新用户,你应该添加空的待办事项列表(到该州的其他分支)。或者删除用户意味着您应该将用户的待办事项重新分配给某些(默认管理员)用户。
您可以将此关系直接硬编码到源代码中。它很好,工作正常。
但是想象一下,对于这样的数据,您有数百万个小关系。一些小的"automatic"operations/checks(对于support/guard关系)自动按规则执行就好了。
可能存在一些常见的 approach/library/experience 可以通过一些规则来完成:比如 SQL 中的触发器:
on add new user => add new empty todos
on user delete => reassign todos to default user
这里有两种解决方法。我不认为你应该在 redux 应用程序中拥有这种功能,所以我的第一个例子不是你正在寻找的,但我认为更圆锥形。第二个例子采用了DB/orm的模式,可能效果不错,但不是圆锥形的,需要
这些可以通过 vanilla redux 和 redux-thunk
轻松安全地添加。 Redux thunk 基本上允许你分派一个动作,它自己分派多个其他动作——所以当你触发 CREATE_USER
时,只需按照触发 CREATE_EMPTY_TODO
、CREATE_USER
和ASSIGN_TODO
在 createUser
动作中。要删除用户,REASSIGN_USER_TODOS
然后 DELETE_USER
。
对于你提供的例子,这里有例子:
function createTodoList(todos = []) {
return dispatch => {
return API.createTodoList(todos)
.then(res => { // res = { id: 15543, todos: [] }
dispatch({ type: 'CREATE_TODO_LIST_SUCCESS', res });
return res;
});
}
}
function createUser (userObj) {
return dispatch => {
dispatch(createTodoList())
.then(todoListObj => {
API.createUser(Object.assign(userObj, { todoLists: [ todoListObj.id ] }))
.then(res => { // res = { id: 234234, name: userObj.name, todoLists: [ 15534 ]}
dispatch({ type: 'CREATE_USER_SUCCESS', payload: res });
return res;
})
})
.catch(err => console.warn('Could not create user because there was an error creating todo list'));
}
}
正在删除,没有异步内容。
function deleteUser (userID) {
return (dispatch, getState) => {
dispatch({
type: 'REASSIGN_USER_TODOS',
payload: {
fromUser: userID,
toUser: getState().application.defaultReassignUser
});
dispatch({
type: 'DELETE_USER',
payload: { userID }
});
}
}
如评论中所述,此方法的问题在于,新开发人员可能会在不知道已经存在哪些操作的情况下进入该项目,然后创建他们自己的 createUser
版本,而该版本并不存在知道创建待办事项。虽然您永远无法完全消除他们编写错误代码的能力,但您可以通过使您的操作更有条理来尝试更具防御性。例如,如果您的操作如下所示:
const createUserAction = {
type: 'CREATE',
domain: 'USERS',
payload: userProperies
}
你可以有一个像这样结构的减速器
function createUserTrigger (state, userProperies) {
return {
...state,
todoLists: {
...state.todoLists,
[userProperies.id]: []
}
}
}
const triggers = {
[CREATE]: {
[USERS]: createUserTrigger
}
}
function rootReducer (state = initialState, action) {
const { type, domain, payload } = action;
let result = state;
switch (type) {
case CREATE:
result = {
...state,
[domain]: {
...state[domain],
[payload.id]: payload
}
};
break;
case DELETE:
delete state[domain][payload.id];
result = { ...state };
break;
case UPDATE:
result = {
...state,
[domain]: {
...state[domain],
[payload.id]: _.merge(state[domain][payload.id], payload)
}
}
break;
default:
console.warn('invalid action type');
return state;
}
return triggers[type][domain] ? triggers[type][domain](result, payload) : result;
}
在这种情况下,您基本上是在强制所有开发人员使用非常有限的一组可能的操作类型。它非常死板,我真的不推荐它,但我认为它可以满足您的要求。
假设您开发了一些 react-redux 应用程序(具有全局不可变树状态)。并且一些数据在不同的树分支中具有一些规则关系,例如表之间的 SQL 关系。
即如果您正在处理某个公司的待办事项列表,则每个待办事项都与具体用户有关系(多对一)。如果你添加一些新用户,你应该添加空的待办事项列表(到该州的其他分支)。或者删除用户意味着您应该将用户的待办事项重新分配给某些(默认管理员)用户。
您可以将此关系直接硬编码到源代码中。它很好,工作正常。 但是想象一下,对于这样的数据,您有数百万个小关系。一些小的"automatic"operations/checks(对于support/guard关系)自动按规则执行就好了。
可能存在一些常见的 approach/library/experience 可以通过一些规则来完成:比如 SQL 中的触发器:
on add new user => add new empty todos
on user delete => reassign todos to default user
这里有两种解决方法。我不认为你应该在 redux 应用程序中拥有这种功能,所以我的第一个例子不是你正在寻找的,但我认为更圆锥形。第二个例子采用了DB/orm的模式,可能效果不错,但不是圆锥形的,需要
这些可以通过 vanilla redux 和 redux-thunk
轻松安全地添加。 Redux thunk 基本上允许你分派一个动作,它自己分派多个其他动作——所以当你触发 CREATE_USER
时,只需按照触发 CREATE_EMPTY_TODO
、CREATE_USER
和ASSIGN_TODO
在 createUser
动作中。要删除用户,REASSIGN_USER_TODOS
然后 DELETE_USER
。
对于你提供的例子,这里有例子:
function createTodoList(todos = []) {
return dispatch => {
return API.createTodoList(todos)
.then(res => { // res = { id: 15543, todos: [] }
dispatch({ type: 'CREATE_TODO_LIST_SUCCESS', res });
return res;
});
}
}
function createUser (userObj) {
return dispatch => {
dispatch(createTodoList())
.then(todoListObj => {
API.createUser(Object.assign(userObj, { todoLists: [ todoListObj.id ] }))
.then(res => { // res = { id: 234234, name: userObj.name, todoLists: [ 15534 ]}
dispatch({ type: 'CREATE_USER_SUCCESS', payload: res });
return res;
})
})
.catch(err => console.warn('Could not create user because there was an error creating todo list'));
}
}
正在删除,没有异步内容。
function deleteUser (userID) {
return (dispatch, getState) => {
dispatch({
type: 'REASSIGN_USER_TODOS',
payload: {
fromUser: userID,
toUser: getState().application.defaultReassignUser
});
dispatch({
type: 'DELETE_USER',
payload: { userID }
});
}
}
如评论中所述,此方法的问题在于,新开发人员可能会在不知道已经存在哪些操作的情况下进入该项目,然后创建他们自己的 createUser
版本,而该版本并不存在知道创建待办事项。虽然您永远无法完全消除他们编写错误代码的能力,但您可以通过使您的操作更有条理来尝试更具防御性。例如,如果您的操作如下所示:
const createUserAction = {
type: 'CREATE',
domain: 'USERS',
payload: userProperies
}
你可以有一个像这样结构的减速器
function createUserTrigger (state, userProperies) {
return {
...state,
todoLists: {
...state.todoLists,
[userProperies.id]: []
}
}
}
const triggers = {
[CREATE]: {
[USERS]: createUserTrigger
}
}
function rootReducer (state = initialState, action) {
const { type, domain, payload } = action;
let result = state;
switch (type) {
case CREATE:
result = {
...state,
[domain]: {
...state[domain],
[payload.id]: payload
}
};
break;
case DELETE:
delete state[domain][payload.id];
result = { ...state };
break;
case UPDATE:
result = {
...state,
[domain]: {
...state[domain],
[payload.id]: _.merge(state[domain][payload.id], payload)
}
}
break;
default:
console.warn('invalid action type');
return state;
}
return triggers[type][domain] ? triggers[type][domain](result, payload) : result;
}
在这种情况下,您基本上是在强制所有开发人员使用非常有限的一组可能的操作类型。它非常死板,我真的不推荐它,但我认为它可以满足您的要求。