我可以在 reducer 中发送一个动作吗?
Can I dispatch an action in reducer?
是否可以在 reducer 本身中分派一个动作?我有一个进度条和一个音频元素。目标是在音频元素中更新时间时更新进度条。但是我不知道在哪里放置ontimeupdate 事件处理程序,或者如何在ontimeupdate 的回调中调度一个动作来更新进度条。这是我的代码:
//reducer
const initialState = {
audioElement: new AudioElement('test.mp3'),
progress: 0.0
}
initialState.audioElement.audio.ontimeupdate = () => {
console.log('progress', initialState.audioElement.currentTime/initialState.audioElement.duration);
//how to dispatch 'SET_PROGRESS_VALUE' now?
};
const audio = (state=initialState, action) => {
switch(action.type){
case 'SET_PROGRESS_VALUE':
return Object.assign({}, state, {progress: action.progress});
default: return state;
}
}
export default audio;
在 reducer 中调度 action 是一种反模式。您的 reducer 应该没有副作用,只需消化操作负载并返回一个新的状态对象。在 reducer 中添加侦听器和调度操作可能会导致链式操作和其他副作用。
听起来您的初始化 AudioElement
class 和事件侦听器属于组件而不是状态。在事件侦听器中,您可以分派一个动作,该动作将更新 progress
状态。
您可以在新的 React 组件中初始化 AudioElement
class 对象,或者只是将 class 转换为 React 组件。
class MyAudioPlayer extends React.Component {
constructor(props) {
super(props);
this.player = new AudioElement('test.mp3');
this.player.audio.ontimeupdate = this.updateProgress;
}
updateProgress () {
// Dispatch action to reducer with updated progress.
// You might want to actually send the current time and do the
// calculation from within the reducer.
this.props.updateProgressAction();
}
render () {
// Render the audio player controls, progress bar, whatever else
return <p>Progress: {this.props.progress}</p>;
}
}
class MyContainer extends React.Component {
render() {
return <MyAudioPlayer updateProgress={this.props.updateProgress} />
}
}
function mapStateToProps (state) { return {}; }
return connect(mapStateToProps, {
updateProgressAction
})(MyContainer);
请注意,updateProgressAction
会自动用 dispatch
包装,因此您无需直接调用调度。
您可以尝试使用像 redux-saga 这样的库。它提供了一种非常 简洁的方式来对异步函数进行排序、触发操作、使用延迟等等。很强大!
在你的 reducer 完成之前开始另一个调度是一个反模式,因为你在你的 reducer 开始时收到的状态将不再是当前的应用程序状态,当你减速器完成。但是从reducer 中安排另一个调度不是反模式。事实上,这就是 Elm 语言所做的,正如你所知,Redux 是将 Elm 架构带到 JavaScript.
的尝试
这是一个中间件,它将 属性 asyncDispatch
添加到您的所有操作中。当你的 reducer 完成并返回新的应用程序状态时,asyncDispatch
将触发 store.dispatch
并执行你给它的任何操作。
// This middleware will just add the property "async dispatch" to all actions
const asyncDispatchMiddleware = store => next => action => {
let syncActivityFinished = false;
let actionQueue = [];
function flushQueue() {
actionQueue.forEach(a => store.dispatch(a)); // flush queue
actionQueue = [];
}
function asyncDispatch(asyncAction) {
actionQueue = actionQueue.concat([asyncAction]);
if (syncActivityFinished) {
flushQueue();
}
}
const actionWithAsyncDispatch =
Object.assign({}, action, { asyncDispatch });
const res = next(actionWithAsyncDispatch);
syncActivityFinished = true;
flushQueue();
return res;
};
现在你的减速器可以做到这一点:
function reducer(state, action) {
switch (action.type) {
case "fetch-start":
fetch('wwww.example.com')
.then(r => r.json())
.then(r => action.asyncDispatch({ type: "fetch-response", value: r }))
return state;
case "fetch-response":
return Object.assign({}, state, { whatever: action.value });;
}
}
redux-loop 从 Elm 那里得到启发并提供了这种模式。
reducer 内部的调度和操作似乎出现错误。
我使用 useReducer
做了一个简单的计数器示例,其中“INCREASE”被调度,然后“SUB”也被调度。
在示例中,我预计“INCREASE”会被分派,然后“SUB”也会分派,并且将 cnt
设置为 -1,然后
继续“INCREASE”操作将 cnt
设置为 0,但它是 -1(“INCREASE”被忽略)
看这个:
https://codesandbox.io/s/simple-react-context-example-forked-p7po7?file=/src/index.js:144-154
let listener = () => {
console.log("test");
};
const middleware = (action) => {
console.log(action);
if (action.type === "INCREASE") {
listener();
}
};
const counterReducer = (state, action) => {
middleware(action);
switch (action.type) {
case "INCREASE":
return {
...state,
cnt: state.cnt + action.payload
};
case "SUB":
return {
...state,
cnt: state.cnt - action.payload
};
default:
return state;
}
};
const Test = () => {
const { cnt, increase, substract } = useContext(CounterContext);
useEffect(() => {
listener = substract;
});
return (
<button
onClick={() => {
increase();
}}
>
{cnt}
</button>
);
};
{type: "INCREASE", payload: 1}
{type: "SUB", payload: 1}
// expected: cnt: 0
// cnt = -1
是否可以在 reducer 本身中分派一个动作?我有一个进度条和一个音频元素。目标是在音频元素中更新时间时更新进度条。但是我不知道在哪里放置ontimeupdate 事件处理程序,或者如何在ontimeupdate 的回调中调度一个动作来更新进度条。这是我的代码:
//reducer
const initialState = {
audioElement: new AudioElement('test.mp3'),
progress: 0.0
}
initialState.audioElement.audio.ontimeupdate = () => {
console.log('progress', initialState.audioElement.currentTime/initialState.audioElement.duration);
//how to dispatch 'SET_PROGRESS_VALUE' now?
};
const audio = (state=initialState, action) => {
switch(action.type){
case 'SET_PROGRESS_VALUE':
return Object.assign({}, state, {progress: action.progress});
default: return state;
}
}
export default audio;
在 reducer 中调度 action 是一种反模式。您的 reducer 应该没有副作用,只需消化操作负载并返回一个新的状态对象。在 reducer 中添加侦听器和调度操作可能会导致链式操作和其他副作用。
听起来您的初始化 AudioElement
class 和事件侦听器属于组件而不是状态。在事件侦听器中,您可以分派一个动作,该动作将更新 progress
状态。
您可以在新的 React 组件中初始化 AudioElement
class 对象,或者只是将 class 转换为 React 组件。
class MyAudioPlayer extends React.Component {
constructor(props) {
super(props);
this.player = new AudioElement('test.mp3');
this.player.audio.ontimeupdate = this.updateProgress;
}
updateProgress () {
// Dispatch action to reducer with updated progress.
// You might want to actually send the current time and do the
// calculation from within the reducer.
this.props.updateProgressAction();
}
render () {
// Render the audio player controls, progress bar, whatever else
return <p>Progress: {this.props.progress}</p>;
}
}
class MyContainer extends React.Component {
render() {
return <MyAudioPlayer updateProgress={this.props.updateProgress} />
}
}
function mapStateToProps (state) { return {}; }
return connect(mapStateToProps, {
updateProgressAction
})(MyContainer);
请注意,updateProgressAction
会自动用 dispatch
包装,因此您无需直接调用调度。
您可以尝试使用像 redux-saga 这样的库。它提供了一种非常 简洁的方式来对异步函数进行排序、触发操作、使用延迟等等。很强大!
在你的 reducer 完成之前开始另一个调度是一个反模式,因为你在你的 reducer 开始时收到的状态将不再是当前的应用程序状态,当你减速器完成。但是从reducer 中安排另一个调度不是反模式。事实上,这就是 Elm 语言所做的,正如你所知,Redux 是将 Elm 架构带到 JavaScript.
的尝试这是一个中间件,它将 属性 asyncDispatch
添加到您的所有操作中。当你的 reducer 完成并返回新的应用程序状态时,asyncDispatch
将触发 store.dispatch
并执行你给它的任何操作。
// This middleware will just add the property "async dispatch" to all actions
const asyncDispatchMiddleware = store => next => action => {
let syncActivityFinished = false;
let actionQueue = [];
function flushQueue() {
actionQueue.forEach(a => store.dispatch(a)); // flush queue
actionQueue = [];
}
function asyncDispatch(asyncAction) {
actionQueue = actionQueue.concat([asyncAction]);
if (syncActivityFinished) {
flushQueue();
}
}
const actionWithAsyncDispatch =
Object.assign({}, action, { asyncDispatch });
const res = next(actionWithAsyncDispatch);
syncActivityFinished = true;
flushQueue();
return res;
};
现在你的减速器可以做到这一点:
function reducer(state, action) {
switch (action.type) {
case "fetch-start":
fetch('wwww.example.com')
.then(r => r.json())
.then(r => action.asyncDispatch({ type: "fetch-response", value: r }))
return state;
case "fetch-response":
return Object.assign({}, state, { whatever: action.value });;
}
}
redux-loop 从 Elm 那里得到启发并提供了这种模式。
reducer 内部的调度和操作似乎出现错误。
我使用 useReducer
做了一个简单的计数器示例,其中“INCREASE”被调度,然后“SUB”也被调度。
在示例中,我预计“INCREASE”会被分派,然后“SUB”也会分派,并且将 cnt
设置为 -1,然后
继续“INCREASE”操作将 cnt
设置为 0,但它是 -1(“INCREASE”被忽略)
看这个: https://codesandbox.io/s/simple-react-context-example-forked-p7po7?file=/src/index.js:144-154
let listener = () => {
console.log("test");
};
const middleware = (action) => {
console.log(action);
if (action.type === "INCREASE") {
listener();
}
};
const counterReducer = (state, action) => {
middleware(action);
switch (action.type) {
case "INCREASE":
return {
...state,
cnt: state.cnt + action.payload
};
case "SUB":
return {
...state,
cnt: state.cnt - action.payload
};
default:
return state;
}
};
const Test = () => {
const { cnt, increase, substract } = useContext(CounterContext);
useEffect(() => {
listener = substract;
});
return (
<button
onClick={() => {
increase();
}}
>
{cnt}
</button>
);
};
{type: "INCREASE", payload: 1}
{type: "SUB", payload: 1}
// expected: cnt: 0
// cnt = -1