需求
在状态管理中,A 数据更新依赖 B 数据的更新结果并需要对 B 数据进行异步操作。
Class Component 中的示例
https://codesandbox.io/s/rematch-count-demo-q7ld0?module=/ClassComponent.js
import React from "react";
import delay from "./delay";
export default class Component extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: [
{
id: Date.now(),
title: "react"
}
],
ids: [],
evens: []
};
}
async transformatTodosId(todos) {
await delay(1000); // 模拟异步,异步中可能依赖 todos
return todos.map((todo, index) => ({ ...todo, id: `${todo.id}_${index}` }));
}
async getEvens(ids) {
await delay(1000); // 模拟异步,异步中可能依赖 ids
return ids.map(id => id.split("_")[1]).filter(id => !(id % 2));
}
addTodo = todo => {
this.setState(preState => ({
todos: [...preState.todos, todo]
}));
};
setIds = () => {
this.setState(preState => ({
ids: preState.todos.map(todo => todo.id)
}));
};
setIdsByTodos = todos => {
this.setState({
ids: todos.map(todo => todo.id)
});
};
setIdsByStateTodos = async () => {
const todos = await this.transformatTodosId(this.state.todos);
this.setIdsByTodos(todos);
};
setEvens = evens => {
this.setState({ evens });
};
setEvensByStateIds = async () => {
const evens = await this.getEvens(this.state.ids);
this.setEvens(evens);
};
handleClick = async () => {
// await this.addTodo({
// id: Date.now(),
// title: "alvin"
// });
// await this.setIdsByStateTodos();
// await this.setEvensByStateIds();
this.addTodo({
id: Date.now(),
title: "alvin"
});
};
componentDidUpdate(prevProps, prevState) {
if (prevState.todos.length !== this.state.todos.length) {
this.setIdsByStateTodos();
}
if (prevState.ids.length !== this.state.ids.length) {
this.setEvensByStateIds();
}
}
render() {
console.log("class component render...");
return (
<div>
<span>{this.state.todos.length}</span>
<br />
<span>ids: {this.state.ids.join(",")}</span>
<br />
<span>evens: {this.state.evens.join(",")}</span>
<br />
<button onClick={this.handleClick}>click me</button>
</div>
);
}
}
Function Component 中的示例
https://codesandbox.io/s/rematch-count-demo-q7ld0?module=/FunctionComponent.js
import React, { useState, useEffect } from "react";
import delay from "./delay";
export default function() {
const [todos, setTodos] = useState([
{
id: Date.now(),
title: "react"
}
]);
const [ids, setIds] = useState([]);
const [evens, setEvens] = useState([]);
async function transformatTodosId(todos) {
await delay(1000); // 模拟异步,异步中可能依赖 todos
return todos.map((todo, index) => ({ ...todo, id: `${todo.id}_${index}` }));
}
async function getEvens(ids) {
await delay(1000); // 模拟异步,异步中可能依赖 ids
return ids.map(id => id.split("_")[1]).filter(id => !(id % 2));
}
const addTodo = todo => {
return setTodos(preState => [...preState, todo]);
};
const setIdsByTodos = data => {
const ids = data.map(todo => todo.id);
return setIds(ids);
};
function handleClick() {
addTodo({
id: Date.now(),
title: "alvin"
});
}
useEffect(() => {
async function doSomeThing() {
const newTodos = await transformatTodosId(todos);
setIdsByTodos(newTodos);
}
doSomeThing();
}, [todos]);
useEffect(() => {
async function doSomeThing() {
const evens = await getEvens(ids);
setEvens(evens);
}
doSomeThing();
}, [ids]);
console.log("function component render...");
return (
<div>
<span>{todos.length}</span>
<br />
<span>ids: {ids.join(",")}</span>
<br />
<span>evens: {evens.join(",")}</span>
<br />
<button onClick={handleClick}>click me</button>
</div>
);
}
dva 中的示例
https://stackblitz.com/edit/dva-example-count-9eyczj?file=index.js
import React from 'react';
import dva, { connect } from 'dva';
const delay = timeout => new Promise(resolve => setTimeout(resolve, timeout));
function *transformatTodosId(todos) {
yield delay(1000); // 模拟异步,异步中可能依赖 todos
return todos.map((todo, index) => ({ ...todo, id: `${todo.id}_${index}` }));
}
function *getEvens(ids) {
yield delay(1000); // 模拟异步,异步中可能依赖 ids
return ids.map(id => id.split("_")[1]).filter(id => !(id % 2));
}
const app = dva();
app.model({
namespace: 'todos',
state: [
{
id: Date.now(),
title: "react"
}
],
reducers: {
addTodo(preState, {payload: todo}) {
return [...preState, todo];
}
},
effects: {
*addByAsync({payload}, { put, call, select }) {
yield put({ type: 'addTodo', payload });
const todos = yield select(state => state.todos);
const nextTodos = yield call(transformatTodosId, todos);
yield put({ type: 'ids/setByTodos', payload: nextTodos });
const ids = yield select(state => state.ids);
yield put({ type: 'evens/setEvensByIds', payload: ids });
}
},
});
app.model({
namespace: 'ids',
state: [],
reducers: {
setByTodos(preState, { payload: todos }) {
return todos.map(todo => todo.id);
}
},
effects: {
*setByTodosAsync({ payload: todos }, { put, call }) {
const nextTodos = yield call(transformatTodosId, todos);
yield put({ type: 'setByTodos', payload: nextTodos });
}
}
});
app.model({
namespace: 'evens',
state: [],
reducers: {
update(preState, { payload }) {
return payload;
}
},
effects: {
*setEvensByIds({ payload: ids }, { call, put }) {
const evens = yield call(getEvens, ids);
yield put({ type: 'update', payload: evens });
}
}
});
const App = connect(({ todos, ids, evens }) => ({
todos, ids, evens
}))(function({ todos, ids, evens, dispatch }) {
function handleClick() {
dispatch({
type: 'todos/addByAsync',
payload: {
id: Date.now(),
title: "alvin"
},
});
}
return (
<div>
<span>{todos.length}</span>
<br />
<span>ids: {ids.join(",")}</span>
<br />
<span>evens: {evens.join(",")}</span>
<br />
<button onClick={handleClick}>click me</button>
</div>
);
});
app.router(() => <App />);
app.start('#root');
当前在 icestore 中如何实现?
https://codesandbox.io/s/cool-darkness-vqlhp
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import { createStore } from "@ice/store";
const delay = time => new Promise(resolve => setTimeout(() => resolve(), time));
async function transformatTodosId(todos) {
await delay(1000); // 模拟异步,异步中可能依赖 todos
return todos.map((todo, index) => ({ ...todo, id: `${todo.id}_${index}` }));
}
async function getEvens(ids) {
await delay(1000); // 模拟异步,异步中可能依赖 ids
return ids.map(id => id.split("_")[1]).filter(id => !(id % 2));
}
const todos = {
state: [
{
id: Date.now(),
title: "react"
}
],
reducers: {
addTodo(preState, todo) {
return [...preState, todo];
}
},
effects: {
async addByAsync(state, payload, actions) {
actions.addTodo(payload);
}
}
};
const ids = {
state: [],
reducers: {
setByTodos(preState, todos) {
return todos.map(todo => todo.id);
}
},
effects: {
async setByTodosAsync(state, todos, actions) {
const nextTodos = await transformatTodosId(todos);
actions.setByTodos(nextTodos);
}
}
};
const evens = {
state: [],
reducers: {
update(preState, nextState) {
return nextState;
}
},
effects: {
async setEvensByIds(state, ids, actions) {
const evens = await getEvens(ids);
actions.update(evens);
}
}
};
const models = {
todos,
ids,
evens
};
const store = createStore(models);
const { useModel } = store;
function Counter() {
const [todos, actions] = useModel("todos");
const [ids, idsActions] = useModel("ids");
const [evens, evensActions] = useModel("evens");
function handleClick() {
actions.addByAsync({
id: Date.now(),
title: "alvin"
});
}
useEffect(() => {
idsActions.setByTodosAsync(todos);
}, [todos]);
useEffect(() => {
evensActions.setEvensByIds(ids);
}, [ids]);
return (
<div>
<span>{todos.length}</span>
<br />
<span>ids: {ids.join(",")}</span>
<br />
<span>evens: {evens.join(",")}</span>
<br />
<button onClick={handleClick}>click me</button>
</div>
);
}
方案
参考 rematch:https://codesandbox.io/s/rematch-count-demo-b4rux?module=/models.js
import store from "./store";
const delay = time => new Promise(resolve => setTimeout(() => resolve(), time));
async function transformatTodosId(todos) {
await delay(1000); // 模拟异步,异步中可能依赖 todos
return todos.map((todo, index) => ({ ...todo, id: `${todo.id}_${index}` }));
}
async function getEvens(ids) {
await delay(1000); // 模拟异步,异步中可能依赖 ids
return ids.map(id => id.split("_")[1]).filter(id => !(id % 2));
}
export const todos = {
state: [
{
id: Date.now(),
title: "react"
}
],
reducers: {
addTodo(preState, todo) {
return [...preState, todo];
}
},
// 提供返回的声明方式
effects: dispatch => ({
async addByAsync(payload, state) {
dispatch.todos.addTodo(payload);
const todos = store.getState().todos;
await dispatch.ids.setByTodosAsync(todos);
// const nextTodos = await transformatTodosId(todos);
// dispatch.ids.setByTodos(nextTodos);
const ids = store.getState().ids;
await dispatch.evens.setEvensByIds(ids);
// const evens = await getEvens(ids);
// dispatch.evens.update(evens);
}
})
};
export const ids = {
state: [],
reducers: {
setByTodos(preState, todos) {
return todos.map(todo => todo.id);
}
},
effects: dispatch => ({
async setByTodosAsync(todos, state) {
const nextTodos = await transformatTodosId(todos);
this.setByTodos(nextTodos);
}
})
};
export const evens = {
state: [],
reducers: {
update(preState, nextState) {
return nextState;
}
},
effects: dispatch => ({
async setEvensByIds(ids, state) {
const evens = await getEvens(ids);
this.update(evens);
}
})
};