使用 Redux 分发操作的不同方法
介绍
在 Javascript 中,Redux 被用作许多应用程序的状态容器,主要是 React。它依赖于由 Reducer 调度和监听的操作,从而相应地更新状态。
在本指南中,我们将研究分派动作和将组件与 Redux 隔离的不同方法。
让我们考虑一个示例,其中服务允许用户向群组发送任何消息。我们将创建一个表单 MessageSender.js 并点击发送按钮将消息发送到 API 进行处理。
将调度方法传递给我们的组件
存储对象上有 dispatch 方法。调度操作以触发对 Redux 存储的更新。
// App.js
import { createStore } from 'redux';
import { MessageSender } from './MessageSender';
import reducer from './reducer';
const store = createStore(reducer);
class App extends React.Component {
render() {
<MessageSender store={store} />
};
};
// MessageSender.js
import { sendMsg } from './actions';
// ...
this.props.store.dispatch(sendMsg(msg))
// ...
使用 React-Redux 制作愚蠢/智能的组件
上述方法的缺点是我们的 React 组件知道应用程序逻辑。最好将连接到商店的智能组件中的逻辑与用户界面(即哑组件)分开。我们可以创建一个 MessageSender.container.js,在其中使用 connect 方法连接组件。连接的第二个参数是函数 mapDispatchToProps,它是使用 dispatch 方法的所有动作创建者的包装器,并传递给组件。
从 connect 的官方文档中,我们可以这样描述 mapDispatchToProps:如果传递了一个对象,则其中的每个函数都被视为 Redux 动作创建者。具有相同函数名称但每个动作创建者都包装在调度调用中以便可以直接调用的对象将合并到组件的 props 中。
以上内容意味着我们不需要分派我们的操作。我们只需要将一个对象传递给“connect”。然后我们就可以从我们的 props 中调用包装的操作。
下面是代码:
// MessageSender.container.js
import { connect } from 'react-redux';
import { sendMsg } from './actions';
import MessageSender from './MessageSender';
const mapDispatchToProps = {
sendMsg
};
export default connect(null, mapDispatchToProps)(MessageSender);
// MessageSender.js
// ...
this.props.sendMsg(msg);
// ...
如果我们想在单个方法中分派多个操作,我们可以按如下所示进行操作:
import { connect } from 'react-redux';
import { sendMsg, navigateTo } from './actions';
const mapDispatchToProps = dispatch => ({
sendMsg: msg => {
dispatch(sendMsg(msg));
dispatch(navigateTo({ routeName: 'myMsgList' }));
}
});
export default connect(null, mapDispatchToProps)(MessageSender);
// MessageSender.js
// ...
this.props.sendMsg(msg);
// ...
});
使用 Redux-saga 库调度操作
为了依次运行多个异步指令并保持代码的可读性,我们可以调度一个动作,然后触发一个 saga。我们可以以相同的方式使用 redux-saga 库。使用 saga,我们可以使用 put 效果调度动作。
要安装该库,我们使用以下代码:
npm install redux-saga
下面是我们组件的 JS:
// my-sagas.js
import { put } from 'redux-saga/effects';
import { sendMsg, setLoading, navigateTo } from './actions';
export function* sendMsg(action) {
yield put(setLoading('sendMsgPage', true));
yield put(sendMsg(action.payload.message));
yield put(navigateTo({routeName: 'myMsgList'}));
yield put(setLoading('sendMsgPage', false));
}
下面是使用 saga 进行异步调用的另一个示例。
我们将创建一个基本的 Saga:
export function* heySaga() {
console.log('Hi there from a basic Sagas example!')
}
下面是我们的main.js:
// ...
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
// ...
import { heySaga } from './sagas'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(heySaga)
const action = type => store.dispatch({type})
// rest
下面是我们展示异步调用的反例:
const MyCounter = ({ value, onInc, onDec, onIncAsync }) =>
<div>
<button onClick={onIncAsync}>
Increase after 1 second
</button>
{' '}
<button onClick={onInc}>
Increase
</button>
{' '}
<button onClick={onDec}>
Decrease
</button>
<hr />
<div>
You clicked on the button {value} times
</div>
</div>
然后,我们将 React 组件的 onIncAsync 连接到 Store 操作。我们将更新 main.js 模块,如下所示:
function render() {
ReactDOM.render(
<MyCounter
value={store.getState()}
onInc={() => action('INC')}
onDec={() => action('DEC')}
onIncAsync={() => action('INC_ASYNC')} />,
document.getElementById('root')
)
}
Unlike in redux-thunk, our component here dispatches a plain object action. We'll add the following code in sagas.js:
import { put, takeEvery } from 'redux-saga/effects'
const delay = (ms) => new Promise(response => setTimeout(response, ms))
// ...
// Our Saga worker would perform the async inc task
export function* incAsync() {
yield delay(2000)
yield put({ type: 'INC' })
}
// Our Saga watcher would spawn a new incAsync task on each INC_ASYNC
export function* watchIncAsync() {
yield takeEvery('INC_ASYNC', incAsync)
}
Sagas are generally implemented using generator functions, which yield objects to our redux-saga middleware. The yielded objects are like instructions which are meant to be interpreted by our middleware. When we yield a promise to our middleware, the middleware will suspend the saga until the time the pPromise is completed. As per the above code, the incAsync saga is suspended until the promise that is returned by delay is resolved, which would happen after 2 seconds.
Once we resolve the promise, our middleware will resume the Saga and run code until the next yield. In our example, the next statement is other yielded object, i.e., the result of calling put({type: 'INC'}), thus instructing our middleware to dispatch an INC action.
In the example above, "put" can be called as an "effect." Effects are plain JS objects which have instructions to be fulfilled by our middleware. When our middleware retrieves an effect yielded by a saga, the saga is paused until the effect is completed.
Thus, in short, the incAsync Saga sleeps for 2 seconds through the call to delay(2000). It then dispatches an INC action.
We have also created another saga, watchIncAsync. We use "takeEvery" helper function provided by redux-saga library to listen for dispatched INC_ASYNC actions and run "incAsync" each time.
We have 2 sagas now, and we have to start them both immediately. To do that, we'll add a mainSaga which would be responsible for starting our other sagas. We'll update the code in sagas.js as shown below:
import { put, takeEvery, all } from 'redux-saga/effects'
const delay = (ms) => new Promise(response => setTimeout(response, ms))
function* heySaga() {
console.log('Hi there from a basic Sagas example!');
}
function* incAsync() {
yield delay(2000)
yield put({ type: 'INC' })
}
function* watchIncAsync() {
yield takeEvery('INC_ASYNC', incAsync)
}
// you can see that we just export the mainSaga
// This is the single entry point to start all our sagas immediately
export default function* mainSaga() {
yield all([
heySaga(),
watchIncAsync()
])
}
This saga would yield an array containing the results of calling the two sagas; heySaga and watchIncAsync. Thus the two resulting generators would get started in parallel. Now we only need to call sagaMiddleware.run on the main saga in main.js.
// ...
import mainSaga from './sagas'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(rootSaga)
const action = type => store.dispatch({type})
// rest
Passing callback function to our child component
It is considered good practice to call all actions from containers only. To do that, we can pass our callback function as props to all our child components. Thus, each child component would contain a list of all the callback functions defined in their prop types. The parent component can just pass function reference to each of our child component.
Below is an example. Let's say we have a container class called MyReactContainer.
@connect(store => ({ myReactStore: store.myReactStore }))
class MyReactContainer extends Component {
onSomeTaskStart = (payload) => {
this.props.dispatch(MyActionTypes.SOME_TASK_START, payload);
}
onSomeTaskEnd = (payload) => {
this.props.dispatch(MyActionTypes.SOME_TASK_END, payload);
}
render() {
return (
<MyComponent
onSomeTaskStart={this.onSomeTaskStart}
onSomeTaskEnd={this.onSomeTaskEnd}
/>
}
}
Below is our component code:
class MyReactComponent extends Component {
static propTypes = {
onSomeTaskStart: PropsTypes.func,
onSomeTaskEnd: PropTypes.func,
}
render() {
return (
<div>
// we'll have the view code here
</div>
)
}
}
Thus, we can see that the child can specify all the functions it exposes to the parent using its prop-types. Also, it does not have to worry about which actions to call. The parent component can either implement certain
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~